diff --git a/.gitignore b/.gitignore index cd55c6eb25..2af2073aef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,16 @@ +# General temp files +.DS_Store +*.ser +*.pyc + +# Files related to Play build or usage +id +dist +out bin framework/classes framework/test-classes +framework/tests-results framework/out framework/.idea framework/play.jar @@ -13,121 +23,33 @@ nbproject/private framework/tests-results framework/src/version framework/src/play/version -framework/src/play/version -framework/src/play/version framework/tests/test-application/db +framework/play-*.jar +framework/docs +samples-and-tests/*/test-result/ + +# IDE and editors +/bin +*~ +.idea +nbproject +eclipse +.amateras .settings .classpath .project -framework/tests/tmp -id -framework/tests-tmp -framework/docs -dist -modules/bespin/lib/bespin.jar -modules/ecss/lib/ecss.jar -modules/test-runner/lib/test-runner.jar -resources/catalog.xml -resources/nbproject/private -modules/gae/lib/gae.jar -catalog.xml -modules/gae/lib/gae.jar -play-*.jar -modules/gae/bin -modules/gwt/lib/play-gwt.jar -tmp -test-result -logs -logs -tmp -db -*.pyc -test-result -samples-and-tests/with-gwt/nbproject -samples-and-tests/jobboard/db -samples-and-tests/jobboard/tmp -samples-and-tests/jobboard/test-result -samples-and-tests/jobboard/attachments -samples-and-tests/just-test-cases/tmp -samples-and-tests/just-test-cases/test-result -samples-and-tests/chat/tmp -samples-and-tests/chat/logs -.git -modules/spring/lib/play-spring.jar -modules/search/bin -modules/search/lib/search.jar -samples-and-tests/just-test-cases/data -modules/siena/lib/play-siena.jar -samples-and-tests/lists-with-gae/logs -samples-and-tests/lists-with-gae/tmp -samples-and-tests/lists-with-gae/war/WEB-INF/appengine-generated -./.settings -samples-and-tests/just-test-cases/.settings -samples-and-tests/just-test-cases/eclipse -framework/ecClasses -documentation/api -support/textmate -samples-and-tests/yabe/logs -samples-and-tests/yabe/test-result -samples-and-tests/yabe/tmp -*.ser -modules/cobertura/lib -modules/scala/lib/play-scala.jar -samples-and-tests/with-scala/logs -samples-and-tests/yabe-with-scala/logs -samples-and-tests/yabe-with-scala/tmp -samples-and-tests/yabe/.amateras -samples-and-tests/yabe/.settings -samples-and-tests/yabe/eclipse -samples-and-tests/yabe/nbproject -samples-and-tests/with-scala/.amateras -samples-and-tests/with-scala/.externalToolBuilders -samples-and-tests/with-scala/.manager -samples-and-tests/with-scala/.settings -samples-and-tests/with-scala/eclipse -samples-and-tests/test-scala/tmp -samples-and-tests/test-scala/logs -samples-and-tests/i-am-a-developer/i-am-working-here -samples-and-tests/i-am-a-developer/i-am-creating-jobs-here -samples-and-tests/i-am-a-developer/i-am-testing-log-levels-here -samples-and-tests/just-test-cases/attachments -samples-and-tests/booking/logs -samples-and-tests/booking/tmp -samples-and-tests/twitter-oauth/.settings -samples-and-tests/twitter-oauth/eclipse -help +samples-and-tests/java8Support/lib/ *.iml *.ipr *.iws -modules/router/lib/router.jar -modules/grizzly/lib/play-grizzly.jar -play.ipr -play.iws -*iml -modules/router-head -modules/gwt/samples-and-tests/logs -modules/gwt/samples-and-tests/test-result -modules/gwt/samples-and-tests/tmp -modules/siena-1.0 -samples-and-tests/zencontact/logs -samples-and-tests/zencontact/test-result -samples-and-tests/zencontact/tmp -modules/scala +atlassian-ide-plugin.xml + +# modules/: exclude except for core modules modules/* -build.classes -modules/crud/nbproject -nbproject -samples-and-tests/jobboard/attachments -samples-and-tests/jobboard/logs -samples-and-tests/jobboard/test-result -samples-and-tests/jobboard/tmp -samples-and-tests/validation/logs -samples-and-tests/zencontact/precompiled !modules/docviewer !modules/crud !modules/console !modules/grizzly !modules/secure !modules/testrunner -!framework/src/play/db -.idea + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..156742981f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,30 @@ +sudo: required +language: java +jdk: + - openjdk7 +#before_install: +# - cat /etc/hosts # optionally check the content *before* +# - sudo hostname "$(hostname | cut -c1-63)" +# - sed -e "s/^\\(127\\.0\\.0\\.1.*\\)/\\1 $(hostname | cut -c1-63)/" /etc/hosts | sudo tee /etc/hosts +# - sudo mv /tmp/hosts /etc/hosts +# - cat /etc/hosts # optionally check the content *after* +addons: + hosts: + - myshorthost + hostname: myshorthost +script: ant -buildfile ./framework/build.xml test +after_failure: + cat ./samples-and-tests/just-test-cases/test-result/*.failed.html + cat ./samples-and-tests/forum/test-result/*.failed.html + cat ./samples-and-tests/zencontact/test-result/*.failed.html + cat ./samples-and-tests/jobboard/test-result/*.failed.html + cat ./samples-and-tests/yabe/test-result/*.failed.html +notifications: + webhooks: + urls: + - https://webhooks.gitter.im/e/6da3b55e59cded84b8bd + on_success: always + on_failure: always + email: + on_success: change + on_failure: always \ No newline at end of file diff --git a/COPYING b/COPYING index ad8941cdfb..85c9b90fdb 100644 --- a/COPYING +++ b/COPYING @@ -38,6 +38,7 @@ http://www.apache.org/licenses/LICENSE-2.0 - Signpost http://www.gnu.org/licenses/lgpl.html + - Bytecodeparser - c3p0 - Hibernate diff --git a/README.textile b/README.textile index 38b61b1777..c36429b76b 100644 --- a/README.textile +++ b/README.textile @@ -1,18 +1,20 @@ -h1. Welcome to Play framework +h1. Welcome to Play framework + +!https://travis-ci.org/playframework/play1.svg?branch=master!:https://travis-ci.org/playframework/play1 !https://badges.gitter.im/playframework/play1.svg(Join the chat at https://gitter.im/playframework/play1)!:https://gitter.im/playframework/play1?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge Play framework makes it easier to build Web applications with Java. It is a clean alternative to bloated Enterprise Java stacks. It focuses on developer productivity and targets RESTful architectures. Play is a perfect companion to agile software development. -Learn more on the "http://www.playframework.org":http://www.playframework.org website. +Learn more on the "https://www.playframework.com":https://www.playframework.com website. h2. Getting started 1. Install the latest version of Play framework and unzip it anywhere you want: -bc. unzip play-1.1.zip -d /opt/play-1.1 +bc. unzip play-1.3.0-RC1.zip -d /opt/play-1.3.0-RC1 2. Add the **play** script to your PATH: -bc. export PATH=$PATH:/opt/play-1.1 +bc. export PATH=$PATH:/opt/play-1.3.0-RC1 3. Create a new Play application: @@ -26,10 +28,10 @@ bc. play run /opt/myFirstApp 6. Start developing your new application: -* "Your first application — the ‘Hello World’ tutorial":http://www.playframework.org/documentation/1.1/firstapp -* "Tutorial — Play guide, a real world app step-by-step":http://www.playframework.org/documentation/1.1/guide1 -* "The essential documentation":http://www.playframework.org/documentation/1.0.3/main -* "Java API":http://www.playframework.org/@api/index.html +* "Your first application — the ‘Hello World’ tutorial":https://www.playframework.com/documentation/1.3.0-RC1/firstapp +* "Tutorial — Play guide, a real world app step-by-step":https://www.playframework.com/documentation/1.3.0-RC1/guide1 +* "The essential documentation":https://www.playframework.com/documentation/1.3.0-RC1/home +* "Java API":https://www.playframework.com/@api/index.html h2. Get the source @@ -45,6 +47,21 @@ h2. Reporting bugs Please report bugs on "our lighthouse tracker":http://play.lighthouseapp.com/projects/57987-play-framework. +h2. Learn More + +* "www.playframework.com":https://www.playframework.com +* "Download":https://www.playframework.com/download +* "Install":https://www.playframework.com/documentation/1.3.x/install +* "Create a new application":https://www.playframework.com/documentation/1.3.x/guide1 +* "Build from source":https://www.playframework.com/documentation/1.3.x/install#build +* "Modules":https://www.playframework.com/modules +* "Search or create issues":http://play.lighthouseapp.com/projects/57987-play-framework +* "Get help":http://stackoverflow.com/questions/tagged/playframework +* "Code of Conduct":https://www.playframework.com/conduct +* "Contribute":https://github.com/playframework/play1/wiki/Contributor-guide +* "Play contributor guidelines":https://www.playframework.com/contributing + + h2. Licence -Play framework is distributed under "Apache 2 licence":http://www.apache.org/licenses/LICENSE-2.0.html. \ No newline at end of file +Play framework is distributed under "Apache 2 licence":http://www.apache.org/licenses/LICENSE-2.0.html. diff --git a/documentation/cheatsheets/controllers/ch08-ControllerLibraries.textile b/documentation/cheatsheets/controllers/ch08-ControllerLibraries.textile index a4d1b14fe2..4eab0e7401 100644 --- a/documentation/cheatsheets/controllers/ch08-ControllerLibraries.textile +++ b/documentation/cheatsheets/controllers/ch08-ControllerLibraries.textile @@ -3,6 +3,9 @@ h2. Controller - Libraries *==WS.url("http://s.com/posts").get().toJSON();==* HTTP GET request to JSON +*==WS.withEncoding("iso-8859-1").url("http://s.com/posts").get().toJSON();==* +HTTP GET request to JSON using iso-8859-1 encoding + *==WS.url("http://s.com/").post().toXML();==* HTTP POST request to XML diff --git a/documentation/cheatsheets/multiEnvironment/ch26-MultiEnvironment.textile b/documentation/cheatsheets/multiEnvironment/ch26-MultiEnvironment.textile index cea0d3ad53..2e845433b6 100644 --- a/documentation/cheatsheets/multiEnvironment/ch26-MultiEnvironment.textile +++ b/documentation/cheatsheets/multiEnvironment/ch26-MultiEnvironment.textile @@ -1,4 +1,4 @@ -h2. Multi environment +h2. Multi-environment *==# Test configuration==* *==%test.db=mem==* diff --git a/documentation/cheatsheets/tests/ch21-TestUnitTests.textile b/documentation/cheatsheets/tests/ch21-TestUnitTests.textile index b6442748fd..ec792557bb 100644 --- a/documentation/cheatsheets/tests/ch21-TestUnitTests.textile +++ b/documentation/cheatsheets/tests/ch21-TestUnitTests.textile @@ -14,7 +14,7 @@ Run before each unit test *==@After public void cleanupJunk()==* Run after each unit test -*==@BeforeClass void whenTestClassInstanciated();==* +*==@BeforeClass void whenTestClassInstantiated();==* Run once, when the test class gets instantiated *==@AfterClass void whenTestClassCompleted()==* diff --git a/documentation/cheatsheets/tests/ch24-TestDataloader.textile b/documentation/cheatsheets/tests/ch24-TestDataloader.textile index f2aa638052..7a8b9e350d 100644 --- a/documentation/cheatsheets/tests/ch24-TestDataloader.textile +++ b/documentation/cheatsheets/tests/ch24-TestDataloader.textile @@ -1,9 +1,9 @@ h2. Test - Data loader *==@Before public void setUp() { Fixtures.deleteAll();==* -*==Fixtures.load("data.yml");}==* +*==Fixtures.loadModels("data.yml");}==* Fixtures is used to initialise the datastore before running a unit test -*==#{fixture delete:'all', load:'data.yml' /}==* +*==#{fixture delete:'all', load:'data.yml' /}==* or *==#{fixture delete:'all', loadModels:'data.yml' /}==* *==#{selenium} ... #{/selenium}==* -Same idea using a Selenium test +Same idea using a Selenium test(@loadModels@ and @load@ parameters are synonyms). diff --git a/documentation/cheatsheets_ja/commandLine/ch25-CommandLinePlayCommand.textile b/documentation/cheatsheets_ja/commandLine/ch25-CommandLinePlayCommand.textile new file mode 100644 index 0000000000..c96de02dd2 --- /dev/null +++ b/documentation/cheatsheets_ja/commandLine/ch25-CommandLinePlayCommand.textile @@ -0,0 +1,82 @@ +h2. Command line - play command + +*classpath* +算出されたクラスパスを表示します。 + +*id* +複数環境設定用のフレームワーク ID を定義します。 + +*secret* +暗号化に使われる秘密鍵を生成します。 + +*install* +モジュールをインストールします。 + +*list-modules* +セントラルモジュールリポジトリから入手可能なモジュールを列挙します。 + +*modules* +算出されたモジュールのリストを表示します。 + +*new* +新しいアプリケーションを作成します。 + +*new-module* +モジュールを作成します。 + +*build-module* +モジュールをビルドし、パッケージングします。 + +*eclipsify* +Eclipse の全ての設定ファイルを作成します。 + +*netbeansify* +NetBeans の全ての設定ファイルを作成します。 + +*idealize* +IntelliJ Idea の全ての設定ファイルを作成します。 + +*javadoc* +アプリケーションの Javadoc を生成します。 + +*auto-test* +全てのアプリケーションのテストを自動的に実行します。 + +*clean* +(バイトコードキャッシュを含む) 一時ファイルを削除します。 + +*test* +現在のシェルでアプリケーションをテストモードで実行します。 + +*precompile* +アプリケーションの起動を高速化するため全ての Java のソースとテンプレートをプリコンパイルします。 + +*war* +スタンドアロンの WAR アーカイブとしてアプリケーションをエクスポートします。 + +*run* +現在のシェルでアプリケーションを実行します。 + +*start* +バックグラウンドでアプリケーションを起動します。 + +*stop* +実行中のアプリケーションを停止します。 + +*restart* +実行中のアプリケーションを再起動します。 + +*status* +実行中のアプリケーションの状態を表示します。 + +*out* +logs/system.out ファイルの内容をリアルタイムで表示します。 + +*pid* +実行中のアプリケーションの PID を表示します。 + +*check* +現在のものより新しい Play framework のリリースがあるかどうかチェックします。 + +*help* +指定したコマンドのヘルプを表示します。 diff --git a/documentation/cheatsheets_ja/controllers/ch01-ControllerActionSmartbinding.textile b/documentation/cheatsheets_ja/controllers/ch01-ControllerActionSmartbinding.textile new file mode 100644 index 0000000000..0467a11db8 --- /dev/null +++ b/documentation/cheatsheets_ja/controllers/ch01-ControllerActionSmartbinding.textile @@ -0,0 +1,27 @@ +h2. Controller.action - Smart binding + +*==Controller/link?i=32&n=patrick==* +==public static void link(int i, String n)== +==public static void link(Integer i, String n)== +==public static void link(Long i, String n)== + +*==Controller/show?id[0]=1&id[1]=2&id[2]=3&id[3]=4==* +==public static void show(Long[] id)== +==public static void show(List id)== +==public static void show(Set id)== + +*==Controller/get?date=02-18-1972==* +==public static void get(@As("MM-dd-yyyy") Date date)== + +*==(@As(binder=MyCustomStringBinder.class))==* +Custom parameter binder + +*public static void create(String comment, File attachment)* +multipart/form-data エンコードの POST リクエストとしてファイルを送信します。 + +*==?client.name=paul&client.email=p@example.com==* +*public static void create(Client client)* +JavaBean (POJO) binding + +*@NoBinding* +バインドできないフィールドを示します。 diff --git a/documentation/cheatsheets_ja/controllers/ch02-ControllerActionValidation.textile b/documentation/cheatsheets_ja/controllers/ch02-ControllerActionValidation.textile new file mode 100644 index 0000000000..e7a9ce0336 --- /dev/null +++ b/documentation/cheatsheets_ja/controllers/ch02-ControllerActionValidation.textile @@ -0,0 +1,27 @@ +h2. Controller.action - Validation + +*==@Required String lastname==* +*==@IsTrue String agree==* +*==@Max(7500) Integer wordCount==* +*==@Min(18) Long age==* +*==@MaxSize(2083) String value==* +*==@MinSize(42) String value==* +*==@Email String address==* +*==@Equals("passwordConfirmation") String password==* +*==@InFuture String dueDate==* +*==@InFuture("1979-12-31") String birthDate==* +*==@Match("[A-Z]{3}") String abbreviation==* +*==@Match("(directDebit|creditCard|onReceipt)")==* +*==@Past String actualDepartureDate==* +*==@Past("1980-01-01") String birthDate==* +*==@Range(min = 17500, max = 40000) String wordCount==* +*==@URL String address==* +*==@IPv4Address String ip==* +*==@IPv6Address String ip==* + +*==@Phone String phone==* +Relaxed phone validation +電話番号の緩やかな検証 + +*==@Valid Person person==* +JavaBean (POJO) 検証 - Person クラスは検証のアノテーションが必要です。 diff --git a/documentation/cheatsheets_ja/controllers/ch03-ControllerSessionManagement.textile b/documentation/cheatsheets_ja/controllers/ch03-ControllerSessionManagement.textile new file mode 100644 index 0000000000..4761b9909a --- /dev/null +++ b/documentation/cheatsheets_ja/controllers/ch03-ControllerSessionManagement.textile @@ -0,0 +1,27 @@ +h2. Controller - Session Management + +*警告: Play のセッションは J2EE のセッションではありません* +session と flash はクッキーを使います! 4KB と 20 個の上限があります。 + +*session.getId();* +(たいていの場合持っておかなければいけない) セッション ID を返します。 + +*session.put(String key, String value);* +*session.get("user_flag");* +値は 4KB を上限とする文字列に限られます。 + +*flash.put(String key, String value);* +*flash.get(String key);* +flash エントリは次のリクエストの終了まで廃棄されます。 + +*Cache.set("key_" + id, product, "30mn");* +30 分のキャッシュ値をセットします。 + +*Cache.get("key_" + id, Product.class);* +キャッシュ値を取得します。キーに対応する値がなければ null を返します。 + +*Cache.delete("key_" + id);* +ノンブロッキングのキャッシュ削除 + +*Cache.safeDelete("key_" + id);* +ブロッキングのキャッシュ削除 diff --git a/documentation/cheatsheets_ja/controllers/ch04-ControllerActionRedirection.textile b/documentation/cheatsheets_ja/controllers/ch04-ControllerActionRedirection.textile new file mode 100644 index 0000000000..6cc9cf6f60 --- /dev/null +++ b/documentation/cheatsheets_ja/controllers/ch04-ControllerActionRedirection.textile @@ -0,0 +1,22 @@ +h2. Controller.action - Redirection + +*render(params...);* +与えられたパラメータでテンプレートを text/html としてレンダリングします。 + +*renderXML(params...);* +パラメータを application/xml としてレンダリングします。 + +*renderJson(params...);* +パラメータを application/json としてレンダリングします。 + +*renderText(params...);* +パラメータを text/plain としてレンダリングします。 + +*renderTemplate("Clients/showClient.html", id, client);* +デフォルトのテンプレートをバイパスします。 + +*redirect("http://www.crionics.com");* +指定された URL に HTTP リダイレクトします。 + +*From an action, calling another Controller.action()* +透過的にリダイレクトを生成します。 diff --git a/documentation/cheatsheets_ja/controllers/ch05-ControllerJobs.textile b/documentation/cheatsheets_ja/controllers/ch05-ControllerJobs.textile new file mode 100644 index 0000000000..8657f77d96 --- /dev/null +++ b/documentation/cheatsheets_ja/controllers/ch05-ControllerJobs.textile @@ -0,0 +1,12 @@ +h2. Controller - Jobs + +*==@OnApplicationStart==* + +*==@On("0 0 12 ∗ ∗ ?")==* +毎日午後 12:01 + +*==@On("10 30 12 ? ∗ MON-FRI")==* +平日の午後 12:30:10 + +*==@Every("1h")==* +*==public class Bootstrap extends Job {public void doJob() {...} }==* diff --git a/documentation/cheatsheets_ja/controllers/ch06-ControllerInterceptions.textile b/documentation/cheatsheets_ja/controllers/ch06-ControllerInterceptions.textile new file mode 100644 index 0000000000..5fd2bb8e66 --- /dev/null +++ b/documentation/cheatsheets_ja/controllers/ch06-ControllerInterceptions.textile @@ -0,0 +1,18 @@ +h2. Controller - Interceptions + +*==@Before ➟ action ➟ @After ➟ template ➟ @Finally==* +Interceptions evaluation order +インターセプト評価の順 + +*==@Before static void checkAuthentification()==* +*==@After static void log()==* +*==@Finally static void audit()==* +You get the idea + +*==@With(Secure.class)==* +*==public class Admin extends Application==* +コントローラスコープでのカスタムインターセプタ + +*==@Catch(value={RuntimeException.class})==* +*public static void onException(RuntimeException e) {…}* +コントローラ層での例外ハンドリング diff --git a/documentation/cheatsheets_ja/controllers/ch07-ControllerActionOthers.textile b/documentation/cheatsheets_ja/controllers/ch07-ControllerActionOthers.textile new file mode 100644 index 0000000000..ea98e0fc48 --- /dev/null +++ b/documentation/cheatsheets_ja/controllers/ch07-ControllerActionOthers.textile @@ -0,0 +1,15 @@ +h2. Controller.action - Others + +*==Logger.info("Action executed ...");==* +*==Logger.debug("A log message");==* +*==Logger.error(ex, "Oops");==* +ロギングの設定は application.conf にあります。 + +*==@CacheFor("1h") public static void index() { ... }==* +1時間アクションの実行結果をキャッシュします。 + +*==Play.configuration.getProperty("blog.title");==* +設定ファイルにアクセスします。 + +*==Query query = JPA.em().createQuery("query");==* +永続化マネージャにアクセスします。 diff --git a/documentation/cheatsheets_ja/controllers/ch08-ControllerLibraries.textile b/documentation/cheatsheets_ja/controllers/ch08-ControllerLibraries.textile new file mode 100644 index 0000000000..47df5c50e2 --- /dev/null +++ b/documentation/cheatsheets_ja/controllers/ch08-ControllerLibraries.textile @@ -0,0 +1,64 @@ +h2. Controller - Libraries + +*==WS.url("http://s.com/posts").get().toJSON();==* +HTTP GET リクエストを JSON にします。 + +*==WS.withEncoding("iso-8859-1").url("http://s.com/posts").get().toJSON();==* +HTTP GET リクエストを iso-8859-1 エンコーディングで JSON にします。 + +*==WS.url("http://s.com/").post().toXML();==* +HTTP POST リクエストを XML にします。 + +*==DB.execute("raw sql");==* +そのままの SQL を評価します。 + +*==XML.getDocument(String);==* +文字列を XML にします。 + +*==XML.serialize(Document);==* +XML を文字列にします。 + +*==XPath.selectNodes(String xpath, Object node);==* +XPath 表記の評価 + +*==Files.copy(File,File);==* +ファイルコピー + +*==Files.copyDir(File,File);==* +再帰的なディレクトリコピー + +*==Files.delete(File);==* +*==Files.deleteDirectory(File);==* +ファイルやディレクトリの削除 + +*==IO.readLines(File);==* +*==IO.readContentAsString(File);==* +*==IO.readContent(File);==* +*==IO.write(byte[],File);==* +ファイルの内容の読み書き + +*==Images.crop(File orig,File to, int x1, int y1, int x2, int y2);==* +*==Images.resize(File orig, File to, int w, int h);==* +*==Images.toBase64(File image);==* +便利なメソッド + +*==Crypto.encryptAES(String);==* +*==Crypto.decryptAES(String);==* +アプリケーションの秘密鍵を使っての暗号化 + +*==Crypto.passwordHash(String);==* +MD5 パスワードハッシュを生成します。 + +*==Codec.UUID();==* +ユニークな ID を生成します。 + +*==Codec.byteToHexString(byte[] bytes);==* +バイト配列を 16進数表記の文字列で書き出します。 + +*==Codec.encodeBASE64(byte[] value);==* +*==Codec.decodeBASE64(String base64);==* +Encode/Decode a base64 value +base64 のエンコードまたはデコードをします。 + +*==Codec.hexSHA1(String);==* +文字列の 16進数表記の SHA1 ハッシュを生成します。 diff --git a/documentation/cheatsheets_ja/model/ch14-ModelActionQueries.textile b/documentation/cheatsheets_ja/model/ch14-ModelActionQueries.textile new file mode 100644 index 0000000000..8953f98c63 --- /dev/null +++ b/documentation/cheatsheets_ja/model/ch14-ModelActionQueries.textile @@ -0,0 +1,32 @@ +h2. Model.action - Queries + +*==Query query = JPA.em().createQuery("jpql_query");==* +永続化マネージャにアクセスします。 + +*==Post post = Post.findById(id);==* +*==List posts = Post.findAll();==* +find メソッド + +*==post.save();==* +永続化層にオブジェクトを保存します。 + +*==boolean post.validateAndSave();==* +オブジェクトを検証して保存した場合に true を返します。検証はアノテーションで行われます。 + +*==List posts = Post.all().from(50).fetch(100);==* +50 番目から 100 番目のレコードを読み込みます。 + +*==Post.find("select p from Post p, Comment c where c.post==* +*=== p and c.subject like ?", "%hop%");==* +ジョインを使ったパラメタ化された参照 + +*==long userCount = Post.count("author=?", connectedUser);==* +*==long postCount = Post.count();==* +レコードをカウントします。 + +*==JPAPlugin.startTx(boolean readonly);==* +*==JPAPlugin.closeTx(boolean rollback);==* +トランザクションを独自で制御するメソッド + +*==JPA.setRollbackOnly();==* +トランザクションを強制的にロールバックします。 diff --git a/documentation/cheatsheets_ja/model/ch15-ModelBasics.textile b/documentation/cheatsheets_ja/model/ch15-ModelBasics.textile new file mode 100644 index 0000000000..03ce83ea18 --- /dev/null +++ b/documentation/cheatsheets_ja/model/ch15-ModelBasics.textile @@ -0,0 +1,16 @@ +h2. Model - Basics + +*==@Entity(name="sql_tbl") public class Post extends Model==* +当該クラスが永続的なものであることを指定します。 + +*==@Embedded==* +このフィールドが埋め込まれたものであることを定義します。 + +*==@EmbeddedId==* +このフィールドが当該クラスの ID であり、埋め込まれたものであることを定義します。 + +*==@Embeddable==* +当該クラスが他の永続的クラスに永続的に埋め込まれることを指定します。 + +*==@MappedSuperclass==* +このクラスがサブクラスにマッピングされるための永続的な情報を持っていることを指定します。 diff --git a/documentation/cheatsheets_ja/model/ch16-ModelGenerators.textile b/documentation/cheatsheets_ja/model/ch16-ModelGenerators.textile new file mode 100644 index 0000000000..1efe821ed7 --- /dev/null +++ b/documentation/cheatsheets_ja/model/ch16-ModelGenerators.textile @@ -0,0 +1,11 @@ +h2. Model - Generators + +*==@GeneratedValue(strategy = [NONE, TABLE, SEQUENCE,==* +*==IDENTITY, AUTO])==* +インデックスの自動生成に使用します。 + +*==@SequenceGenerator==* +永続的なエンティティに使用するデータストア内のシーケンスを使った値のジェネレータを定義します。 + +*==@TableGenerator==* +永続的なエンティティに使用するデータストア内のテーブルを使ったシーケンスのジェネレータを定義します。 diff --git a/documentation/cheatsheets_ja/model/ch17-ModelRelationalMapping.textile b/documentation/cheatsheets_ja/model/ch17-ModelRelationalMapping.textile new file mode 100644 index 0000000000..cc001128b5 --- /dev/null +++ b/documentation/cheatsheets_ja/model/ch17-ModelRelationalMapping.textile @@ -0,0 +1,31 @@ +h2. Model - Relational mapping + +*==@Table(name="sql_tbl", catalog="", schema="")==* +このクラスが保存されるテーブルを定義します。 + +*==@Id==* +ID となるフィールドを定義します。 + +*==@Version==* +バージョンを保持しているフィールドを定義します。 + +*==@Basic==* +永続的なフィールドを定義します。省略可能です。 + +*==@Transient==* +(永続的でない) 一時的なフィールドを定義します。 + +*==@Lob(fetch=[LAZY, EAGER], type=[BLOB,CLOB])==* +ラージオブジェクトとして保存されるフィールドを定義します。 + +*==@UniqueConstraint(primary=false, String columns[])==* +セカンダリインデックスを定義します。 + +*==@Temporal(DATE,TIME,TIMESTAMP)==* +java.util.Date や Calendar 型のフィールドにだけ使われるべきです。 + +*==@Enumerated(ORDINAL, STRING)==* +列挙型のクラスを保持するフィールドを定義します。 + +*==@Column(name="sql_column_name")==* +フィールド名と異なるテーブルのカラム名を定義します。 diff --git a/documentation/cheatsheets_ja/model/ch18-ModelCallbacks.textile b/documentation/cheatsheets_ja/model/ch18-ModelCallbacks.textile new file mode 100644 index 0000000000..bd459c3fc2 --- /dev/null +++ b/documentation/cheatsheets_ja/model/ch18-ModelCallbacks.textile @@ -0,0 +1,21 @@ +h2. Model - Callbacks + +データベース依存のトリガを実装する便利な方法 + +*==@PrePersist==* +永続化前のコールバックメソッドを定義します。 + +*==@PostPersist==* +永続化後のコールバックメソッドを定義します。 + +*==@PreRemove==* +削除前のコールバックメソッドを定義します。 + +*==@PostRemove==* +削除後のコールバックメソッドを定義します。 + +*==@PreUpdate==* +更新前のコールバックメソッドを定義します。 + +*==@PostLoad==* +更新後のコールバックメソッドを定義します。 diff --git a/documentation/cheatsheets_ja/model/ch19-ModelRelations.textile b/documentation/cheatsheets_ja/model/ch19-ModelRelations.textile new file mode 100644 index 0000000000..568ba1adf7 --- /dev/null +++ b/documentation/cheatsheets_ja/model/ch19-ModelRelations.textile @@ -0,0 +1,23 @@ +h2. Model - Relations + +*==@OneToOne(entity, fetch=[LAZY,EAGER], nullable=true)==* +他の永続的なエンティティとの 1 対 1 の関連フィールドを定義します。 + +*==@OneToMany(mappedBy="remote_attribute")==* +他の永続的なエンティティとの 1 対 N の関連フィールドを定義します。 + +*==@ManyToMany(cascade=[ALL, PERSIST, MERGE,==* +*==REMOVE, REFRESH, DETACH])==* +他の永続的なエンティティとの M 対 N の関連フィールドを定義します。 + +*==@ManyToOne==* +他の永続的なエンティティとの N 対 1 の関連フィールドを定義します。 + +*==@JoinColumn(name = "id_connector")==* +テーブルか外部キーとジョインするときに使用するカラムを定義します。 + +*==@JoinTable(name = "nm_table", joinColumns ===* +*=={ @JoinColumn(name = "id_coupon", nullable = false) },==* +*==inverseJoinColumns = { @JoinColumn(name ===* +*=="id_campaign", nullable = false) })==* +ManyToMany の関連をマッピングするために使用します。 diff --git a/documentation/cheatsheets_ja/model/ch20-ModelJPAQueries.textile b/documentation/cheatsheets_ja/model/ch20-ModelJPAQueries.textile new file mode 100644 index 0000000000..2a2812fff8 --- /dev/null +++ b/documentation/cheatsheets_ja/model/ch20-ModelJPAQueries.textile @@ -0,0 +1,12 @@ +h2. Model - JPA Queries + +*==@NamedQuery(name="q1", "jpql_query");==* +永続化単位で使用される名前付き JPQL クエリを定義します。 + +*==@NamedNativeQuery(name="q2","sql_query")==* +永続化単位で使用されるネイティブな SQL クエリを定義します。 + +*==@SqlResultSetMapping==* +ネイティブな SQL クエリの実行結果をオブジェクトモデルにマッピングするために使用します。 + +p(note). これは JPA2 アノテーションのサブセットに過ぎません。Hibernate もまた非標準のセットを持っています。 diff --git a/documentation/cheatsheets_ja/multiEnvironment/ch26-MultiEnvironment.textile b/documentation/cheatsheets_ja/multiEnvironment/ch26-MultiEnvironment.textile new file mode 100644 index 0000000000..59b7c8d22a --- /dev/null +++ b/documentation/cheatsheets_ja/multiEnvironment/ch26-MultiEnvironment.textile @@ -0,0 +1,20 @@ +h2. Multi-environment + +*==# Test configuration==* +*==%test.db=mem==* +*==%test.jpa.ddl=create-drop==* + +*==# Dev configuration==* +*==%dev.http.port=8080==* +*==%dev.application.log=DEBUG==* +*==%dev.application.mode=dev==* + +*==# Production configuration==* +*==%prod.http.port=80==* +*==%prod.application.log=INFO==* +*==%prod.application.mode=prod==* + +*==play run --%prod==* +*==play run --%dev==* +*==play test==* +これは適切な設定を選びます。 diff --git a/documentation/cheatsheets_ja/templates/ch09-TemplateImplicitObjects.textile b/documentation/cheatsheets_ja/templates/ch09-TemplateImplicitObjects.textile new file mode 100644 index 0000000000..06fef2b97e --- /dev/null +++ b/documentation/cheatsheets_ja/templates/ch09-TemplateImplicitObjects.textile @@ -0,0 +1,28 @@ +h2. Template - Implicit objects + +*errors* +コントローラで発生した検証エラー + +*flash* +flash スコープ + +*lang* +通信に使用されている言語 + +*messages* +ローカライズされたメッセージの map + +*out* +The output stream writer + +*params* +パラメータ + +*play* +フレームワークのメインのクラス + +*request* +HTTP リクエスト + +*session* +セッションスコープ diff --git a/documentation/cheatsheets_ja/templates/ch10-TemplateTagGrammar.textile b/documentation/cheatsheets_ja/templates/ch10-TemplateTagGrammar.textile new file mode 100644 index 0000000000..48fe6911ea --- /dev/null +++ b/documentation/cheatsheets_ja/templates/ch10-TemplateTagGrammar.textile @@ -0,0 +1,31 @@ +h2. Template - Tag grammar + +*==${ client.name }==* +変数を評価し出力します。 + +*==${ client?.name }==* +client が null でなければ client.name を表示します。 + +*==@{ Controller.action() }==* +アクションへの相対パスの URL を計算します。 + +*==@{ Controller.action().secure() }==* +アクションへの HTTPS での相対パスの URL を計算します。 + +*==@@{ Controller.action() }==* +アクションへの絶対パスの URL を計算します。 + +*==@{'path/to/static_content'}==* +==<img src="@{'/public/images/jpdf.png'}" class="center"/>== + +*==&{ message.key }==* +メッセージは conf/messages で管理され、多言語対応しています。 + +*==∗{ これはコメントです }∗==* +これ以上何を言えばいい? + +*==%{ out.print("HelloWorld") }%==* +UI ロジック用の Groovy スクリプト + +*==#{ my.custom.tag /}==* +典型的なカスタムタグ - page コンテキストは共有しません。 diff --git a/documentation/cheatsheets_ja/templates/ch11-TemplateStandardTags.textile b/documentation/cheatsheets_ja/templates/ch11-TemplateStandardTags.textile new file mode 100644 index 0000000000..6d2fd3a7ac --- /dev/null +++ b/documentation/cheatsheets_ja/templates/ch11-TemplateStandardTags.textile @@ -0,0 +1,52 @@ +h2. Template - Standard Tags + +*==#{extends ʻpage.htmlʼ/}==* +*==#{doLayout /}==* +マスタテンプレートのデコレータ + +*==#{get 'title'}Used if title not set#{/get}==* +*==#{set title:ʻHome Pageʼ}==* +ページとマスタテンプレート間で共有される変数 + +*==#{include 'tree.html'/}==* +ページの断片をインクルードします。 - page コンテキストは共有されます。 + +*==#{script id:'myscript' , src:ʻscript.js', charset:'utf-8' /}==* +*==#{stylesheet id:'main', media:'print', src:'print.css' /}==* +スクリプトとスタイルシートをインポートします。 + +*==#{a @Application.logout() }Disconnect#{/a}==* +*==#{form @Client.create() , id:'form' enctype:'multipart/form-==* +*==data' } ... #{/form}==* +アンカーやフォームを作るのに便利なタグ + +*==#{verbatim}${'&'}#{/verbatim}==* +HTML エスケープを無効化します。 + +*==#{i18n /}==* +ローカライズされたメッセージを Javascript にエクスポートします。 + +*==#{ifErrors} <p>Error(s) found!</p> #{/ifErrors}==* +検証エラーをチェックします。 + +*==#{ifError 'user.name'} #{error 'user.name' /} #{/ifError}==* +指定されたエラーをチェックします。 + +*==#{errors} <li>${error}</li> #{/errors}==* +検証エラーを繰り返し処理します。 + +*==#{if cond}...#{/if}#{elseif cond}...#{/elseif}#{else}...#{/else}==* +*==#{ifnot cond}...#{/ifnot}==* +条件構文 + +*==#{list items:0..10, as:'i'}${i}#{/list}==* +*==#{list items:'a'..'z', as:'l'}${l} ${l_isLast ?'':'|' }#{/list}==* +*==#{list users}${_}#{/list}==* +ループ構文 + +*==#{list items:task, as:'task'}${task}#{/list}==* +*==#{else}No tasks on the list#{/else}==* +Tip: else は list と一緒に使えます。 + +*==#{cache ʻkeyʼ, for:ʼ15minʼ}...#{/cache}==* +15 分間コンテンツをキャッシュします。 diff --git a/documentation/cheatsheets_ja/templates/ch12-TemplateCustomTags.textile b/documentation/cheatsheets_ja/templates/ch12-TemplateCustomTags.textile new file mode 100644 index 0000000000..f294c25d81 --- /dev/null +++ b/documentation/cheatsheets_ja/templates/ch12-TemplateCustomTags.textile @@ -0,0 +1,8 @@ +h2. Template - Custom Tags + +*==@FastTags.Namespace("domain")==* +*==public class RecaptchaTag extends FastTags {==* +*==public static void _recaptcha(Map args, Closure body, PrintWriter out, ExecutableTemplate template, int fromLine) { …==* + +*==/app/view/tags/domain/mytag.tag==* +カスタムタグは {#domain.mytag/} で呼び出せます。 diff --git a/documentation/cheatsheets_ja/templates/ch13-TemplateGroovyExtension.textile b/documentation/cheatsheets_ja/templates/ch13-TemplateGroovyExtension.textile new file mode 100644 index 0000000000..c4f5c1643e --- /dev/null +++ b/documentation/cheatsheets_ja/templates/ch13-TemplateGroovyExtension.textile @@ -0,0 +1,77 @@ +h2. Template - Groovy extension + +*==${ ['red', 'green', 'blue'].join('/') }==* +red/green/blue + +*==${ (["red", "green", "blue"] as String[]).add('pink').join(' ') }==* +red green blue pink + +*==${ (['red', 'green', 'blue'] as String[]).contains('green') }==* +true + +*==${(['red', 'gr', 'blue'] as String[]).remove('gr').join(' ')}==* +red blue + +*==${ ['red', 'green', 'blue'].last() }==* +blue + +*==${ new Date(new Date().getTime() - 1000000).since() }==* +16 分前 + +*==${new Date(1275910970000).format('dd MMMM yyyy==* +*==hh:mm:ss')}==* +07 June 2010 01:42:50 + +*==${ 1275910970000.asdate('dd MMMM yyyy hh:mm:ss') }==* +07 June 2010 01:42:50 + +*==${726016L.formatSize()}==* +709KB + +*==${ 42.formatCurrency('EUR').raw() }==* +€ 42.00 + +*==${ 42.page(10) }==* +5 + +*==journ${ ['cnn', 'c+', 'f2'].pluralize('al', 'aux') }==* +journaux + +*==${ "lorum ipsum dolor".capAll() }==* +Lorum Ipsum Dolor + +*==${ "lorum ipsum dolor".camelCase() }==* +LorumIpsumDolor + +*==${ "lorum ipsum dolor".capFirst() }==* +Lorum ipsum dolor + +*==${ "lorum ipsum dolor".cut('um') }==* +lor ips dolor + +*==${ "The <blink>tag</blink> is evil".escape().raw() }==* +The <blink>tag</blink> is evil + +*==${ "one\ntwo".nl2br() }==* +==one<br/>two== + +*==${ '<' } ${ '<'.raw() }==* +< < + +*==${ " (') (\") ".escapeJavaScript().raw() }==* +==(\') (\")== + +*==${ "".yesno('yes', 'no') }==* +no + +*==${ "not empty".yesno('yes', 'no') }==* +yes + +*==${"Stéphane Épardaud".noAccents()}==* +Stephane Epardaud + +*==${ "The Play! frameworkʼs manual".slugify() }==* +the-play-framework-s-manual + +*==${ "x".pad(4).raw() }==* +x    diff --git a/documentation/cheatsheets_ja/tests/ch21-TestUnitTests.textile b/documentation/cheatsheets_ja/tests/ch21-TestUnitTests.textile new file mode 100644 index 0000000000..4eb1e6e1b3 --- /dev/null +++ b/documentation/cheatsheets_ja/tests/ch21-TestUnitTests.textile @@ -0,0 +1,24 @@ +h2. Test - Unit Tests + +*==@Test public void getRental() { ... }==* +*==@Test (expected = NumberFormatException.class )==* +*==@Ignore==* +全てのエラーを無視します。 + +*==@Test (timeout=400)==* +テストは 400 ミリ秒後に失敗します。 + +*==@Before public void prepareRecords();==* +各ユニットテストの前に実行されます。 + +*==@After public void cleanupJunk()==* +各ユニットテストの後に実行されます。 + +*==@BeforeClass void whenTestClassInstantiated();==* +当該テストクラスがインスタンス化されたときに一度だけ実行されます。 + +*==@AfterClass void whenTestClassCompleted()==* +当該テストクラスにある全てのテストが終了した時に一度だけ実行されます。 + +*==Assert.assert==* +たくさんのアサーションがあります。どのようなものがあるかは調べてください。 diff --git a/documentation/cheatsheets_ja/tests/ch22-TestFunctionalTests.textile b/documentation/cheatsheets_ja/tests/ch22-TestFunctionalTests.textile new file mode 100644 index 0000000000..5581c3bb35 --- /dev/null +++ b/documentation/cheatsheets_ja/tests/ch22-TestFunctionalTests.textile @@ -0,0 +1,16 @@ +h2. Test - Functional Tests + +*==public class ApplicationTest extends FunctionalTest==* +機能テストは HTTP を取り入れたユニットテストです。 + +*==newRequest()==* +*==newResponse()==* +*==GET(request, url)==* +*==PUT(request, url)==* +*==POST(request,url)==* +*==DELETE(request,url)==* +*==assertStatus(404, response)==* +*==assertHeaderEquals(headerName, value, response)==* +*==assertContentEquals(content, response)==* +*==assertContentType(type,response)==* +*==clearCookies()==* diff --git a/documentation/cheatsheets_ja/tests/ch23-TestSeleniumTests.textile b/documentation/cheatsheets_ja/tests/ch23-TestSeleniumTests.textile new file mode 100644 index 0000000000..92bf0b3f36 --- /dev/null +++ b/documentation/cheatsheets_ja/tests/ch23-TestSeleniumTests.textile @@ -0,0 +1,13 @@ +h2. Test - Selenium Tests + +*==#{selenium}==* +clearSession() +open('/admin') +assertTextPresent('Login') +type('login', 'admin') +type('password', 'secret') +clickAndWait('signin') + +// ユーザが正常にログインしているか検証します。 +assertText('success', 'Welcome admin!') +*==#{/selenium}==* diff --git a/documentation/cheatsheets_ja/tests/ch24-TestDataloader.textile b/documentation/cheatsheets_ja/tests/ch24-TestDataloader.textile new file mode 100644 index 0000000000..9b40db43c4 --- /dev/null +++ b/documentation/cheatsheets_ja/tests/ch24-TestDataloader.textile @@ -0,0 +1,9 @@ +h2. Test - Data loader + +*==@Before public void setUp() { Fixtures.deleteAll();==* +*==Fixtures.loadModels("data.yml");}==* +Fixtures はユニットテストを実行する前にデータストアを初期化するために使用します。 + +*==#{fixture delete:'all', load:'data.yml' /}==* or *==#{fixture delete:'all', loadModels:'data.yml' /}==* +*==#{selenium} ... #{/selenium}==* +Selenium テストを使うときも同様に出来ます(@loadModels@ and @load@ parameters are synonyms).。 diff --git a/documentation/commands/cmd-auto-test.txt b/documentation/commands/cmd-auto-test.txt index f7f59619ce..6003f4fae1 100644 --- a/documentation/commands/cmd-auto-test.txt +++ b/documentation/commands/cmd-auto-test.txt @@ -1,10 +1,14 @@ ~ Name: ~ ~~~~~ ~ auto-test -- Automatically run all application tests -~ +~ +~ Alias: +~ ~~~~~ +~ autotest +~ ~ Synopsis: ~ ~~~~~~~~~ -~ play auto-test [app_path] [--deps] +~ play auto-test [app_path] [--deps] [--unit] [--functional] [--selenium] [--timeout=xxx] ~ ~ Description: ~ ~~~~~~~~~~~~ @@ -18,7 +22,7 @@ ~ The script first tries to locate the java command using the $JAVA_HOME environment variable (from $JAVA_HOME/bin). ~ If the $JAVA_HOME variable is not defined, the default java command available from the PATH is used. ~ -~ All options specified after the application path are passed to the JVM (example. -Xms=64m -Xmx=256m). +~ All options specified after the application path are passed to the JVM (example. -Xms64m -Xmx256m). ~ All the shell environment variables are passed to the JVM. ~ ~ The script tries to run a browser using the OS default registred web browser. If the application has compilation @@ -35,4 +39,11 @@ ~ ~~~~~~~~~ ~ --deps: ~ Resolve and install dependencies before running the command. -~ \ No newline at end of file +~ --unit: +~ Run the unit tests. +~ --functional: +~ Run the functional tests. +~ --selenium: +~ Run the selenium tests. +~ --timeout=xxx: +~ Specify the timeout for the webclient (the value of the timeout in milliseconds, 0 for an infinite wait) \ No newline at end of file diff --git a/documentation/commands/cmd-dependencies.txt b/documentation/commands/cmd-dependencies.txt index 428cb6702f..84165b1718 100644 --- a/documentation/commands/cmd-dependencies.txt +++ b/documentation/commands/cmd-dependencies.txt @@ -8,16 +8,25 @@ ~ ~ Synopsis: ~ ~~~~~~~~~ -~ play dependencies [app_path] [--verbose] [--debug] [--sync] [--%fwk_id] +~ play dependencies [app_path] [--verbose] [--debug] [--sync] [--%fwk_id] [--forProd] [--clearcache] [--jpda] ~ ~ 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. +~ +~ Some options specified after the application path are passed to the JVM : java memory( example. -Xms64m -Xmx256m). +~ If JVM memory options (starting with -Xm') are not specified in command line arguments, then any +~ arguments in the conf/application.conf file's jvm.memory property are passed to the JVM. +~ All the shell environment variables are passed to the JVM. +~ +~ If the option [--jpda] is present, a JPDA session is automatically opened on the port specified by the +~ conf/application.conf file's jpda.port property (defaulting to 8000). If the JPDA port is already in use, +~ another available port is automatically chosen. ~ ~ Options: ~ ~~~~~~~~ @@ -25,7 +34,7 @@ ~ Verbose mode ~ ~ --debug: -~ Debug mode (even more informations logged than in verbose mode) +~ Debug mode (even more information logged than in verbose mode) ~ ~ --jpda: ~ Listen for JPDA connection. The process will be suspended until @@ -33,8 +42,19 @@ ~ ~ --sync: ~ Keep lib/ and modules/ directory synced. -~ Delete unknow dependencies. +~ Delete unknown 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) -~ \ No newline at end of file +~ +~ --forProd +~ Removes the module directories that are not necessary for production +~ deployment: documentation/, src/, tmp/, *sample*/ and *test*/ +~ +~~ --clearcache: +~ Clear the ivy cache (equivalent to rm -r ~/.ivy2/cache) +~ diff --git a/documentation/commands/cmd-evolutions.txt b/documentation/commands/cmd-evolutions.txt new file mode 100644 index 0000000000..6698714b5b --- /dev/null +++ b/documentation/commands/cmd-evolutions.txt @@ -0,0 +1,40 @@ +~ Name: +~ ~~~~~ +~ evolutions -- Run the evolution check +~ evolutions:apply -- Automatically apply pending evolutions +~ evolutions:markApplied -- Mark pending evolutions as manually applied +~ evolutions:resolve -- Resolve partially applied evolution +~ +~ Alias: +~ ~~~~~ +~ ev +~ +~ Synopsis: +~ ~~~~~~~~~ +~ play evolutions [app_path] [--jpda] +~ play evolutions:apply [app_path] [--jpda] +~ play evolutions:markApplied [app_path] [--jpda] +~ play evolutions:resolve [app_path] [--jpda] +~ +~ Description: +~ ~~~~~~~~~~~~ +~ Track and organize your database schema evolutions evolutions +~ Play tracks your database evolutions using several "evolutions script". +~ These scripts are written in plain old SQL and should be located in the db/evolutions directory of your application. +~ +~ All options specified after the application path are passed to the JVM (example. -Xms64m -Xmx256m). +~ If JVM memory options (starting with -Xm') are not specified in command line arguments, then any +~ arguments in the conf/application.conf file's jvm.memory property are passed to the JVM. +~ All the shell environment variables are passed to the JVM. +~ +~ If the option [--jpda] is present, a JPDA session is automatically opened on the port specified by the +~ conf/application.conf file's jpda.port property (defaulting to 8000). If the JPDA port is already in use, +~ another available port is automatically chosen. +~ +~ Options: +~ ~~~~~~~~ +~ +~ --jpda: +~ Listen for JPDA connection. The process will be suspended until +~ a client is plugged to the JPDA port. +~ diff --git a/documentation/commands/cmd-install.txt b/documentation/commands/cmd-install.txt index 6a908cc9a1..4be7e74a94 100644 --- a/documentation/commands/cmd-install.txt +++ b/documentation/commands/cmd-install.txt @@ -4,13 +4,13 @@ ~ ~ Synopsis: ~ ~~~~~~~~~ -~ play install [module_name] +~ play install [module_name] [--force-server] ~ ~ Description: ~ ~~~~~~~~~~~~ ~ Modules are 3rd party software to extend Play's capabilities. ~ Available modules are listed with the play list-modules command, -~ or online at http://www.playframework.org/modules +~ or online at https://www.playframework.com/modules ~ ~ By default only the official repository is used, but you can add ~ other repositories (for example your ozn internal repository) by @@ -19,4 +19,8 @@ ~ ~ Once installed, Play modules can be included in any Play application ~ by adding the correct line to conf/application.conf. -~ \ No newline at end of file +~ +~ Options: +~ ~~~~~~~~~ +~ --force-server: +~ Specify just one custom repository for module installation. \ No newline at end of file diff --git a/documentation/commands/cmd-javadoc.txt b/documentation/commands/cmd-javadoc.txt index b705cf455e..c8c46b543b 100644 --- a/documentation/commands/cmd-javadoc.txt +++ b/documentation/commands/cmd-javadoc.txt @@ -8,11 +8,18 @@ ~ ~ Synopsis: ~ ~~~~~~~~~ -~ play javadoc [app_path] +~ play javadoc [app_path] [additional_args] [--links] ~ ~ Description: ~ ~~~~~~~~~~~~ ~ Generate the Javadoc for your project, not including Play classes. Resulting ~ html file will be in a javadoc folder in your application directory. The -~ commands' logs are logs/javadoc.log and logs/javadoc.err. +~ commands' logs are logs/javadoc.log and logs/javadoc.err. Any additonal +~ arguments provided are passed to the javadoc command. +~ + +~ Options: +~ ~~~~~~~~ +~ --links: +~ Build Javadoc with link to JAVA Api and the play framework API ~ diff --git a/documentation/commands/cmd-restart.txt b/documentation/commands/cmd-restart.txt index 36a7de861e..ec209e9971 100644 --- a/documentation/commands/cmd-restart.txt +++ b/documentation/commands/cmd-restart.txt @@ -16,7 +16,7 @@ ~ The script first tries to locate the java command using the $JAVA_HOME environment variable (from $JAVA_HOME/bin). ~ If the $JAVA_HOME variable is not defined, the default java command available from the PATH is used. ~ -~ All options specified after the application path are passed to the JVM (example. -Xms=64m -Xmx=256m). +~ All options specified after the application path are passed to the JVM (example. -Xms64m -Xmx256m). ~ All the shell environment variables are passed to the JVM. ~ ~ The PID of the created Java process is written to the server.pid file. diff --git a/documentation/commands/cmd-run.txt b/documentation/commands/cmd-run.txt index 7513a80d20..4bd5e9af5b 100644 --- a/documentation/commands/cmd-run.txt +++ b/documentation/commands/cmd-run.txt @@ -4,7 +4,7 @@ ~ ~ Synopsis: ~ ~~~~~~~~~ -~ play run [app_path] [--deps] [--%fwk_id] [-f] [--java_options] +~ play run [app_path] [-f] [--deps] [--%fwk_id] [--http[s].port=] [--jpda.port=] [--java_options] ~ ~ Description: ~ ~~~~~~~~~~~~ @@ -14,7 +14,7 @@ ~ The script first tries to locate the java program using the $JAVA_HOME environment variable (from $JAVA_HOME/bin). ~ If the $JAVA_HOME variable is not defined, the default java command available from the PATH is used. ~ -~ All options specified after the application path are passed to the JVM (example. -Xms=64m -Xmx=256m). +~ All options specified after the application path are passed to the JVM (example. -Xms64m -Xmx256m). ~ If JVM memory options (starting with -Xm') are not specified in command line arguments, then any ~ arguments in the conf/application.conf file's jvm.memory property are passed to the JVM. ~ All the shell environment variables are passed to the JVM. @@ -34,3 +34,14 @@ ~ --deps: ~ Resolve and install dependencies before running the command. ~ +~ --silent: +~ Suppress output of the Play ASCII art logo and framework version information. +~ +~ --http.port=: +~ Override the http.port and %fwk_id.http.port variables in application.conf, and listen to the specified http port +~ +~ --https.port=: +~ Override the https.port and %fwk_id.https.port variables in application.conf, and listen to the specified https port +~ +~ --jpda.port=: +~ Override the jpda.port and %fwk_id.jpda.port variables in application.conf. Use the specified port () as the remote debugging port for the application. Can be combined with the option -f diff --git a/documentation/commands/cmd-start.txt b/documentation/commands/cmd-start.txt index 0908464176..c14fce0423 100644 --- a/documentation/commands/cmd-start.txt +++ b/documentation/commands/cmd-start.txt @@ -4,7 +4,7 @@ ~ ~ Synopsis: ~ ~~~~~~~~~ -~ play start [app_path] [--deps] [--%fwk_id] [--java_options] +~ play start [app_path] [-f] [--deps] [--%fwk_id] [--http[s].port=] [--jpda.port=] [--pid_file=] [--java_options] [--jvm_version=] ~ ~ Description: ~ ~~~~~~~~~~~~ @@ -14,7 +14,7 @@ ~ The script first tries to locate the java command using the $JAVA_HOME environment variable (from $JAVA_HOME/bin). ~ If the $JAVA_HOME variable is not defined, the default java command available from the PATH is used. ~ -~ All options specified after the application path are passed to the JVM (example. -Xms=64m -Xmx=256m). +~ All options specified after the application path are passed to the JVM (example. -Xms64m -Xmx256m). ~ All the shell environment variables are passed to the JVM. ~ ~ The PID of the created Java process is written to the server.pid file. @@ -25,9 +25,26 @@ ~ ~ Options: ~ ~~~~~~~~~ +~ -f: +~ Disable the JPDA port checking and force the jpda.port value. +~ ~ --%fwk_id: ~ Use this ID to run the application (override the default framework ID) ~ ~ --deps: ~ Resolve and install dependencies before running the command. -~ \ No newline at end of file +~ +~ --pid_file=: +~ Specify where to write the process id (pid) of the background server process. +~ +~ --http.port=: +~ Override the http.port and %fwk_id.http.port variables in application.conf, and listen to the specified http port +~ +~ --https.port=: +~ Override the https.port and %fwk_id.https.port variables in application.conf, and listen to the specified https port +~ +~ --jpda.port=: +~ Override the jpda.port and %fwk_id.jpda.port variables in application.conf. Use the specified port () as the remote debugging port for the application. Can be combined with the option -f +~ +~ --jvm_version=: +~ Specify which version of JVM runs the application. If omitted, the script will spawn a sub-process to obtain the value. This will have repercussions in environments where forking behaviour needs to be deterministic (e.g. Upstart). diff --git a/documentation/commands/cmd-status.txt b/documentation/commands/cmd-status.txt index 2969bbbf40..9ccf8ceb12 100644 --- a/documentation/commands/cmd-status.txt +++ b/documentation/commands/cmd-status.txt @@ -13,7 +13,7 @@ ~ Description: ~ ~~~~~~~~~~~~ ~ This script tries to connect to the running application's /@status URL to request the application status. -~ The application status contains useful informations about the running application. +~ The application status contains useful information about the running application. ~ ~ The status command is aimed at monitoring applications running on production servers. ~ diff --git a/documentation/commands/cmd-test.txt b/documentation/commands/cmd-test.txt index aecaf8dad7..9e0070c331 100644 --- a/documentation/commands/cmd-test.txt +++ b/documentation/commands/cmd-test.txt @@ -20,7 +20,7 @@ ~ The script first tries to locate the java command using the $JAVA_HOME environment variable (from $JAVA_HOME/bin). ~ If the $JAVA_HOME variable is not defined, the default java command available from the PATH is used. ~ -~ All options specified after the application path are passed to the JVM (example. -Xms=64m -Xmx=256m). +~ All options specified after the application path are passed to the JVM (example. -Xms64m -Xmx256m). ~ All the shell environment variables are passed to the JVM. ~ ~ A JPDA session is automatically opened on the port specified by the conf/application.conf file's jpda.port property diff --git a/documentation/commands/cmd-version.txt b/documentation/commands/cmd-version.txt new file mode 100644 index 0000000000..8bbfb0baed --- /dev/null +++ b/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. +~ diff --git a/documentation/files/jquery.tools-1.2.5.toolbox.expose.min.js b/documentation/files/jquery.tools-1.2.5.toolbox.expose.min.js new file mode 100644 index 0000000000..3e00b66b28 --- /dev/null +++ b/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("
").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); diff --git a/documentation/files/main.css b/documentation/files/main.css index e1d52c7287..baad7112da 100644 --- a/documentation/files/main.css +++ b/documentation/files/main.css @@ -60,7 +60,7 @@ body { -moz-border-radius: 16px; } -/** A little hacky to create arrows without images **/ +/** A little hack to create arrows without images **/ .about { text-indent: -999em; display: block; diff --git a/documentation/images/chat-websocket.png b/documentation/images/chat-websocket.png new file mode 100644 index 0000000000..c54800e1bc Binary files /dev/null and b/documentation/images/chat-websocket.png differ diff --git a/documentation/images/cheatsheet.png b/documentation/images/cheatsheet.png new file mode 100644 index 0000000000..72ace0750e Binary files /dev/null and b/documentation/images/cheatsheet.png differ diff --git a/documentation/images/dependencies.png b/documentation/images/dependencies.png new file mode 100644 index 0000000000..8d753a1892 Binary files /dev/null and b/documentation/images/dependencies.png differ diff --git a/documentation/images/eclipse-test-runner.png b/documentation/images/eclipse-test-runner.png new file mode 100644 index 0000000000..e858dac19a Binary files /dev/null and b/documentation/images/eclipse-test-runner.png differ diff --git a/documentation/images/evolutions-conflict.png b/documentation/images/evolutions-conflict.png new file mode 100644 index 0000000000..f1bd00e8db Binary files /dev/null and b/documentation/images/evolutions-conflict.png differ diff --git a/documentation/images/evolutions-inconsistent.png b/documentation/images/evolutions-inconsistent.png new file mode 100644 index 0000000000..6d9a565455 Binary files /dev/null and b/documentation/images/evolutions-inconsistent.png differ diff --git a/documentation/images/evolutions-resolve.png b/documentation/images/evolutions-resolve.png new file mode 100644 index 0000000000..bf225751a7 Binary files /dev/null and b/documentation/images/evolutions-resolve.png differ diff --git a/documentation/images/evolutions.png b/documentation/images/evolutions.png new file mode 100644 index 0000000000..cc680d8c4c Binary files /dev/null and b/documentation/images/evolutions.png differ diff --git a/documentation/images/guide9-2.png b/documentation/images/guide9-2.png index 1c382ca198..5fe17c1229 100644 Binary files a/documentation/images/guide9-2.png and b/documentation/images/guide9-2.png differ diff --git a/documentation/images/h2console.png b/documentation/images/h2console.png new file mode 100644 index 0000000000..377e085b6a Binary files /dev/null and b/documentation/images/h2console.png differ diff --git a/documentation/manual/5things.textile b/documentation/manual/5things.textile index 2ee28309bd..6a84ad0e46 100644 --- a/documentation/manual/5things.textile +++ b/documentation/manual/5things.textile @@ -10,7 +10,7 @@ For example, with this request: bc. /articles/archive?date=08/01/08&page=2 -You can retrieve the **date** and **page** parameters by declaring them as Java method parameters: +You can retrieve the @date@ and @page@ parameters by declaring them as Java method parameters: bc. public static void archive(Date date, Integer page) { List
articles = Articles.fromArchive(date, page); @@ -42,14 +42,14 @@ bc.
h2. Redirect to an action by calling the corresponding Java method -There is no equivalent to the Java Servlet **forward** command with Play. But redirecting to another action is really simple. Just call the corresponding Java method and Play will generate the correct HTTP ‘Redirect’ response. +There is no equivalent to the Java Servlet @forward@ command with Play. But redirecting to another action is really simple. Just call the corresponding Java method and Play will generate the correct HTTP ‘redirect’ response. bc. public static void show(Long id) { Article article = Article.findById(id); render(article); } -public static void edit(Long id, String title) { +bc. public static void edit(Long id, String title) { Article article = Article.findById(id); article.title = title; article.save(); @@ -91,7 +91,7 @@ h2. JPA on steroids JPA is surely the best object-relational mapping (ORM) API available for Java. If you know it you will be amazed how much simpler it becomes with Play. With nothing to configure, Play will automatically start the JPA Entity Manager using Hibernate and magically synchronize it when code is reloaded. -Moreover, if you use the provided **play.db.jpa.Model** superclass it will help to make your code prettier. Take a look: +Moreover, if you use the provided @play.db.jpa.Model@ superclass it will help to make your code prettier. Take a look: bc. public void messages(int page) { User connectedUser = User.find("byEmail", connected()).first(); @@ -108,11 +108,11 @@ File upload management is very simple with Play. The HTML form: -bc. +bc. #{form @uploadPhoto(), enctype:'multipart/form-data'} -
+#{/} And the Java code: diff --git a/documentation/manual/ajax.textile b/documentation/manual/ajax.textile index ed36a8ce96..6b94c85714 100644 --- a/documentation/manual/ajax.textile +++ b/documentation/manual/ajax.textile @@ -1,13 +1,12 @@ h1. Ajax in the Play framework -The Play framework allows you to easily perform Ajax requests, and is shipped with "jQuery":http://jquery.com by default. -This section describes how to effectively use "jQuery":http://jquery.com within the framework. +The Play framework allows you to easily perform Ajax requests, and is shipped with "jQuery":http://jquery.com by default. This section describes how to effectively use "jQuery":http://jquery.com within the framework. -The Play framework also comes with the handy **jsAction** tag to transparently get a method definition from the controller. +The Play framework also comes with the handy "jsAction":tags#jsaction tag to transparently get a method definition from the controller. h2. Using jQuery with the jsAction tag -The **#{jsAction /}** tag returns a JavaScript function which constructs a URL based on a server action and free variables. It does not perform an AJAX request; these have to be done by hand using the returned URL. +The @#{jsAction /}@ tag returns a JavaScript function which constructs a URL based on a server action and free variables. It does not perform an AJAX request; these have to be done by hand using the returned URL. Let's see an example: @@ -17,12 +16,15 @@ Now you can import this route client side: bc. -In this example we are requesting the list method from the default Application controller. We are also passing three parameters: search, size and page. The request we perform is then saved into the listAction variable. Now using jQuery and the **load** function we are performing a request (an HTTP GET request in fact). +In this example we are requesting the list method from the default Application controller. We are also passing three parameters: search, size and page. The request we perform is then saved into the listAction variable. Now using jQuery and the @load@ function we are performing a request (an HTTP GET request in fact). In fact, the following request is sent: @@ -36,7 +38,31 @@ Please refer to the "jQuery":http://docs.jquery.com/Main_Page documentation for Also please note that we could perform a POST; the jQuery method should then be changed to: -bc. $.post(listAction, function(data) { +bc. $.post(listAction(), function(data) { $('#result').html(data); }); +h2. Using jQuery with the jsRoute tag + +To have more control on the generated route, you also have the "jsRoute":tags#jsroute tag, which is similar to the **#{jsAction /}** tag but it returns an object containing both the function which consctructs the URL based on the server action, and the corresponding HTTP method (GET, POST, etc.). + +Example: + +bc. PUT /users/{id} Users.update + +Then, in a template: + +bc. + +With this approach, you won't have to update your templates in case you decide to change the HTTP method in the routes file. + +p(note). **Continuing the discussion** + +Handle %(next)"Internationalization":i18n%. diff --git a/documentation/manual/asynchronous.textile b/documentation/manual/asynchronous.textile new file mode 100644 index 0000000000..9cc3d7beba --- /dev/null +++ b/documentation/manual/asynchronous.textile @@ -0,0 +1,199 @@ +h1. Asynchronous programming with HTTP + +This section explains how to deal with asynchronism in a Play application to achieve typical long-polling, streaming and other "Comet-style":http://en.wikipedia.org/wiki/Comet_(programming%29 applications that can scale to thousands of concurrent connections. + +h2. Suspending HTTP requests + +Play is intended to work with very short requests. It uses a fixed thread pool to process requests queued by the HTTP connector. To get optimum results, the thread pool should be as small as possible. We typically use the optimum value of @number of processors + 1@ to set the default pool size. + +That means that if a request’s processing time is very long (for example waiting for a long computation) it will block the thread pool and penalise your application’s responsiveness. Of course you could add more threads to the pool, but that would result in wasted resources, and anyway the pool size will never be infinite. + +Consider a chat application where browsers send a blocking HTTP request that waits for a new message to display. These requests can be very very long (typically several seconds) and will block the thread pool. If you plan to allow 100 users to connect simultaneously to your chat application you will need to provision at least 100 threads. OK it’s feasible. But what about 1,000 users? Or 10,000? + +To resolve these use cases, Play allows you to temporarily suspend a request. The HTTP request will stay connected, but the request execution will be popped out of the thread pool and tried again later. You can either tell Play to try the request later after a fixed delay, or wait for a @Promise@ value to be available. + +p(note). **Tip**. You can see a real example in @samples-and-tests/chat@. + +For example, this action will launch a very long job and wait for its completion before returning the result to the HTTP response: + +bc. public static void generatePDF(Long reportId) { + Promise pdf = new ReportAsPDFJob(report).now(); + InputStream pdfStream = await(pdf); + renderBinary(pdfStream); +} + +Here we use @await(…)@ to ask Play to suspend the request until the @Promise@ value is redeemed. + +h3. Continuations + +Because the framework needs to recover the thread you were using in order to use it to serve other requests, it has to suspend your code. In the previous Play version the @await(…)@ equivalent was @waitFor(…)@, which suspended your action, and then called it again later from the beginning. + +To make it easier to deal with asynchronous code we have introduced continuations. Continuations allow your code to be suspended and resumed transparently. So you write your code in a very imperative way, as: + +bc. public static void computeSomething() { + Promise delayedResult = veryLongComputation(…); + String result = await(delayedResult); + render(result); +} + +In fact here, your code will be executed in two steps, in two different threads. But as you see, it is transparent to your application code. + +Using @await(…)@ and continuations, you could write a loop: + +bc. public static void loopWithoutBlocking() { + for(int i=0; i<=10; i++) { + Logger.info(i); + await("1s"); + } + renderText("Loop finished"); +} + +While using only one thread to process requests, the default in development mode, Play is able to run concurrently these loops for several requests at the same time. + +A more realistic example is asynchronously fetching content from remote URLs. The following example, performs three remote HTTP requests in parallel: each call to the @play.libs.WS.WSRequest.getAsync()@ method executes a GET request asynchronously and returns a @play.libs.F.Promise@. The action method suspends the incoming HTTP request by calling @await(…)@ on the combination of the three @Promise@ instances. When all three remote calls have a response, a thread will resume processing and render a response. + +bc. public class AsyncTest extends Controller { + + public static void remoteData() { + F.Promise r1 = WS.url("http://example.org/1").getAsync(); + F.Promise r2 = WS.url("http://example.org/2").getAsync(); + F.Promise r3 = WS.url("http://example.org/3").getAsync(); + + F.Promise> promises = F.Promise.waitAll(r1, r2, r3); + + // Suspend processing here, until all three remote calls are complete. + List httpResponses = await(promises); + + render(httpResponses); + } +} + + +h3. Callbacks + +A different way to implement the previous example of three asynchronous remote requests is to use a callback. This time, the call to @await(…)@ includes a @play.libs.F.Action@ implementation, which is a callback that is executed when the @promises@ are done. + +bc. public class AsyncTest extends Controller { + + public static void remoteData() { + F.Promise r1 = WS.url("http://example.org/1").getAsync(); + F.Promise r2 = WS.url("http://example.org/2").getAsync(); + F.Promise r3 = WS.url("http://example.org/3").getAsync(); + + F.Promise> promises = F.Promise.waitAll(r1, r2, r3); + + // Suspend processing here, until all three remote calls are complete. + await(promises, new F.Action>() { + public void invoke(List httpResponses) { + render(httpResponses); + } + }); + } +} + + +h2. HTTP response streaming + +Now that you can loop without blocking the request, you may want to send data to the browser as soon you have part of the result available. That is the point of the @Content-Type:Chunked@ HTTP response type. It allows to send your HTTP response several times using multiples chunks. The browser will receive these chunks as soon as they are published. + +Using @await(…)@ and continuations, you can now achieve that using: + +bc. public static void generateLargeCSV() { + CSVGenerator generator = new CSVGenerator(); + response.contentType = "text/csv"; + while(generator.hasMoreData()) { + String someCsvData = await(generator.nextDataChunk()); + response.writeChunk(someCsvData); + } +} + +Even if the CSV generation takes one hour, Play is able to simultaneously process several requests using a single thread, sending back the generated data to the client as soon as they are available. + + +h2. Using WebSockets + +WebSockets are a way to open a two-way communication channel between a browser and your application. On the browser side, you open a socket using a "ws://" url: + +bc. new Socket("ws://localhost:9000/helloSocket?name=Guillaume") + +On the Play side you declare a WS route: + +bc. WS /helloSocket MyWebSocket.hello + +@MyWebSocket@ is a @WebSocketController@. A WebSocket controller is like a standard HTTP controller but handles different concepts. + +* It has a request object, but no response object. +* It has access to the session, but read-only. +* It doesn't have @renderArgs@, @routeArgs@ or flash scope. +* It can read params only from the route pattern or from the QueryString. +* It has two communication channels: inbound and outbound. + +When the client connects to the @ws://localhost:9000/helloSocket@ socket, Play will invoke the @MyWebSocket.hello@ action method. Once the @MyWebSocket.hello@ action method exits, the socket is closed. + +So a very basic socket example would be: + +bc. public class MyWebSocket extends WebSocketController { + + public static void hello(String name) { + outbound.send("Hello %s!", name); + } +} + +In this example, when the client connects to the socket, it receives the ‘Hello Guillaume’ message, and then Play closes the socket. + +Of course usually you don’t want to close the socket immediately. But it is easy to achieve using @await(…)@ and continuations. + +For example a basic Echo server: + +bc. public class MyWebSocket extends WebSocketController { + + public static void echo() { + while(inbound.isOpen()) { + WebSocketEvent e = await(inbound.nextEvent()); + if(e instanceof WebSocketFrame) { + WebSocketFrame frame = (WebSocketFrame)e; + if(!e.isBinary) { + if(frame.textData.equals("quit")) { + outbound.send("Bye!"); + disconnect(); + } else { + outbound.send("Echo: %s", frame.textData); + } + } + } + if(e instanceof WebSocketClose) { + Logger.info("Socket closed!"); + } + } + } + +} + +In this example, the nested ‘if’ and ‘cast’ soup was tedious to write and error prone. And here Java sucks. Even for this simple case it is not easy to handle. And for more complicated cases where you combine several streams and have even more event types, it becomes a nightmare. + +That’s why we have introduced a basic pattern matching in Java in the "play.libs.F":libs#FunctionalprogrammingwithJava library. + +So with some static imports, we can rewrite our previous echo sample as: + +bc. public static void echo() { + while(inbound.isOpen()) { + WebSocketEvent e = await(inbound.nextEvent()); + + for(String quit: TextFrame.and(Equals("quit")).match(e)) { + outbound.send("Bye!"); + disconnect(); + } + + for(String message: TextFrame.match(e)) { + outbound.send("Echo: %s", message); + } + + for(WebSocketClose closed: SocketClosed.match(e)) { + Logger.info("Socket closed!"); + } + } +} + +p(note). **Continuing the discussion** + +Next, doing %(next)"Ajax requests":ajax%. \ No newline at end of file diff --git a/documentation/manual/cache.textile b/documentation/manual/cache.textile index 86a9b08a8a..4f78a1a7ae 100644 --- a/documentation/manual/cache.textile +++ b/documentation/manual/cache.textile @@ -1,12 +1,20 @@ h1. Use a cache -To create high-performance systems, sometimes you need to cache data. Play has a cache library and will use "Memcached":http://www.danga.com/memcached/ when used in a distributed environment. +To create high-performance systems, sometimes you need to cache data. -If you don’t configure Memcached, Play will use a standalone cache that stores data in the JVM heap. Caching data in the JVM application breaks the "share nothing" assumption made by Play: you can’t run your application on several servers, and expect the application to behave consistently. Each application instance will have a different copy of the data. +Play provides a pluggable cache infrastructure that you may use via: +* the @cacheFor@ annotation your actions +* the cache API -It is important to understand that the cache contract is clear: when you put data in a cache, you can’t expect that data to remain there forever. In fact you shouldn’t. A cache is fast, but values expire, and the cache generally exists only in memory (without persistent backup). +h2. Configuration -So the best way to use the cache is to repopulate it when it doesn’t have what you expect: +Play will use "Memcached":http://www.danga.com/memcached/ when used in a distributed environment. + +If you don’t configure Memcached, Play will use a standalone cache that stores data in the JVM heap. Caching data in the JVM application breaks the ‘share nothing’ assumption made by Play: you can’t run your application on several servers, and expect the application to behave consistently. Each application instance will have a different copy of the data. + +It is important to understand that the cache contract is clear: when you put data in a cache, you can’t expect that data to remain there forever. In fact you shouldn't. A cache is fast, but values expire, and the cache generally exists only in memory (without persistent backup). + +So the best way to use the cache is to repopulate it when it doesn't have what you expect: bc. public static void allProducts() { List products = Cache.get("products", List.class); @@ -17,9 +25,40 @@ bc. public static void allProducts() { render(products); } +p(note). **Modules might configure the cache for you** + +For example, the "GAE module":https://www.playframework.com/modules/gae configures the cache to use "Google cache":https://developers.google.com/appengine/docs/java/memcache when deployed to the GAE servers. + +h2. The cacheFor annotation + +The @cacheFor@ annotation provides an easy way to cache the output (HTML, JSON, etc) of an action. + +bc. import play.cache.*; +public class Application extends Controller { + @CacheFor("5s") + public static void indexCachedInSeconds() { + Date date = new Date(); + renderText("Current time is: " + date); + } + @CacheFor("1min") + public static void indexCachedInMinutes() { + Date date = new Date(); + renderText("Current time is: " + date); + } +} + +The annotation takes an optional @duration@ parameter to specify the expiration of the cached content. +The expiration can be specified in days, hours, minutes or seconds using respectively the following shorthands @d@, @h@, @min@ or @s@. + +The *default expiration* is @1h@. + +p(note). **Using expiration 0s** + +When specifying expiration == "0s" (zero seconds) the actual expiration-time may vary between different cache implementations + h2. The cache API -The cache API is provided by the **play.cache.Cache** class. This class contains the set of methods to set, replace, and get data from the cache. Refer to the Memcached documentation to understand the exact behavior of each method. +The cache API is provided by the @play.cache.Cache@ class. This class contains the set of methods to set, replace, and get data from the cache. Refer to the Memcached documentation to understand the exact behavior of each method. Some examples: @@ -35,7 +74,7 @@ bc. public static void showProduct(String id) { public static void addProduct(String name, int price) { Product product = new Product(name, price); product.save(); - showProduct(id); + showProduct(product.id); } public static void editProduct(String id, String name, int price) { @@ -53,13 +92,13 @@ public static void deleteProduct(String id) { allProducts(); } -Some methods start with the **safe** prefix - e.g. **safeDelete**, **safeSet**. The standard methods are non-blocking. That means that when you issue the call: +Some methods start with the @safe@ prefix - e.g. @safeDelete@, @safeSet@. The standard methods are non-blocking. That means that when you issue the call: bc. Cache.delete("product_" + id); -The **delete** method will return immediately and will not wait until the cached object is actually deleted. So if an error occurs - e.g. an IO error - the object may still be present. +The @delete@ method will return immediately and will not wait until the cached object is actually deleted. So if an error occurs - e.g. an IO error - the object may still be present. -When you need to make sure that the object is deleted before continuing, you can use the **safeDelete** method: +When you need to make sure that the object is deleted before continuing, you can use the @safeDelete@ method: bc. Cache.safeDelete("product_" + id); @@ -70,7 +109,9 @@ bc. if(!Cache.safeDelete("product_" + id)) { } ... -Note that those being blocking calls, **safe** methods will slow down your application. So use them only when needed. +Note that those being blocking calls, @safe@ methods will slow down your application. So use them only when needed. + +Also note that when specifying @expiration == "0s"@ (zero seconds) the actual expiration-time may vary between different cache implementations. h2. Don’t use the Session as a cache! @@ -94,17 +135,8 @@ Here we have used a unique UUID to keep unique information in the Cache for each h2. Configure memcached -When you want to enable a real Memcached implementation, enable Memcached and define the daemon address in your **application.conf**: - -bc. memcached=enabled -memcached.host=127.0.0.1:11211 - -You can connect to a distributed cache by specifying multiple daemon addresses: - -bc. memcached=enabled -memcached.1.host=127.0.0.1:11211 -memcached.2.host=127.0.0.1:11212 +When you want to enable a real Memcached implementation, enable Memcached with the "memcached configuration":configuration#memcached and define the daemon address in the "memcached.host configuration":configuration#memcached.host. p(note). **Continuing the discussion** -Now we’ll check how to perform operations outside any HTTP request using %(next)"Jobs":jobs%. +Learn about %(next)"Sending emails":emails%. diff --git a/documentation/manual/configuration.textile b/documentation/manual/configuration.textile new file mode 100644 index 0000000000..253c098fa1 --- /dev/null +++ b/documentation/manual/configuration.textile @@ -0,0 +1,1104 @@ +h1. Configuration parameters + +Configure your Play application by setting values for configuration keys in @conf/application.conf@ file. See also: + +* "Main concepts - the conf directory":main#conf +* "Managing application.conf in several environments":ids +* "Put your application in production":production + +h2(#application). Application configuration + + +h3(#application.baseUrl). application.baseUrl + +The application base URL used for reverse-resolving absolute URLs. This is used by the @@{..} template syntax and in Jobs, which do not have an inbound @Http.Request@), such as rendering e-mail. For example, for @dev@ mode: + +bc. application.baseUrl=http://localhost:9000/ + +For @prod@ mode: + +bc. %production.application.baseUrl=http://www.yourdomain.com/ + + +h3(#application.defaultCookieDomain). application.defaultCookieDomain + +Enables session/cookie sharing between subdomains. For example, to make cookies valid for all domains ending with ‘.example.com’, e.g. @foo.example.com@ and @bar.example.com@: + +bc. application.defaultCookieDomain=.example.com + +Default: a cookie is only valid for a specific domain. + + +h3(#application.forceSecureReverseRoutes). application.forceSecureReverseRoutes + +Forces all reverse routes and controller action redirection to be secure (https). This is useful for SSL-enabled apps to prevent insecure (http) url generation when redirecting to another action from inside your controllers, or when using the @{..} template syntax. For example: + +bc. application.forceSecureReverseRoutes=true + +Default: @false@ + + +h3(#application.lang.cookie). application.lang.cookie + +The name of the cookie that is used to store the current language, set by @play.i18n.Lang.change(String locale)@, which you can change if you want separate language settings for separate Play applications. For example: + +bc. application.lang.cookie=MYAPP_LANG + +Default: @PLAY_LANG@ + + +h3(#application.langs). application.langs + +Defines locales used by your application. You can then place localised messages in @conf/messages.{locale}@ files. The value is a comma-separated list of language codes, for example: + +bc. application.langs=fr,en,ja + +Default: no additional languages. + + +h3(#application.log). application.log + +Specifies log level for your application. For example: + +bc. application.log=DEBUG + +Default: @INFO@ + +See also: "Logging configuration":logs. + + +h3(#application.log.path). application.log.path + +Path to a Log4J configuration file, to customise log output. If you do not specify a path, Play will load a @log4j.properties@ file in the @conf@ directory if present. + +bc. application.log.path=/log4j.properties + +Default: @/log4j.xml@ falling back to @/log4j.properties@ + + +h3(#application.log.recordCaller). application.log.recordCaller + +Configures the value of @play.Logger.recordCaller@ to record and display the caller method. For example: + +bc. application.log.recordCaller=true + +Default: @false@ + + +h3(#application.mode). application.mode + +Application mode (case insensitive). For example: + +bc. application.mode=prod + +Values: + +* @DEV@ - enable instant reloading and other development help +* @PROD@ - pre-compiles and caches Java sources and templates. + +Default: @DEV@ + + +h3(#application.name). application.name + +The application’s name, usually set by the @play new@ command. + +Default: no value. + + +h3(#application.secret). application.secret + +The secret key is used to secure cryptographic functions, usually set by the @play new@ or @play secret@ command. If you deploy your application to several instances be sure to use the same key. For example: + +bc. application.secret=mNuAvlsFVjeuynN4IWZxZzFOHYVagafzjruHmWTL26VISKr46rUtyGcJuX7aYx4q + +If not set, @play.libs.Crypto.sign@ will not encrypt messages; in particular, sessions will not be encrypted. + +Default: no value. + + +h3(#application.session.cookie). application.session.cookie + +Session cookie name. The cookies are not secured by default, only set it to true if you’re serving your pages through HTTPS. For example: + +bc. application.session.cookie=PLAY + +Default: sessions are written to the transient @PLAY_SESSION@ cookie. + + +h3(#application.session.httpOnly). application.session.httpOnly + +Enables the ‘HTTP only’ flag on cookies, which mitigates some XSS attacks. For example: + +bc. application.session.httpOnly=true + +Default: @false@ + +For more information see the "OWASP page on HttpOnly":http://www.owasp.org/index.php/HttpOnly. + + +h3(#application.session.maxAge). application.session.maxAge + +Session time-out, i.e. the maximum age of the session cookie. If not set, the session expires when you close your web browser. For example, to set the session to one hour: + +bc. application.session.maxAge=1h + +Remember the session for one week: + +bc. application.session.maxAge=7d + +Default: the session is based on a transient cookie expires when the browser is closed. + + +h3(#application.session.secure). application.session.secure + +Enables Cookie-based sessions for HTTPS connections. For example: + +bc. application.session.secure=true + +Default: @false@ + + +h3(#application.session.sendOnlyIfChanged). application.session.sendOnlyIfChanged + +Avoid sending the session cookie if there were no changes to the session. For example: + +bc. application.session.sendOnlyIfChanged=true + +Default: @false@ + + +h3(#application.web_encoding). application.web_encoding + +The text encoding that Play uses when communicating with the web browser and for the "Web Service client":libs#WebServiceclient. You do not normally need to set this, since Play defaults to using @UTF-8@. For example: + +bc. application.web_encoding=ISO-8859-1 + +Default: @UTF-8@ + +Changing @application.web_encoding@ affects the @charset@ part of the @Content-type@ HTTP header. It also affects which encoding is used when transmitting rendered dynamic results, but it **does not** affect the bytes sent when Play serves static content: So, if you have modified the default response encoding and you have static text-files (in the @public/@ folder) that contain special characters, you must make sure that these files are stored according to the specified encoding. All other files should be stored in UTF-8. + + +h2(#attachments). Attachments + + +h3(#attachments.path). attachments.path + +Storage path for @play.db.jpa.Blob@ content. This can be an absolute path, or a relative path to a folder inside the Play application folder. For example: + +bc. attachments.path=data/attachments + +Default: @attachments@ + + +h2(#certificate). X509 certificates + + +h3(#certificate.key.file). certificate.key.file + +Specifies an X509 certificate key, for HTTPS support. The file must be named @host.key@. For example: + +bc. certificate.key.file=/certificates/host.key + +Default: @conf/host.key@ + + +h3(#certificate.file). certificate.file + +Specifies an X509 certificate file, for HTTPS support. The file must be named @host.cert@. For example: + +bc. certificate.file=/certificates/host.cert + +Default: @conf/host.cert@ + + +h3(#certificate.password). certificate.password + +Password for a password-protected X509 certificate key file, for use with the "certificate.key.file":#certificate.key.file configuration. For example: + +bc. certificate.password=secret + +Default: @secret@ + + +h2(#cron). Scheduled jobs + +You can configure cron expressions for scheduled jobs as configuration keys that start with @cron.@ and use the key as the value of a @play.jobs.On or @Every annotation. For example, @On("cron.noon") refers to: + +bc. cron.noon=0 0 12 * * ? + + +h2(#date). Date formats + + +h3(#date.format). date.format + +Sets the default date format, using a @java.text.SimpleDateFormat@ pattern. For example: + +bc. date.format=dd-MM-yyyy + +This property also affects how @${date.format()}@ renders dates in templates. It also set the default date format when binding a date parameter. + +Default: @yyyy-MM-dd@ + +You can also set a different date format for specific languages that you have configured with "application.langs":#application.langs, for example: + +bc. date.format.fr=dd-MM-yyyy + + +h2(#dbconf). Database configuration + +h3(#db). db + +Database engine configuration. To quickly set up a development database use a transient in memory database (H2 in memory): + +bc. db=mem + +For a simple file written database (H2 file stored): + +bc. db=fs + +For a local MySQL5 database: + +bc. db=mysql:user:pwd@database_name + +To reuse an existing Datasource from your application server: + +bc. db=java:/comp/env/jdbc/myDatasource@ + +If you specify a @Datasource@, the database plugin detects the pattern @db=java:@ and will de-activate the default JDBC system. + +Default: none. + +See also: "Support for multiple databases":model#multiple. + + +h3(#db.destroyMethod). db.destroyMethod + +A generic ‘destroy’ method name. When using an existing Datasource, this is sometimes needed to destroy it when the application is stopped. For example: + +bc. db.destroyMethod=close + +Default: none. + + +h3(#db.driver). db.driver + +Database driver class name, for use with "db.url":#db.url. For example: + +bc. db.driver=org.postgresql.Driver + +Default: + +* @org.h2.Driver@ when "db":#db is set to @mem@ or @fs@, or if "db.url":#db.url starts with @jdbc:h2:mem:@ +* @com.mysql.jdbc.Driver@ if "db":#db is a @mysql:…@ configuration. + +h3(#db.isolation). db.isolation + +Database transaction isolation level. For example: + +bc. db.isolation=SERIALIZABLE + +Valid values are @NONE@, @READ_UNCOMMITTED@, @READ_COMMITTED@, @REPEATABLE_READ@, @SERIALIZABLE@, or an integer value to be passed to @java.sql.Connection.setTransactionIsolation()@. Note that not all databases support all transaction isolation levels. + +Default: database dependent, usually @READ_COMMITTED@ + +h3(#db.pass). db.pass + +Database connection password, used with "db.url":#db.url. + +Default: no value, or an empty string when "db":#db is set to @mem@ or @fs@. + +h3(#db.pool.acquireIncrement). db.pool.acquireIncrement + +Determines how many connections at a time c3p0 will try to acquire when the pool is exhausted. + +Default: @3@ + +h3(#db.pool.acquireRetryAttempts). db.pool.acquireRetryAttempts + +Defines how many times c3p0 will try to acquire a new Connection from the database before giving up. If this value is less than or equal to zero, c3p0 will keep trying to fetch a Connection indefinitely. + +Default: @10@ + +h3(#db.pool.acquireRetryDelay). db.pool.acquireRetryDelay + +Milliseconds, time c3p0 will wait between acquire attempts. + +Default: @1000@ + +h3(#db.pool.breakAfterAcquireFailure). db.pool.breakAfterAcquireFailure + +If true, a pooled DataSource will declare itself broken and be permanently closed if a Connection cannot be obtained from the database after making acquireRetryAttempts to acquire one. If false, failure to obtain a Connection will cause all Threads waiting for the pool to acquire a Connection to throw an Exception, but the DataSource will remain valid, and will attempt to acquire again following a call to getConnection(). + +Default: @false@ + +h3(#db.pool.unreturnedConnectionTimeout). db.pool.unreturnedConnectionTimeout + +Seconds. If set, if an application checks out but then fails to check-in [i.e. close()] a Connection within the specified period of time, the pool will unceremoniously destroy() the Connection. This permits applications with occasional Connection leaks to survive, rather than eventually exhausting the Connection pool. And that's a shame. Zero means no timeout, applications are expected to close() their own Connections. Obviously, if a non-zero value is set, it should be to a value longer than any Connection should reasonably be checked-out. Otherwise, the pool will occasionally kill Connections in active use, which is bad. + +Use this temporarily in combination with debugUnreturnedConnectionStackTraces to figure out where Connections are being checked-out that don't make it back into the pool! + +Default: @0@ + +h3(#db.pool.debugUnreturnedConnectionStackTraces). db.pool.debugUnreturnedConnectionStackTraces + +f true, and if unreturnedConnectionTimeout is set to a positive value, then the pool will capture the stack trace (via an Exception) of all Connection checkouts, and the stack traces will be printed when unreturned checked-out Connections timeout. This is intended to debug applications with Connection leaks, that is applications that occasionally fail to return Connections, leading to pool growth, and eventually exhaustion (when the pool hits maxPoolSize with all Connections checked-out and lost). This parameter should only be set while debugging, as capturing the stack trace will slow down every Connection check-out. + +Default: @false@ + +h3(#db.pool.testConnectionOnCheckout). db.pool.testConnectionOnCheckout + +If true, an operation will be performed at every connection checkout to verify that the connection is valid. Be sure to set an efficient preferredTestQuery or automaticTestTable if you set this to true. Performing the (expensive) default Connection test on every client checkout will harm client performance. Testing Connections in checkout is the simplest and most reliable form of Connection testing, but for better performance, consider verifying connections periodically using idleConnectionTestPeriod. + +Default: @false@ + +h3(#db.pool.testConnectionOnCheckin). db.pool.testConnectionOnCheckin + +If true, an operation will be performed asynchronously at every connection checkin to verify that the connection is valid. Use in combination with idleConnectionTestPeriod for quite reliable, always asynchronous Connection testing. Also, setting an automaticTestTable or preferredTestQuery will usually speed up all connection tests. + +Default: @true@ + +h3(#db.pool.maxConnectionAge). db.pool.maxConnectionAge + +Seconds, effectively a time to live. A Connection older than maxConnectionAge will be destroyed and purged from the pool. This differs from maxIdleTime in that it refers to absolute age. Even a Connection which has not been much idle will be purged from the pool if it exceeds maxConnectionAge. Zero means no maximum absolute age is enforced. + +Default: @0@ + +h3(#db.pool.maxIdleTime). db.pool.maxIdleTime + +Seconds a Connection can remain pooled but unused before being discarded. Zero means idle connections never expire. + +Default: @0@ + +h3(#db.pool.idleConnectionTestPeriod). db.pool.idleConnectionTestPeriod + +If this is a number greater than 0, c3p0 will test all idle, pooled but unchecked-out connections, every this number of seconds. + +Default: @10@ + +h3(#db.pool.maxIdleTimeExcessConnections). db.pool.maxIdleTimeExcessConnections + +The number of seconds before idle connections beyond "db.pool.minSize":#db.pool.minSize are ‘culled’. See the "c3p0 documentation":http://www.mchange.com/projects/c3p0/#maxIdleTimeExcessConnections. + +Default: @0@ - ‘no enforcement’. + + +h3(#db.pool.maxSize). db.pool.maxSize + +Connection pool maximum size (number of connections). See the "c3p0 documentation":http://www.mchange.com/projects/c3p0/#maxPoolSize. For example: + +bc. db.pool.maxSize=60 + +Default: @30@ + + +h3(#db.pool.minSize). db.pool.minSize + +Connection pool minimum size (number of connections). See the "c3p0 documentation":http://www.mchange.com/projects/c3p0/#minPoolSize. For example: + +bc. db.pool.minSize=10 + +Default: @1@ + +h3(#db.pool.initialSize). db.pool.initialSize + +Number of Connections a pool will try to acquire upon startup. Should be between minPoolSize and maxPoolSize. See the "c3p0 documentation":http://www.mchange.com/projects/c3p0/#initialPoolSize. For example: + +bc. db.pool.initialSize=5 + +Default: @1@ + + +h3(#db.pool.timeout). db.pool.timeout + +Connection pool time-out in milliseconds. See the "c3p0 documentation":http://www.mchange.com/projects/c3p0/#checkoutTimeout. For example: + +bc. db.pool.timeout=10000 + +Default: @5000@ + +h3(#db.pool.loginTimeout). db.pool.loginTimeout + +Sets the maximum time in seconds that this data source will wait while attempting to connect to a database. + +Default: @0@ + +h3(#db.pool.maxStatements). db.pool.maxStatements + +The size of c3p0's global PreparedStatement cache. If both maxStatements and maxStatementsPerConnection are zero, statement caching will not be enabled. + +Default: @0@ + +h3(#db.pool.maxStatementsPerConnection). db.pool.maxStatementsPerConnection + +The number of PreparedStatements c3p0 will cache for a single pooled Connection. If both maxStatements and maxStatementsPerConnection are zero, statement caching will not be enabled. + +Default: @0@ + + +h3(#db.url). db.url + +A full JDBC configuration, in combination with "db.user":#db.user, "db.pass":#db.pass and "db.driver":#db.driver. For example: + +bc. db.url=jdbc:postgresql:database_name + +Default: none. + + +h3(#db.user). db.user + +Database connection user name, used with "db.url":#db.url. + +Default: none, or @sa@ when "db":#db is set to @mem@ or @fs@. + +h3(#db.testquery). db.testquery + +Override query to use when keepalive connection polling is being performed. The default value will cause keepalive to fetch metadata. This means it does a getTables() which can be a heavy operation on a busy databse. + +Default: null +Default on MySQL: /* ping */ SELECT 1 + +h3(#db.pool.maxAdministrativeTaskTime). db.pool.maxAdministrativeTaskTime + +Seconds before c3p0's thread pool will try to interrupt an apparently hung task. Rarely useful. + +Default: @0@ + +h3(#db.pool.numHelperThreads). db.pool.numHelperThreads + +c3p0 is very asynchronous. Slow JDBC operations are generally performed by helper threads that don't hold contended locks. Spreading these operations over multiple threads can significantly improve performance by allowing multiple operations to be performed simultaneously. + +Default: @3@ + + +h2(#evolutions). Database evolutions + + +h3(#evolutions.enabled). evolutions.enabled + +Used to disable "database evolutions":evolutions. + +bc. evolutions.enabled=false + +Default: @true@ + + + +h3(#modules.evolutions.enabled). modules.evolutions.enabled + +Used to disable "database evolutions":evolutions from all modules + +bc. modules.evolutions.enabled=false + +Default: @true@ + +h3(#[module name].evolutions.enabled). [module name].evolutions.enabled + +Used to disable "database evolutions":evolutions from a given module + +bc. [module name].evolutions.enabled=false + +Default: @true@ + +h3(#evolution.PLAY_EVOLUTIONS.textType). evolution.PLAY_EVOLUTIONS.textType + +If you are having problems with the default datatype text (clob for Oracle), you can specify your own datatype + +bc. evolution.PLAY_EVOLUTIONS.textType=clob + +Default: @clob@ + +h2(#test). Test runner + +h3(#headlessBrowser). headlessBrowser + +Specifies the web browser compatibility mode for the HtmlUnit headless web browser used when running @play auto-test@. + +bc. headlessBrowser=FIREFOX_38 + +Values: + +* @CHROME@ +* @FIREFOX_31@ +* @INTERNET_EXPLORER_8@ +* @INTERNET_EXPLORER_11@ +* @EDGE@ + +Default: @FIREFOX_38@ + +h3(#webclient.timeout). webclient.timeout + +Specifies the timeout of the WebConnection. Set to zero for an infinite wait. + +Note: The timeout is used twice. The first is for making the socket connection, the second is for data retrieval. If the time is critical you must allow for twice the time specified here. + +bc. webclient.timeout=60 + +This value can be set directly in the command @play auto-test@ by adding @--timeout=60@ + + +h2(#hibernate). Hibernate + +You can specify additional Hibernate properties. For example, to enable SQL comments: + +bc. hibernate.use_sql_comments=true + +See also: "Using JPA with multiple databases":jpa#multiple. + + +h3(#hibernate.connection.datasource). hibernate.connection.datasource + +Hibernate datasource configuration. + + +h2(#http). Server configuration + + +h3(#http.address). http.address + +HTTP listener address, to restrict addresses the server listens on. For example: + +bc. http.address=127.0.0.1 + +Default: the server listens for HTTP on the wildcard address. + + +h3(#http.cacheControl). http.cacheControl + +HTTP Response headers control for static files: sets the default max-age in seconds, telling the user’s browser how long it should cache the page. This is only read in @prod@ mode, in @dev@ mode the cache is disabled. For example, to send @no-cache@: + +bc. http.cacheControl=0 + +Default: @3600@ - set cache expiry to one hour. + + +h3(#http.exposePlayServer). http.exposePlayServer + +Disable the HTTP response header that identifies the HTTP server as Play. For example: + +bc. http.exposePlayServer=false + +Default: @true@ + + +h3(#http.path). http.path + +The URL path where the application runs on the server: use this if you do not host your Play application at the root of the domain you’re serving it from. This parameter has no effect when deployed as a WAR, because the path will be handled by the application server. For example: + +bc. http.path=/myapp/ + +Default: @/@ + + +h3(#http.port). http.port + +The port that the HTTP server listens on. + +Default: @9000@ + + +h3(#http.proxyHost). http.proxyHost + +Proxy server for web services requests. For example: + +bc. http.proxyHost=localhost + +Default: @http.proxyHost@ system property. + + +h3(#http.proxyPassword). http.proxyPassword + +Proxy password for web services requests. + +Default: @http.proxyPassword@ system property. + + +h3(#http.proxyPort). http.proxyPort + +Proxy port for web services requests. For example: + +bc. http.proxyPort=3128 + +Default: @http.proxyPort@ system property. + + +h3(#http.proxyUser). http.proxyUser + +Proxy user for web services requests. + +Default: @http.proxyUser@ system property. + + +h3(#http.nonProxyHosts). http.nonProxyHosts + +Indicates the hosts which should be connected to directly and not through the proxy server. +The value can be a list of hosts, each seperated by a @|@, and in addition a wildcard character @*@ can be used for matching. + +For example: + +bc. http.nonProxyHosts=localhost|*.example.com + +Default: @http.nonProxyHosts@ system property. + + + +h3(#http.useETag). http.useETag + +If enabled, Play will generate entity tags automatically and send a 304 when needed. For example, to deactivate use of entity tags: + +bc. http.useETag=false + +Default: @true@ + + +h3(#http.userAgent). http.userAgent + +Custom @USER_AGENT@ header value for web services requests. For example: + +bc. http.userAgent=myApp 1.0 + +Default: none. + + +h3(#https.port). https.port + +Enables an HTTPS connector, listening on the specified port. For example: + +bc. https.port=9443 + +Default: none - no HTTPS configuration. + + +h2(#java). Java source + + +h3(#java.source). java.source + +Java source level, which overrides the @java.version@ system property. For example: + +bc. java.source=1.7 + +Values: @1.5@ (No longer supported since 1.3.0), @1.6@, @1.7@, @1.8@ (experimental). + +Default: @1.6@ + + +h2(#jpa). JPA + + +h3(#jpa.dialect). jpa.dialect + +Specify the custom JPA dialect to use here. For example: + +bc. jpa.dialect=org.hibernate.dialect.PostgreSQLDialect + +Default: Play will guess the dialect based on the "db.driver":#db.driver configuration. + + +h3(#jpa.ddl). jpa.ddl + +Specify the DDL generation pattern to use. For example, to enable automatic database structure updates. For example: + +bc. jpa.ddl=create-drop + +Default: @update@ (@dev@ mode) or @none@ (@prod@ mode). + + +h3(#jpa.debugSQL). jpa.debugSQL + +Debug SQL statements (logged using DEBUG level). For example: + +bc. jpa.debugSQL=true + +Default: @false@ + + +h3(#jpa.entities). jpa.entities + +Comma-separated list of names of additional JPA entity classes to load. This is useful when you have additional entities that are not in the @models@ package, such as model classes in a separate JAR. For example: + +bc. org.example.model.Person, org.example.model.Organisation + +Default: none. + + +h3(#jpa.mapping-file). jpa.mapping-file + +JPA mapping file. + +Default: none. + + +h2(#jpda). JVM + + +h3(#jpda.port). jpda.port + +Defines which port is used by JPDA when application is in debug mode. For example: + +Default: @8000@ + + +h2(#keystore). keystore + + +h3(#keystore.algorithm). keystore.algorithm + +A JDK Security API standard algorithm name, for use with the "keystore.file":#keystore.file configuration. + +bc. keystore.algorithm=pkcs12 + +Values - ‘standard names’ from the JDK Security API: + +* @jceks@ (‘SunJCE’ provider) +* @jks@ (‘SUN" provider) +* @pkcs12@ + +Default: @JKS@ + + +h3(#keystore.file). keystore.file + +Specifies a keystore certificate, for HTTPS support. The file should be named @certificate.jks@. For example: + +bc. keystore.file=conf/certificate.jks + + +h3(#keystore.password). keystore.password + +Keystore configuration, for use with the "keystore.file":#keystore.file configuration. + +bc. keystore.password=secret + +Default: @secret@ + + +h2(#memcachedconfig). Memcached + + +h3(#memcached). memcached + +Enable "Memcached":http://www.danga.com/memcached/; if you don’t configure Memcached, Play will use a standalone cache that stores data in the JVM heap. + +bc. memcached=enabled + +Default: @disabled@ + +See also: "using a cache":cache. + + +h3(#memcached.host). memcached.host + +Specify memcached host. For example: + +bc. memcached.host=127.0.0.1:11211 + +Default: @127.0.0.1:11211@ + +You can specify multiple hosts to build a distributed cache. For example: + +bc. memcached.1.host=127.0.0.1:11211 +memcached.2.host=127.0.0.1:11212 + + +h2(#mimetype). Custom MIME types + +You can declare additional MIME types. For example: + +bc. mimetype.xpi=application/x-xpinstall + + +h2(#webserviceconfig). Web services + +h3(#webservice). webservice + +Class name of the Web services implementation, or one of the built-in implementations. For example: + +bc. webservice=urlfetch + +Values: + +* @urlfetch@ - the JDK’s internal implementation +* @async@ - the engine is Async Http Client +* class name of a @play.libs.WS.WSImpl@ implementation + +Default: @async@ - the engine is Async Http Client. + + +h2(#mail). Mail + + +h3(#mail.debug). mail.debug + +Enables SMTP transaction logging; under the hood, Play uses JavaMail to perform the actual SMTP transactions. + +bc. mail.debug=true + +Default: @false@ + + +h3(#mail.smtp). mail.smtp + +Simple mail configuration key. + +Default: @mock@ - use a mock Mailer + +See also: "SMTP configuration":emails#smtp. + + +h3(#mail.smtp.authenticator). mail.smtp.authenticator + +Class name for a custom SMTP authenticator (@javax.mail.Authenticator@) implementation. + +Default: none. + + +h3(#mail.smtp.channel). mail.smtp.channel + +There are two ways to send the e-mail over an encrypted channel, which you can choose with this configuration property. Values: + +* @clear@ - no encryption +* @ssl@ - SMTP-over-SSL (SMTPS) connector; an SSL socket listening on port 465 +* @starttls@ - a clear connection on port 25 that will switch to SSL/TLS, if your server supports the @starttls@ command (see: "RFC 2487":http://www.apps.ietf.org/rfc/rfc2487.html). + +Default: @clear@ + + +h3(#mail.smtp.host). mail.smtp.host + +Outgoing mail server. For example: + +bc. mail.smtp.host=127.0.0.1 + +To use a GMail SMTP server: + +bc. mail.smtp.host=smtp.gmail.com + +Default: @localhost@ + + +h3(#mail.smtp.localhost). mail.smtp.localhost + +Local host name override for SMTP commands. + +Default: none - use the Java Mail default. + + +h3(#mail.smtp.pass). mail.smtp.pass + +SMTP server password, used with "mail.smtp.host":#mail.smtp.host, e.g. a GMail password. + +Default: none. + + +h3(#mail.smtp.port). mail.smtp.port + +Port for SMTP server connections, used to override the defaults. For example: + +bc. mail.smtp.port=2500 + +Default: + +* @25@ when "mail.smtp.channel":#mail.smtp.channel is set to @clear@ or @starttls@ +* @465@ when "mail.smtp.channel":#mail.smtp.channel is set to @ssl@ + + +h3(#mail.smtp.protocol). mail.smtp.protocol + +Sets whether to use SSL. Values: + +* @smtp@ +* @smtps@ + +Default: @smtp@ + + + +h3(#mail.smtp.socketFactory.class). mail.smtp.socketFactory.class + +When using SSL connections with JavaMail, the default SSL behaviour is to drop the connection if the remote server certificate is not signed by a root certificate. This is the case in particular when using a self-signed certificate. Play’s default behaviour is to skip that check. You can control this using this property. + + +h3(#mail.smtp.user). mail.smtp.user + +SMTP server user name, used with "mail.smtp.host":#mail.smtp.host, e.g. a GMail user name. + +Default: none. + + +h2(#play). Play run-time + + +h3(#play.bytecodeCache). play.bytecodeCache + +Used to disable the bytecode cache in @dev@ mode; has no effect in @prod@ mode. + +bc. play.bytecodeCache=false + +Default: @true@ + + +h3(#play.editor). play.editor + +Open file from error pages. If your text editor supports opening files by URL, Play will dynamically link error pages to files. For Textmate, for example: + +bc. play.editor=txmt://open?url=file://%s&line=%s + + +h3(#play.jobs.pool). play.jobs.pool + +Size of the Jobs pool. For example: + +bc. play.jobs.pool=20 + +Default: @10@ + + +h3(#play.netty.clientAuth). play.netty.clientAuth + +Configures @javax.net.ssl.SSLEngine@ client authentication. For example: + +bc. play.netty.clientAuth=need + +Values: + +* @want@ - the server will _request_ client authentication +* @need@ - the server will _require_ client authentication +* @none@ - no client authentication + +Default: @none@ + + +h3(#play.netty.maxContentLength). play.netty.maxContentLength + +HTTP server maximum content length for response streaming, in bytes. + +Default: none - no maximum. + + +h3(#play.ssl.enabledCiphers). play.ssl.enabledCiphers + +This setting allows to specify certain SSL ciphers to be used. This might be needed in case you have to be PCI compliant, as some ciphers in the default settings are vulnerable to the so-called BEAST attack. + +bc. play.ssl.enabledCiphers=SSL_RSA_WITH_RC4_128_MD5,SSL_RSA_WITH_RC4_128_SHA + +Default: none - the default ciphers are chosen. + +h3(#play.ssl.enabledProtocols). play.ssl.enabledProtocols + +This setting allows to specify certain SSL protocols to be used. + +bc. play.ssl.enabledProtocols=TLSv1,TLSv1.1,TLSv1.2 + +Default: none - the default protocols are chosen. + + +h3(#play.pool). play.pool + +Execution pool size. Try to keep this as low as possible. Setting this to 1 thread will serialise all requests (very useful for debugging purpose). For example: + +bc. play.pool=10 + +Default: @1@ (in @dev@ mode), number of processors + 1 (in @prod@ mode). + + +h3(#play.tmp). play.tmp + +Folder used to store temporary files. For example: + +bc. play.tmp=/tmp/play + +Values: + +* an absolute path +* a relative path, relative to the application directory +* @none@ so that no temporary directory will be used + +Default: @tmp@ + + +h2(#ssl). SSL + +See also: "https.port":#https.port. + + +h3(#ssl.KeyManagerFactory.algorithm). ssl.KeyManagerFactory.algorithm + +The standard name of a Java Secure Socket Extension (JSSE) trust management algorithm, for use with the "keystore.file":#keystore.file configuration. + +bc. ssl.KeyManagerFactory.algorithm=SunX509 + +Default: @SunX509@ + + +h2(#trustmanager). trustmanager + + +h3(#trustmanager.algorithm). trustmanager.algorithm + +A JDK Security API standard algorithm name, for use with "X509 certificates":#certificate and "keystore":#keystore configurations. + +bc. trustmanager.algorithm=pkcs12 + +Values - ‘standard names’ from the JDK Security API: + +* @jceks@ (‘SunJCE’ provider) +* @jks@ (‘SUN" provider) +* @pkcs12@ + +Default: @JKS@ + + +h2(#upload). File upload + +h3(#upload.threshold). upload.threshold + +The threshold in bytes at which upload files will be written to disk, for @org.apache.commons.io.output.DeferredFileOutputStream@. For example: + +bc. upload.threshold=20480 + +Default: @10240@ + + +h2(#xforwarded). Proxy forwarding + +h3(#XForwardedHost). XForwardedHost + +Overrides the @X-Forwarded-Host@ HTTP header value - the original host requested by the client, for use with proxy servers. + +Default: @X-Forwarded-Host@ HTTP header value. + + +h3(#XForwardedProto). XForwardedProto + +Sets the proxy request to SSL, overriding the @X-Forwarded-Proto@ and @X-Forwarded-SSL@ HTTP header values - the protocol originally requested by the client. For example: + +bc. XForwardedProto=https + + +h3(#XForwardedSupport). XForwardedSupport + +A comma-separated list of IP addresses that are allowed @X-Forwarded-For@ HTTP request header values, used to restrict local addresses when an @X-Forwarded-For@ request header is set by a proxy server. Alternatively you can use 'ALL' if you do not want to restrict local addresses. **Note**: only use 'ALL' if you are very sure that if it safe to do so (e.g. you have a proper firewall in place that blocks public requests). A valid use-case would be when using Play! behind a Amazon ELB loadbalancer, which internal IP's tend to change over time. + +Default: @127.0.0.1@ + +h3(#XForwardedOverwriteDomainAndPort). XForwardedOverwriteDomainAndPort + +Set to true to restore the request domain and port to original domain and port. + +For example: + +bc. XForwardedOverwriteDomainAndPort=true + +Default: @false@ + +h2(#enhancers). Enhancers + +h3(#play.propertiesEnhancer.enabled). play.propertiesEnhancer.enabled + +Used to disable play enhancing of play class (can be used to switch off getter/setter generation). For example: + +bc. play.propertiesEnhancer.enabled=false + +Default: @true@ diff --git a/documentation/manual/controllers.textile b/documentation/manual/controllers.textile index b3348c0914..471e4255a9 100644 --- a/documentation/manual/controllers.textile +++ b/documentation/manual/controllers.textile @@ -19,7 +19,7 @@ The web’s principles are not fundamentally object-oriented. So a layer is need h2. Controller overview -A Controller is a Java class, hosted by the **controllers** package, and subclassing **play.mvc.Controller**. +A Controller is a Java class, hosted by the @controllers@ package, and subclassing @play.mvc.Controller@. This is a Controller: @@ -34,39 +34,55 @@ public class Clients extends Controller { Client client = Client.findById(id); render(client); } - - public static void delete(Long id) { + + public void delete(Long id) { Client client = Client.findById(id); client.delete(); } } -Each public, static method in a Controller is called an action. The signature for an action method is always: +Each public method in a Controller is called an action. In case an action method is not static, a new instance of the controller class is created for each request, and all controller methods are invoked on the same instance. This works only if the controller has a zero-argument constructor (or no constructors at all). + +The signature for an action method is: + +bc. public void static action_name(params...); -bc. public static void action_name(params...); +or + +bc. public void action_name(params...); You can define parameters in the action method signature. These parameters will be automatically resolved by the framework from the corresponding HTTP parameters. -Usually, an action method doesn’t include a return statement. The method exit is done by the invocation of a **result** method. In this example, **render(…)** is a result method that executes and displays a template. +Usually, an action method doesn’t include a return statement. The method exit is done by the invocation of a **result** method. In this example, @render(…)@ is a result method that executes and displays a template. + +h2. Static or non-static? + +In the past, all controller methods had to be static. However, non-static methods have many advantages in Java: + +* They can be overridden in subclasses. +* They are easier to mock/unit-test. +* They allow storing of request-scoped state in controller fields, if needed. + +Having that said, both static and non-static controller methods are supported. h2. Retrieving HTTP parameters An HTTP request contains data. This data can be extracted from: -* The URI path: in **/clients/1541**, 1541 is the dynamic part of the URI Pattern. -* The Query String: **/clients?id=1541.** -* The request body: if the request was sent from an HTML form, the request body contains the form data encoded as **x-www-urlform-encoded**. +* The URI path: in @/clients/1541@, 1541 is the dynamic part of the URI Pattern. +* The Query String: @/clients?id=1541@. +* The request body: if the request was sent from an HTML form, the request body contains the form data encoded as @x-www-urlform-encoded@. -In all cases, Play extracts this data and builds a Map which contains all the HTTP parameters. The key is the parameter name. The parameter name is derived from: +In all cases, Play extracts this data and builds a @Map@ which contains all the HTTP parameters. The key is the parameter name. The parameter name is derived from: * The name of the dynamic part of the URI (as specified in the route) * The name portion of a name-value pair taken from the Query String * The contents of a x-www-urlform-encoded body. -h3. Using the params map +h3. Using the params map -The **params** object is available to any Controller class (it is defined in the **play.mvc.Controller** super class). This object contains all the HTTP parameters found for the current request. +The @params@ object is available to any Controller class (it is defined in the @play.mvc.Controller@ super class). This object contains all the HTTP parameters found for the current request. For example: @@ -83,7 +99,7 @@ bc. public static void show() { But wait, there are better ways to do this :) -h3. From the action method signature +h3. From the action method signature You can retrieve HTTP parameters directly from the action method signature. The Java parameter’s name must be the same as the HTTP parameter’s. @@ -91,7 +107,7 @@ For example, in this request: bc. /clients?id=1451 -An action method can retrieve the **id** parameter value by declaring an **id** parameter in its signature: +An action method can retrieve the @id@ parameter value by declaring an @id@ parameter in its signature: bc. public static void show(String id) { System.out.println(id); @@ -125,15 +141,15 @@ If the HTTP parameter corresponding to the action method argument is not found, h2. Advanced HTTP to Java binding -h3. Simple types +h3. Simple types All the native and common Java types are automatically bound: -**int**, **long**, **boolean**, **char**, **byte**, **float**, **double**, **Integer**, **Long**, **Boolean**, **Char**, **String**, **Byte**, **Float**, **Double**. +@int@, @long@, @boolean@, @char@, @byte@, @float@, @double@, @Integer@, @Long@, @Boolean@, @Char@, @String@, @Byte@, @Float@, @Double@. Note that if the parameter is missing in the HTTP Request, or if automatic conversion fails, Object types will be set to null and native types will be set to their default values. -h3. Date +h3. Date A date object can be automatically bound if the date’s string representation matches one of the following patterns: @@ -149,7 +165,7 @@ A date object can be automatically bound if the date’s string representation m * MM-dd-yy * MM'/'dd'/'yy -Using the **@As** annotation, you can specify the date format. +Using the @As annotation, you can specify the date format. For example: @@ -162,33 +178,24 @@ bc. public static void articlesSince(@As("dd/MM/yyyy") Date from) { You can of course refine the date format according to the language. For example: -bc. public static void articlesSince(@As(lang={"fr,de","*"}, value={"dd-MM-yyyy","MM-dd-yyyy"}) Date from) { +bc. public static void articlesSince(@As(lang={"fr,de","*"}, + value={"dd-MM-yyyy","MM-dd-yyyy"}) Date from) { List
articles = Article.findBy("date >= ?", from); render(articles); } -In this example, we specified that for the French and German languages the date format is **dd-MM-yyyy** and for all the other languages it's **MM-dd-yyyy**. Please note that the lang value can be comma separated. The important thing is that the number of parameters for lang matches the number of parameters for value. - -If no **@As** annotation is specified, then Play! uses the default date format according to your locale. -To set the default date format to use, edit your application.conf and set the following property: - -bc. date.format=yyy-MM-dd - date.format.fr=dd/MM/yyyy - -Please note that the language fr in the application.conf must be enabled as well: - -bc. application.langs=fr +In this example, we specified that for the French and German languages the date format is @dd-MM-yyyy@ and for all the other languages it's @MM-dd-yyyy@. Please note that the lang value can be comma separated. The important thing is that the number of parameters for lang matches the number of parameters for value. -This property also affects how the dates are rendered in the templates using **${date.format()}**. +If no @As annotation is specified, then Play! uses the default date format according to your locale. The "date.format configuration":configuration#date.format specifies the default date format to use. -h3. Calendar +h3. Calendar -The calendar binding works exactly as with the date, except that Play is choosing the Calendar object according to your locale. The **@Bind** annotation can also be used. +The calendar binding works exactly as with the date, except that Play is choosing the Calendar object according to your locale. The @Bind annotation can also be used. -h3. Files +h3. File -File upload is easy with Play. Use a **multipart/form-data** encoded request to post files to the server, and then use the **java.io.File** type to retrieve the file object: +File upload is easy with Play. Use a @multipart/form-data@ encoded request to post files to the server, and then use the @java.io.File@ type to retrieve the file object: bc. public static void create(String comment, File attachment) { String s3Key = S3.post(attachment); @@ -199,40 +206,49 @@ bc. public static void create(String comment, File attachment) { The created file has the same name as the original file. It’s stored in a temporary directory and deleted at the end of the request. So you have to copy it in a safe directory or it will be lost. -h3. Arrays or collections of supported types +The uploaded file's MIME type should normally be specified by the HTTP request’s @Content-type@ header. However, when uploading files from a web browser, this might not happen for uncommon types. In this case, you can map the file name’s extension to a MIME type, using the @play.libs.MimeTypes@ class. + +bc. String mimeType = MimeTypes.getContentType(attachment.getName()); + +The @play.libs.MimeTypes@ class looks up the MIME type for the given file name’s extension in the file @$PLAY_HOME/framework/src/play/libs/mime-types.properties@ + +You can also add your own types using the "Custom MIME types configuration":configuration#mimetype. + + +h3. Arrays or collections of supported types All supported types can be retrieved as an Array or a collection of objects: bc. public static void show(Long[] id) { - ... + … } or: bc. public static void show(List id) { - ... + … } or: bc. public static void show(Set id) { - ... + … } Play also handles the special case of binding a Map like this: bc. public static void show(Map client) { - ... + … } A query string like the following: -bc. ?user.name=John&user.phone=111-1111&user.phone=222-2222 +bc. ?client.name=John&client.phone=111-1111&client.phone=222-2222 -Would bind the client variable to a map with two elements. The first element with key "name" and value "John", and the second with key "phone" and value "111-1111, 222-2222" +would bind the client variable to a map with two elements. The first element with key @name@ and value @John@, and the second with key @phone@ and value @111-1111, 222-2222@. -h3. POJO object binding +h3. POJO object binding Play also automatically binds any of your model classes using the same simple naming convention rules. @@ -254,7 +270,7 @@ bc. ?client.name=Zenexity &client.address.zip=75009 &client.address.country=France -In order to update a list of model objects, use array notation and reference the object’s ID. For example imagine the Client model has a list of Customer models declared as **List Customer customers**. To update the list of Customers you would provide a query string like the following: +In order to update a list of model objects, use array notation and reference the object’s ID. For example imagine the Client model has a list of Customer models declared as @List Customer customers@. To update the list of Customers you would provide a query string like the following: bc. ?client.customers[0].id=123 &client.customers[1].id=456 @@ -265,96 +281,118 @@ h2. JPA object binding You can automatically bind a JPA object using the HTTP to Java binding. -You can provide the **user.id** field yourself in the HTTP parameters. When Play finds the **id** field, it loads the matching instance from the database before editing it. The other parameters provided by the HTTP request are then applied. So you can save it directly. +You can provide the @user.id@ field yourself in the HTTP parameters. When Play finds the @id@ field, it loads the matching instance from the database before editing it. The other parameters provided by the HTTP request are then applied. So you can save it directly. bc. public static void save(User user) { user.save(); // ok with 1.0.1 } +You can use JPA binding to modify complete object graphs in the same way as POJO mapping works, but you have to supply the ID for each sub object you intend to modify: + +bc. user.id = 1 +&user.name=morten +&user.address.id=34 +&user.address.street=MyStreet h2. Custom binding The binding system now supports more customization. -h3. @play.data.binding.As +h3. @play.data.binding.As -The first thing is the new **@play.data.binding.As** annotation that makes it possible to contextually configure a binding. You can use it for example to specify the date format that must be used by the **DateBinder**: +The first thing is the new @play.data.binding.As annotation that makes it possible to contextually configure a binding. You can use it for example to specify the date format that must be used by the @DateBinder@: bc. public static void update(@As("dd/MM/yyyy") Date updatedAt) { - ... + … } -The **@As** annotation also has internationalisation support, which means that you can provide a specific annotation for each locale: +The @As annotation also has internationalisation support, which means that you can provide a specific annotation for each locale: bc. public static void update( - @As( - lang={"fr,de","en","*"}, - value={"dd/MM/yyyy","dd-MM-yyyy","MM-dd-yy"} - ) - Date updatedAt - ) { - ... + @As( + lang={"fr,de","en","*"}, + value={"dd/MM/yyyy","dd-MM-yyyy","MM-dd-yy"} + ) + Date updatedAt + ) { + … } -The **@As** annotation can work with all binders that support it, including your own binder. For example, using the **ListBinder**: +The @As annotation can work with all binders that support it, including your own binder. For example, using the @ListBinder@: bc. public static void update(@As(",") List items) { - ... + … } -This binds a simple comma separated **String** as a **List**. +This binds a simple comma separated @String@ as a @List@. -h3. @play.data.binding.NoBinding +h3. @play.data.binding.NoBinding -The new **@play.data.binding.NoBinding** annotation allows yous to mark non-bindable fields, resolving potential security issues. For example: +The new @play.data.binding.NoBinding annotation allows yous to mark non-bindable fields, resolving potential security issues. For example: bc. public class User extends Model { - @NoBinding("profile") public boolean isAdmin; - @As("dd, MM yyyy") Date birthDate; - public String name; + @NoBinding("profile") public boolean isAdmin; + @As("dd, MM yyyy") Date birthDate; + public String name; } public static void editProfile(@As("profile") User user) { - ... + … } -In this case, the **isAdmin** field will never be bound from the **editProfile** action, even if an malicious user includes a **user.isAdmin=true** field in a fake form post. +In this case, the @isAdmin@ field will never be bound from the @editProfile@ action, even if an malicious user includes a @user.isAdmin=true@ field in a fake form post. -h3. play.data.binding.TypeBinder +h3. play.data.binding.TypeBinder / TypeUnbinder -The *@As* annotation also allows you to define a completely custom binder. A custom binder is subclass of **TypeBinder** that you define in your project. For example: +The *@As* annotation also allows you to define a completely custom binder and/or unbinder. A custom binder / unbinder is subclass of @TypeBinder@ / @TypeUnbinder@ that you define in your project. For example: -bc. public class MyCustomStringBinder implements TypeBinder { +bc. public class MyCustomStringBinder implements TypeBinder, TypeUnbinder { - public Object bind(String name, Annotation[] anns, String value, Class clazz) { + public Object bind(String name, Annotation[] anns, String value, + Class clazz) { return "!!" + value + "!!"; } + + @Override + public void unBind(Map result, Object src, Class srcClazz, + String name, Annotation[] annotations) throws Exception { + String value = (String) src; + if(value != null){ + value = value.replaceAll("^!!", "").replaceAll("!!$", ""); + } + result.put(name, value); + } } You can use it in any action, like: -bc. public static void anyAction(@As(binder=MyCustomStringBinder.class) String name) { - ... +bc. public static void anyAction(@As(binder=MyCustomStringBinder.class) +String name) { + … } -h3. @play.data.binding.Global +h3. @play.data.binding.Global -Alternatively, you can define a global custom binder that will apply for the corresponding type. For example, you define a binder for the **java.awt.Point** class like this: +Alternatively, you can define a global custom binder that will apply for the corresponding type. For example, you define a binder for the @java.awt.Point@ class like this: bc. @Global public class PointBinder implements TypeBinder { - public Object bind(String name, Annotation[] anns, String value, Class class) { - String[] values = value.split(","); - return new Point( - Integer.parseInt(values[0]), - Integer.parseInt(values[1]) - ); + public Object bind(String name, Annotation[] anns, String value, + Class class) { + String[] values = value.split(","); + return new Point( + Integer.parseInt(values[0]), + Integer.parseInt(values[1]) + ); } } As you see a global binder is a classical binder annotated with *@play.data.binding.Global*. An external module can contribute binders to a project, which makes it possible to define reusable binder extensions. +h4. Exception throwing in TypeBinders + +If a typebinder throws an exception, by default the exception is trapped and the object that was bound is null. If a TypeBinder author wants an Exception to propagate instead, a BinderException should be thrown which will not be trapped by the Binder. h2. Result types @@ -368,11 +406,11 @@ bc. public static void show(Long id) { System.out.println("This message will never be displayed !"); } -The **render(…)** method emits a Result object and stops further method execution. +The @render(…)@ method emits a Result object and stops further method execution. -h3. Return some textual content +h3. Return some textual content -The **renderText(…)** method emits a simple Result event which writes some text directly to the underlying HTTP Response. +The @renderText(…)@ method emits a simple Result event which writes some text directly to the underlying HTTP Response. Example: @@ -388,7 +426,67 @@ bc. public static void countUnreadMessages() { renderText("There are %s unread messages", unreadMessages); } -h3. Execute a template +h3. Return a JSON String + +Play comes equiped with a method to simply return a JSON string, by making use of the @renderJSON(…)@ methods. These methods return a JSON string and set the response content type to @application/json@. + +You can specify your own JSON string, or you can pass in an @Object@, which will be serialised by the @GsonBuilder@. + +Example: + +bc. public static void countUnreadMessages() { + Integer unreadMessages = MessagesBox.countUnreadMessages(); + renderJSON("{\"messages\": " + unreadMessages +"}"); +} + +Alternatively, if you had a more complex object structure, you may wish to build the JSON using the @GsonBuilder@. + +bc. public static void getUnreadMessages() { + List unreadMessages = MessagesBox.unreadMessages(); + renderJSON(unreadMessages); +} + +If you need more control over the JSON builder when passing an @Object@ to the @renderJSON(…)@ method, you can also pass in GSON serialisers and @Type@ objects to customise the output. + +h3. Return an XML String + +As with the JSON methods, there are several methods for rendering XML directly from the controller. The @renderXml(…)@ methods return XML strings with the content type set to @text/xml@. + +Here you can specify your own XML string, pass a @org.w3c.dom.Document@ object, or pass a POJO which will be serialised by the XStream serialiser. + +Example: + +bc. public static void countUnreadMessages() { + Integer unreadMessages = MessagesBox.countUnreadMessages(); + renderXml(""+unreadMessages+""); +} + +Alternatively, you can use a @org.w3c.dom.Document@ object. + +bc. public static void getUnreadMessages() { + Document unreadMessages = MessagesBox.unreadMessagesXML(); + renderXml(unreadMessages); +} + +h3. Return binary content + +To serve binary data, such as a "file stored on the server":jpa#file, use the @renderBinary@ method. For example, if you have a @User@ model with a @play.db.jpa.Blob photo@ property, add a controller method to load the model object and render the image with the stored MIME type: + +bc. public static void userPhoto(long id) { + final User user = User.findById(id); + response.setContentTypeIfNotSet(user.photo.type()); + InputStream binaryData = user.photo.get(); + renderBinary(binaryData); +} + +h3. Download a file as an attachment + +You can set an HTTP header to instruct the web browser to treat a binary response as an ‘attachment’, which generally results in the web browser downloading the file to the user’s computer. To do this, pass a file name as a parameter to the @renderBinary@ method, which causes Play to set the @Content-Disposition@ response header, providing a file name. For example, supposing the @User@ model from the previous example as a @photoFileName@ property: + +bc. renderBinary(binaryData, user.photoFileName); + + +h3. Execute a template If the generated content is complex, you should use a template to generate the response content. @@ -397,7 +495,6 @@ bc. public class Clients extends Controller { public static void index() { render(); } - } A template name is automatically deduced from the Play conventions. The default template path is resolved using the Controller and action names. @@ -408,7 +505,7 @@ bc. app/views/Clients/index.html h4. Add data to the template scope -Often the template needs data. You can add these data to the template scope using the **renderArgs** object: +Often the template needs data. You can add these data to the template scope using the @renderArgs@ object: bc. public class Clients extends Controller { @@ -417,10 +514,9 @@ bc. public class Clients extends Controller { renderArgs.put("client", client); render(); } - } -During template execution, the **client** variable will be defined. +During template execution, the @client@ variable will be defined. For example: @@ -428,7 +524,7 @@ bc.

Client ${client.name}

h4. A simpler way to add data to the template scope -You can pass data directly to the template using **render(…)** method arguments: +You can pass data directly to the template using @render(…)@ method arguments: bc. public static void show(Long id) { Client client = Client.findById(id); @@ -451,7 +547,7 @@ You can only pass **local variables** in this way. h4. Specify another template -If you don’t want to use the default template, you can specify your own template file using the **renderTemplate(…)** method, by passing the template name as the first parameter: +If you don’t want to use the default template, you can specify your own template file using the @renderTemplate(…)@ method, by passing the template name as the first parameter: Example: @@ -460,18 +556,18 @@ bc. public static void show(Long id) { renderTemplate("Clients/showClient.html", id, client); } -h3. Redirect to another URL +h3. Redirect to another URL -The **redirect(…)** method emits a Redirect event that in turn generates an HTTP Redirect response. +The @redirect(…)@ method emits a Redirect event that in turn generates an HTTP Redirect response. bc. public static void index() { redirect("http://www.zenexity.fr"); } -h3. Action chaining +h3. Action chaining -There is no equivalent to the Servlet API **forward**. An HTTP request can only invoke one action. If you need to invoke another action, you have to redirect the browser to the URL able to invoke that action. In this way, the browser URL is always consistent with the executed action, and the **Back/Forward/Refresh** management is much easier. +There is no equivalent to the Servlet API @forward@. An HTTP request can only invoke one action. If you need to invoke another action, you have to redirect the browser to the URL able to invoke that action. In this way, the browser URL is always consistent with the executed action, and the **Back/Forward/Refresh** management is much easier. You can send a Redirect response to any action, simply by invoking the action method in a Java way. The Java call is intercepted by the framework and the correct HTTP Redirect is generated. @@ -489,7 +585,6 @@ bc. public class Clients extends Controller { client.save(); show(client.id); } - } With these routes: @@ -497,28 +592,48 @@ With these routes: bc. GET /clients/{id} Clients.show POST /clients Clients.create -* The browser sends a POST to the **/clients** URL. -* The Router invokes the **Clients** controller’s **create** action. -* The action method calls the **show** action method directly. +* The browser sends a POST to the @/clients@ URL. +* The Router invokes the @Clients@ controller’s @create@ action. +* The action method calls the @show@ action method directly. * The Java call is intercepted and the Router reverse route generation creates the URL needed to invoke Clients.show with an id parameter. -* The HTTP Response is **302 Location:/clients/3132**. -* The browser then issues **GET /clients/3132**. +* The HTTP Response is @302 Location:/clients/3132@. +* The browser then issues @GET /clients/3132@. * … +h3. Customise web encoding + +Play emphasises the use of UTF-8, but there are situations where some responses, or the whole application, must use a different encoding. + +h4. Custom encoding for current response + +To change encoding for current response, you can do it like this in your controller: + +bc. response.encoding = "ISO-8859-1"; + +When posting a form using an encoding other than the server default, you should include the encoding/charset twice in the form, both in the @accept-charset@ attribute and in a special hidden form field named @_charset_@. The @accept-charset@ attribute tells the browser which encoding to use when posting the from, and the form-field @_charset_@ tells Play what that encoding is: + +bc.
+ +
+ +h4. Custom encoding for the entire application + +Configure "application.web_encoding":configuration#application.web_encoding to specify which encoding Play uses when communicating with the browser. + h2. Interceptions A controller can define interception methods. Interceptors are invoked for all actions of the controller class and its descendants. It’s a useful way to define treatments that are common to all actions: verifying that a user is authenticated, loading request-scope information… -These methods have to be **static** but not **public**. You have to annotate these methods with a valid interception marker. +These methods have to be @static@ or not but not @public@, but same as action methods, they can be either @static@ or non-static. You have to annotate these methods with a valid interception marker. -h3. @Before +h3. @Before -Methods annotated with the **@Before** annotation are executed before each action call for this Controller. +Methods annotated with the @Before annotation are executed before each action call for this Controller. So, to create a security check: -bc. public class Admin extends Application { +bc. public class Admin extends Controller { @Before static void checkAuthentification() { @@ -529,14 +644,12 @@ bc. public class Admin extends Application { List users = User.findAll(); render(users); } - - ... - + … } If you don’t want the @Before method to intercept all action calls, you can specify a list of actions to exclude: -bc. public class Admin extends Application { +bc. public class Admin extends Controller { @Before(unless="login") static void checkAuthentification() { @@ -548,29 +661,27 @@ bc. public class Admin extends Application { render(users); } - ... - + … } Or if you want the @Before method to intercept a list of action calls, you can specify a only param : -bc. public class Admin extends Application { +bc. public class Admin extends Controller { @Before(only={"login","logout"}) - static void doSomething() { - ... - } - - ... + static void doSomething() { + … + } + … } -unless and only params are available for @After @Before and @Finally +The @unless@ and @only@ parameters are available for the @After, @Before and @Finally annotations. -h3. @After +h3. @After -Methods annotated with the **@After** annotation are executed after each action call for this Controller. +Methods annotated with the @After annotation are executed after each action call for this Controller. -bc. public class Admin extends Application { +bc. public class Admin extends Controller { @After static void log() { @@ -582,16 +693,60 @@ bc. public class Admin extends Application { render(users); } - ... + … +} + +h3. @Catch + +Methods annotated with @Catch are called if another action method throws the specified exception. The thrown exception is passed as a parameter to the @Catch method. + +bc. public class Admin extends Controller { + + @Catch(IllegalStateException.class) + public static void logIllegalState(Throwable throwable) { + Logger.error("Illegal state %s…", throwable); + } + + public static void index() { + List users = User.findAll(); + if (users.size() == 0) { + throw new IllegalStateException("Invalid database - 0 users"); + } + render(users); + } +} + +As with normal Java exception handling, you can catch a super-class to catch more exception types. If you have more than one catch method, you can specify their **priority** so that they are executed in order of priority (priority 1 is executed first). + +bc. public class Admin extends Controller { + + @Catch(value = Throwable.class, priority = 1) + public static void logThrowable(Throwable throwable) { + // Custom error logging… + Logger.error("EXCEPTION %s", throwable); + } + + @Catch(value = IllegalStateException.class, priority = 2) + public static void logIllegalState(Throwable throwable) { + Logger.error("Illegal state %s…", throwable); + } + public static void index() { + List users = User.findAll(); + if(users.size() == 0) { + throw new IllegalStateException("Invalid database - 0 users"); + } + render(users); + } } -h3. @Finally -Methods annotated with the **@Finally** annotation are always executed after each action call to this Controller. +h3. @Finally + +Methods annotated with the @Finally annotation are always executed after each action call to this Controller. @Finally-methods are called both after successful action calls and if an error occurred. -bc. public class Admin extends Application { +bc. public class Admin extends Controller { @Finally static void log() { @@ -602,41 +757,37 @@ bc. public class Admin extends Application { List users = User.findAll(); render(users); } - - ... - + … } If the method annotated with @Finally takes one argument of type Throwable, The Exception will be passed in if available: -bc. public class Admin extends Application { +bc. public class Admin extends Controller { @Finally static void log(Throwable e) { - if( e == null ){ - Logger.info("action call was successful"); - }else{ - Logger.info("action call failed", e); - } + if( e == null ){ + Logger.info("action call was successful"); + } else{ + Logger.info("action call failed", e); + } } public static void index() { List users = User.findAll(); render(users); } - - ... - + … } -h3. Controller hierarchy +h3. Controller hierarchy If a Controller class is a subclass of another Controller class, interceptions are applied to the full Controller hierarchy. -h3. Adding more interceptors using the @With annotation +h3. Adding more interceptors using the @With annotation -Because Java does not allow multiple inheritance, it can be very limiting to rely on the Controller hierarchy to apply interceptors. But you can define some interceptors in a totally different class, and link them with any controller using the **@With** annotation. +Because Java does not allow multiple inheritance, it can be very limiting to rely on the Controller hierarchy to apply interceptors. But you can define some interceptors in a totally different class, and link them with any controller using the @With annotation. Example: @@ -653,10 +804,9 @@ bc. public class Secure extends Controller { And on another Controller: bc. @With(Secure.class) -public class Admin extends Application { +public class Admin extends Controller { - ... - + … } h2. Session and Flash scopes @@ -679,11 +829,9 @@ bc. public static void index() { render(messages); } -The session expires when you close your web browser, unless the **application.session.maxAge** configuration property is set. For example: - -bc. application.session.maxAge=7d # Remember for one week. +The session expires when you close your web browser, unless you configure "application.session.maxAge":configuration#application.session.maxAge. -The cache has different semantics than the classic Servlet HTTP session object. You can’t assume that these objects will be always in the cache. So it forces you to handle the cache miss cases, and keeps your application fully stateless. +The cache has different semantics to the classic Servlet HTTP session object. You can’t assume that these objects will be always in the cache. So it forces you to handle the cache miss cases, and keeps your application fully stateless. p(note). **Continuing the discussion** diff --git a/documentation/manual/crud.textile b/documentation/manual/crud.textile index de2a6b8f59..598752d49c 100644 --- a/documentation/manual/crud.textile +++ b/documentation/manual/crud.textile @@ -8,14 +8,17 @@ Let’s see a simple example of using the CRUD module to manage user data. h3. Enable the CRUD module for the application -In the **/conf/application.conf** file, enable the CRUD module by adding this line: +In the @/conf/dependencies.yml@ file, enable the CRUD module by adding a line after @require:@ + +bc. require: + - play -> crud + +Now run the @play dependencies@ command to resolve the new module dependency. -bc. # The CRUD module -module.crud=${play.path}/modules/crud h3. Import default CRUD routes -In the **conf/routes** file, import the default module routes by adding this line: +In the @conf/routes@ file, import the default module routes by adding this line: bc. # Import CRUD routes * /admin module:crud @@ -116,7 +119,7 @@ Refresh the User form and you will see that the validation is automatically appl h3. Change the form label -Add these lines to the **conf/messages** file in your application: +Add these lines to the @conf/messages@ file in your application: bc. name=Name email=Email address @@ -128,17 +131,17 @@ and refresh the User form: h3. Create a User and customize the list view -The default list view uses only one column containing the result of the object’s **toString()** method. +The default list view uses only one column containing the result of the object’s @toString()@ method. !images/crud5! -To customize this view, we need to create the **/app/views/Users/list.html** template in the application. +To customize this view, we need to create the @/app/views/Users/list.html@ template in the application. Open a shell, go the application directory and type: bc. play crud:ov --template Users/list -This will copy the default CRUD **list.html** template to the **Users/list.html** template in your application, overwriting it if present. +This will copy the default CRUD @list.html@ template to the @Users/list.html@ template in your application, overwriting it if present. Edit the template like this: @@ -172,9 +175,9 @@ and refresh the list. h3. Custom field rendering: the crud.custom tag -You can go a bit further by customizing the way each field of your **User** entity is displayed in the list and form views. +You can go a bit further by customizing the way each field of your @User@ entity is displayed in the list and form views. -To customize a field, use the **#{crud.custom}** tag: +To customize a field, use the @#{crud.custom}@ tag: bc. #{crud.table fields:['name', 'company']} @@ -214,7 +217,7 @@ public class Account extends Model { @CollectionOfElements public Set usernames; - public Account(Set usernames { + public Account(Set usernames) { super(); this.usernames = usernames; } @@ -224,8 +227,8 @@ This is shown as: "myEnumId1","myEnumId2" for the contentTypes and "string1","st h2. Customize the _show_ and _blank_ views in a generic way -A central influence on the behavior of CRUD views is the **ObjectType** for each field. So if you want to -change the CRUD module’s behaviour in a generic way, for example to hide fields with the **@Version** annotation, you can create you own **ObjectType** class. You must also declare a static method in your controller or a superclass of your controller. +A central influence on the behavior of CRUD views is the @ObjectType@ for each field. So if you want to +change the CRUD module’s behaviour in a generic way, for example to hide fields with the @Version annotation, you can create you own @ObjectType@ class. You must also declare a static method in your controller or a superclass of your controller. bc. protected static ObjectType createObjectType(Class type) { return new VersionObjectType(type); @@ -277,26 +280,35 @@ bc. public class CustomAdminCompany extends CRUD { } } -This is not the end; you can also customize **findPage** and other methods. Have a look at the source code. +This is not the end; you can also customize @findPage@ and other methods. Have a look at the source code. + +h2. Localisation + +You can localise the user-interface text by overriding the CRUD module’s messages in your application’s @conf/messages@ file. + +bc. crud.add=Create new &{%s} + +To see which message keys are used, look in the CRUD module’s own message file: @$PLAY_HOME/modules/crud/conf/messages@. + h2. Commands -The CRUD module provides a *crud:override* command that you use on the command line to override the default templates. This works because the CRUD module loads templates from your application if they are present, instead of its own. You can also use **crud:ov** instead of **crud:override**. +The CRUD module provides a *crud:override* command that you use on the command line to override the default templates. This works because the CRUD module loads templates from your application if they are present, instead of its own. You can also use @crud:ov@ instead of @crud:override@. h3. play crud:override --template [path] -Copies the template specified by the path, e.g. **Users/list**, to your application’s **app/views/CRUD/** directory. You can also use **-t** instead of **--template**. +Copies the template specified by the path, e.g. @Users/list@, to your application’s @app/views/CRUD/@ directory. You can also use @-t@ instead of @--template@. h3. play crud:override --layout -Overrides the main page layout template, **layout.html**. +Overrides the main page layout template, @layout.html@. h3. play crud:override --css -Overrides the style sheet, **crud.css**, copying it to your the **public/stylesheets/** directory. +Overrides the style sheet, @crud.css@, copying it to your the @public/stylesheets/@ directory. h2. Limitation -Limitation The CRUD module only shows bi-directional relationships in one of the two entities: the one that does not have the **mappedBy** attribute. +Limitation The CRUD module only shows bi-directional relationships in one of the two entities: the one that does not have the @mappedBy@ attribute. diff --git a/documentation/manual/dependency.textile b/documentation/manual/dependency.textile index 872239258c..5955a318de 100644 --- a/documentation/manual/dependency.textile +++ b/documentation/manual/dependency.textile @@ -1,22 +1,28 @@ h1. Dependency management -Play’s dependency management system allows you to express your application’s external dependencies in a single **dependencies.yml** file. +Play’s dependency management system allows you to express your application’s external dependencies in a single @dependencies.yml@ file. A Play application can have three kinds of dependencies: * The Play framework itself, since a Play application always depends on the Play framework. -* Any Java library, provided as **JAR** file installed in your application’s **lib/** directory. -* A Play module (in fact an application fragment) installed in your application’s **modules/** directory. +* Some Play framework modules: CRUD and secure module +* Any Java library, provided as **JAR** file installed in your application’s @lib/@ directory. +* A Play module (in fact an application fragment) installed in your application’s @modules/@ directory. -Once you have expressed these dependencies in your application’s **conf/dependencies.yml** file, Play will resolve, download and install all required dependencies. +Once you have expressed these dependencies in your application’s @conf/dependencies.yml@ file, Play will resolve, download and install all required dependencies. h2. Dependency format -A dependency is described by an organisation a name and a revision number. In the **dependencies.yml** file you will write it like this: +A dependency is described by : +* an organisation a name, +* a revision number +* a classifier _(optional)_ -bc. organisation -> name revision +In the @dependencies.yml@ file you will write it like this: -So, for instance version 1.0 of the "Play PDF module":http://www.playframework.org/modules/pdf is expressed like this: +bc. organisation -> name revision [classifier] + +So, for instance version 1.0 of the "Play PDF module":https://www.playframework.com/modules/pdf is expressed like this: bc. play -> pdf 1.0 @@ -28,31 +34,51 @@ In this case, you can omit the organisation from the dependency declaration: bc. commons-lang 2.5 +When your dependency has a classifier, you can use this: + +bc. net.sf.json-lib -> json-lib 2.4 jdk15 + +For Play and Play framework modules (CRUD and secure module), the revision number is not required: + +bc. play +play -> crud +play -> secure + h3. Dynamic revisions The revision can be fixed (1.2, for instance) or dynamic. A dynamic revision expresses a range of allowed revisions. -For example: +There are many things you can configure: see the "Ivy version-matchers documentation":http://ant.apache.org/ivy/history/2.3.0/settings/version-matchers.html. -* **[1.0,2.0]** matches all versions greater or equal to 1.0 and lower or equal to 2.0 -* **[1.0,2.0[** matches all versions greater or equal to 1.0 and lower than 2.0 -* **]1.0,2.0]** matches all versions greater than 1.0 and lower or equal to 2.0 -* **]1.0,2.0[** matches all versions greater than 1.0 and lower than 2.0 -* **[1.0,)** matches all versions greater or equal to 1.0 -* **]1.0,)** matches all versions greater than 1.0 -* **(,2.0]** matches all versions lower or equal to 2.0 -* **(,2.0[** matches all versions lower than 2.0 +For example: +Sub Revision Matcher +* @1.0.+@ matches all revisions starting with '1.0.', like 1.0.1, 1.0.5, 1.0.a +* @1.1+@ matches all revisions starting with '1.1', like 1.1, 1.1.5, but also 1.10, 1.11 +Latest (Status) Matcher +* @latest.integration@ matches all versions +* @latest.milestone@ matches all modules having at least 'milestone' as status +* @latest.release@ matches all modules having at least 'release' as status +* @latest.[any status]@ all modules having at least the specified status +Version Range Matcher +* @[1.0,2.0]@ matches all versions greater or equal to 1.0 and lower or equal to 2.0 +* @[1.0,2.0[@ matches all versions greater or equal to 1.0 and lower than 2.0 +* @]1.0,2.0]@ matches all versions greater than 1.0 and lower or equal to 2.0 +* @]1.0,2.0[@ matches all versions greater than 1.0 and lower than 2.0 +* @[1.0,)@ matches all versions greater or equal to 1.0 +* @]1.0,)@ matches all versions greater than 1.0 +* @(,2.0]@ matches all versions lower or equal to 2.0 +* @(,2.0[@ matches all versions lower than 2.0 h2. dependencies.yml -When you create a new Play application, a **dependencies.yml** descriptor is automatically created in the **conf/** directory: +When you create a new Play application, a @dependencies.yml@ descriptor is automatically created in the @conf/@ directory: bc. # Application dependencies require: - play 1.2 -The **require** section list all dependencies needed by your application. Here the new application only depends of **Play version 1.2**. But let’s say your application needs "Google Guava":http://code.google.com/p/guava-libraries/; you would have: +The @require@ section list all dependencies needed by your application. Here the new application only depends on **Play version 1.2**. But let’s say your application needs "Google Guava":http://code.google.com/p/guava-libraries/; you would have: bc. # Application dependencies @@ -62,7 +88,7 @@ require: h3. The ‘play dependencies’ command -To ask Play to resolve, download and install the new dependencies, run **play dependencies**: +To ask Play to resolve, download and install the new dependencies, run @play dependencies@: bc. $ play dependencies ~ _ _ @@ -71,10 +97,10 @@ bc. $ play dependencies ~ | __/|_|\____|\__ (_) ~ |_| |__/ ~ -~ play! 1.2, http://www.playframework.org +~ play! 1.2, https://www.playframework.com ~ framework ID is gbo ~ -~ Resolving dependencies using ~/Desktop/scrapbook/coco/conf/dependencies.yml, +~ Resolving dependencies using ~/Documents/coco/conf/dependencies.yml, ~ ~ com.google.guava->guava r07 (from mavenCentral) ~ com.google.code.findbugs->jsr305 1.3.7 (from mavenCentral) @@ -92,7 +118,7 @@ bc. $ play dependencies ~ Done! ~ -Now Play has downloaded two JARs (guava-r07.jar, jsr305-1.3.7.jar) from the central Maven repository, and installed them into the application **lib/** directory. +Now Play has downloaded two JARs (**guava-r07.jar**, **jsr305-1.3.7.jar**) from the central Maven repository, and installed them into the application @lib/@ directory. Why two jars, since we only declared one dependency? Because Google Guava has a transitive dependency. In fact this dependency is not really required and we would like to exclude it. @@ -119,7 +145,7 @@ require: - play 1.2 - com.google.guava -> guava r07 -3. You can exclude any specific dependency explicitely: +3. You can exclude any specific dependency explicitly: bc. # Application dependencies @@ -129,9 +155,37 @@ require: exclude: - com.google.code.findbugs -> * +4. You can alter the ant configuration mappings used during resolution. See "Custom Configuration Mappings":#config_mappings + +h3(#config_mappings). Custom Configuration Mappings + +By default, Play will use the @default->*@ ant mapping. This means all transtive ant configurations (or maven scopes) are pulled in as dependencies. + +You can customize this behavior by using the @configurations@ block in @dependencies.yml@: + +bc. # Application dependencies + +configurations: + - optional: + exclude: true + +This will map to @default -> *, !optional@, or all configurations except @optional@. The way this functions is if no configs are provided then the default mapping of @default -> *@ is used. If only exclude mappings are provided, then the mapping becomes @default -> *, !excludedConfig1, !excludedConfig2,....@. Ivy will then depend on all configurations that are not excluded. If at least one non-excluded configuration is provided, then the listed configurations are all that will be used. For example: + +bc. # Application dependencies + +configurations: + - compile + - runtime + - optional: + exclude: true + +This will map to @default -> compile, runtime, !optional@. + +For more information on ant configuration mapping, please refer to the ant documentation + h3. Keep lib/ and modules/ directory in sync -Now if you run **play dependencies** again, the findbugs dependency will be omitted: +Now if you run @play dependencies@ again, the findbugs dependency will be omitted: bc. $ play deps ~ _ _ @@ -140,10 +194,10 @@ bc. $ play deps ~ | __/|_|\____|\__ (_) ~ |_| |__/ ~ -~ play! 1.2, http://www.playframework.org +~ play! 1.2, https://www.playframework.com ~ framework ID is gbo ~ -~ Resolving dependencies using ~/Desktop/scrapbook/coco/conf/dependencies.yml, +~ Resolving dependencies using ~/Documents/coco/conf/dependencies.yml, ~ ~ com.google.guava->guava r07 (from mavenCentral) ~ @@ -151,28 +205,36 @@ bc. $ play deps ~ ~ lib/guava-r07.jar ~ -~ ****************************************************************************************************************************** -~ WARNING: Your lib/ and modules/ directories and not synced with current dependencies (use --sync to automatically delete them) +~ ******************************************************************** +~ WARNING: Your lib/ and modules/ directories and not synced with +~ current dependencies (use --sync to automatically delete them) ~ -~ Unknown: ~/Desktop/scrapbook/coco/lib/jsr305-1.3.7.jar -~ ****************************************************************************************************************************** +~ Unknown: ~/Documents/coco/lib/jsr305-1.3.7.jar +~ ******************************************************************** ~ ~ Done! ~ -However the **jsr305-1.3.7.jar** artifact downloaded before is still present in the application **lib/** directory. +However the **jsr305-1.3.7.jar** artifact downloaded before is still present in the application @lib/@ directory. -To keep the **lib/** and **modules/** directory synced with the dependency management system, you can specify the **--sync** command to the **dependencies** command: +To keep the @lib/@ and @modules/@ directory synced with the dependency management system, you can specify the @--sync@ command to the @dependencies@ command: bc. play dependencies --sync -If you run this command again the unwanted **jar** will be deleted. +If you run this command again the unwanted **JAR** will be deleted. + +When you deploy an application in production, you can reduce the size of its modules by removing module source code and documentation. You can do this by adding the @--forProd@ option to the command: + +bc. play dependencies --forProd + +This removes the @documentation/@, @src/@, @tmp/@, @*sample*/@ and @*test*/@ directories from each module. + h2. Conflict resolution Whenever two components need different revisions of the same dependency, the conflicts manager will choose one. The default is to keep the latest revision and to evict the others. -But there is an exception. When a core dependency of Play framework itself is involved in a conflict, the version available in **$PLAY/framework/lib** is preferred. For instance, Play depends of **commons-lang 2.5**. If your application requires **commons-lang 3.0**: +But there is an exception. When a core dependency of Play framework itself is involved in a conflict, the version available in @$PLAY/framework/lib@ is preferred. For instance, Play depends on @commons-lang 2.5@. If your application requires @commons-lang 3.0@: bc. # Application dependencies @@ -182,7 +244,7 @@ require: transitive: false - commons-lang 3.0 -Running **play dependencies** will evict **commons-lang 3.0** even if this version is newer: +Running @play dependencies@ will evict @commons-lang 3.0@ even if this version is newer: bc. play dependencies ~ _ _ @@ -191,16 +253,16 @@ bc. play dependencies ~ | __/|_|\____|\__ (_) ~ |_| |__/ ~ -~ play! 1.2, http://www.playframework.org +~ play! 1.2, https://www.playframework.com ~ framework ID is gbo ~ -~ Resolving dependencies using ~/Desktop/scrapbook/coco/conf/dependencies.yml, +~ Resolving dependencies using ~/Documents/coco/conf/dependencies.yml, ~ ~ com.google.guava->guava r07 (from mavenCentral) ~ ~ Some dependencies have been evicted, ~ -~ commons-lang 3.0 is overriden by commons-lang 2.5 +~ commons-lang 3.0 is overridden by commons-lang 2.5 ~ ~ Installing resolved dependencies, ~ @@ -209,11 +271,11 @@ bc. play dependencies ~ Done! ~ -Also, note that dependencies already available in **$PLAY/framework/lib** will not be installed in your application’s **lib/** directory. +Also, note that dependencies already available in @$PLAY/framework/lib@ will not be installed in your application’s @lib/@ directory. Sometimes you want to force a specific dependency version, either to override a core dependency or to choose another revision that the latest version available. -So you can specify the **force** option on any dependency: +So you can specify the @force@ option on any dependency: bc. # Application dependencies @@ -226,9 +288,9 @@ require: h2. Adding new repositories -By default, Play will search for **JAR** dependencies in the "central Maven repository":http://repo1.maven.org/maven2/, and will search for **Play modules** in the "central Play modules repository":http://www.playframework.org/modules. +By default, Play will search for **JAR** dependencies in the "central Maven repository":http://repo1.maven.org/maven2/, and will search for **Play modules** in the "central Play modules repository":https://www.playframework.com/modules. -You can, of course, specify new custom repositories in the **repositories** section: +You can, of course, specify new custom repositories in the @repositories@ section: bc. # Application dependencies @@ -245,15 +307,15 @@ repositories: - zenexity: type: http - artifact: "http://www.zenexity.com/repo/[module]-[revision].[ext]" + artifact: "http://www.zenexity.com/repo/[module]-[revision].zip" contains: - com.zenexity -> * -Using this configuration all dependencies of the **com.zenexity** organisation will be retrieved and downloaded from a remote HTTP server. +Using this configuration all dependencies of the @com.zenexity@ organisation will be retrieved and downloaded from a remote HTTP server. h3. Maven repositories -You can also add maven2-compatible repositories using the **iBiblio** type, like this: +You can also add maven2-compatible repositories using the @iBiblio@ type, like this: bc. # Application dependencies @@ -273,4 +335,103 @@ repositories: - org.jbpm -> * - org.drools -> * +h3. Local repositories + +Finally and probably foremost, you may want to define a repository that references local modules. With this scenario, dependencies work very much like @application.conf@’s module resolution (now deprecated). + +So given the following folder structure, + +bc. myplayapp/ +myfirstmodule/ +mysecondmodule/ + +The following @myplayapp/conf/depencencies.yml@ will achieve its goal. + +bc. # Application dependencies + +require: + - play + - myfirstmodule -> myfirstmodule + - mysecondmodule -> mysecondmodule + +repositories: + - My modules: + type: local + artifact: ${application.path}/../[module] + contains: + - myfirstmodule + - mysecondmodule + +Note: don't forget to run @play dependencies myplayapp@. + + +h3. Custom ivy settings + +Play is using Ivy under the hood. If you require a special configuration such as setting a proxy, basic authentication for an internal maven nexus repository, you can edit the ivysettings.xml file. It is located in the @.ivy2@ folder in your home directory. + +Example 1, you want Ivy to ignore checksums: + +bc. + + + + +Example 2, you want to use basic authentication: + +bc. + + + + +Example 3, reuse local maven repository and repository manager: + +bc. + + + + + + + + + + + + + + + + + + + + + + + + + + + +There are many things you can configure: see the "Ivy settings documentation":http://ant.apache.org/ivy/history/2.3.0/settings.html. + + +h3. Clearing the Ivy cache + +The Ivy cache can become corrupted, especially when using the @http@ type in the repositories section of @conf/dependencies.yml@. If this happens, and dependency resolution does not work, you can clear the cache with the @--clearcache@ option. + +bc. $ play dependencies --clearcache + +This is equivalent to @rm -r ~/.ivy2/cache@. + + +p(note). **Continuing the discussion** +Next: %(next)"Database evolutions":evolutions%. diff --git a/documentation/manual/deployment.textile b/documentation/manual/deployment.textile index d045d7bed7..5b4aa8aed7 100644 --- a/documentation/manual/deployment.textile +++ b/documentation/manual/deployment.textile @@ -1,16 +1,16 @@ h1. Deployment options -Play applications can be deployed virtually anywhere: inside Servlet containers, as standalone servers, in Google Application Engine, Stack, a Cloud, etc... +Play applications can be deployed virtually anywhere: inside Servlet containers, as standalone servers, on Heroku, Google Application Engine, Stack, a Cloud, etc... h2. Standalone Play applications -The simplest and the more robust way is to simply run your Play application without any container. You can use a frontal HTTP server like Lighttpd or Apache if you need more advanced HTTP features like virtual hosting. +The simplest and the most robust way is to simply run your Play application without any container. You can use a frontal HTTP server like Lighttpd or Apache if you need more advanced HTTP features like virtual hosting. -The built-in HTTP server can serve thousands of HTTP requests per second so it will never be the performance bottleneck. Moreover it uses a more efficient threading model (where a Servlet container uses 1 thread per request). Different "modules":http://playframework.org/modules allow you to use different servers (Grizzly, Netty, etc...) as well. +The built-in HTTP server can serve thousands of HTTP requests per second so it will never be the performance bottleneck. Moreover it uses a more efficient threading model (where a Servlet container uses 1 thread per request). Different "modules":http://playframework.com/modules allow you to use different servers (Grizzly, Netty, etc...) as well. Those servers support long polling and allow to manage very long requests (waiting for a long task to complete), and direct streaming of File objects (and any InputStream if you specify the Content-Length), without blocking the execution thread. -You will have less problems running your application this way, as you will use the same environment that you used during the development process. A lot of bugs can be discovered only when you deploy to a JEE application server (different home dir, classloader issues, library conflicts, etc...). +You will have fewer problems running your application this way, as you will use the same environment that you used during the development process. A lot of bugs can be discovered only when you deploy to a JEE application server (different home dir, classloader issues, library conflicts, etc...). Please refer to the "'Put your application in production' page":production for more information. @@ -18,28 +18,54 @@ h2. Java EE application servers Your Play application can also run inside your favorite application server. Most application servers are supported out of the box. -h3. Application server compatibility matrix +h3. Supported application servers -|| JBoss 4.2.x || JBoss 5.x || JBoss 6M2 || Glasshfish v3 || IBM Websphere 6.1 ||IBM Websphere 7 || Geronimo 2.x || Tomcat 6.x || Jetty 7.x || Resin 4.0.5 || -|| ✓ || ✓ || ✓ || ✓ || ✓ || ✓ || ✓ || ✓ || ✓ || ✓ || +The following application servers are known to work with Play. Please feel free to report any other working deployment. -These application server are known to work with Play 1.1 but feel free to report any other working deployment. +* JBoss 4.2.x +* JBoss 5.x +* JBoss 6M2 +* Glassfish v3 +* IBM Websphere 6.1 +* IBM Websphere 7 +* Geronimo 2.x +* Tomcat 6.x +* Jetty 7.x +* Resin 4.0.5 h3. Deploying -You need to package your application as a war file. This is easily done with the following command: +You need to package your application as a WAR file. This is easily done with the following command: bc. play war myapp -o myapp.war +It can be invoke with the following parameters: + +* --output, or -o: + The path where the WAR directory will be created. The contents of this directory will first be deleted. + +* --zip: + By default, the script creates an exploded WAR. If you want a zipped archive, specify the --zip option. + +* --%fwk_id: + Use this ID to run the application (override the default framework ID) + +* --deps: + Resolve and install dependencies before running the command. + +* --exclude: + Excludes a list of directories (separator is ':'). + Example: --exclude .svn:tmp:logs:target + p(note). Please note that your application server must support deployment of exploded WAR files. You are now ready to deploy your application. -You are advised to ‘isolate’ your Play application from the other applications to avoid version mismatches between the application libraries. This step is not standardized by the JEE / Servlet Container specification, and is therefore vendor specific. +You are advised to ‘isolate’ your Play application from the other applications to avoid version mismatches between the application libraries. This step is not standardized by the JEE/Servlet specification, and is therefore vendor-specific. We recommend you refer to your application server manual in order to ‘isolate’ your WAR. As an example below is how you isolate a war file in JBoss Application server. Note that this is an optional step: -Insert the following content (or create the file) in your application war directory at myapp.war/WEB-INF/jboss-web.xml: +Insert the following content (or create the file) in your application war directory at @myapp.war/WEB-INF/jboss-web.xml@: bc. @@ -53,21 +79,23 @@ bc. Replace com.example:archive=unique-archive-name with whatever you wish as long as it is unique. + h3. Datasource -Play also supports Datasource and resource look-up. To use a JNDI Datasource, configure the Play database plugin (**play.db.DBPlugin**) in application.conf as shown below: +Play also supports Datasource and resource look-up. To use a JNDI Datasource, set the "database configuration":configuration#dbconf as shown below: bc. db=java:comp/env/jdbc/mydb jpa.dialect=org.hibernate.dialect.Oracle10gDialect jpa.ddl=verify -The database plugin detects the pattern "db=java:" and will unactivate the default JDBC system. +The database plugin detects the pattern @db=java:@ and will de-activate the default JDBC system. + h3. Custom web.xml -Some application servers, such as IBM Websphere, require you to declare a datasource in a **resource-ref** element in the Servlet API’s **web.xml** configuration file. By default, **web.xml** is a file that is generated by Play when you execute the **play war** command. To customise the generated **web.xml**, generate the exploded WAR version, then copy the generated **web.xml** file to the **war/WEB-INF** folder in your application. The next time you will execute the **play war** command, it will copy your custom **web.xml** from to the generated folder. +Some application servers, such as IBM Websphere, require you to declare a datasource in a @resource-ref@ element in the Servlet API’s @web.xml@ configuration file. By default, @web.xml@ is a file that is generated by Play when you execute the @play war@ command. To customise the generated @web.xml@, generate the exploded WAR version, then copy the generated @web.xml@ file to the @war/WEB-INF@ folder in your application. The next time you will execute the @play war@ command, it will copy your custom @web.xml@ from to the generated folder. -For instance, to declare a datasource for IBM Websphere 7, we can declare a **resource-ref** in our **war/WEB-INF/web.xml** file: +For instance, to declare a datasource for IBM Websphere 7, we can declare a @resource-ref@ in our @war/WEB-INF/web.xml@ file: bc. -h2. Google Application Engine (GAE) -A Play application can very easily be deployed to the GAE. It is a matter of installing the relevant "GAE module.":http://playframework.org/modules/gae +h2. Cloud-based hosting + + +h3. AWS Elastic Beanstalk + +"AWS Elastic Beanstalk":http://aws.amazon.com/en/elasticbeanstalk/ is Amazon’s Java hosting platform, based on their Amazon Web Services infrastructure. For more information about deploying a Play application, see "Java development 2.0: Play-ing with Amazon RDS":http://www.ibm.com/developerworks/java/library/j-javadev2-19/. + + +h3. CloudBees + +"CloudBees":http://www.cloudbees.com/ is a Java application hosting platform. For more information, see the "CloudBees module":https://www.playframework.com/modules/cloudbees. + + +h3. Cloud Foundry + +"Cloud Foundry":http://www.cloudfoundry.com/ is VMware’s cloud provider. For more information, see "Running Play Framework Application on CloudFoundry":http://iambivas.blogspot.com/2011/08/running-play-framework-application-on.html and the "CloudFoundry module":https://www.playframework.com/modules/cloudfoundry. + + +h3. Google App Engine (GAE) + +The "Google App Engine":http://code.google.com/appengine/ is one of the first cloud hosting platforms, which has different pros and cons compared to generic hosting solutions. In particular, GAE does not support JPA persistence. For more information, see the "GAE module":https://www.playframework.com/modules/gae. + + +h3. Heroku + +The "Heroku cloud application platform":http://www.heroku.com/ has Play-specific application hosting support. To deploy and run your Play application, use the following steps. + +1. Install the Heroku command line client on "Linux":http://toolbelt.herokuapp.com/linux/readme, "Mac":http://toolbelt.herokuapp.com/osx/download, or "Windows":http://toolbelt.herokuapp.com/windows/download. +2. Install "git":http://git-scm.com/ and setup your SSH key. +3. Create an account on "Heroku.com":http://heroku.com/signup. +4. Log in to Heroku from the command line: + +bc. heroku auth:login + +5. Create a git repository: + +bc. git init + +6. Create a @.gitignore@ file containing the following content, to ignore files generated by Play: + +bc. /tmp +/modules +/lib +/test-result +/logs + +7. Add the files to the git repository and commit them: + +bc. git add . +git commit -m init + +8. Create a new application on Heroku: + +bc. heroku create -s cedar + +9. Push the application to Heroku: + +bc. git push heroku master + +10. Open the application in your browser: + +bc. heroku open + + +To view the logs run: + +bc. heroku logs + +To scale the application to multiple ‘dynos’ run: + +bc. heroku scale web=2 + +To use the "Heroku Shared Database":http://devcenter.heroku.com/articles/database in production add the following to your @conf/application.conf@ file: -bc. play install gae +bc. %prod.db=${DATABASE_URL} +%prod.jpa.dialect=org.hibernate.dialect.PostgreSQLDialect +%prod.jpa.ddl=update -Deploying to the Google Application Engine is again really easy: +For more information, see the "Heroku Dev Center":http://devcenter.heroku.com. -bc. play gae:deploy myapp -Refer to the "module documentation":http://playframework.org/modules/gae-1.0.2/home for more information. -h2. Stax cloud hosting platform -Easy deployment to the "Stax cloud hosting platform":http://www.stax.net: again nothing could be easier. Install the Stax module and deploy within seconds. +h3. VPS / Custom Server -Refer to the "module documentation":http://www.playframework.org/modules/stax-1.0.1/home for more information. +If you want to run play as a service on your own server you can start it with this simple upstart script +This upstart script works with ubuntu 10.04 and upwards (upstart 1.4) -p(note). **Continuing the discussion** - -Next: %(next)"Production deployment":production%. \ No newline at end of file +bc. +description "Play Framework" +version "1.0" +env PLAY_BIN=/opt/playframework/play +env PROFILE=prod +start on runlevel [2345] +stop on runlevel [016] +respawn +respawn limit 10 5 +umask 022 +setuid youruser +setgid www-data +chdir /home/youruser/app/current +pre-start script + test -x $PLAY_BIN || { stop; exit 0; } + rm /home/youruser/app/current/server.pid || true +end script +exec $PLAY_BIN run $HOME -Xms1024M -Xmx1024M --%$PROFILE diff --git a/documentation/manual/emails.textile b/documentation/manual/emails.textile index 9efbb3d87a..12d9b1048e 100644 --- a/documentation/manual/emails.textile +++ b/documentation/manual/emails.textile @@ -1,6 +1,6 @@ h1. Sending e-mail -E-mail functionality uses the "Apache Commons Email":http://commons.apache.org/email/userguide.html library under the hood. You can use the **play.libs.Mail** utility class to send e-mail very easily. +E-mail functionality uses the "Apache Commons Email":http://commons.apache.org/email/userguide.html library under the hood. You can use the @play.libs.Mail@ utility class to send e-mail very easily. A simple e-mail: @@ -15,15 +15,15 @@ An HTML e-mail: bc. HtmlEmail email = new HtmlEmail(); email.addTo("info@lunatech.com"); -email.setFrom(sender@lunatech.com", "Nicolas"); +email.setFrom("sender@lunatech.com", "Nicolas"); email.setSubject("Test email with inline image"); // embed the image and get the content id -URL url = new URL("http://www.zenexity.fr/wp-content/themes/zenexity/images/logo.png"); +URL url = new URL("http://www.zenexity.fr/wp-content/themes/images/logo.png"); String cid = email.embed(url, "Zenexity logo"); // set the html message email.setHtmlMsg("Zenexity logo - "); // set the alternative message -email.setTextMsg("Your email client does not support HTML messages, too bad :("); +email.setTextMsg("Your email client does not support HTML, too bad :("); For more information see the "Commons Email documentation":http://commons.apache.org/email/userguide.html. @@ -31,12 +31,13 @@ h2. Mail and MVC integration You can also send complex, dynamic e-mail using the standard templates mechanism and syntax. -First, define a **Mailer notifier** in your application. Your mailer notifier must subclass **play.mvc.Mailer** and be part of the **notifiers** package. +First, define a **Mailer notifier** in your application. Your mailer notifier must subclass @play.mvc.Mailer@ and be part of the @notifiers@ package. Each public static method will be an e-mail sender, in a similar manner as actions for an MVC controller. For example: bc. package notifiers; +import org.apache.commons.mail.*; import play.*; import play.mvc.*; import java.util.*; @@ -66,7 +67,7 @@ public class Mails extends Mailer { h3. text/html e-mail -The **send** method call will render the **app/views/Mails/welcome.html** template as the e-mail message body. +The @send@ method call will render the @app/views/Mails/welcome.html@ template as the e-mail message body. bc.

Welcome ${user.name},

... @@ -74,10 +75,13 @@ bc.

Welcome ${user.name},

The template for the lostPassword method could look like this: -**app/views/Mails/lostPassword.html** +@app/views/Mails/lostPassword.html@ -bc. ...

Hello ${user.name},
-Your new password is ${newpassword}. +bc. +... + +

+ Hello ${user.name}, Your new password is ${newpassword}.

@@ -86,82 +90,89 @@ h3. text/plain e-mail If no HTML template is defined, then a text/plain e-mail is sent using the text template. -The **send** method call will render the **app/views/Mails/welcome.txt** template as the e-mail message body. +The @send@ method call will render the @app/views/Mails/welcome.txt@ template as the e-mail message body. bc. Welcome ${user.name}, ... The template for the lostPassword method could look like this: -**app/views/Mails/lostPassword.txt** +@app/views/Mails/lostPassword.txt@ -bc.Hello ${user.name}, +bc. Hello ${user.name}, Your new password is ${newpassword}. h3. text/html e-mail with text/plain alternative -If an HTML template is defined and a text template exists, then the text template will be used as an alternative message. In our previous example, if both **app/views/Mails/lostPassword.html** and **app/views/Mails/lostPassword.txt** are defined, then the e-mail will be sent in text/html as defined in lostPassword.html with an alternative part as defined in lostPassword.txt. So you can send nice HMTL e-mail to your friends and still please those geeky friends that still use mutt ;) +If an HTML template is defined and a text template exists, then the text template will be used as an alternative message. In our previous example, if both @app/views/Mails/lostPassword.html@ and @app/views/Mails/lostPassword.txt@ are defined, then the e-mail will be sent in text/html as defined in lostPassword.html with an alternative part as defined in lostPassword.txt. So you can send nice HMTL e-mail to your friends and still please those geeky friends that still use mutt ;) h3. Links to your application in e-mail Your can include links to your application in e-mails like this: -bc.@@{application.index} +bc. @@{application.index} -If you send mails from Jobs you have to configure **application.baseUrl** in **application.conf**. -**application.baseUrl** must be a valid external baseurl to your application. +If you send mails from Jobs you have to set "application.baseUrl":configuration#application.baseUrl to a valid external base URL for your application. -If the website playframework.org where to send you an e-mail from inside a Job, its configuration -would look like this: +For example, to send an e-mail from a Job running on the playframework.com web site, the configuration would look like this: -bc.application.baseUrl=http://www.playframework.org/ +bc. application.baseUrl=https://www.playframework.com/ -h2. SMTP configuration +h3. Links to embedded images + +The tag use the following parameters: + +* @src@ url/path to the image +* Optional @name@ name of the attach image file (so must be unique for a given image) +* Optional arguments can be added (alt, class,...) -E-mail functionality is configured in your application’s **conf/application.conf** file. First of all, you need to define the SMTP server to use: +You can add links to embedded images by using the embeddedImage tag like this: -bc. mail.smtp.host=smtp.taldius.net +Distant image: -If your SMTP server requires authentication, use the following properties: +bc. #{embeddedImage src:'https://www.playframework.com/assets/images/logo.png', name:'distantImage' /} -bc. mail.smtp.user=jfp -mail.smtp.pass=topsecret +Locale image: -h3. Channel & ports +bc. #{embeddedImage src:'file:///public/images/favicon.png', name:'LocaleImage', alt:'LocaleImage' /} -There are two ways to send the e-mail over an encrypted channel. If your server supports the **starttls** command (see: "RFC 2487":http://www.apps.ietf.org/rfc/rfc2487.html), you can use a clear connection on port 25 that will switch to SSL/TLS. You can do so by adding this configuration option: +or: -bc. mail.smtp.channel=starttls +bc. #{embeddedImage src:'public/images/favicon.png', name:'LocaleImage', alt:'LocaleImage' /} -Your server may also provide a SMTP-over-SSL (SMTPS) connector, that is an SSL socket listening on port 465. In that case, you tell Play to use this setup using the configuration option: +This will attach the image to the email and create an internal reference inside the the html/text content. -bc. mail.smtp.channel=ssl +h2. SMTP configuration + +E-mail functionality is configured by several "mail configuration":configuration#mail properties: -h2. More about configuration +* SMTP server - "mail.smtp.host":configuration#mail.smtp.host +* SMTP server authentication - "mail.smtp.user":configuration#mail.smtp.user and "mail.smtp.pass":configuration#mail.smtp.pass +* encrypted channel - "mail.smtp.channel":configuration#mail.smtp.channel +* JavaMail SMTP transaction logging - "mail.debug":configuration#mail.debug. -Under the hood, Play uses JavaMail to perform the actual SMTP transactions. If you need to see what’s going on, try: +Two additional configuration properties let you override default behaviour: -bc. mail.debug=true +* "mail.smtp.socketFactory.class":configuration#mail.smtp.socketFactory.class +* "mail.smtp.port":configuration#mail.smtp.port -When using SSL connections with JavaMail, the default SSL behavior is to drop the connection if the remote server certificate is not signed by a root certificate. This is the case in particular when using a self-signed certificate. Play’s default behavior is to skip that check. You can control this using the following property: -bc. mail.smtp.socketFactory.class +By default, in DEV mode, the e-mail will be printed to the console, while in PROD mode it will be sent to the actual SMTP server. You can change the default behaviour in DEV mode by commenting the following line: -If you need to connect to servers using non-standard ports, the following property will override the defaults: +bc. # Default is to use a mock Mailer +mail.smtp=mock -bc. mail.smtp.port=2500 -h2. Using Gmail +h3. Using Gmail -To use Gmail’s servers, use this configuration: +To use Gmail’s servers, for example when you deploy with "playapps":https://www.playframework.com/modules/playapps, use this configuration: bc. mail.smtp.host=smtp.gmail.com mail.smtp.user=yourGmailLogin mail.smtp.pass=yourGmailPassword mail.smtp.channel=ssl - p(note). **Continuing the discussion** -Next: %(next)"Security guide":security%. +Now we shall move on to %(next)"Testing the application":test%. diff --git a/documentation/manual/evolutions.textile b/documentation/manual/evolutions.textile new file mode 100644 index 0000000000..71b892fafd --- /dev/null +++ b/documentation/manual/evolutions.textile @@ -0,0 +1,332 @@ +h1. Managing database evolutions + +When you use a relational database, you need a way to track and organize your database schema evolutions. Typically there are several situations where you need a more sophisticated way to track your database schema changes: + +* When you work within a team of developers, each person needs to know about any schema change. +* When you deploy on a production server, you need to have a robust way to upgrade your database schema. +* If you work on several machines, you need to keep all database schemas synchronized. + +p(note). If you work with JPA, Hibernate can handle database evolutions for you automatically. Evolutions are useful if you don't use JPA or if you prefer to manually take care of your database schema for finer tuning. + +h2. Evolutions scripts + +Play tracks your database evolutions using several **evolutions script**. These scripts are written in plain old SQL and should be located in the @db/evolutions@ directory of your application. + +The first script is named @1.sql@, the second script @2.sql@, and so on… + +Each script contains two parts: + +* The *Ups* part the describes the required transformations. +* The *Downs* part that describes how to revert them. + +For example, take a look at this first evolution script that bootstraps a basic application: + +bc. # Users schema + +# --- !Ups + +CREATE TABLE User ( + id bigint(20) NOT NULL AUTO_INCREMENT, + email varchar(255) NOT NULL, + password varchar(255) NOT NULL, + fullname varchar(255) NOT NULL, + isAdmin boolean NOT NULL, + PRIMARY KEY (id) +); + +# --- !Downs + +DROP TABLE User; + +As you see you have to delimitate the both *Ups* and *Downs* section by using comments in your SQL script. + +Evolutions are automatically activated if a database is configured in @application.conf@ and evolution scripts are present. You can disable them by setting "evolutions.enabled":configuration#evolutions.enabled to @false@. For example when tests set up their own database you can disable evolutions for the test environment. + +When **evolutions** are activated, Play will check your database schema state before each request in DEV mode, or before starting the application in PROD mode. In DEV mode, if your database schema is not up to date, an error page will suggest that you synchronise your database schema by running the appropriate SQL script. + +!images/evolutions! + +If you agree with the SQL script, you can apply it directly by clicking on the 'Apply evolutions' button. + +p(note). If you use an in-memory database (*db=mem*), Play will automatically run all evolutions scripts if your database is empty. + +h2. Synchronizing concurrent changes + +Now let's imagine that we have two developers working on this project. Developer A will work on a feature that require a new database table. So he will create the following @2.sql@ evolution script: + +bc. # Add Post + +# --- !Ups +CREATE TABLE Post ( + id bigint(20) NOT NULL AUTO_INCREMENT, + title varchar(255) NOT NULL, + content text NOT NULL, + postedAt date NOT NULL, + author_id bigint(20) NOT NULL, + FOREIGN KEY (author_id) REFERENCES User(id), + PRIMARY KEY (id) +); + +# --- !Downs +DROP TABLE Post; + +Play will apply this evolution script to Developer A’s database. + +On the other hand, developer B will work on a feature that requires to alter the User table. So it will also create the following @2.sql@ evolution script: + +bc. # Update User + +# --- !Ups +ALTER TABLE User ADD age INT; + +# --- !Downs +ALTER TABLE User DROP age; + +Developer B finishes his feature and commits (let's say they are using Git). Now developer A has to merge the his colleague’s work before continuing, so he runs @git pull@, and the merge has a conflict, like: + +bc. Auto-merging db/evolutions/2.sql +CONFLICT (add/add): Merge conflict in db/evolutions/2.sql +Automatic merge failed; fix conflicts and then commit the result. + +Each developer has created a @2.sql@ evolution script. So developer A needs to merge the contents of this file: + +bc. <<<<<<< HEAD +# Add Post + +# --- !Ups +CREATE TABLE Post ( + id bigint(20) NOT NULL AUTO_INCREMENT, + title varchar(255) NOT NULL, + content text NOT NULL, + postedAt date NOT NULL, + author_id bigint(20) NOT NULL, + FOREIGN KEY (author_id) REFERENCES User(id), + PRIMARY KEY (id) +); + +# --- !Downs +DROP TABLE Post; +======= +# Update User + +# --- !Ups +ALTER TABLE User ADD age INT; + +# --- !Downs +ALTER TABLE User DROP age; +>>>>>>> devB + +The merge is really easy to do: + +bc. # Add Post and update User + +# --- !Ups +ALTER TABLE User ADD age INT; + +CREATE TABLE Post ( + id bigint(20) NOT NULL AUTO_INCREMENT, + title varchar(255) NOT NULL, + content text NOT NULL, + postedAt date NOT NULL, + author_id bigint(20) NOT NULL, + FOREIGN KEY (author_id) REFERENCES User(id), + PRIMARY KEY (id) +); + +# --- !Downs +ALTER TABLE User DROP age; + +DROP TABLE Post; + +This evolution script represents the new **revision 2** of the database, that is different of the previous revision 2 that developer A has already applied. + +So Play will detect it and ask developer A to synchronize his database by first reverting the old revision 2 already applied, and by applying the new revision 2 script: + +!images/evolutions-conflict! + +h2. Inconsistent states + +Sometimes you will make a mistake in your evolution scripts, and they will fail. In this case, Play will mark your database schema as being in an inconsistent state and will ask you to manually resolve the problem before continuing. + +For example, the **Ups** script of this evolution has an error: + +bc. # Add another column to User + +# --- !Ups +ALTER TABLE Userxxx ADD company varchar(255); + +# --- !Downs +ALTER TABLE User DROP company; + +So trying to apply this evolution will fail, and Play will mark your database schema as inconsistent: + +!images/evolutions-inconsistent! + +Now before continuing you have to fix this inconsistency. So you run the fixed SQL command: + +bc. ALTER TABLE User ADD company varchar(255); + +… and then mark this problem as manually resolved by clicking on the button. + +But because your evolution script has errors, you probably want to fix it. So you modify the @3.sql@ script: + +bc. # Add another column to User + +# --- !Ups +ALTER TABLE User ADD company varchar(255); + +# --- !Downs +ALTER TABLE User DROP company; + +Play detects this new evolution that replaces the previous *3* one, and will run the following script: + +!images/evolutions-resolve! + +Now everything is fixed, and you can continue to work. + +p(note). In development mode however it is often simpler to simply trash your developement database and reapply all evolutions from the beginning. + +h2. Evolutions commands + +The evolutions run interactively in DEV mode. However in PROD mode you will have to use the @evolutions@ command to fix your database schema before running your application. + +If you try to run a application in production mode on a database that is not up to date, the application will not start. + +bc. ~ _ _ +~ _ __ | | __ _ _ _| | +~ | '_ \| |/ _' | || |_| +~ | __/|_|\____|\__ (_) +~ |_| |__/ +~ +~ play! master-localbuild, https://www.playframework.com +~ framework ID is prod +~ +~ Ctrl+C to stop +~ +13:33:22 INFO ~ Starting ~/test +13:33:22 INFO ~ Precompiling ... +13:33:24 INFO ~ Connected to jdbc:mysql://localhost +13:33:24 WARN ~ +13:33:24 WARN ~ Your database is not up to date. +13:33:24 WARN ~ Use `play evolutions` command to manage database evolutions. +13:33:24 ERROR ~ + +@662c6n234 +Can't start in PROD mode with errors + +Your database needs evolution! +An SQL script will be run on your database. + +play.db.Evolutions$InvalidDatabaseRevision + at play.db.Evolutions.checkEvolutionsState(Evolutions.java:323) + at play.db.Evolutions.onApplicationStart(Evolutions.java:197) + at play.Play.start(Play.java:452) + at play.Play.init(Play.java:298) + at play.server.Server.main(Server.java:141) +Exception in thread "main" play.db.Evolutions$InvalidDatabaseRevision + at play.db.Evolutions.checkEvolutionsState(Evolutions.java:323) + at play.db.Evolutions.onApplicationStart(Evolutions.java:197) + at play.Play.start(Play.java:452) + at play.Play.init(Play.java:298) + at play.server.Server.main(Server.java:141) + +The error message asks you to run the @play evolutions@ command: + +bc. $ play evolutions +~ _ _ +~ _ __ | | __ _ _ _| | +~ | '_ \| |/ _' | || |_| +~ | __/|_|\____|\__ (_) +~ |_| |__/ +~ +~ play! master-localbuild, https://www.playframework.com +~ framework ID is gbo +~ +~ Connected to jdbc:mysql://localhost +~ Application revision is 3 [15ed3f5] and Database revision is 0 [da39a3e] +~ +~ Your database needs evolutions! + +# ---------------------------------------------------------------------------- + +# --- Rev:1,Ups - 6b21167 + +CREATE TABLE User ( + id bigint(20) NOT NULL AUTO_INCREMENT, + email varchar(255) NOT NULL, + password varchar(255) NOT NULL, + fullname varchar(255) NOT NULL, + isAdmin boolean NOT NULL, + PRIMARY KEY (id) +); + +# --- Rev:2,Ups - 9cf7e12 + +ALTER TABLE User ADD age INT; +CREATE TABLE Post ( + id bigint(20) NOT NULL AUTO_INCREMENT, + title varchar(255) NOT NULL, + content text NOT NULL, + postedAt date NOT NULL, + author_id bigint(20) NOT NULL, + FOREIGN KEY (author_id) REFERENCES User(id), + PRIMARY KEY (id) +); + +# --- Rev:3,Ups - 15ed3f5 + +ALTER TABLE User ADD company varchar(255); + +# ---------------------------------------------------------------------------- + +~ Run `play evolutions:apply` to automatically apply this script to the db +~ or apply it yourself and mark it done using `play evolutions:markApplied` +~ + +If you want Play to automatically run this evolution for you, then run: + +bc. $ play evolutions:apply + +If you prefer running this script manually on your production database, you need to tell Play that your database is up-to-date by running: + +bc. $ play evolutions:markApplied + +If there are any errors while automatically running the evolutions scripts, as in DEV mode, you need to manually resolve them, and mark your database schema as fixed by running: + +bc. $ play evolutions:resolve + + +h2. Evolutions and modules + +If you specify a dependency on a module which itself contains Evolutions scripts, then Evolutions will now detect and apply those scripts as well. These changes will always be run before your application's changes in case you have any foreign key dependencies on tables maintained in the module. + +If your application does not have any Evolutions, but a dependent module does, this will still run and apply those changes. + +p(note). By default all included modules with Evolutions scripts (in their respective 'db/evolutions' folders) will be passed into Evolutions. The application will also be included if it has Evolutions scripts. + +If you wish to only work with a subset of your modules when running the evolutions commands you can pass a command-line switch specifying with a comma separated list of modules you wish to include: + +bc. $play evolutions -Dmodules=application,module1,module2 + +or for a single module: + +bc. $play evolutions:apply -Dmodules=module3 + +p(note). If you use the command-line switch, your application will **NOT** be included by default, unless you specify it in the list of modules. + +If you wish to only work with the application's evolutions, you can use the @modules.evolutions.enabled@ parameters to configure the evolutions from all modules + +bc. modules.evolutions.enabled=false + +Default: @true@ + +If you wish to disable evolutions from a specific module, you can use the @[module name].evolutions.enabled@ parameters to configure the evolutions from the given module + +bc. [module name].evolutions.enabled=false + +Default: @true@ + + +p(note). **Continuing the discussion** + +Learn how to configure %(next)"Logging":logs%. diff --git a/documentation/manual/faq.textile b/documentation/manual/faq.textile index 7399fb2f40..8256efbd28 100644 --- a/documentation/manual/faq.textile +++ b/documentation/manual/faq.textile @@ -2,7 +2,7 @@ h1. Frequently Asked Questions h2. Where do I ask questions that are not answered here? -The "Community":http://www.playframework.org/community page links to various places where you can read and post about Play. In general, the best place to ask questions is the "play-framework Google Group":http://groups.google.com/group/play-framework. +The "Community":https://www.playframework.com/community page links to various places where you can read and post about Play. In general, the best place to ask questions is the "play-framework Google Group":http://groups.google.com/group/play-framework. h2. How does Play compare to framework X? @@ -12,7 +12,7 @@ However Play is a unique Java framework. It does not really rely on the so-calle h2. Why don’t you rename the ‘play’ package to ‘org.playframework’?! -You are missing the point. Play is not another library that you add to your Servlet container. It’s really a full stack Java framework that runs your application stand-alone. The Java package naming conventions exist to avoid name clashes when you use several different libraries from several vendors. But believe us, you will never put the **play.jar** library in your own project. This is not the way Play works. Play **is the platform**. Don’t worry about that. +You are missing the point. Play is not another library that you add to your Servlet container. It’s really a full stack Java framework that runs your application stand-alone. The Java package naming conventions exist to avoid name clashes when you use several different libraries from several vendors. But believe us, you will never put the @play.jar@ library in your own project. This is not the way Play works. Play **is the platform**. Don’t worry about that. h2. Why do I need Python (I would prefer Maven)? @@ -40,7 +40,7 @@ The biggest CPU consumer in the Play stack at the moment is the Groovy-based tem h2. Can I already use Play for a production application? -Sure, "a lot of people":http://www.playframework.org/community/testimonials already use Play in production. The 1.0 branch is now in maintenance mode, which means that we will just fix bugs and keep API compatibility in that branch. +Sure, "a lot of people":https://www.playframework.com/community/testimonials already use Play in production. The 1.0 branch is now in maintenance mode, which means that we will just fix bugs and keep API compatibility in that branch. h2. Is library X supported? diff --git a/documentation/manual/firstapp.textile b/documentation/manual/firstapp.textile index 9004995933..63f0394a2e 100644 --- a/documentation/manual/firstapp.textile +++ b/documentation/manual/firstapp.textile @@ -15,9 +15,9 @@ h2. Installing the Play framework Installation is very simple. Just download the latest binary package from the download page and unzip it to any path. -p(note). If you're using Windows, it is generally a good idea to avoid space characters in the path, so for example **c:\Play** would be a better choice than **c:\Documents And Settings\user\play**. +p(note). If you're using Windows, it is generally a good idea to avoid space characters in the path, so for example @c:\Play@ would be a better choice than @c:\Documents And Settings\user\play@. -To work efficiently, you need to add the Play directory to your working path. It allows to type just **play** at the command prompt to use the Play utility. To check that the installation worked, just open a new command line and type **play**; it should show you the Play basic usage help. +To work efficiently, you need to add the Play directory to your working path. It allows to type just @play@ at the command prompt to use the Play utility. To check that the installation worked, just open a new command line and type @play@; it should show you the Play basic usage help. h2. Project creation @@ -27,21 +27,23 @@ Open a new command line and type: bc. ~$ play new helloworld -It will prompt you for the application full name. Type **Hello world**. +It will prompt you for the application full name. Type @Hello world@. !images/firstapp-1! -The **play new** command creates a new directory **helloworld/** and populates it with a series of files and directories, the most important being: +The @play new@ command creates a new directory @helloworld/@ and populates it with a series of files and directories, the most important being: -**app/** contains the application’s core, split between models, controllers and views directories. It can contain other Java package as well. This is the directory where **.java** source files live. +@app/@ contains the application’s core, split between models, controllers and views directories. It can contain other Java package as well. This is the directory where @.java@ source files live. -**conf/** contains all the application’s configuration files, especially the main **application.conf** file, the **routes** definition files and the **messages** files used for internationalization. +@conf/@ contains all the application’s configuration files, especially the main @application.conf@ file, the @routes@ definition files and the @messages@ files used for internationalization. -**lib/** contains all optional Java libraries packaged as standard .jar files. +@documentation/@ contains application documentation. You can add your project specific documentation there and access it by "@projectdocs":http://localhost:9000/@projectdocs url. -**public/** contains all the publicly available resources, which includes JavaScript, stylesheets and images directories. +@lib/@ contains all optional Java libraries packaged as standard .jar files. -**test/** contains all the application tests. Tests are either written either as Java JUnit tests or as Selenium tests. +@public/@ contains all the publicly available resources, which includes JavaScript, stylesheets and images directories. + +@test/@ contains all the application tests. Tests are written either as Java JUnit tests or as Selenium tests. p(note). Because **Play uses UTF-8** as single encoding, it's very important that all text files hosted in these directories are encoded using this charset. Make sure to configure your text editor accordingly. @@ -51,7 +53,7 @@ That allows two very important things in the development process. The first one h2. Running the application -We can now test the newly-created application. Just return to the command line, go to the newly-created **helloworld/** directory and type **play run**. Play will now load the application and start a Web server on port 9000. +We can now test the newly-created application. Just return to the command line, go to the newly-created @helloworld/@ directory and type @play run@. Play will now load the application and start a Web server on port 9000. You can see the new application by opening a browser to "http://localhost:9000":http://localhost:9000. A new application has a standard welcome page that just tells you that it was successfully created. @@ -59,11 +61,11 @@ You can see the new application by opening a browser to "http://localhost:9000": Let’s see how the new application can display this page. -The main entry point of your application is the **conf/routes** file. This file defines all of the application’s accessible URLs. If you open the generated routes file you will see this first ‘route’: +The main entry point of your application is the @conf/routes@ file. This file defines all of the application’s accessible URLs. If you open the generated routes file you will see this first ‘route’: bc. GET / Application.index -That simply tells Play that when the web server receives a **GET** request for the **/** path, it must call the **Application.index** Java method. In this case, **Application.index** is a shortcut for **controllers.Application.index**, because the controllers package is implicit. +That simply tells Play that when the web server receives a **GET** request for the @/@ path, it must call the @Application.index@ Java method. In this case, @Application.index@ is a shortcut for @controllers.Application.index@, because the controllers package is implicit. When you create standalone Java applications you generally use a single entry point defined by a method such as: @@ -73,7 +75,7 @@ bc. public static void main(String[] args) { A Play application has several entry points, one for each URL. We call these methods **action** methods. Action methods are defined in special classes that we call **controllers**. -Let’s see how the **controllers.Application** controller looks like. Open the **helloworld/app/controllers/Application.java** source file: +Let’s see how the @controllers.Application@ controller looks like. Open the @helloworld/app/controllers/Application.java@ source file: bc. package controllers; @@ -87,26 +89,26 @@ public class Application extends Controller { } -You see that controller classes extend the **play.mvc.Controller** class. This class provides all useful methods for controllers, like the **render()** method we use in the index action. +You see that controller classes extend the @play.mvc.Controller@ class. This class provides all useful methods for controllers, like the @render()@ method we use in the index action. -The index action is defined as a **public static void** method. This is how action methods are defined. You can see that action methods are static, because the controller classes are never instantiated. They are marked public to authorize the framework to call them in response of a URL. They always return void. +The index action is defined as a @public static void@ method. This is how action methods are defined. You can see that action methods are static, because the controller classes are never instantiated. They are marked public to authorize the framework to call them in response of a URL. They always return void. -The default index action is simple: it calls the **render()** method which tells Play to render a template. Using a template is the most common way (but not the only one) to generate the HTTP response. +The default index action is simple: it calls the @render()@ method which tells Play to render a template. Using a template is the most common way (but not the only one) to generate the HTTP response. -Templates are simple text files that live in the **/app/views** directory. Because we didn’t specify a template, the default one for this action will be used: **Application/index.html** +Templates are simple text files that live in the @/app/views@ directory. Because we didn’t specify a template, the default one for this action will be used: @Application/index.html@ -To see what the template looks like, open the **helloworld/app/views/Application/index.html** file: +To see what the template looks like, open the @helloworld/app/views/Application/index.html@ file: bc. #{extends 'main.html' /} #{set title:'Home' /} #{welcome /} -The template content seems pretty light. In fact, all you see are Play tags. Play tags are very close to JSP taglib. This is the **#{welcome /}** tag that generates the welcome message you’ve seen in the browser. +The template content seems pretty light. In fact, all you see are Play tags. Play tags are very close to JSP taglib. This is the @#{welcome /}@ tag that generates the welcome message you’ve seen in the browser. -The **#{extends /}** tags tells Play that this template inherits another template called **main.html**. Template inheritance is a powerful concept that allows to create complex web pages by reusing common parts. +The @#{extends /}@ tags tells Play that this template inherits another template called @main.html@. Template inheritance is a powerful concept that allows to create complex web pages by reusing common parts. -Open the **helloworld/app/views/main.html** template: +Open the @helloworld/app/views/main.html@ template: bc. @@ -123,13 +125,13 @@ bc. -Can you see the **#{doLayout /}** tag? This is where the content of **Application/index.html** will be inserted. +Can you see the @#{doLayout /}@ tag? This is where the content of @Application/index.html@ will be inserted. h2. Creating the form We will start the ‘Hello World’ application with a form where you can enter your name. -Edit the **helloworld/app/views/Application/index.html** template: +Edit the @helloworld/app/views/Application/index.html@ template: bc. #{extends 'main.html' /} #{set title:'Home' /} @@ -141,11 +143,11 @@ bc. #{extends 'main.html' /} p(note). Note that we use GET as form submission method, because the form submission does not have any side effect and is idempotent. -We use the **@{…}** notation to ask Play to automatically generate the URL able to invoke the **Application.sayHello** action. Now, refresh the home page in the browser. +We use the @{…} notation to ask Play to automatically generate the URL able to invoke the @Application.sayHello@ action. Now, refresh the home page in the browser. !images/firstapp-2! -Oops, you get an error. This is because you reference the non-existent action **Application.sayHello**. Let’s create it in the **helloworld/app/controllers/Application.java** file: +Oops, you get an error. This is because you reference the non-existent action @Application.sayHello@. Let’s create it in the @helloworld/app/controllers/Application.java@ file: bc. package controllers; @@ -163,7 +165,7 @@ public class Application extends Controller { } -We have declared the **myName** parameter in the action method signature, so it will automatically be filled with the value of the HTTP **myName** parameter, coming from the form submission. And we call render to just display a template; as we pass the **myName** variable to the **render()** call, this one will be available from the template. +We have declared the @myName@ parameter in the action method signature, so it will automatically be filled with the value of the HTTP @myName@ parameter, coming from the form submission. And we call render to just display a template; as we pass the @myName@ variable to the @render()@ call, this one will be available from the template. !images/firstapp-3! @@ -171,7 +173,7 @@ Now, if you try to enter your name and submit the form, you will get another err !images/firstapp3! -The error is pretty clear. Play tries to render the default template for this action method, but it doesn’t exist. Let’s create it in the file **helloworld/app/views/Application/sayHello.html**: +The error is pretty clear. Play tries to render the default template for this action method, but it doesn’t exist. Let’s create it in the file @helloworld/app/views/Application/sayHello.html@: bc. #{extends 'main.html' /} #{set title:'Home' /} @@ -184,7 +186,7 @@ You can refresh the page. !images/firstapp-4! -Look how we have used Groovy’s **?:** operator. It switches to a default value if the **myName** variable is not filled. So if you try to submit the form without entering any name, it will display ‘Hello guest’. +Look how we have used Groovy’s @?:@ operator. It switches to a default value if the @myName@ variable is not filled. So if you try to submit the form without entering any name, it will display ‘Hello guest’. h2. Providing a better URL @@ -196,7 +198,7 @@ It is not very pretty. This is because Play used the default ‘catch all’ rou bc. * /{controller}/{action} {controller}.{action} -We can have a better URL by specifying a custom path for the **Application.sayHello** action. Edit the **helloworld/conf/routes** file and add this route after the first one: +We can have a better URL by specifying a custom path for the @Application.sayHello@ action. Edit the @helloworld/conf/routes@ file and add this route after the first one: bc. GET /hello Application.sayHello @@ -204,7 +206,7 @@ Now go back to the form and submit it again to check that it uses the new URL pa h2. Customizing the layout -As both templates that the application use so far inherit from the same **main.html** template, you can easily add a custom layout. Edit the **helloworld/app/views/main.html** file: +As both templates that the application use so far inherit from the same @main.html@ template, you can easily add a custom layout. Edit the @helloworld/app/views/main.html@ file: bc. ... @@ -223,7 +225,7 @@ h2. Adding validation We will add a little validation to the form, to make the name field required. We can use the Play validation framework to do that. -Edit the **helloworld/app/controllers/Application.java** controller, and the **sayHello** action: +Edit the @helloworld/app/controllers/Application.java@ controller, and the @sayHello@ action: bc. ... public static void sayHello(@Required String myName) { @@ -235,11 +237,11 @@ public static void sayHello(@Required String myName) { } ... -And don’t forget to import the **play.data.validation.*** package to get the **@Required** annotation. Play will automatically check that the myName field is filled or will add an error object to the **errors** scope. Then if there is any error, we add a message to the **flash** scope and redirect to the **index** action. +And don’t forget to import the @play.data.validation.*@ package to get the @Required annotation. Play will automatically check that the myName field is filled or will add an error object to the **errors** scope. Then if there is any error, we add a message to the **flash** scope and redirect to the @index@ action. The flash scope allows to keep messages during action redirection. -Now you just have to display the **error** message if any. Edit the **helloworld/app/views/Application/index.html**: +Now you just have to display the **error** message if any. Edit the @helloworld/app/views/Application/index.html@: bc. #{extends 'main.html' /} #{set title:'Home' /} @@ -263,11 +265,11 @@ h2. Writing an automated test suite We will finish by writing a test for the application. As there is no Java logic to test, we need to test the Web application itself. So we will write a Selenium test. -First, you need to run your application in **test mode**. Stop the application and restart it with the **test** command: +First, you need to run your application in **test mode**. Stop the application and restart it with the @test@ command: bc. $ play test -The **play test** command is almost the same as **play run**, except that it loads a test runner module that allows to run test suite directly from a browser. +The @play test@ command is almost the same as @play run@, except that it loads a test runner module that allows to run test suite directly from a browser. Open a browser to the "http://localhost:9000/@tests":http://localhost:9000/@tests URL to see the test runner. Try to select all the default tests and run them; all should be green… But these default tests don’t really test anything. @@ -275,7 +277,7 @@ Open a browser to the "http://localhost:9000/@tests":http://localhost:9000/@test A selenium test suite is typically written as an HTML file. The HTML syntax required by selenium is a little tedious (formatted using an HTML table element) to write. The good news is that Play will help you to generate it using the Play template engine and a set of tags that support a simplified syntax for selenium scenarios. -The default test suite of a newly created Play application already contains a selenium test. Open the **helloworld/test/Application.test.html** file: +The default test suite of a newly created Play application already contains a selenium test. Open the @helloworld/test/Application.test.html@ file: bc. *{ You can use plain selenium command using the selenium tag }* @@ -285,7 +287,7 @@ bc. *{ You can use plain selenium command using the selenium tag }* assertNotTitle('Application error') #{/selenium} -This test should run without any problem with the application for now. It just open the home page and check that the page content does not contain the ‘Application error’ text. +This test should run without any problem with the application for now. It just opens the home page and checks that the page content does not contain the ‘Application error’ text. Let’s write a test for our application. Edit the test content: @@ -294,7 +296,7 @@ bc. #{selenium} open('/') assertNotTitle('Application error') - // Check that it is the form + // Check that it is the Hello World application assertTextPresent('The Hello world app.') // Submit the form diff --git a/documentation/manual/guide1.textile b/documentation/manual/guide1.textile index 35a3b645bb..f58f3e4ef6 100644 --- a/documentation/manual/guide1.textile +++ b/documentation/manual/guide1.textile @@ -1,6 +1,6 @@ h1. Starting up the project -h2. Introduction +h2. Introduction In this tutorial you will learn the Play framework by coding a real web application, from start to finish. In this application, we will try to use everything you would need in a real project, while introducing good practices for Play application development. @@ -8,7 +8,7 @@ We have split the tutorial into several independent parts. Each part will introd p(note). **All the code** included in this tutorial can be used for your projects. We encourage you to copy and paste snippets of code or steal whole chunks. -h2. The project +h2. The project We chose to create yet another blog engine. It’s not a very imaginative choice but it will allow us to explore most of the functionality needed by a modern web application. @@ -18,9 +18,9 @@ We will call this blog engine project **yabe**. !images/guide1-0! -p(note). This tutorial is also distributed as a sample application. You can find the final code in your Play installation’s **samples-and-tests/yabe/** directory. +p(note). This tutorial is also distributed as a sample application. You can find the final code in your Play installation’s @samples-and-tests/yabe/@ directory. -h2. Prerequisites +h2. Prerequisites First of all, make sure that you have a working Java installation. Play requires **Java 5 or later**. @@ -32,15 +32,15 @@ You will of course need a text editor. If you are accustomed to using a fully-fe Later in this tutorial we will use Lighttpd and MySql to show how to deploy a Play application in ‘production’ mode. But Play can work without these components so if you can’t install them, it’s not a problem. -h2. Installing the Play framework +h2. Installing the Play framework Installation is very simple. Just download the latest binary package from the download page and unzip it to any path. -p(note). If you’re using Windows, it is generally a good idea to avoid space characters in the path, so for example **c:\play** would be a better choice than **c:\Documents And Settings\user\play**. +p(note). If you’re using Windows, it is generally a good idea to avoid space characters in the path, so for example @c:\play@ would be a better choice than @c:\Documents And Settings\user\play@. -To work efficiently, you need to add the Play directory to your working path. It allows you to just type **play** at the command prompt to use the play utility. To check that the installation worked, just open a new command line and type **play**; it should show you the play basic usage help. +To work efficiently, you need to add the Play directory to your working path. It allows you to just type @play@ at the command prompt to use the play utility. To check that the installation worked, just open a new command line and type @play@; it should show you the play basic usage help. -h2. Project creation +h2. Project creation Now that Play is correctly installed, it’s time to create the blog application. Creating a Play application is pretty easy and fully managed by the play command line utility. That allows for standard project layouts between all Play applications. @@ -52,17 +52,17 @@ It will prompt you for the application full name. Type **Yet Another Blog Engine !images/guide1-1! -The **play new** command creates a new directory **yabe/** and populates it with a series of files and directories, the most important being: +The @play new@ command creates a new directory @yabe/@ and populates it with a series of files and directories, the most important being: -**app/** contains the application’s core, split between models, controllers and views directories. It can contain other Java packages as well. This is the directory where **.java** source files live. +@app/@ contains the application’s core, split between models, controllers and views directories. It can contain other Java packages as well. This is the directory where @.java@ source files live. -**conf/** contains all the configuration files for the application, especially the main **application.conf** file, the **routes** definition files and the **messages** files used for internationalization. +@conf/@ contains all the configuration files for the application, especially the main @application.conf@ file, the @routes@ definition files and the @messages@ files used for internationalization. -**lib/** contains all optional Java libraries packaged as standard **.jar** files. +@lib/@ contains all optional Java libraries packaged as standard @.jar@ files. -**public/** contains all the publicly available resources, which includes JavaScript files, stylesheets and images directories. +@public/@ contains all the publicly available resources, which includes JavaScript files, stylesheets and images directories. -**test/** contains all the application tests. Tests are either written either as Java JUnit tests or as Selenium tests. +@test/@ contains all the application tests. Tests are written either as Java JUnit tests or as Selenium tests. p(note). Because **Play uses UTF-8** as single encoding, it’s very important that all text files hosted in these directories are encoded using this charset. Make sure to configure your text editor accordingly. @@ -70,11 +70,11 @@ Now if you’re a seasoned Java developer, you may wonder where all the .class f That allows two very important things in the development process. The first one is that Play will detect changes you make to any Java source file and automatically reload them at runtime. The second is that when a Java exception occurs, Play will create better error reports showing you the exact source code. -p(note). In fact Play keeps a bytecode cache in the application’s **tmp/** directory, but only to speed things up between restart on large applications. You can discard this cache using the **play clean** command if needed. +p(note). In fact Play keeps a bytecode cache in the application’s @tmp/@ directory, but only to speed things up between restart on large applications. You can discard this cache using the @play clean@ command if needed. -h2. Running the application +h2. Running the application -We can now test the newly-created application. Just return to the command line, go to the newly-created **yabe/** directory and type **play run**. Play will now load the application and start a web server on port 9000. +We can now test the newly-created application. Just return to the command line, go to the newly-created @yabe/@ directory and type @play run@. Play will now load the application and start a web server on port 9000. You can see the new application by opening a browser to "http://localhost:9000":http://localhost:9000. A new application has a standard welcome page that just tells you that it was successfully created. @@ -82,11 +82,11 @@ You can see the new application by opening a browser to "http://localhost:9000": Let’s see how the new application can display this page. -The main entry point of your application is the **conf/routes** file. This file defines all accessible URLs for the application. If you open the generated routes file you will see this first ‘route’: +The main entry point of your application is the @conf/routes@ file. This file defines all accessible URLs for the application. If you open the generated routes file you will see this first ‘route’: bc. GET / Application.index -That simply tells Play that when the web server receives a **GET** request for the **/** path, it must call the **Application.index** Java method. In this case, **Application.index** is a shortcut for **controllers.Application.index**, because the **controllers** package is implicit. +That simply tells Play that when the web server receives a **GET** request for the @/@ path, it must call the @Application.index@ Java method. In this case, @Application.index@ is a shortcut for @controllers.Application.index@, because the @controllers@ package is implicit. When you create standalone Java applications you generally use a single entry point defined by a method such as: @@ -94,9 +94,9 @@ bc. public static void main(String[] args) { ... } -A Play application has several entry points, one for each URL. We call these methods **action** methods. Action methods are defined in special classes that we call **controllers**. +A Play application has several entry points, one for each URL. We call these methods @action@ methods. Action methods are defined in special classes that we call @controllers@. -Let’s see what the **controllers.Application** controller looks like. Open the **yabe/app/controllers/Application.java** source file: +Let’s see what the @controllers.Application@ controller looks like. Open the @yabe/app/controllers/Application.java@ source file: bc. package controllers; @@ -110,45 +110,49 @@ public class Application extends Controller { } -Notice that controller classes extend the **play.mvc.Controller** class. This class provides many useful methods for controllers, like the **render()** method we use in the index action. +Notice that controller classes extend the @play.mvc.Controller@ class. This class provides many useful methods for controllers, like the @render()@ method we use in the index action. -The index action is defined as a **public static void** method. This is how action methods are defined. You can see that action methods are static, because the controller classes are never instantiated. They are marked public to authorize the framework to call them in response to a URL. They always return void. +The index action is defined as a @public static void@ method. This is how action methods are defined. You can see that action methods are static, because the controller classes are never instantiated. They are marked public to authorize the framework to call them in response to a URL. They always return void. -The default index action is simple: it calls the **render()** method which tells Play to render a template. Using a template is the most common way (but not the only one) to generate the HTTP response. +The default index action is simple: it calls the @render()@ method which tells Play to render a template. Using a template is the most common way (but not the only one) to generate the HTTP response. -Templates are simple text files that live in the **/app/views** directory. Because we didn’t specify a template, the default one for this action will be used: **Application/index.html** +Templates are simple text files that live in the @/app/views@ directory. Because we didn’t specify a template, the default one for this action will be used: @Application/index.html@ -To see what the template looks like, open the **/yabe/app/views/Application/index.html** file: +To see what the template looks like, open the @/yabe/app/views/Application/index.html@ file: bc. #{extends 'main.html' /} #{set title:'Home' /} #{welcome /} -The template content seems pretty light. In fact, all you see are Play tags. Play tags are similar to JSP tags. This is the **#{welcome /}** tag that generates the welcome message you saw in the browser. +The template content seems pretty light. In fact, all you see are Play tags. Play tags are similar to JSP tags. This is the @#{welcome /}@ tag that generates the welcome message you saw in the browser. -The **#{extends /}** tag tells Play that this template inherits another template called **main.html**. Template inheritance is a powerful concept that allows you to create complex web pages by reusing common parts. +The @#{extends /}@ tag tells Play that this template inherits another template called @main.html@. Template inheritance is a powerful concept that allows you to create complex web pages by reusing common parts. -Open the **/yabe/app/views/main.html** template: +Open the @/yabe/app/views/main.html@ template: bc. - - #{get 'title' /} - - - - - - #{doLayout /} - + + #{get 'title' /} + + + #{get 'moreStyles' /} + + + #{get 'moreScripts' /} + + + #{doLayout /} + -Do you see the **#{doLayout /}** tag? This is where the content of **Application/index.html** will be inserted. +Do you see the @#{doLayout /}@ tag near the bottom? This is where the content of @Application/index.html@ will be inserted. -We can try to edit the controller file to see how Play automatically reloads it. Open the **yabe/app/controllers/Application.java** file in a text editor, and add a mistake by removing the trailing semicolon after the **render()** call: +We can try to edit the controller file to see how Play automatically reloads it. Open the @yabe/app/controllers/Application.java@ file in a text editor, and add a mistake by removing the trailing semicolon after the @render()@ call: bc. public static void index() { render() @@ -165,52 +169,83 @@ bc. public static void index() { render(); } -This time, Play has correctly reloaded the controller and replaced the old code in the JVM. Each request to the **/** URL will output the ‘Yop’ message to the console. +This time, Play has correctly reloaded the controller and replaced the old code in the JVM. Each request to the @/@ URL will output the ‘Yop’ message to the console. -You can remove this useless line, and now edit the **yabe/app/views/Application/index.html** template to replace the welcome message: +You can remove this useless line, and now edit the @yabe/app/views/Application/index.html@ template to replace the welcome message: bc. #{extends 'main.html' /} #{set title:'Home' /} -

A blog will be there

+

A blog will be here

Like for Java code changes, just refresh the page in the browser to see the modification. p(note). We will now start to code the blog application. You can either continue to work with a text editor or open the project in a Java IDE like Eclipse or NetBeans. If you want to set-up a Java IDE, see "Setting-up your preferred IDE":ide. -h2. Setting-up the database +h2. Setting-up the database One more thing before starting to code. For the blog engine, we will need a database. For development purposes, Play comes with a stand alone SQL database management system called H2. This is the best way to start a project before switching to a more robust database if needed. You can choose to have either an in-memory database or a filesystem database that will keep your data between application restarts. At the beginning, we will do a lot of testing and changes in the application model. For that reason, it’s better to use an in-memory database so we always start with a fresh data set. -To set-up the database, open the **yabe/conf/application.conf** file and uncomment this line: +To set-up the database, open the @yabe/conf/application.conf@ file and uncomment this line: bc. db=mem As you can see in the comments, you can easily set-up any JDBC compliant database and even configure the connection pool. +p(note). This tutorial is designed to work with the in-memory database; instructions for using JPA with other databases is outside the scope of this tutorial. + Now, go back to your browser and refresh the welcome page. Play will automatically start the database. Check for this line in the application logs: bc. INFO ~ Connected to jdbc:h2:mem:play -h2. Using a version control system to track changes +h2. Using a version control system to track changes -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 is highly recommended to store your source code in a version control system (VCS). This 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 is important to exclude the @tmp/@, @modules/@, @lib/@, @test-result/@ and @logs/@ directories. -bc. $ bzr init +If you are using Eclipse, and the @play eclipsify@ command, then you should also exclude @.classpath@ and @eclipse/@. + +h3. Bazaar + +Here we will use Bazaar as an example. "Bazaar":http://bazaar-vcs.org/ is a distributed source version control system. -When storing a Play application in a VCS, it’s important to exclude the **tmp/** and **logs/** directories. +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 initialise 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: bc. $ bzr add -$ bzr commit -m "YABE inital version" +$ bzr commit -m "YABE initial version" + +h3. Git + +"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 in the same directory, containing the following content: + +bc. /tmp +/modules +/lib +/test-result +/logs + +Add the application’s files and commit: + +bc. $ git add . +$ git commit -m "YABE initial version" Version 1 is committed and we now have a solid foundation for our project. diff --git a/documentation/manual/guide10.textile b/documentation/manual/guide10.textile index 4d3c594af2..999908fde7 100644 --- a/documentation/manual/guide10.textile +++ b/documentation/manual/guide10.textile @@ -4,13 +4,13 @@ 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. Testing the controller part +h2. Testing the controller part 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. -Basically a functional test calls the Play **ActionInvoker** directly, simulating an HTTP request. So we give an HTTP method, an URI and HTTP parameters. Play then routes the request, invokes the corresponding action and sends you back the filled response. You can then analyze it to check that the response content is like you expected. +Basically a functional test calls the Play @ActionInvoker@ directly, simulating an HTTP request. So we give an HTTP method, a URI and HTTP parameters. Play then routes the request, invokes the corresponding action and sends you back the filled response. You can then analyze it to check that the response content is like you expected. -Let’s write a first functional test. Open the **yabe/test/ApplicationTest.java** unit test: +Let’s write a first functional test. Open the @yabe/test/ApplicationTest.java@ unit test: bc. import org.junit.*; import play.test.*; @@ -30,20 +30,20 @@ public class ApplicationTest extends FunctionalTest { } -It looks like a standard JUnit test for now. Note that we use the Play **FunctionalTest** super class in order to get all useful utility helpers. This test is correct and just checks that the application home page (typically the ‘/’ URL renders an HTML response with **‘200 OK’** as status code). +It looks like a standard JUnit test for now. Note that we use the Play @FunctionalTest@ super class in order to get all useful utility helpers. This test is correct and just checks that the application home page (typically the @/@ URL renders an HTML response with **‘200 OK’** as status code). -Now we will check that the administration area’s security works as expected. Add this new test to the **ApplicationTest.java** file: +Now we will check that the administration area’s security works as expected. Add this new test to the @ApplicationTest.java@ file: -bc. ... +bc. … @Test public void testAdminSecurity() { Response response = GET("/admin"); assertStatus(302, response); - assertHeaderEquals("Location", "http://localhost/login", response); + assertHeaderEquals("Location", "/login", response); } -... +… -Now run the yabe application in test mode using the **‘play test’** command, open "http://localhost:9000/@tests":http://localhost:9000/@tests, select the **ApplicationTest.java** test case and run it. +Now run the yabe application in test mode using the @play test@ command, open "http://localhost:9000/@tests":http://localhost:9000/@tests, select the @ApplicationTest.java@ test case and run it. Is it green? @@ -53,15 +53,15 @@ 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. Writing Selenium tests +h2. Writing Selenium tests -"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. +"Selenium":http://seleniumhq.org is a testing tool specifically for testing web applications. The cool thing 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. -A Selenium test suite is typically written as an HTML file. The HTML syntax required by Selenium is a little tedious to write (formatted using an HTML table element). The good news is that Play helps you generate it using the Play template engine and a set of tags that support a simplified syntax for Selenium scenarios. An interesting side effect of using templates is that your are not tied to ‘static scenarios’ any more and you can use the power of Play templates (looping, conditional blocks) to write more complicated tests. +A Selenium test suite is typically written as an HTML file. The HTML syntax required by Selenium is a little tedious to write (formatted using an HTML table element). The good news is that Play helps you generate it using the Play template engine and a set of tags that support a simplified syntax for Selenium scenarios. An interesting side effect of using templates is that you are not tied to ‘static scenarios’ any more and you can use the power of Play templates (looping, conditional blocks) to write more complicated tests. p(note). However you can still write plain HTML Selenium syntax in the template and forget the specific Selenium tags if needed. It can become interesting if you use one of the several Selenium tools that generates the test scenarios for you, like "Selenium IDE":http://seleniumhq.org/projects/ide. -The default test suite of a newly-created Play application already contains a Selenium test. Open the **yabe/test/Application.test.html** file: +The default test suite of a newly-created Play application already contains a Selenium test. Open the @yabe/test/Application.test.html@ file: bc. *{ You can use plain Selenium commands using the selenium tag }* @@ -74,9 +74,9 @@ bc. *{ You can use plain Selenium commands using the selenium tag }* This test should run without any problem with the yabe application. It just opens the home page and checks that the page content does not contain the ‘Application error’ text. -However like any complex test, you need to set-up a set of well-known data before navigating the application and testing it. We will of course reuse the fixture concept and the **yabe/test/data.yml** file that we’ve used before. To import this data set before the test suite, just use the **#{fixtures /}** tag: +However like any complex test, you need to set-up a set of well-known data before navigating the application and testing it. We will of course reuse the fixture concept and the @yabe/test/data.yml@ file that we’ve used before. To import this data set before the test suite, just use the @#{fixture /}@ tag: -bc. #{fixture delete:'all', load:'data.yml' /} +bc. #{fixture delete:'all', loadModels:'data.yml' /} #{selenium} // Open the home page, and check that no error occurred @@ -89,7 +89,7 @@ Another important thing to check is that we have a fresh user session at the tes So let’s start our test with a special command: -bc. #{fixture delete:'all', load:'data.yml' /} +bc. #{fixture delete:'all', loadModels:'data.yml' /} #{selenium} clearSession() @@ -104,7 +104,7 @@ Run it to be sure that there is no mistake. It should be green. So we can write a more specific test. Open the home page and check that the default posts are present: -bc. #{fixture delete:'all', load:'data.yml' /} +bc. #{fixture delete:'all', loadModels:'data.yml' /} #{selenium 'Check home page'} clearSession() @@ -123,13 +123,13 @@ bc. #{fixture delete:'all', load:'data.yml' /} assertTextPresent('Just a test of YABE') #{/selenium} -We use the standard Selenium syntax, called "selenese":http://seleniumhq.org/docs/04_selenese_commands.html. +We use the standard Selenium syntax, called "Selenese":http://seleniumhq.org/docs/02_selenium_ide.html#selenium-commands-selenese. Run it (you can run in a different window just by opening the test link in a new window). !images/guide10-2! -We will now test the comments form. Just add a new **#{selenium /}** tag to the template: +We will now test the comments form. Just add a new @#{selenium /}@ tag to the template: bc. #{selenium 'Test comments'} @@ -158,41 +158,47 @@ And run it. Well it fails; and we have a serious problem here. !images/guide10-3! -We can’t really correctly test the captcha mechanism, so we have to cheat. In test mode we will validate any code as a correct captcha. We know that we’re in test mode when the framework id is **test**. So let’s modify the **postComment** action in the **yabe/app/controllers/Application.java** file to skip this validation in test mode: +We can’t really correctly test the captcha mechanism, so we have to cheat. In test mode we will validate any code as a correct captcha. We know that we’re in test mode when the framework id is @test@. So let’s modify the @postComment@ action in the @yabe/app/controllers/Application.java@ file to skip this validation in test mode: -bc. ... +bc. … if(!Play.id.equals("test")) { validation.equals(code, Cache.get(randomID)).message("Invalid code. Please type it again"); } -... +… Now just modify the test case to type any code in the text field, as is: -bc. ... +bc. … type('author', 'Me') type('code', 'XXXXX') clickAndWait('css=input[type=submit]') -... +… And now run the test again, it should work. -h2. Measuring code coverage +h2. Measuring code coverage 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":https://www.playframework.com/modules/cobertura generates code coverage reports using the "Cobertura":http://cobertura.sourceforge.net/ tool. Install the module using the @install@ command: -bc. # Import the cobertura module in test mode -%test.module.cobertura=${play.path}/modules/cobertura +bc. play install cobertura-{version} + +We need to enable this module only for test mode. So add this line to the @dependencies.yml@ file, and restart the application in test mode. + +bc. # Import the cobertura module in application + - play -> cobertura {version} Now reopen the browser at the "http://localhost:9000/@tests":http://localhost:9000/@tests URL, select all tests and run them. All should be green. !images/guide10-5! -When all tests are passed, stop the application and cobertura will then generate the code coverage report. You can then open the **yabe/test-result/code-coverage/index.html** in your browser and check the report. +When all tests are passed, stop the application and cobertura will then generate the code coverage report. You can then open the @yabe/test-result/code-coverage/index.html@ in your browser and check the report. !images/guide10-4! +If you start the application again, you can also view it at "http://localhost:9000/@cobertura":http://localhost:9000/@cobertura. + As you see we’re far from testing all of the application’s cases. A good testing suite should approach 100%, even if it is of course nearly impossible to check all the code. Typically because we often need to hack in test mode, like we did for the captcha. p(note). Next: %(next)"Preparing for production":guide11%. diff --git a/documentation/manual/guide11.textile b/documentation/manual/guide11.textile index a67eff0cb4..b25a3cb3ad 100644 --- a/documentation/manual/guide11.textile +++ b/documentation/manual/guide11.textile @@ -6,19 +6,19 @@ h2. Defining a framework ID You will typically deploy your application on a different computer (a server) than the one you used for development. So you will have a different Play installation. -Play allows to assign each framework installation a different ID, then to manage different configurations in the same **application.conf** file. Let’s say that the **server01** will host your production application. +Play allows to assign each framework installation a different ID, then to manage different configurations in the same @application.conf@ file. Let’s say that the @server01@ will host your production application. -Once the Play framework is installed on this server, let’s define the framework ID using the **‘play id’** command. Type: +Once the Play framework is installed on this server, let’s define the framework ID using the @play id@ command. Type: bc. $ play id -And assign **server01** as id. Now we can define special keys in the yabe configuration file that will be used only when the application is running on the server. +And assign @server01@ as id. Now we can define special keys in the yabe configuration file that will be used only when the application is running on the server. h2. Setting the application in PROD mode -The first configuration key we want specialize for the server deployment is the **application.mode** property. So far we have used the **DEV** mode that allows Play to hot reload and recompile Java files and display detailed messages when an error occurs. In **PROD** mode however, Play will compile all Java sources and templates at startup time and will never check again for changes. +The first configuration key we want specialize for the server deployment is the @application.mode@ property. So far we have used the **DEV** mode that allows Play to hot reload and recompile Java files and display detailed messages when an error occurs. In **PROD** mode however, Play will compile all Java sources and templates at startup time and will never check again for changes. -In the **yabe/conf/application.conf** file, define: +In the @yabe/conf/application.conf@ file, define: bc. %server01.application.mode=PROD @@ -28,17 +28,17 @@ h2. Configuring a MySQL server For production use, we will use a MySQL server as database server instead of the in-memory H2 Database we have used so far. Play comes with the JDBC driver for MySQL so we don’t need to install anything more. -Edit the database configuration in the **yabe/conf/application.conf** file: +Edit the database configuration in the @yabe/conf/application.conf@ file: bc. %server01.db=mysql:root:secret@yabe We will now tweak the way Hibernate manages the database schema for us. It’s very useful when Hibernate automatically updates the database schema as the Java model objects change. -Change the **jpa.ddl** configuration key: +Change the @jpa.ddl@ configuration key: bc. %server01.jpa.ddl=update -However it’s kind of unpredictable, and running magical things on a production database is never a good thing. So you should always have a backup before a new deployement. If you don't want that hibernate update your database, change the **jpa.ddl** configuration key to validate: +However it’s kind of unpredictable, and running magical things on a production database is never a good thing. So you should always have a backup before a new deployement. If you don't want Hibernate to update your database, change the @jpa.ddl@ configuration key to @validate@: bc. %server01.jpa.ddl=validate @@ -55,13 +55,13 @@ bc. server.modules = ( "mod_proxy", "mod_accesslog" ) -... +… $HTTP["host"] =~ "www.yabe.com" { proxy.balance = "round-robin" proxy.server = ( "/" => ( ( "host" => "127.0.0.1", "port" => 9000 ) ) ) } -And then allow a local reverse proxy to connect to your Play application by adding this key to the **application.conf** file: +And then allow a local reverse proxy to connect to your Play application by adding this key to the @application.conf@ file: bc. %server01.XForwardedSupport=127.0.0.1 @@ -75,4 +75,4 @@ There are still more features we haven’t explored yet, especially related to W If you are convinced that Play will help you save time for your next Java web application, you’re now ready to start. And don’t hesitate to ask us any question on the "Play Google Group":http://groups.google.com/group/play-framework. -**Thank you!** \ No newline at end of file +**Thank you!** diff --git a/documentation/manual/guide12.textile b/documentation/manual/guide12.textile index 11791250a8..1367604d6f 100644 --- a/documentation/manual/guide12.textile +++ b/documentation/manual/guide12.textile @@ -1,6 +1,6 @@ h1. Internationalisation and localisation -Having built our fully-functional blog engine, we will now consider an optional extra: web application internationalization and language localisation. Although we could have done this from the start, it is more realistic to build the first version of an application in a single language, and add multiple languages later. +Having built our fully-functional blog engine, we will now consider an optional extra: web application internationalisation and language localisation. Although we could have done this from the start, it is more realistic to build the first version of an application in a single language, and add multiple languages later. h2. Internationalisation and localisation @@ -8,15 +8,15 @@ There are two steps to perform: "internationalisation":http://en.wikipedia.org/w *Internationalisation*, in programming terms, is a refactoring to remove locale-specific code from the application code. In a web application, this is almost entirely about replacing user-interface text in view templates with references to messages. It also includes formatting non-text data types: dates, currency and other numbers. -*Localisation* is making a locale-specific version of an application. If the application is internationlised, this means having one or more selectable locale-specific versions. In a web application, this localisation is mostly about translating the user-interface text into the chosen natural language. Language selection is typically a combination of language preferences set in the web browser, and a language selection user-interface in the application itself. +*Localisation* is making a locale-specific version of an application. If the application is internationalised, this means having one or more selectable locale-specific versions. In a web application, this localisation is mostly about translating the user-interface text into the chosen natural language. Language selection is typically a combination of language preferences set in the web browser, and a language selection user-interface in the application itself. In practice, the two steps go together: you both internationalise and localise one part of the application at a time. h2. Yet Another Blog Engine -p(note). The starting point for this section is the finished tutorial code, which you can find in the Play distribution's **samples-and-tests/yabe** directory. The goal is to fully internationalise the application, and add French and Dutch language localisations. +p(note). The starting point for this section is the finished tutorial code, which you can find in the Play distribution's @samples-and-tests/yabe@ directory. The goal is to fully internationalise the application, and add French and Dutch language localisations. -To get started, first edit **conf/application.conf** and uncomment (in the default configuration file) or add a line with three supported languages: +To get started, first edit @conf/application.conf@ and uncomment (in the default configuration file) or add a line with three supported languages: bc. # Localisations for English, Dutch and French. application.langs=en,nl,fr @@ -29,11 +29,11 @@ bc. 16:19:04,728 WARN ~ Messages file missing for locale en h2. UTF-8 message files -The warnings above mean that you need to replace the existing **conf/messages** file with one message file for each language: +The warnings above mean that you need to replace the existing @conf/messages@ file with one message file for each language: -* **messages.en** -* **messages.nl** -* **messages.fr** +* @messages.en@ +* @messages.nl@ +* @messages.fr@ At this point we encounter the first improvement over the normal Java way of doing things. These files use the same syntax as Java properties files, but they are not properties files because they must use UTF-8 encoding. Java "Properties":http://java.sun.com/javase/6/docs/api/java/util/Properties.html, on the other hand, specifies ISO-8859-1 'Latin-1' character encoding for streaming to and from text files. @@ -51,7 +51,7 @@ For the rest of this tutorial, code samples will either define messages in one o h2. Simple messages -The simple case is a text string that does not change, and that is not interrupted by other markup. For example, the first such text in the **yabe/app/views/main.html** template, in the **tools** list: +The simple case is a text string that does not change, and that is not interrupted by other markup. For example, the first such text in the @yabe/app/views/main.html@ template, in the @tools@ list: bc.
  • @@ -59,7 +59,7 @@ bc.
    -To internationalise this, we replace the text with a message look-up, using the **&{'key'**} syntax: +To internationalise this, we replace the text with a message look-up, using the @&{'key'}@ syntax: bc.
    • @@ -67,32 +67,32 @@ bc.
      -To add the localisations, add the corresponding line in each of the three message files. In **conf/messages.en** +To add the localisations, add the corresponding line in each of the three message files. In @conf/messages.en@ bc. views.main.tools.login = Log in to write something -In **conf/messages.nl** +In @conf/messages.nl@ bc. views.main.tools.login = Inloggen om iets te schrijven -In **conf/messages.fr** +In @conf/messages.fr@ bc. views.main.tools.login = Connectez-vous pour écrire quelque chose -The message key can be anything you like; in this example I have used a key to indicate the location **views/main.html#tools** +The message key can be anything you like; in this example I have used a key to indicate the location @views/main.html#tools@ -Once you have saved these changes, you can see the different language versions in your web browser by changing the setting that results in a different **Accept-Language** HTTP request header. In Firefox, select _Preferences » Content » Languages » Choose…_, add _French \[fr]_ and _Dutch \[nl]_ if they are not already in the list, change which one is at the top of the list, close the dialogue box and reload the page. +Once you have saved these changes, you can see the different language versions in your web browser by changing the setting that results in a different @Accept-Language@ HTTP request header. In Firefox, select _Preferences » Content » Languages » Choose…_, add _French [fr]_ and _Dutch [nl]_ if they are not already in the list, change which one is at the top of the list, close the dialogue box and reload the page. !images/guide12-1! h2. Application model localisation -If you use that link to log in to the blog's 'admin' pages, you can access lists of posts, tags, comments and users. These pages are provided by the "CRUD":http://www.playframework.org/documentation/1.0.1/crud module. For each of these pages, the title (light pink) and the column headers are terms from the application's model, i.e. JavaBean class and property names. +If you use that link to log in to the blog's 'admin' pages, you can access lists of posts, tags, comments and users. These pages are provided by the "CRUD":https://www.playframework.com/documentation/1.0.1/crud module. For each of these pages, the title (light pink) and the column headers are terms from the application's model, i.e. JavaBean class and property names. The CRUD module internationalises these names using the JavaBean class or property name as the message key, which means you can localise them with messages such as the following. -In **conf/messages.nl** +In @conf/messages.nl@ bc. post = artikel Post = Artikel @@ -107,7 +107,7 @@ User = Gebruiker users = gebruikers Users = Gebruikers -In **conf/messages.fr** +In @conf/messages.fr@ bc. post = article Post = Article @@ -126,7 +126,7 @@ You will notice that this does not change the rounded purple navigation links: !images/guide12-2! -Those are defined **views/admin.html** which you can internationalise to use the same localisations simply by surrounding the existing text with **&{'…'**} as follows: +Those are defined in @views/admin.html@ which you can internationalise to use the same localisations simply by surrounding the existing text with @&{'…'}@ as follows: bc. &{'Posts'} … @@ -160,18 +160,18 @@ In this example, we would also like to use the correct plural form for the word bc. views.Admin.index.welcome = Welcome %1$s, you have written %2$s %3$s so far -… and use the **pluralize** extension in the template +… and use the @pluralize@ extension in the template bc. &{'views.Admin.index.welcome', user, posts.size(), posts.pluralize(messages.get('post'), messages.get('posts'))} -Note that we have to use **messages.get** to look up the localised singular and plural. +Note that we have to use @messages.get@ to look up the localised singular and plural. h2. Play module localisation -Play module localisation works the same was as localisation within your application. This application uses the CRUD and Secure modules, which means that we must localise the messages in **play/modules/crud/conf/messages** and **play/modules/secure/conf/messages** that our application uses. +Play module localisation works the same way as localisation within your application. This application uses the CRUD and Secure modules, which means that we must localise the messages in @play/modules/crud/conf/messages@ and @play/modules/secure/conf/messages@ that our application uses. -In **conf/messages.nl** +In @conf/messages.nl@ bc. # play/modules/crud (administration) crud.title = Beheer @@ -221,7 +221,7 @@ secure.username = Uw e-mailadres: secure.password = Uw wachtwoord: secure.signin = Nu inloggen -In **conf/messages.fr** +In @conf/messages.fr@ bc. # play/modules/crud (administration) crud.title = Administration @@ -276,7 +276,7 @@ Of course, once you have done this it is also a good idea to contribute the loca h2. Special cases -When you are localising a web application, there are a few special cases that be awkward to implement if you are using a component-based web application framework, such as JavaServer Faces: +When you are localising a web application, there are a few special cases that can be awkward to implement if you are using a component-based web application framework, such as JavaServer Faces: # Parameterised message used in an attribute value # Formatted message parameter @@ -292,7 +292,7 @@ This is a problem in JSF, because you would normally use an XML tag to perform t bc. -The second case is when you want to format a value, such as a date, for use as a message parameter in a phrase like **By Bob on 2009-06-14**. Again, the problem in JSF is caused by having to an XML tag to format the value, while needing to be able to use the result in an XML attribute value. In Play the formatting extensions do not get in the way of the message parameter syntax, so you can do: +The second case is when you want to format a value, such as a date, for use as a message parameter in a phrase like @By Bob on 2009-06-14@. Again, the problem in JSF is caused by having to use an XML tag to format the value, while needing to be able to use the result in an XML attribute value. In Play the formatting extensions do not get in the way of the message parameter syntax, so you can do: bc. &{'views.tags.display.author', _post.author.fullname, comment.postedAt.format('yyyy-MM-dd')}"} @@ -300,9 +300,9 @@ You can, of course, localise the format pattern as well: bc. &{'views.tags.display.author', _post.author.fullname, comment.postedAt.format(messages.get('views.dateFormat'))}"} -The third case typically occurs when you want part of a localised message to be a hyperlink, as in the message **+Log in+ to write something**. This is a problem in JSF because the hyperlink is a JSF component that is rendered in a way that means the link's mark-up cannot be in the message file. Play on the other hand, lets you use plain HTML in your templates, so you can just put the mark-up in your message with a parameter for the URL: +The third case typically occurs when you want part of a localised message to be a hyperlink, as in the message Log in to write something. This is a problem in JSF because the hyperlink is a JSF component that is rendered in a way that means the link's mark-up cannot be in the message file. Play, on the other hand, lets you use plain HTML in your templates, so you can just put the mark-up in your message with a parameter for the URL: -bc. logIn = Log in to write something +bc. logIn = Log in to write something bc. &{'logIn', '/admin'} diff --git a/documentation/manual/guide2.textile b/documentation/manual/guide2.textile index f6cf84acd1..74734b60aa 100644 --- a/documentation/manual/guide2.textile +++ b/documentation/manual/guide2.textile @@ -1,8 +1,8 @@ -h1. A first iteration for the data model +h1. A first iteration of the data model Here we will start to write the model for our blog engine. -h2. Introduction to JPA +h2. Introduction to JPA The model layer has a central position in a Play application (and in fact in all well-designed applications). It is the domain-specific representation of the information on which the application operates. As we want to create a blog engine, the model layer will certainly contain classes like User, Post and Comment. @@ -14,9 +14,9 @@ If you have used Hibernate or JPA before you will be surprised by the simplicity If you don’t know JPA, you can read "some of these simple presentations":http://java.sun.com/javaee/5/docs/tutorial/doc/bnbpz.html before continuing. -h2. The User class +h2. The User class -We will start to code the blog engine by creating the User class. Create a new file **/yabe/app/models/User.java**, and declare a first implementation of the User class: +We will start to code the blog engine by creating the User class. Create a new file @/yabe/app/models/User.java@, and declare a first implementation of the User class: bc. package models; @@ -41,21 +41,21 @@ public class User extends Model { } -The **@Entity** annotation marks this class as a managed JPA entity, and the **Model** superclass automatically provides a set of useful JPA helpers that we will discover later. All fields of this class will be automatically persisted to the database. +The @Entity annotation marks this class as a managed JPA entity, and the @Model@ superclass automatically provides a set of useful JPA helpers that we will discover later. All fields of this class will be automatically persisted to the database. -p(note). By default, the table name is ‘User’. If you change the configuration to use a database where ‘user’ is a reserved keyword, then you will need to specify a different table name for the JPA mapping. To do this, annotate the **User** class with **@Table(name="blog_user")**. +p(note). By default, the table name is ‘User’. If you change the configuration to use a database where ‘user’ is a reserved keyword, then you will need to specify a different table name for the JPA mapping. To do this, annotate the @User@ class with @Table(name="blog_user"). -p(note). It’s not required that your model objects extend the **play.db.jpa.Model** class. You can use plain JPA as well. But extending this class is a good choice in most cases as it will make a lot of the JPA stuff easier. +p(note). It’s not required that your model objects extend the @play.db.jpa.Model@ class. You can use plain JPA as well. But extending this class is a good choice in most cases as it will make a lot of the JPA stuff easier. -If you have used JPA before, you know that every JPA entity must provide an **@Id** property. Here the Model superclass provides an automatically generated numeric ID, and in most cases this is good enough. +If you have used JPA before, you know that every JPA entity must provide an @Id property. Here the Model superclass provides an automatically generated numeric ID, and in most cases this is good enough. -p(note). Don’t think about this provided **id** field as a **functional identifier** but as a **technical identifier**. It is generally a good idea to keep both concepts separated and to keep an automatically generated numeric ID as a technical identifier. +p(note). Don’t think about this provided @id@ field as a **functional identifier** but as a **technical identifier**. It is generally a good idea to keep both concepts separated and to keep an automatically generated numeric ID as a technical identifier. Now if you’re a Java developer with any experience at all, warning bells are probably clanging like mad at the sight of a public variable. In Java (as in other object-oriented languages), best practice says to make all fields private and provide accessors and mutators. This is to promote encapsulation, a concept critical to object oriented design. In fact, Play takes care of that for you and automatically generates getters and setters while preserving encapsulation; we will see how it works later in this tutorial. You can now refresh the application home page, to see the result. In fact, unless you made a mistake, you should not see any change: Play has automatically compiled and loaded the User class, but this does not add any new features to the application. -h2. Writing the first test +h2. Writing the first test A good way to test the newly created User class is to write a JUnit test case. It will allow you to incrementally complete the application model and ensure that all is fine. @@ -65,15 +65,15 @@ bc. ~$ play test !images/guide2-0! -The **play test** command is almost the same as **play run**, except that it loads a test runner module that allows you to run test suite directly from a browser. +The @play test@ command is almost the same as @play run@, except that it loads a test runner module that allows you to run test suite directly from a browser. -p(note). When you run a Play application in **test mode**, Play will automatically switch to the **test** framework ID and load the **application.conf** file accordingly. Check the "framework ID documentation":ids for more information. +p(note). When you run a Play application in @test mode@, Play will automatically switch to the @test@ framework ID and load the @application.conf@ file accordingly. Check the "framework ID documentation":ids for more information. Open a browser to the "http://localhost:9000/@tests":http://localhost:9000/@tests URL to see the test runner. Try to select all the default tests and run them; all should be green… But these default tests don’t really test anything. !images/guide2-1! -To test the model part of the application we will use a JUnit test. As you can see, a default **BasicTests.java** already exists, so let’s open it (**/yabe/test/BasicTest.java**): +To test the model part of the application we will use a JUnit test. As you can see, a default @BasicTests.java@ already exists, so let’s open it (@/yabe/test/BasicTest.java@): bc. import org.junit.*; import play.test.*; @@ -88,7 +88,7 @@ public class BasicTest extends UnitTest { } -Remove the useless default test (**aVeryImportantThingToTest**) and create a test that tries to create a new user and retrieve it: +Remove the useless default test (@aVeryImportantThingToTest@) and create a test that tries to create a new user and retrieve it: bc. @Test public void createAndRetrieveUser() { @@ -103,15 +103,15 @@ public void createAndRetrieveUser() { assertEquals("Bob", bob.fullname); } -As you can see, the Model superclass gives us two very useful methods: **save()** and **find()**. +As you can see, the Model superclass gives us two very useful methods: @save()@ and @find()@. p(note). You can read more about the Model class’ methods in the Play manual’s "JPA support":jpa chapter. -Select the **BasicTests.java** in the test runner, click start and check that all is green. +Select the @BasicTests.java@ in the test runner, click start and check that all is green. We will need a method on the User class that checks if a user with a specified username and password exists. Let’s write it and test it. -In the **User.java** source, add the **connect()** method: +In the @User.java@ source, add the @connect()@ method: bc. public static User connect(String email, String password) { return find("byEmailAndPassword", email, password).first(); @@ -132,9 +132,9 @@ public void tryConnectAsUser() { Each time you make a modification you can run all the tests from the Play test runner to make sure you didn’t break anything. -h2. The Post class +h2. The Post class -The **Post** class will represent blog posts. Let’s write a first implementation: +The @Post@ class will represent blog posts. Let’s write a first implementation: bc. package models; @@ -164,26 +164,27 @@ public class Post extends Model { } -Here we use the **@Lob** annotation to tell JPA to use a large text database type to store the post content. We have declared the relation to the **User** class using **@ManyToOne**. That means that each **Post** is authored by a single **User**, and that each **User** can author several **Posts**. +Here we use the @Lob annotation to tell JPA to use a large text database type to store the post content. We have declared the relation to the @User@ class using @ManyToOne. That means that each @Post@ is authored by a single @User@, and that each @User@ can author several @Post@ instances. -We will write a new test case to check that the **Post** class works as expected. But before writing more tests, we need to do something in the JUnit test class. In the current test, the database content is never deleted, so each new run creates more and more objects. This will soon become problematic, when more advanced tests count objects to check that all is fine. +p(note). Recent versions of PostgreSQL do not store @String@ fields annotated with @Lob as text unless you also annotate the field with @Type(type = "org.hibernate.type.TextType"). -So let’s write a JUnit **setup()** method to delete the database before each test: +We will write a new test case to check that the @Post@ class works as expected. But before writing more tests, we need to do something in the JUnit test class. In the current test, the database content is never deleted, so each new run creates more and more objects. This will soon become problematic, when more advanced tests count objects to check that all is fine. + +So let’s write a JUnit @setup()@ method to delete the database before each test: bc. public class BasicTest extends UnitTest { @Before public void setup() { - Fixtures.deleteAll(); + Fixtures.deleteDatabase(); } - - ... + … } -p(note). The **@Before** concept is a core concept of the JUnit testing tool. +p(note). The @Before concept is a core concept of the JUnit testing tool. -As you can see, the **Fixtures** class is a helper to deal with your database during tests. Run the test again to check that you haven’t broken anything, and start to write the next test: +As you can see, the @Fixtures@ class is a helper to deal with your database during tests. Run the test again to check that you haven’t broken anything, and start to write the next test: bc. @Test public void createPost() { @@ -209,13 +210,13 @@ public void createPost() { assertNotNull(firstPost.postedAt); } -p(note). **Don’t forget** to import the **java.util.List** or will get a compilation error. +p(note). **Don’t forget** to import the @java.util.List@ or you will get a compilation error. -h2. Adding Comments +h2. Adding Comments The last thing that we need to add to this first model draft is the ability to attach comments to posts. -Creating the **Comment** class is pretty straightforward. +Creating the @Comment@ class is pretty straightforward. bc. package models; @@ -278,9 +279,9 @@ public void postComments() { assertNotNull(secondComment.postedAt); } -You can see that navigation between **Post** and **Comments** is not very easy: we need to use a query to retrieve all comments attached to a **Post**. We can do better by setting up the other side of the relationship with the **Post** class. +You can see that navigation between **Post** and **Comments** is not very easy: we need to use a query to retrieve all comments attached to a @Post@. We can do better by setting up the other side of the relationship with the @Post@ class. -Add the **comments** field to the **Post** class: +Add the @comments@ field to the @Post@ class: bc. ... @OneToMany(mappedBy="post", cascade=CascadeType.ALL) @@ -295,11 +296,11 @@ public Post(User author, String title, String content) { } ... -Note how we have used the **mappedBy** attribute to tell JPA that the **Comment** class’s post field maintains the relationship. When you define a bi-directional relation with JPA it is very important to tell it which side will maintain the relationship. In this case, since the **Comments** belong to the **Post**, it’s better that the **Comment** class maintains the relationship. +Note how we have used the @mappedBy@ attribute to tell JPA that the @Comment@ class’ post field is the owning side that maintains the relationship. When you define a bi-directional relation with JPA it is important to specify which side will maintain the relationship. In this case, since @Comment@ instances belong to a @Post@, we define the relationship on the @Comment.post@ inverse relation. -We have set the **cascade** property to tell JPA that we want **Post** deletion be cascaded to **comments**. This way, if you delete a post, all related comments will be deleted as well. +We have set the @cascade@ property to tell JPA that we want @Post@ deletion be cascaded to @comments@. This way, if you delete a post, all related comments will be deleted as well. -With this new relationship, we will add a helper method to the **Post** class to simplify adding comments: +With this new relationship, we will add a helper method to the @Post@ class to simplify adding comments: bc. public Post addComment(String author, String content) { Comment newComment = new Comment(this, author, content).save(); @@ -348,11 +349,11 @@ Is it green? !images/guide2-2! -h2. Using Fixtures to write more complicated tests +h2. Using Fixtures to write more complicated tests When you start to write more complex tests, you often need a set of data to test on. Fixtures let you describe your model in a "YAML":http://en.wikipedia.org/wiki/Yaml file and load it at any time before a test. -Edit the **/yabe/test/data.yml** file and start to describe a User: +Edit the @/yabe/test/data.yml@ file and start to describe a User: bc. User(bob): @@ -363,13 +364,13 @@ User(bob): ... -Well, because the **data.yml** file is a litle big, you can "download it here":files/data.yml. +Well, because the @data.yml@ file is a litle big, you can "download it here":files/data.yml. -Now we create create a test case that loads this data and runs some assertions over it: +Now we create a test case that loads this data and runs some assertions over it: bc. @Test public void fullTest() { - Fixtures.load("data.yml"); + Fixtures.loadModels("data.yml"); // Count things assertEquals(2, User.count()); @@ -406,19 +407,19 @@ public void fullTest() { p(note). You can read more about Play and YAML in the "YAML manual page":yaml. -h2. Save your work +h2. Save your work We have now finished a huge part of the blog engine. Now that we have created and tested all these things, we can start to develop the web application itself. -But before continuing, it’s time to save your work using Bazaar. Open a command line and type **bzr st** to see the modifications made since the last commit: +But before continuing, it’s time to save your work using Bazaar. Open a command line and type @bzr st@ to see the modifications made since the last commit: bc. $ bzr st -As you can see, some new files are not under version control. The **test-result** folder doesn’t need to be versioned, so let’s ignore it. +As you can see, some new files are not under version control. The @test-result@ folder doesn’t need to be versioned, so let’s ignore it. bc. $ bzr ignore test-result -Add all the other files to version control using **bzr add**. +Add all the other files to version control using @bzr add@. bc. $ bzr add diff --git a/documentation/manual/guide3.textile b/documentation/manual/guide3.textile index f155331ff0..c30e482c08 100644 --- a/documentation/manual/guide3.textile +++ b/documentation/manual/guide3.textile @@ -12,7 +12,7 @@ In fact, before coding the first screen we need one more thing. Working on a web One way to inject default data into the blog is to load a fixture file at application load time. To do that we will create a Bootstrap Job. A Play job is something that executes itself outside of any HTTP request, for example at the application start or at specific interval using a CRON job. -Let’s create the **/yabe/app/Bootstrap.java** job that will load a set of default data using **Fixtures**: +Let’s create the @/yabe/app/Bootstrap.java@ job that will load a set of default data using @Fixtures@: bc. import play.*; import play.jobs.*; @@ -26,29 +26,29 @@ public class Bootstrap extends Job { public void doJob() { // Check if the database is empty if(User.count() == 0) { - Fixtures.load("initial-data.yml"); + Fixtures.loadModels("initial-data.yml"); } } } -We have annotated this Job with the **@OnApplicationStart** annotation to tell Play that we want to run this job synchronously at application start-up. +We have annotated this Job with the @OnApplicationStart annotation to tell Play that we want to run this job synchronously at application start-up. -p(note). In fact this job will be run differently in DEV or PROD modes. In DEV mode, Play waits for a first request to start. So this job will be executed synchronously at the first request. That way, if the job fails, you will get the error message in your browser. In PROD mode however, the job will be executed at application start-up (synchrously with the **play run** command) and will prevent the application from starting in case of an error. +p(note). In fact this job will be run differently in DEV or PROD modes. In DEV mode, Play waits for a first request to start. So this job will be executed synchronously at the first request. That way, if the job fails, you will get the error message in your browser. In PROD mode however, the job will be executed at application start-up (synchrously with the @play run@ command) and will prevent the application from starting in case of an error. -You have to create an **initial-data.yml** in the **yabe/conf/** directory. You can of course reuse the **data.yml** content that we just used for tests previously. +You have to create an @initial-data.yml@ in the @yabe/conf/@ directory. You can of course reuse the @data.yml@ content that we just used for tests previously. -Now run the application using **play run** and display the page "http://localhost:9000/":http://localhost:9000/ in the browser. +Now run the application using @play run@ and display the page "http://localhost:9000/":http://localhost:9000/ in the browser. h2. The blog home page This time, we can really start to code the home page. -Do you remember how the first page is displayed? First the routes file specifies that the **/** URL will invoke the **controllers.Application.index()** action method. Then this method calls **render()** and executes the **/yabe/app/views/Application/index.html** template. +Do you remember how the first page is displayed? First the routes file specifies that the @/@ URL will invoke the @controllers.Application.index()@ action method. Then this method calls @render()@ and executes the @/yabe/app/views/Application/index.html@ template. We will keep these components but add code to them to load the posts list and display them. -Open the **/yabe/app/controllers/Application.java** controller and modify the **index()** action to load the posts list, as is: +Open the @/yabe/app/controllers/Application.java@ controller and modify the @index()@ action to load the posts list, as is: bc. package controllers; @@ -71,9 +71,9 @@ public class Application extends Controller { } -Can you see how we pass objects to the **render** method? It will allow us to access them from the template using the same name. In this case, the variables **frontPost** and **olderPosts** will be available in the template. +Can you see how we pass objects to the @render@ method? It will allow us to access them from the template using the same name. In this case, the variables @frontPost@ and @olderPosts@ will be available in the template. -Open the **/yabe/app/views/Application/index.html** and modify it to display these objects: +Open the @/yabe/app/views/Application/index.html@ and modify it to display these objects: bc. #{extends 'main.html' /} #{set title:'Home' /} @@ -91,7 +91,7 @@ bc. #{extends 'main.html' /} ${frontPost.comments.size() ?: 'no'} comment${frontPost.comments.size().pluralize()} #{if frontPost.comments} - , latest by ${frontPost.comments[0].author} + , latest by ${frontPost.comments[-1].author} #{/if}
@@ -100,7 +100,7 @@ bc. #{extends 'main.html' /} - #{if olderPosts.size() > 1} + #{if olderPosts}

Older posts from this blog

@@ -120,7 +120,7 @@ bc. #{extends 'main.html' /} ${oldPost.comments.size() ?: 'no'} comment${oldPost.comments.size().pluralize()} #{if oldPost.comments} - - latest by ${oldPost.comments[0].author} + - latest by ${oldPost.comments[-1].author} #{/if}
@@ -138,7 +138,7 @@ bc. #{extends 'main.html' /} #{/else} -You can read about this template works in the "Templates chapter":templates. Basically, it allows you to access your Java objects dynamically. Under the hood we use Groovy. Most of the pretty constructs you see (like the **?:** operator) come from Groovy. But you don’t really need to learn Groovy to write Play templates. If you’re already familiar with another template language like JSP with JSTL you won’t be lost. +You can read about how this template works in the "Templates chapter":templates. Basically, it allows you to access your Java objects dynamically. Under the hood we use Groovy. Most of the pretty constructs you see (like the @?:@ operator) come from Groovy. But you don’t really need to learn Groovy to write Play templates. If you’re already familiar with another template language like JSP with JSTL you won’t be lost. OK, now refresh the blog home page. @@ -148,7 +148,7 @@ Not pretty but it works! However you can see we have already started to duplicate code. Because we will display posts in several ways (full, full with comment, teaser) we should create something like a function that we could call from several templates. This is exactly what a Play tag does! -To create a tag, just create the new **/yabe/app/views/tags/display.html** file. A tag is just another template. It has parameters (like a function). The **#{display /}** tag will have two parameters: the Post object to display and the display mode which will be one of ‘home’, ‘teaser’ or ‘full’. +To create a tag, just create the new @/yabe/app/views/tags/display.html@ file. A tag is just another template. It has parameters (like a function). The @#{display /}@ tag will have two parameters: the Post object to display and the display mode which will be one of @home@, @teaser@ or @full@. bc. *{ Display a post in one of these modes: 'full', 'home' or 'teaser' }* @@ -164,7 +164,7 @@ bc. *{ Display a post in one of these modes: 'full', 'home' or 'teaser' }*  |  ${_post.comments.size() ?: 'no'} comment${_post.comments.size().pluralize()} #{if _post.comments} - , latest by ${_post.comments[0].author} + , latest by ${_post.comments[-1].author} #{/if} #{/if} @@ -235,9 +235,9 @@ Reload the page and check that all is fine. h2. Improving the layout -As you can see, the **index.html** template extends **main.html**. Because we want to provide a common layout for all blog pages, with the blog title and authentication links, we need to modify this file. +As you can see, the @index.html@ template extends @main.html@. Because we want to provide a common layout for all blog pages, with the blog title and authentication links, we need to modify this file. -Edit the **/yabe/app/views/main.html** file: +Edit the @/yabe/app/views/main.html@ file: bc. @@ -273,18 +273,18 @@ bc. -Refresh and check the result. It seems to work, except that the **blogTitle** and the **blogBaseLine** variables are not displayed. This is because we didn’t pass them to the **render(…)** call. Of course we could add them to the **render()** call in the **index** action. But because the **main.html** file will be used as main template for all application actions, we don’t want to add them every time. +Refresh and check the result. It seems to work, except that the @blogTitle@ and the @blogBaseLine@ variables are not displayed. This is because we didn’t pass them to the @render(…)@ call. Of course we could add them to the @render()@ call in the @index@ action. But because the @main.html@ file will be used as main template for all application actions, we don’t want to add them every time. -One way to execute the same code for each action of a controller (or a hierarchy of controllers) is to define a **@Before** interceptor. +One way to execute the same code for each action of a controller (or a hierarchy of controllers) is to define a @Before interceptor. -Let’s add the **addDefaults()** method to the Application controller: +Let’s add the @addDefaults()@ method to the Application controller: bc. @Before static void addDefaults() { @@ -292,16 +292,16 @@ static void addDefaults() { renderArgs.put("blogBaseline", Play.configuration.getProperty("blog.baseline")); } -p(note). You will need to import **play.Play** in the **Application.java** file. +p(note). You will need to import @play.Play@ in the @Application.java@ file. -All variables added to the **renderArgs** scope will be available from the templates. And you can see that the method reads the variable’s values from the **Play.configuration** object. This object contains all configuration keys from the **/yabe/conf/application.conf** file. +All variables added to the @renderArgs@ scope will be available from the templates. And you can see that the method reads the variable’s values from the @Play.configuration@ object. This object contains all configuration keys from the @/yabe/conf/application.conf@ file. Add these two keys to the configuration file: bc. # Blog engine configuration # ~~~~~ blog.title=Yet another blog -blog.baseline=We won't write about anything +blog.baseline=We will write about nothing Reload the home page and check that it works! @@ -309,9 +309,9 @@ Reload the home page and check that it works! h2. Adding some style -Now the blog home page is almost done, but it’s not very pretty. We’ll add some style to make it shinier. As you have seen, the main template file main.html includes the **/public/stylesheets/main.css** stylesheet. We’ll keep it but add more style rules to it. +Now the blog home page is almost done, but it’s not very pretty. We’ll add some style to make it shinier. As you have seen, the main template file main.html includes the @/public/stylesheets/main.css@ stylesheet. We’ll keep it but add more style rules to it. -You can "download it here":files/main.css, and copy it to the **/public/stylesheets/main.css** file. +You can "download it here":files/main.css, and copy it to the @/public/stylesheets/main.css@ file. Refresh the home page and you should now see a styled page. @@ -323,7 +323,7 @@ The blog home page is now finished. As usual we can commit this blog version to bc. $ bzr st $ bzr add -$ bzr commit -m 'Home page' +$ bzr commit -m "Home page" p(note). Next: %(next)"The comments page":guide4%. diff --git a/documentation/manual/guide4.textile b/documentation/manual/guide4.textile index e6f1b2886d..ac3fc09a7d 100644 --- a/documentation/manual/guide4.textile +++ b/documentation/manual/guide4.textile @@ -4,51 +4,51 @@ The blog home page is now set, and we will continue by writing the post details h2. Creating the ‘show’ action -To display the post details page, we will need a new action method on the **Application** controller. Let’s call it **show()**: +To display the post details page, we will need a new action method on the @Application@ controller. Let’s call it @show()@: bc. public static void show(Long id) { Post post = Post.findById(id); render(post); } -As you can see this action is pretty simple. We declare the **id** method parameter to automatically retrieve the HTTP **id** parameter as a **Long** Java object. This parameter will be extracted either from the query string, from the URL path or from the request body. +As you can see this action is pretty simple. We declare the @id@ method parameter to automatically retrieve the HTTP @id@ parameter as a @Long@ Java object. This parameter will be extracted either from the query string, from the URL path or from the request body. -p(note). If we try to send an **id** HTTP parameter that is not a valid number, the **id** variable value will be **null** and Play will automatically add a validation error to the **errors** container. +p(note). If we try to send an @id@ HTTP parameter that is not a valid number, the @id@ variable value will be @null@ and Play will automatically add a validation error to the @errors@ container. -This action will display the **/yabe/app/views/Application/show.html** template: +This action will display the @/yabe/app/views/Application/show.html@ template: bc. #{extends 'main.html' /} #{set title:post.title /} #{display post:post, as:'full' /} -Because we’ve already written the **display** tag, this page is really simple to write. +Because we’ve already written the @display@ tag, this page is really simple to write. h2. Adding links to the details page -In the display tag we’ve left all links empty (using **#**). It’s now time to make these links point to the **Application.show** action. With Play you can easily build links in a template using the **@{...} notation**. This syntax uses the router to ‘reverse’ the URL needed to call the specified action. +In the display tag we’ve left all links empty (using @#@). It’s now time to make these links point to the @Application.show@ action. With Play you can easily build links in a template using the @{…} notation. This syntax uses the router to ‘reverse’ the URL needed to call the specified action. -Let’s edit the **/yabe/app/views/tags/display.html** tag: +Let’s edit the @/yabe/app/views/tags/display.html@ tag: -bc. ... +bc. …

${_post.title}

-... +… -You can new refresh the home page, and click a post title to display the post. +You can now refresh the home page, and click a post title to display the post. !images/guide4-0! -It’s great, but it lacks a link to go back to the home page. Edit the **/yabe/app/views/main.html** template to complete the title link: +It’s great, but it lacks a link to go back to the home page. Edit the @/yabe/app/views/main.html@ template to complete the title link: -bc. ... +bc. …
About this blog

${blogTitle}

${blogBaseline}

-... +… We can now navigate between the home page and the post detail pages. @@ -62,11 +62,11 @@ This is because Play has used the default ‘catch all’ route. bc. * /{controller}/{action} {controller}.{action} -We can have a better URL by specifying a custom path for the **Application.show** action. Edit the **/yabe/conf/routes** file and add this route after the first one: +We can have a better URL by specifying a custom path for the @Application.show@ action. Edit the @/yabe/conf/routes@ file and add this route after the first one: bc. GET /posts/{id} Application.show -p(note). This way the **id** parameter will be extracted from the URL path. You can read more about URI patterns on the manual page about "Route File Syntax":routes#syntax. +p(note). This way the @id@ parameter will be extracted from the URL path. You can read more about URI patterns on the manual page about "Route File Syntax":routes#syntax. Refresh the browser and check that it now uses the correct URL. @@ -82,7 +82,7 @@ public Post next() { return Post.find("postedAt > ? order by postedAt asc", postedAt).first(); } -We will call these methods several times during a request so they could be optimized, but they’re good enough for now. Also, add the pagination links at the top of the **show.html** template (before the **#{display/}** tag): +We will call these methods several times during a request so they could be optimized, but they’re good enough for now. Also, add the pagination links at the top of the @show.html@ template (before the @#{display/}@ tag): bc.