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..19a517c331 --- /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 whenTestClassInstanciated();==* +当該テストクラスがインスタンス化されたときに一度だけ実行されます。 + +*==@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..a4957daff3 --- /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.load("data.yml");}==* +Fixtures はユニットテストを実行する前にデータストアを初期化するために使用します。 + +*==#{fixture delete:'all', load:'data.yml' /}==* +*==#{selenium} ... #{/selenium}==* +Selenium テストを使うときも同様に出来ます。 diff --git a/documentation/manual_ja/5things.textile b/documentation/manual_ja/5things.textile new file mode 100644 index 0000000000..ee3b00e621 --- /dev/null +++ b/documentation/manual_ja/5things.textile @@ -0,0 +1,123 @@ +h1. Play でできる 5 つのすごいこと + +5 つの例を通して Play framework の背後にある哲学を見てみましょう。 + +h2. HTTP パラメータの Java メソッド引数への紐付け + +Play では Java のコードから簡単に HTTP パラメータを探すことができます。HTTP パラメータと同じ名前でメソッド引数を宣言するだけです。 + +例えば、このようなリクエスト場合: + +bc. /articles/archive?date=08/01/08&page=2 + +Java のメソッド引数として宣言することで @data@ パラメータと @page@ パラメータを検索することができます: + +bc. public static void archive(Date date, Integer page) { + List
articles = Articles.fromArchive(date, page); + render(articles); +} + +Play は HTTP パラメータの値を Java オブジェクトに変換するために、static メソッドの引数を使用します。 + +このスマートな紐付けは **どのようなクラス** についても動作します。 + +bc. public class Person { + public String name; + public Integer age; +} + +person を追加するシンプルなアクションコントローラはこんな感じかもしれません: + +bc. public static void add(Person p) { + p.save(); +} + +HTML フォームのフィールドには person を構成する名前を定義します: + +bc.
+ Name: + Age: +
+ + +h2. 対応する Java メソッドの実行によるアクションへのリダイレクト + +Play には Java Servlet の @forward@ コマンドに該当するものは存在しません。しかし、とてもシンプルに別のアクションへリダイレクトすることができます。単に対応する Java メソッドを実行すれば、 Play は適切な HTTP 'Redirect' レスポンスを生成します。 + +bc. public static void show(Long id) { + Article article = Article.findById(id); + render(article); +} + +public static void edit(Long id, String title) { + Article article = Article.findById(id); + article.title = title; + article.save(); + show(id); +} + + +edit アクションの終わりの部分に注目してください。show アクションにリダイレクトします。 + +テンプレートにおいても、同じような構文でリンクを生成することができます: + +bc. ${article.title} + +これは以下ような HTML を生成します: + +bc. My new article + + +h2. Java オブジェクトをテンプレートに引き渡すとき、同じことをくり返してはいけません + +ほとんどの Java フレームワークでは、Java オブジェクトをテンプレートシステムに引き渡すためには以下のように書かなければなりません: + +bc. Article article = Article.findById(id); +User user = User.getConnected(); +Map model = new HashMap(); +model.put("article", article); +model.put("user", user); +render(model); + +Play では単純にこのように書くことができます: + +bc. Article article = Article.findById(id); +User user = User.getConnected(); +render(article, user); + +テンプレートでは Java のローカル名を使ってオブジェクトを参照してください。これで役に立たないコードを省くことができます... + +h2. 強化された JPA + +JPA は間違いなく Java におけるベストなオブジェクト-リレーショナル マッピング (ORM) API です。もし JPA を知っているなら、Play で JPA がいとも簡単に利用できることに驚くでしょう。なんの設定も無しに Play は自動的に JPA のエンティティマネージャを起動し、コードがリロードされたときはまるで魔法のように同期を行います。 + +さらに、スーパークラス @play.db.jpa.Model@ を使うことでコードをよりきれいにすることもできます。こんな感じです: + +bc. public void messages(int page) { + User connectedUser = User.find("byEmail", connected()).first(); + List messages = Message.find( + "user = ? and read = false order by date desc", + connectedUser + ).from(page * 10).fetch(10); + render(connectedUser, messages); +} + +h2. 簡単なファイルアップロード管理 + +Play におけるファイルアップロード管理は非常にシンプルです。 + +HTML フォームはこんな感じで: + +bc. #{form @uploadPhoto(), enctype:'multipart/form-data'} + + + +#{/} + +Java コードはこんな感じです: + +bc. public static void uploadPhoto(String title, File photo) { + ... +} + +これ以上簡単になりますかね? \ No newline at end of file diff --git a/documentation/manual_ja/ajax.textile b/documentation/manual_ja/ajax.textile new file mode 100644 index 0000000000..76ac1c50c1 --- /dev/null +++ b/documentation/manual_ja/ajax.textile @@ -0,0 +1,68 @@ +h1. Play フレームワークにおける Ajax + +Play フレームワークは、はじめから "jQuery":http://jquery.com を含めてリリースされており、簡単に Ajax リクエストを行うことができます。このセクションは、どのようにして Play フレームワークにおいて "jQuery":http://jquery.com を効率的に使用するかについて記載します。 + +Play フレームワークには、コントローラから透過的にメソッド定義を取得する、手軽な "jsAction":tags#jsaction タグも含まれています。 + +h2. jQuery を jsAction タグと使う + +この @#{jsAction /}@ タグは、サーバアクションに基づいた URL と自由な変数から成る JavaScript 関数を返却します。この関数では AJAX リクエストを行わないので、返却された URL を使って手作業で Ajax リクエストを実行する必要があります。 + +例を見てみましょう: + +bc. GET /hotels/list Hotels.list + +クライアント側でこのルートをインポートすることができます: + +bc. + +この例では、デフォルトの Application コントローラの list メソッドを指定しています。3 つの引数: search、size そして page も渡しています。この実行結果は listAction 変数に保存されます。ここで、jQuery と @load@ 関数を使ってリクエスト (実際には HTTP GET リクエスト) を実行します。 + +実際のところ、以下のリクエストが発行されます: + +bc. GET /hotels/list?search=x&size=10&page=1 + +このケースでは、発行したリクエストは HTML データを返します。 + +jQuery にデータを解釈させるために JSON または XML を返すことも可能です。コントローラで適切な render メソッド (renderJSON、renderXML または XML テンプレート) を使用してください。 + +より詳しい情報については、"jQuery":http://docs.jquery.com/Main_Page ドキュメントを参照してください。 + +jQuery メソッドを以下のように変更することで POST も発行できることに注意してください: + +bc. $.post(listAction(), function(data) { + $('#result').html(data); +}); + +h2. jsRoute タグを jQuery と使う + +生成されたルートをもっと制御するために、 **#{jsAction /}** タグと似ていますが、サーバのアクションに基づく URL を構築する関数と、関連する HTTP メソッド (GET, POST, その他) の両方を含むオブジェクトを返却する "jsRoute":tags#jsroute タグがあります。 + +例: + +bc. PUT /users/{id} Users.update + +その後、テンプレートでは: + +bc. + +このアプローチなら、routes ファイルにて HTTP メソッドの変更を決断した場合でも、テンプレートを更新する必要はありません。 + +p(note). **考察を続けます** + +%(next)"国際化":i18n% を取り扱ってみましょう。 \ No newline at end of file diff --git a/documentation/manual_ja/asynchronous.textile b/documentation/manual_ja/asynchronous.textile new file mode 100644 index 0000000000..0b3fc4cf5c --- /dev/null +++ b/documentation/manual_ja/asynchronous.textile @@ -0,0 +1,199 @@ +h1. HTTP による非同期プログラミング + +この節では、数千の同時接続までスケールし得る典型的な長時間ポーリング、ストリーミング、そしてその他の "Comet スタイル":http://en.wikipedia.org/wiki/Comet_(programming%29 のアプリケーションを達成するために、Play アプリケーションで非同期処理を取り扱う方法を説明します。 + +h2. HTTP リクエストの中断 + +Play はとても短いリクエストで動作することを意図しています。Play は HTTP コネクタによってキューイングされたリクエストを処理するために固定のスレッドプールを使用します。最適な結果を得るために、このスレッドプールは可能な限り小さくあるべきです。デフォルトのプールサイズを設定するための最適値として、典型的には @プロセッサ数 + 1@ を使用します。 + +これは、もしリクエストの処理時間がとても長い場合 (例えば、長い計算を待つなど) に、リクエストがスレッドプールをブロックし、アプリケーションの応答性に不利益をもたらすことを意味します。もちろん、プールにより多くのスレッドを追加することもできますが、リソースを浪費する結果となるかも知れませんし、いずれにしてもプールのサイズは決して無限にはなりません。 + +ブラウザが画面に表示する新しいメッセージを待つためにブロッキング HTTP リクエストを送信するチャットアプリケーションを考えてみましょう。これらのリクエストはとてもとても長く (典型的には数秒) なることがあり、スレッドプールをブロックします。もしこのチャットアプリケーションに 100 ユーザが同時に接続できるよう計画しているのであれば、最低でも 100 スレッドを供給する必要があるでしょう。ええ、それくらいなら実現可能です。でも 1,000 ユーザではどうでしょう? 10,000 ユーザなら? + +これらのユースケースを解決するために、Play ではリクエストを一時的に中断することができます。HTTP リクエストは接続されたままですが、リクエストの実行はスレッドプールの外に押し出され、あとで再実行されます。固定した遅延のあとにリクエストを実行するか、または @Promise@ の値が利用可能になるのを待つよう Play に伝えることができます。 + +p(note). **Tip**. 本物の例を @samples-and-tests/chat@ で見ることができます。 + +例えば、このアクションはとても長いジョブを起動し、HTTP レスポンスに結果を返す前にジョブの完了を待機します: + +bc. public static void generatePDF(Long reportId) { + Promise pdf = new ReportAsPDFJob(report).now(); + InputStream pdfStream = await(pdf); + renderBinary(pdfStream); +} + +ここでは @Promise@ が回復するまでリクエストを中断するよう Play に依頼するために @await(…)@ を使用しています。 + +h3. 継続 + +フレームワークは、他のリクエストに供給するために使用していたスレッドを回収する必要があるので、コードの実行を中断しなければなりません。以前のバージョンの Play の @await(…)@ は、アクションを中断し、その後に最初から再度実行する @waitFor(…)@ と等価でした。 + +非同期処理をより簡単に扱うために、継続を紹介します。継続はコードの中断と透過的な再開を可能にします。このため、以下のようにとても命令的にコードを書くことができます: + +bc. public static void computeSomething() { + Promise delayedResult = veryLongComputation(…); + String result = await(delayedResult); + render(result); +} + +ここでは実際のところ、コードは二つのステップ、二つの異なるスレッドで実行されます。しかし、ご覧のとおりアプリケーションコードはとても透過的です。 + +@await(…)@ と継続を使って、以下のようにループを書くことができます: + +bc. public static void loopWithoutBlocking() { + for(int i=0; i<=10; i++) { + Logger.info(i); + await("1s"); + } + renderText("Loop finished"); +} + +リクエストを処理するためにひとつのスレッドしか使いませんが、Play はこれらのループを複数のリクエストに対して同時に並列実行することができます。 + +より現実的な例として、リモート URL からの非同期なコンテンツ取得があります。次の例では、三つのリモート HTTP リクエストを平行して実行します: それぞれの @play.libs.WS.WSRequest.getAsync()@ メソッドに対する呼び出しは非同期に GET リクエストを実行し、 @play.libs.F.Promise@ を返却します。このアクションメソッドは、三つの @Promise@ インスタンスの組み合わせについて @await(…)@ を呼び出すことで、受け取った HTTP リクエストを中断します。三つ全てのリモート呼び出しがレスポンスを生成したら、スレッドは処理を再開し、レスポンスをレンダリングします。 + +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. コールバック + +前述した三つの非同期リモートリクエストの例を実装する別の方法は、コールバックを使うことです。ここでは @await(…)@ の呼び出しに、すべての @promises@ が完了した時に実行されるコールバックである @play.libs.F.Action@ の実装が含まれています。 + +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 レスポンスストリーミング + +これで、リクエストをブロックせずにループを行うことができるようになったので、処理結果の一部が利用可能になり次第、ブラウザにデータを送りたくなるのではないでしょうか。それこそが @Content-Type:Chunked@ HTTP レスポンス型のポイントです。複数のチャンクを使って HTTP レスポンスを何度も送ることができます。ブラウザはこれらのチャンクが発行され次第、これを受け取ります。 + +今では @await(…)@ と継続を使ってこれを達成することができます: + +bc. public static void generateLargeCSV() { + CSVGenerator generator = new CSVGenerator(); + response.contentType = "text/csv"; + while(generator.hasMoreData()) { + String someCsvData = await(generator.nextDataChunk()); + response.writeChunk(someCsvData); + } +} + +もし CSV の生成に一時間かかったとしても、生成されたデータが利用可能になり次第、クライアントに送り返すことで、Play はひとつのスレッドを使って複数のリクエストを同時に処理することができます。 + + +h2. WebSockets の使用 + +WebSockets は、ブラウザとアプリケーション間の双方向コミュニケーションチャンネルを開く、ひとつの方法です。ブラウザ側で "ws://" という url を使ってソケットを開きます。 + +bc. new Socket("ws://localhost:9000/helloSocket?name=Guillaume") + +Play 側では WS ルートを定義します: + +bc. WS /helloSocket MyWebSocket.hello + +@MyWebSocket@ は @WebSocketController@ です。WebSocket コントローラは標準的な HTTP コントローラに似ていますが、異なる概念を取り扱います。 + +* リクエストオブジェクトを持ちますが、レスポンスオブジェクトは持ちません。 +* セッションにアクセスできますが、読み出し専用です。 +* @renderArgs@, @routeArgs@ とフラッシュスコープを持ちません。 +* ルートパターンまたはクエリ文字列からのパラメータのみ読むことができます。 +* 二つのコミュニケーションチャンネル: inbound と outbound を持ちます。 + +クライアントが @ws://localhost:9000/helloSocket@ ソケットに接続すると、Play は @MyWebSocket.hello@ アクションメソッドを起動します。一旦 @MyWebSocket.hello@ アクションメソッドが終了するとソケットは閉じられます。 + +このため、とても基本的なソケットの例は以下のようになります: + +bc. public class MyWebSocket extends WebSocketController { + + public static void hello(String name) { + outbound.send("Hello %s!", name); + } +} + +ここでは、クライアントはソケットに接続すると‘Hello Guillaume’というメッセージを受け取り、その後 Play はソケットを閉じます。 + +もちろん、通常は直ちにソケットを閉じたいと思わないでしょう。これは @await(…)@ と継続を使って容易に達成できます。 + +基本的なエコーサーバの例です: + +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!"); + } + } + } + +} + +上記の例において、ネストされた‘if’と‘キャスト’の海は書くのが退屈でエラーを起こしがちでした。この点で Java は最低です。このようなシンプルな場合でさえ容易に扱えません。複数のストリームを結びつけ、より多くのイベントタイプが存在するような、より複雑なケースにおいては悪夢のようになることでしょう。 + +これこそが、私たちが "play.libs.F":libs#FunctionalprogrammingwithJava ライブラリにおいてある種の基本的なパターンマッチングを導入した理由です。 + +これにより上記の例を次のように書き直すことができます: + +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). **考察を続けます** + +次は %(next)"Ajax リクエスト":ajax% を行ってみましょう。 \ No newline at end of file diff --git a/documentation/manual_ja/cache.textile b/documentation/manual_ja/cache.textile new file mode 100644 index 0000000000..c93a90e6f1 --- /dev/null +++ b/documentation/manual_ja/cache.textile @@ -0,0 +1,103 @@ +h1. キャッシュの使用 + +パフォーマンスの高いシステムを作成するため、データのキャッシュが必要になる場合があります。Play にはキャッシュライブラリがあり、分散環境下では "Memcahed":http://www.danga.com/memcached/ を使用します。 + +Memcached を設定しない場合、Play は JVM ヒープにデータを保存するスタンドアロンキャッシュを使用します。JVM へのデータのキャッシュは、Play の ‘何も共有しない’ という前提を覆します: 複数のサーバで実行したアプリケーションの振る舞いが一貫していると期待することはできません。それぞれのアプリケーションインスタンスは異なるデータのコピーを持ちます。 + +キャッシュの規約がはっきりしていることを理解することは重要です: キャッシュにデータを置いた場合、このデータが永久に残り続けると期待することはできません。実際のところ、期待すべきではありません。キャッシュは高速ですが、その値は失効しますし、(永続的にバックアップしない限り) 基本的にキャッシュはメモリ上にのみ存在します。 + +このため、期待するデータが無い場合、再度データを置くことが、キャッシュを使う最善の方法です: + +bc. public static void allProducts() { + List products = Cache.get("products", List.class); + if(products == null) { + products = Product.findAll(); + Cache.set("products", products, "30mn"); + } + render(products); +} + +h2. キャッシュ API + +キャッシュ API は @play.cache.Cache@ クラスによって提供されます。このクラスには、キャッシュのデータを設定する、置き換える、取得する、と言ったひと揃えのメソッドがあります。それぞれのメソッドの振る舞いを正確に理解するには、Memcached のドキュメントを参照してください。 + +いくつか例を示します: + +bc. public static void showProduct(String id) { + Product product = Cache.get("product_" + id, Product.class); + if(product == null) { + product = Product.findById(id); + Cache.set("product_" + id, product, "30mn"); + } + render(product); +} + +public static void addProduct(String name, int price) { + Product product = new Product(name, price); + product.save(); + showProduct(product.id); +} + +public static void editProduct(String id, String name, int price) { + Product product = Product.findById(id); + product.name = name; + product.price = price; + Cache.set("product_" + id, product, "30mn"); + showProduct(id); +} + +public static void deleteProduct(String id) { + Product product = Product.findById(id); + product.delete(); + Cache.delete("product_" + id); + allProducts(); +} + +いくつかのメソッドは @safe@ という接頭辞で始まります - 例えば、 @safeDelete@ や @safeSet@ という具合です。標準のメソッドは処理をブロックしません。これは、以下のメソッド呼び出しを発行した際: + +bc. Cache.delete("product_" + id); + +@delete@ メソッドが、キャッシュオブジェクトが実際に削除されるまで待たずに、直ちにリターンすることを意味します。このため、このオブジェクトはまだ存在するかもしれない場合には、エラー - 例えば IO エラー - が発生します。 + +処理を続行する前に、このオブジェクトを確実に削除したい場合には、 @safeDelete@ メソッドを使うことができます: + +bc. Cache.safeDelete("product_" + id); + +このメソッドは処理をブロックし、オブジェクトが削除されたかどうかを示す boolean 型の値を返します。以上より、キャッシュからあるアイテムが削除されたことを保証する完全なパターンは、以下のようになります: + +bc. if(!Cache.safeDelete("product_" + id)) { + throw new Exception("Oops, the product has not been removed from the cache"); +} +... + +これらの処理をブロックする @safe@ メソッドの呼び出しは、アプリケーションを遅くすることに注意してください。このため、これらのメソッドは必要な場合にのみ使用してください。 + +@expiration == "0s"@ (ゼロ秒) を指定した場合の実際の有効期限はキャッシュ実装によって異なることにも注意してください。 + +h2. セッションをキャッシュとして使ってはいけません! + +インメモリのセッション実装を使用するフレームワークを使ってきたならば、Play が小さな String 型のデータのみを HTTP セッションに保存することを許可することを不満に感じるかもしれません。しかし、セッションはアプリケーションデータをキャッシュする場所ではないので、このほうが良いのです! + +このため、以下のようなやり方に慣れているならば: + +bc. httpServletRequest.getSession().put("userProducts", products); +... +// and then in subsequent requests +products = (List)httpServletRequest.getSession().get("userProducts"); + +Play ではちょっと違ったやり方で同じ効果を得られます。以下のほうがより良いアプローチだと考えます: + +bc. Cache.set(session.getId(), products); +... +// and then in subsequent requests +List products = Cache.get(session.getId(), List.class) + +ここでは、Cache の中のそれぞれの情報が一意性を保てるように、一意な UUID を使用しました。セッションオブジェクトとは違い、キャッシュはどのユーザにも紐付かないことを忘れないでください! + +h2. memcached の設定 + +Memcached の実際の実装を利用する場合は、 "memcached configuration":configuration#memcached において Memcached を利用可能にし、"memcached.host configuration":configuration#memcached.host でデーモンのアドレスを定義します: + +p(note). **考察を続けます** + +%(next)"メール送信":emails% について学びましょう。 diff --git a/documentation/manual_ja/configuration.textile b/documentation/manual_ja/configuration.textile new file mode 100644 index 0000000000..6670367310 --- /dev/null +++ b/documentation/manual_ja/configuration.textile @@ -0,0 +1,916 @@ +h1. 設定パラメータ + +@conf/application.conf@ ファイルの設定キーに値を設定して Play のアプリケーションを設定しましょう。参照: + +* "主要な概念 - conf ディレクトリ":main#conf +* "複数環境用 application.conf の管理":ids +* "アプリケーションの本番稼動":production + +h2(#application). アプリケーション設定 + + +h3(#application.baseUrl). application.baseUrl + +アプリケーションベース URL は絶対 URL の逆引きに使われます。このアプリケーションベース URL は e メールのレンダリングのように @Http.Request@ を介さない @@{..} テンプレート構文やジョブ内で使われます。例えば @dev@ モード用の設定は次のようにし: + +bc. application.baseUrl=http://localhost:9000/ + +@prod@ モード用の設定は次のようにします: + +bc. %production.application.baseUrl=http://www.yourdomain.com/ + + +h3(#application.defaultCookieDomain). application.defaultCookieDomain + +サブドメイン間で共有される session/cookie を有効にします。例えば、 @foo.example.com@ や @bar.example.com@ などのように ‘.example.com’ で終わる全てのドメインで有効な cookie を生成するには次のようにします: + +bc. application.defaultCookieDomain=.example.com + +デフォルト: cookie は特定のドメインでのみ有効です。 + + +h3(#application.lang.cookie). application.lang.cookie + +現在の言語を格納するために使われる cookie の名前です。言語は @play.i18n.Lang.change(String locale)@ で設定されます。Play アプリケーションと言語設定を分けたい場合に変更できます。設定例: + +bc. application.lang.cookie=MYAPP_LANG + +デフォルト: @PLAY_LANG@ + + +h3(#application.langs). application.langs + +アプリケーションで使われるロケールを定義します。 @conf/messages.{locale}@ ファイルにローカライズされたメッセージを置くことができます。設定する値はカンマ区切りの言語コードのリストです。設定例: + +bc. application.langs=fr,en,ja + +デフォルト: 追加言語はありません。 + + +h3(#application.log). application.log + +アプリケーションのログレベルを指定します。設定例: + +bc. application.log=DEBUG + +デフォルト: @INFO@ + +関連項目: "ログの設定":logs. + + +h3(#application.log.path). application.log.path + +ログ出力をカスタマイズするための Log4J 設定ファイルのパス。パスを指定しないとき Play は @conf@ ディレクトリに @log4j.properties@ ファイルがあればロードします。 + +bc. application.log.path=/log4j.properties + +デフォルト: @/log4j.xml@ がなければ @/log4j.properties@ + + +h3(#application.log.recordCaller). application.log.recordCaller + +呼び出し元のメソッドを記録または表示するための @play.Logger.recordCaller@ の値を設定します。設定例: + +bc. application.log.recordCaller=true + +デフォルト: @false@ + + +h3(#application.mode). application.mode + +アプリケーションのモード (大文字小文字を区別します) です。設定例: + +bc. application.mode=prod + +Values: + +* @DEV@ - 即時リロードやその他開発のヘルプを有効にします。 +* @PROD@ - プリコンパイルや Java のソースまたはテンプレートをキャッシュします。 + +デフォルト: @DEV@ + + +h3(#application.name). application.name + +アプリケーション名。たいていは @play new@ コマンドで設定されます。 + +デフォルト: なし + + +h3(#application.secret). application.secret + +暗号化に使われる秘密鍵です。たいていは @play new@ または @play secret@ コマンドで設定されます。アプリケーションを複数デプロイする場合は同じ鍵を使うようにしてください。設定例: + +bc. application.secret=mNuAvlsFVjeuynN4IWZxZzFOHYVagafzjruHmWTL26VISKr46rUtyGcJuX7aYx4q + +設定されていなければ @play.libs.Crypto.sign@ はメッセージを暗号化しません。具体的にいうと、セッションが暗号化されないということです。 + +デフォルト: なし + + +h3(#application.session.cookie). application.session.cookie + +セッション cookie 名です。cookie の secure 属性はデフォルトで有効ではありません。HTTPS で通信されている場合に secure 属性を true に設定します。設定例: + +bc. application.session.cookie=PLAY + +デフォルト: session は一時的な @PLAY_SESSION@ cookie に書かれます。 + + +h3(#application.session.httpOnly). application.session.httpOnly + +cookie の ‘HTTP only’ フラグを有効にします。有効にすることで XSS 攻撃の影響を受けにくくなります。設定例: + +bc. application.session.httpOnly=true + +デフォルト: @false@ + +更に情報が必要なときは "OWASP の HttpOnly":http://www.owasp.org/index.php/HttpOnly のページを参照してください。 + + +h3(#application.session.maxAge). application.session.maxAge + +セッションタイムアウト、言い換えるとセッション cookie の最大生存期間です。設定されていない場合は、ブラウザを閉じたときにセッションが切れます。セッションの有効期間を 1 時間に設定したいときは次のようにします: + +bc. application.session.maxAge=1h + +セッションの有効期間を 1 週間にしたいときは次のようにします: + +bc. application.session.maxAge=7d + +デフォルト: ブラウザを閉じるまで + + +h3(#application.session.secure). application.session.secure + +HTTPS 接続の cookie ベースのセッションを有効にします。設定例: + +bc. application.session.secure=true + +デフォルト: @false@ + + +h3(#application.session.sendOnlyIfChanged). application.session.sendOnlyIfChanged + +セッションに変更がない時には、セッション cookie を送らないようにします。設定例: + +bc. application.session.sendOnlyIfChanged=true + +デフォルト: @false@ + + +h3(#application.web_encoding). application.web_encoding + +Play が Web ブラウザとやり取りするときや "Web サービスクライアント":libs#WebServiceclient が使用するテキストエンコーディングです。Play はデフォルトで @UTF-8@ を使用するため、普通は設定する必要はありません。設定例: + +bc. application.web_encoding=ISO-8859-1 + +デフォルト: @UTF-8@ + +@application.web_encoding@ を変更すると HTTP ヘッダの @Content-type@ の @charset@ に影響します。また動的にレンダリングされる箇所には影響しますが、静的なコンテンツには影響 **しません**。ですから、デフォルトのレスポンスエンコーディングを変更していて、特殊文字を含む (@public/@ フォルダ内の) テキストファイルがあるときは、これらのテキストファイルのエンコーディングを指定したエンコーディングに合わせなければなりません。その他の全てのファイルは UTF-8 で保存すべきです。 + + +h2(#attachments). 添付ファイル + + +h3(#attachments.path). attachments.path + +@play.db.jpa.Blob@ の内容の格納パスです。これは、絶対パス、または Play アプリケーションフォルダの中にあるフォルダへの相対パスにすることができます。設定例: + +bc. attachments.path=data/attachments + +デフォルト: @attachments@ + + +h2(#certificate). X509 証明書 + + +h3(#certificate.key.file). certificate.key.file + +HTTPS をサポートするための X509 証明書の秘密鍵を指定します。ファイル名は @host.key@ でなければなりません。設定例: + +bc. certificate.key.file=/certificates/host.key + +デフォルト: @conf/host.key@ + + +h3(#certificate.file). certificate.file + +HTTPS をサポートするための X509 証明書を指定します。ファイル名は @host.cert@ でなければなりません。設定例: + +bc. certificate.file=/certificates/host.cert + +デフォルト: @conf/host.cert@ + + +h3(#certificate.password). certificate.password + +パスワード保護された X509 証明書の秘密鍵のパスワードです。"certificate.key.file":#certificate.key.file で設定したものに対応するものを指定します。設定例: + +bc. certificate.password=secret + +デフォルト: @secret@ + + +h2(#cron). スケジューリングされたジョブ + +@cron.@ で始まるキーでスケジューリングされたジョブの cron 式を設定すると、@play.jobs.On または @Every アノテーションの設定値に指定したキーを使用することができます。例えば、@On("cron.noon") は以下の設定を参照します: + +bc. cron.noon=0 0 12 * * ? + + +h2(#date). 日付フォーマット + + +h3(#date.format). date.format + +@java.text.SimpleDateFormat@ パターンを使って、デフォルトの日付フォーマットを設定します。設定例: + +bc. date.format=dd-MM-yyyy + +このプロパティはテンプレート内で @${date.format()}@ がどのように日付を出力するかにも影響します。また、日付パラメータを変数にバインドするときのデフォルトの日付フォーマットも設定します。 + +デフォルト: @yyyy-MM-dd@ + +"application.langs":#application.langs で設定した言語ごとに異なる日付フォーマットを設定することも出来ます。設定例: + +bc. date.format.fr=dd-MM-yyyy + + +h2(#dbconf). データベース設定 + +h3(#db). db + +データベースエンジンの設定です。素早く開発用データベースをセットアップするために、一時的なインメモリデータベース (H2 インメモリデータベース) を使うには次のようにします: + +bc. db=mem + +単純なファイルに書き出すデータベース (H2 ファイルストアド) を使うには次のようにします: + +bc. db=fs + +ローカルの MySQL5 データベースを使うには次のようにします: + +bc. db=mysql:user:pwd@database_name + +アプリケーションサーバに設定されているデータソースを利用するには次のようにします: + +bc. db=java:/comp/env/jdbc/myDatasource@ + +もし @Datasource@ を指定すると、データベースプラグインは @db=java:@ のパターンを検知し、デフォルトの JDBC システムを不活性化します。 + +デフォルト: none. + + +h3(#db.destroyMethod). db.destroyMethod + +一般的な ‘destroy’ メソッドの名前を指定します。既存のデータソースを使用するとき、アプリケーション停止時に始末する必要があることがあります。設定例: + +bc. db.destroyMethod=close + +デフォルト: なし + + +h3(#db.driver). db.driver + +"db.url":#db.url で設定したデータベースを使うための、データベースドライバのクラス名を指定します。設定例: + +bc. db.driver=org.postgresql.Driver + +デフォルト: + +* @org.h2.Driver@ "db":#db が @mem@ か @fs@、または "db.url":#db.url が @jdbc:h2:mem:@ から始まっている場合 +* @com.mysql.jdbc.Driver@ "db":#db が @mysql:…@ という設定である場合 + +h3(#db.isolation). db.isolation + +データベーストランザクションの隔離レベルです。設定例: + +bc. db.isolation=SERIALIZABLE + +妥当な値は @NONE@, @READ_UNCOMMITTED@, @READ_COMMITTED@, @REPEATABLE_READ@, @SERIALIZABLE@, または @java.sql.Connection.setTransactionIsolation()@ に渡される整数型の値です。すべてのデータベースがあらゆるトランザクション隔離レベルをサポートするわけではないことに注意してください。 + +デフォルト: データベース依存、一般的には @READ_COMMITTED@ + +h3(#db.pass). db.pass + +"db.url":#db.url への接続に使うデータベース接続パスワードです。 + +デフォルト: 設定値なし、または "db":#db に @mem@ か @fs@ が設定されている場合は空文字列 + + +h3(#db.pool.maxIdleTimeExcessConnections). db.pool.maxIdleTimeExcessConnections + +"db.pool.minSize":#db.pool.minSize を超えるアイドルコネクションが ‘処分される’ までの秒数です。"c3p0 documentation":http://www.mchange.com/projects/c3p0/#maxIdleTimeExcessConnections を参照してください。 + +デフォルト: @0@ - ‘処分されません’. + + +h3(#db.pool.maxSize). db.pool.maxSize + +コネクションプールの最大サイズ (コネクション数) です。"c3p0 documentation":http://www.mchange.com/projects/c3p0/#maxPoolSize を参照してください。設定例: + +bc. db.pool.maxSize=60 + +デフォルト: @30@ + + +h3(#db.pool.minSize). db.pool.minSize + +コネクションプールの最小サイズ (コネクション数) です。"c3p0 documentation":http://www.mchange.com/projects/c3p0/#minPoolSize を参照してください。設定例: + +bc. db.pool.minSize=10 + +デフォルト: @1@ + + +h3(#db.pool.timeout). db.pool.timeout + +ミリ秒単位のコネクションプールタイムアウト時間です。"c3p0 documentation":http://www.mchange.com/projects/c3p0/#checkoutTimeout を参照してください。設定例: + +bc. db.pool.timeout=10000 + +デフォルト: @5000@ + + +h3(#db.url). db.url + +"db.user":#db.user と "db.pass":#db.pass と "db.driver":#db.driver との組み合わせの完全な JDBC の設定です。設定例: + +bc. db.url=jdbc:postgresql:database_name + +デフォルト: なし + + +h3(#db.user). db.user + +"db.url":#db.url 接続時に使われるデータベース接続ユーザ名です。 + +デフォルト: なし、または "db":#db が @mem@ か @fs@ と設定されているときは @sa@ + + +h2(#evolutions). データベースエボリューションズ + + +h3(#evolutions.enabled). evolutions.enabled + +"データベースエボリューションズ":evolutions を無効にするときに使います。 + +bc. evolutions.enabled=false + +デフォルト: @true@ + + +h2(#test). テストランナー + +h3(#headlessBrowser). headlessBrowser + +@play auto-test@ 実行時に使用される、 HtmlUnit ヘッドレス web ブラウザの互換モードを指定します。 + +bc. headlessBrowser=INTERNET_EXPLORER_7 + +値: + +* @FIREFOX_3@ +* @FIREFOX_3_6@ +* @INTERNET_EXPLORER_6@ +* @INTERNET_EXPLORER_7@ +* @INTERNET_EXPLORER_8@ + +デフォルト: @INTERNET_EXPLORER_8@ + + +h2(#hibernate). Hibernate + +Hibernate のプロパティを追加で指定することも出来ます。例えば、SQL コメントを有効にするには次のようにします: + +bc. hibernate.use_sql_comments=true + +h3(#hibernate.connection.datasource). hibernate.connection.datasource + +Hibernate のデータソース設定 + + +h2(#http). サーバ設定 + + +h3(#http.address). http.address + +HTTP をリッスンするアドレスです。サーバがリッスンするアドレスを制限するときに指定します。設定例: + +bc. http.address=127.0.0.1 + +デフォルト: サーバが持つ全てのアドレス + + +h3(#http.cacheControl). http.cacheControl + +静的ファイルを制御するための HTTP レスポンスヘッダ: 秒単位でデフォルトの最大期間を設定すると、ユーザのブラウザにページをどれだけの期間キャッシュすべきかを伝えます。この値は @prod@ モードの時のみ読み取られ、 @dev@ モードではキャッシュは無効になります。例えば、 @no-cache@ を送りたいときは次のようにします: + +bc. http.cacheControl=0 + +デフォルト: @3600@ - キャッシュの有効期限を 1時間に設定します。 + + +h3(#http.exposePlayServer). http.exposePlayServer + +HTTP サーバを Play と特定する HTTP レスポンスヘッダを無効にします。設定例: + +bc. http.exposePlayServer=false + +デフォルト: @true@ + + +h3(#http.path). http.path + +サーバ上でアプリケーションが動作する URL パス: Play アプリケーションをドメインのルートに置きたくないときに使います。このパラメータは WAR としてデプロイしたときには効果がありません。なぜならパスはアプリケーションサーバにハンドリングされるからです。設定例: + +bc. http.path=/myapp/ + +デフォルト: @/@ + + +h3(#http.port). http.port + +HTTP サーバがリッスンするポート + +デフォルト: @9000@ + + +h3(#http.proxyHost). http.proxyHost + +Web サービスをリクエストする際に使用するプロキシサーバです。設定例: + +bc. http.proxyHost=localhost + +デフォルト: @http.proxyHost@ システムプロパティ + + +h3(#http.proxyPassword). http.proxyPassword + +Web サービスをリクエストする際に使用するプロキシのパスワード + +デフォルト: @http.proxyPassword@ システムプロパティ + + +h3(#http.proxyPort). http.proxyPort + +Web サービスをリクエストする際に使用するプロキシのポートです。設定例: + +bc. http.proxyPort=3128 + +デフォルト: @http.proxyPort@ システムプロパティ + + +h3(#http.proxyUser). http.proxyUser + +Web サービスをリクエストする際に使用するプロキシのユーザ + +デフォルト: @http.proxyUser@ システムプロパティ + + + +h3(#http.nonProxyHosts). http.nonProxyHosts + +プロキシサーバを通さずに直接接続するべきホストを示します。 +ホストを @|@ で区切ったリストを値とすることができるほか、ワイルドカード文字 @*@ をマッチングに使うことができます。 + +設定例: + +bc. http.nonProxyHosts=localhost|*.example.com + +デフォルト: @http.nonProxyHosts@ システムプロパティ + + + +h3(#http.useETag). http.useETag + +有効にすると Play はエンティティタグを自動的に生成し、必要なときに 304 を送ります。例えば、エンティティタグの利用を停止するには次のようにします: + +bc. http.useETag=false + +デフォルト: @true@ + + +h3(#http.userAgent). http.userAgent + +Web サービスをリクエストする際に送るカスタムの @USER_AGENT@ ヘッダの値です。設定例: + +bc. http.userAgent=myApp 1.0 + +デフォルト: none. + + +h3(#https.port). https.port + +HTTPS コネクタを有効化し、指定したポートでリッスンします。設定例: + +bc. https.port=9443 + +デフォルト: none - HTTPS の設定なし + + +h2(#java). Java ソース + + +h3(#java.source). java.source + +Java ソースレベルです。これは @java.version@ システムプロパティをオーバーライドします。設定例: + +bc. java.source=1.6 + +Values: @1.5@, @1.6@, @1.7@ (experimental). + +デフォルト: @1.5@ + + +h2(#jpa). JPA + + +h3(#jpa.dialect). jpa.dialect + +使用するカスタムの JPA ダイアレクトをここで指定します。設定例: + +bc. jpa.dialect=org.hibernate.dialect.PostgreSQLDialect + +デフォルト: Play は "db.driver":#db.driver の設定に基づいてダイアレクトを推測します。 + + +h3(#jpa.ddl). jpa.ddl + +使用する DDL 生成パターンを指定します。例えば、自動データベース構造更新を有効にするには次のようにします: + +bc. jpa.ddl=create-drop + +デフォルト: @update@ (@dev@ モード) または @none@ (@prod@ モード). + + +h3(#jpa.debugSQL). jpa.debugSQL + +SQL 文をデバッグ (DEBUG レベルでロギング) します。設定例: + +bc. jpa.debugSQL=true + +デフォルト: @false@ + + +h3(#jpa.entities). jpa.entities + +追加でロードする JPA エンティティクラス名のカンマ区切りのリストです。分けられた JAR ファイル内のモデルクラスのように @models@ パッケージに入っていない追加のエンティティがある場合に便利です。設定例: + +bc. org.example.model.Person, org.example.model.Organisation + +デフォルト: なし + + +h3(#jpa.mapping-file). jpa.mapping-file + +JPA マッピングファイル + +デフォルト: なし + + +h2(#jpda). JVM + + +h3(#jpda.port). jpda.port + +アプリケーションがデバッグモードで動作しているときに JPDA に使われるポートを定義します。設定例: + +デフォルト: @8000@ + + +h2(#keystore). キーストア + + +h3(#keystore.algorithm). keystore.algorithm + +JDK セキュリティ API 標準アルゴリズム名です。これは "keystore.file":#keystore.file の設定とともに使われます。 + +bc. keystore.algorithm=pkcs12 + +Values - JDK セキュリティ API の ‘標準名’: + +* @jceks@ (‘SunJCE’ プロバイダ) +* @jks@ (‘SUN" プロバイダ) +* @pkcs12@ + +デフォルト: @JKS@ + + +h3(#keystore.file). keystore.file + +HTTPS をサポートするための証明書を指定します。ファイル名は @certificate.jks@ にすべきです。設定例: + +bc. keystore.file=conf/certificate.jks + + +h3(#keystore.password). keystore.password + +"keystore.file":#keystore.file の設定を使うためのキーストア設定です。 + +bc. keystore.password=secret + +デフォルト: @secret@ + + +h2(#memcachedconfig). Memcached + + +h3(#memcached). memcached + +"Memcached":http://www.danga.com/memcached/ を有効にします。Memcached を設定しなければ、Play は JVM ヒープにデータを格納するスタンドアロンのキャッシュを使います。 + +bc. memcached=enabled + +デフォルト: @disabled@ + +関連項目: "キャッシュを使う":cache. + + +h3(#memcached.host). memcached.host + +memcached ホストを指定します。設定例: + +bc. memcached.host=127.0.0.1:11211 + +デフォルト: @127.0.0.1:11211@ + +分散キャッシュを構築するために複数のホストを指定することも出来ます。設定例: + +bc. memcached.1.host=127.0.0.1:11211 +memcached.2.host=127.0.0.1:11212 + + +h2(#mimetype). カスタム MIME タイプ + +MIME タイプを追加して定義できます。設定例: + +bc. mimetype.xpi=application/x-xpinstall + + +h2(#webserviceconfig). Web サービス + +h3(#webservice). webservice + +Web サービスの実装クラス名、またはビルトインの実装。設定例: + +bc. webservice=urlfetch + +値: + +* @urlfetch@ - JDK の内部実装 +* @async@ - 非同期の Http クライアント +* @play.libs.WS.WSImpl@ の実装クラス名 + +デフォルト: @async@ - 非同期の Http クライアント + + +h2(#mail). メール + + +h3(#mail.debug). mail.debug + +SMTP トランザクションのロギングを有効にします。内部では Play は SMTP 通信をするために JavaMail を使っています。 + +bc. mail.debug=true + +デフォルト: @false@ + + +h3(#mail.smtp). mail.smtp + +メールの設定キーです。 + +デフォルト: @mock@ - モックの Mailer を使います + +関連項目: "SMTP の設定":emails#smtp. + + +h3(#mail.smtp.authenticator). mail.smtp.authenticator + +カスタムの SMTP 認証 (@javax.mail.Authenticator@) 実装のクラス名です。 + +デフォルト: なし + + +h3(#mail.smtp.channel). mail.smtp.channel + +暗号化された経路で e メールを送信する方法は二通りあり、このプロパティで方法を選択できます。値は以下の通りです: + +* @clear@ - 暗号化なし +* @ssl@ - SMTP-over-SSL (SMTPS) コネクタ; 465 番ポートでリッスンしている SSL ソケットです。 +* @starttls@ - 使用するサーバが @starttls@ コマンド (参考: "RFC 2487":http://www.apps.ietf.org/rfc/rfc2487.html) に対応していれば、SSL/TLS に切り替わる 25 番ポートの接続です。 + +デフォルト: @clear@ + + +h3(#mail.smtp.host). mail.smtp.host + +外向きのメールサーバです。設定例: + +bc. mail.smtp.host=127.0.0.1 + +GMail の SMTP サーバを使うには次のようにします: + +bc. mail.smtp.host=smtp.gmail.com + +デフォルト: @localhost@ + + +h3(#mail.smtp.localhost). mail.smtp.localhost + +SMTP コマンドで使用されるローカルホスト名 + +デフォルト: なし - Java Mail のデフォルト値を使います。 + + +h3(#mail.smtp.pass). mail.smtp.pass + +SMTP サーバのパスワードです。"mail.smtp.host":#mail.smtp.host のものです。例えば GMail のパスワードです。 + +デフォルト: なし + + +h3(#mail.smtp.port). mail.smtp.port + +接続先の SMTP サーバのポート番号です。デフォルト値をオーバーライドするために使います。設定例: + +bc. mail.smtp.port=2500 + +デフォルト: + +* @25@ "mail.smtp.channel":#mail.smtp.channel に @clear@ または @starttls@ がセットされているとき +* @465@ "mail.smtp.channel":#mail.smtp.channel に @ssl@ がセットされているとき + + +h3(#mail.smtp.protocol). mail.smtp.protocol + +Sets whether to use SSL. Values: +SSL を使うかどうかを設定します。値は以下のとおりです: + +* @smtp@ +* @smtps@ + +デフォルト: @smtp@ + + +h3(#mail.smtp.socketFactory.class). mail.smtp.socketFactory.class + +JavaMail で SSL 接続を使うとき、接続先のサーバの証明書がルート証明書によって署名されていなければ、デフォルトでは接続を切断します。具体的には自己署名の証明書を使っているときです。Play はデフォルトでルート証明書のチェックをスキップします。このプロパティを使って振る舞いを制御できます。 + + +h3(#mail.smtp.user). mail.smtp.user + +SMTP サーバのユーザ名です。"mail.smtp.host":#mail.smtp.host のものです。例えば GMail のユーザ名です。 + +デフォルト: none. + + +h2(#play). Play run-time + + +h3(#play.bytecodeCache). play.bytecodeCache + +@dev@ モードでバイトコードキャッシュを無効にするときに使用します。 @prod@ モードには影響しません。 + +bc. play.bytecodeCache=false + +デフォルト: @true@ + + +h3(#play.editor). play.editor + +エラーページからファイルを開きます。使用しているテキストエディタが URL からファイルを開くことが出来れば、Play はエラーページからファイルへ動的にリンクします。Textmate での具体例は以下のとおりです: + +bc. play.editor=txmt://open?url=file://%s&line=%s + + +h3(#play.jobs.pool). play.jobs.pool + +ジョブプールのサイズです。設定例: + +bc. play.jobs.pool=20 + +デフォルト: @10@ + + +h3(#play.netty.clientAuth). play.netty.clientAuth + +@javax.net.ssl.SSLEngine@ のクライアント認証を設定します。設定例: + +bc. play.netty.clientAuth=need + +値: + +* @want@ - サーバはクライアント認証を _要求します_ +* @need@ - サーバはクライアント認証を _必要とします_ +* @none@ - クライアント認証不要 + +デフォルト: @none@ + + +h3(#play.netty.maxContentLength). play.netty.maxContentLength + +HTTP サーバがレスポンスとして返すコンテンツのバイト単位の最大長です。 + +デフォルト: なし - 制限なし + + +h3(#play.pool). play.pool + +実行プールサイズです。この値はできるだけ小さく抑えてください。この値を 1 スレッドに設定すると全てのリクエストは直列に処理されます (デバッグ目的には非常に有用です)。 + +bc. play.pool=10 + +デフォルト: @1@ (@dev@ モード), プロセッサ数 + 1 (@prod@ モード). + + +h3(#play.tmp). play.tmp + +一時ファイルを格納するフォルダです。設定例: + +bc. play.tmp=/tmp/play + +値: + +* 絶対パス +* アプリケーションディレクトリからの相対パス +* @none@ 一時ディレクトリが使われないようにします + +デフォルト: @tmp@ + + +h2(#ssl). SSL + +関連項目: "https.port":#https.port. + + +h3(#ssl.KeyManagerFactory.algorithm). ssl.KeyManagerFactory.algorithm + +"keystore.file":#keystore.file の設定に使用する Java Secure Socket Extension (JSSE) 信頼管理アルゴリズムの標準名です。 + +bc. ssl.KeyManagerFactory.algorithm=SunX509 + +デフォルト: @SunX509@ + + +h2(#trustmanager). trustmanager + + +h3(#trustmanager.algorithm). trustmanager.algorithm + +"X509 証明書":#certificate と "キーストア":#keystore の設定で使用する JDK セキュリティ API の標準アルゴリズム名です。 + +bc. trustmanager.algorithm=pkcs12 + +値 - JDK セキュリティ API の ‘標準名’: + +* @jceks@ (‘SunJCE’ プロバイダ) +* @jks@ (‘SUN" プロバイダ) +* @pkcs12@ + +デフォルト: @JKS@ + +bc. play.netty.clientAuth + +値 + +* @want@ 受け入れたサーバーモードの SSLSockets が、クライアント認証を要求するように初期設定するかどうかを制御します。 +* @need@ 受け入れたサーバーモードの SSLSockets で、クライアント認証が必須とされるように初期設定するかどうかを制御します。 +* @none@ 上記以外 + +デフォルト: @none@ + +h2(#upload). ファイルアップロード + +h3(#upload.threshold). upload.threshold + +ディスクに書き出されるファイルのアップロード時のバイト単位でのしきい値です。 @org.apache.commons.io.output.DeferredFileOutputStream@ に使われます。設定例: + +bc. upload.threshold=20480 + +デフォルト: @10240@ + + +h2(#xforwarded). プロキシ転送 + +h3(#XForwardedHost). XForwardedHost + +@X-Forwarded-Host@ HTTP ヘッダ値を上書きします。このヘッダ値はプロキシサーバとともに使われ、クライアントが要求した元のホストを保持しています。 + +デフォルト: @X-Forwarded-Host@ HTTP ヘッダ値 + + +h3(#XForwardedProto). XForwardedProto + +@X-Forwarded-Proto@ と @X-Forwarded-SSL@ HTTP ヘッダ値を上書きすることで、プロキシへのリクエストを SSL だと設定します。このヘッダ値はクライアントが要求した元のプロトコルを保持します。設定例: + +bc. XForwardedProto=https + + +h3(#XForwardedSupport). XForwardedSupport + +@X-Forwarded-For@ HTTP リクエストヘッダ値として許可された IP アドレスのカンマ区切りのリストです。プロキシサーバが @X-Forwarded-For@ リクエストヘッダをセットする場合、ローカルアドレスを制限するために使用します。 + +デフォルト: @127.0.0.1@ \ No newline at end of file diff --git a/documentation/manual_ja/controllers.textile b/documentation/manual_ja/controllers.textile new file mode 100644 index 0000000000..b1a6a17ffa --- /dev/null +++ b/documentation/manual_ja/controllers.textile @@ -0,0 +1,809 @@ +h1. コントローラ + +ビジネスロジックはドメインモデル層で管理されます。クライアント (通常は web ブラウザ) が直接このコードを呼び出すことができないことから、ドメインオブジェクトの機能性は URI によって表されたリソースとして公開されます。 + +クライアントは、HTTP プロトコルによって提供された統一的な API を使用して、これらのリソースと、暗黙的にその下にあるビジネスロジックを操作します。しかし、ドメインオブジェクトとリソースのマッピングは一対一ではありません: 粒度は異なるレベルで表現され、あるリソースは仮想化されたものかもしれませんし、あるリソースは別名が定義されているかもしれません… + +これは、まさにコントローラ層によって果たされる役割です: ドメインモデルオブジェクトとトランスポート層イベントの間の **接着剤** を提供します。モデル層においては、モデルオブジェクトに容易にアクセスし変更するために、コントローラは純粋な Java で書かれます。HTTP インタフェースのように、コントローラは手続き的で、リクエスト/レスポンス指向です。 + +コントローラ 層 は HTTP とドメインモデルの間の **インピーダンスミスマッチ** を減少させます。 + +p(note). **注意** + +異なる戦略をもった異なるアーキテクチャモデルがあります。いくつかのプロトコルはドメインモデルオブジェクトに直接アクセスします。これは、EJB や CORBA プロトコルによく見られます。これらの場合、そのアーキテクチャスタイルは、RPC (Remote Procedure Call) を使います。これらのコミュニケーションスタイルは、web アーキテクチャとほとんど互換性がありません。 + +SOAP のようないくつかの技術は Web を通してドメインモデルオブジェクトへのアクセスをていきょうします。しかし、SOAP はただのRPC スタイルプロトコルであり、この場合、HTTP はトランスポートプロトコルとして使用されます。アプリケーションプロトコルではありません。 + +web の原則は基本的にオブジェクト指向ではありません。そのため、お気に入りの言語に HTTP を適合させる層が必要になります。 + + +h2. コントローラの概要 + +コントローラは Java のクラスであり、 @controllers@ パッケージで管理される @play.mvc.Controller@ のサブクラスです。 + +コントローラはこのようなものになります: + +bc. package controllers; + +import models.Client; +import play.mvc.Controller; + +public class Clients extends Controller { + + public static void show(Long id) { + Client client = Client.findById(id); + render(client); + } + + public static void delete(Long id) { + Client client = Client.findById(id); + client.delete(); + } + +} + +コントローラの public かつ static なそれぞれのメソッドはアクションと呼ばれます。アクションメソッドのシグネチャは以下の通りです: + +bc. public static void action_name(params...); + +アクションメソッドのシグネチャに引数を定義できます。これらのパラメタは、フレームワークによって対応する HTTP パラメタから自動的に解決されます。 + +通常、アクションメソッドは return 構文を持ちません。アクションメソッドは **result** メソッドの起動によって終了します。今回の例では、テンプレートを実行して表示する @render(…)@ が result メソッドです。 + +h2. HTTP パラメータの取得 + +HTTP リクエストはデータを含んでいます。以下のようにしてこのデータを抽出することができます: + +* URI パス: @/clients/1541@ という URI パターンにおいて、1541 が動的な部分です。 +* クエリ文字列: @/clients?id=1541@ +* リクエスト本文: リクエストが HTML フォームから送信される場合、そのリクエスト本文には @x-www-urlform-encoded@ としてエンコードされたフォームデータを含んでいます。 + +いずれの場合でも、Play はデータを抽出して、すべての HTTP パラメータを含む Map を構築します。このマップのキーはパラメータ名です。パラメータ名は以下のようにして導出されます。 + +* (ルーティングで指定された) URI の動的部分の名前 +* クエリ文字列から取得される名前-値のペアの名前の部分 +* エンコードされた本文の内容 + +h3. パラメータマップの使い方 + +@params@ オブジェクトはすべてのコントローラクラスで利用できます (スーパークラス @play.mvc.Controller@ で定義されています) 。このオブジェクトは、現在のリクエストから見つけられるすべての HTTP パラメータを含んでいます。 + +例えば: + +bc. public static void show() { + String id = params.get("id"); + String[] names = params.getAll("names"); +} + +Play に型変換を指示することもできます: + +bc. public static void show() { + Long id = params.get("id", Long.class); +} + +でも、ちょっと待ってください。もっと良い方法があります:) + +h3. アクションメソッドのシグネチャ + +アクションメソッドのシグネチャから HTTP パラメータを直接検索することができます。Java 引数の名前は HTTP パラメータのものと同じであるに違いありません。 + +例えば、このリクエストでは: + +bc. /clients?id=1451 + +アクションメソッドは、シグネチャにおいて @id@ 引数を宣言することによって、 @id@ パラメータの値を検索することができます: + +bc. public static void show(String id) { + System.out.println(id); +} + +String 以外の Java の型も使えます。この場合、フレームワークはパラメータの値を正しい Java 型にキャストしようとします: + +bc. public static void show(Long id) { + System.out.println(id); +} + +パラメータが多値である場合は、配列引数を宣言することができます: + +bc. public static void show(Long[] id) { + for(String anId : id) { + System.out.println(anid); + } +} + +コレクションも宣言することができます: + +bc. public static void show(List id) { + for(String anId : id) { + System.out.println(anid); + } +} + +p(note). **例外** + +アクションメソッド引数に対応する HTTP パラメータが見つからない場合、対応するメソッド引数はデフォルト値 (通常、オブジェクト型は null、基本データ型は 0) に設定されます。値が見つかっても、要求された Java 型に適切にキャストできない場合、バリデーションエラーのコレクションにエラーが追加され、デフォルト値が設定されます。 + +h2. HTTP と Java の高度な紐付け + +h3. シンプルな型 + +すべての基本データ型と、そして、一般的な Java の型は自動的に紐付けられます: + +@int@, @long@, @boolean@, @char@, @byte@, @float@, @double@, @Integer@, @Long@, @Boolean@, @Char@, @String@, @Byte@, @Float@, @Double@. + +HTTP リクエスト中にパラメータが見つからないか、または自動変換に失敗した場合、オブジェクト型には null、基本データ型にはそれらのデフォルト値が設定されることに注意してください。 + +h3. 日付 + +日付の文字列表現が以下のパターンのいずれか 1 つにマッチする場合、自動的に日付オブジェクトに紐付けられます: + +* yyyy-MM-dd'T'hh:mm:ss'Z' // ISO8601 + timezone +* yyyy-MM-dd'T'hh:mm:ss" // ISO8601 +* yyyy-MM-dd +* yyyyMMdd'T'hhmmss +* yyyyMMddhhmmss +* dd'/'MM'/'yyyy +* dd-MM-yyyy +* ddMMyyyy +* MMddyy +* MM-dd-yy +* MM'/'dd'/'yy + +@As アノテーションを使って日付フォーマットを指定することができます。 + +例えば: + +bc. archives?from=21/12/1980 + +bc. public static void articlesSince(@As("dd/MM/yyyy") Date from) { + List
articles = Article.findBy("date >= ?", from); + render(articles); +} + +言語によって日付フォーマットを最適化することもできます。例えば: + +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); +} + +この例の場合、フランス語とドイツ語には日付フォーマットに @dd-MM-yyyy@ を指定し、その他の言語には @MM-dd-yyyy@ を指定しています。言語の値をカンマで区切れることに注意してください。言語パラメータの数と値パラメータの数を合わせることが重要です。 + +@As アノテーションが指定されていない場合、Play! はロケールに従ったデフォルトの日付フォーマットを使用します。デフォルトで使用する日付フォーマットは "date.format の設定":configuration#date.format で指定します。 + + +h3. カレンダ + +Play がロケールに従って Calendar オブジェクトを選択する場合を除いて、カレンダの紐付けは日付とまるっきり同じように動作します。 @Bind アノテーションを使用することもできます。 + +h3. ファイル + +Play によるファイルアップロードは簡単です。 @multipart/form-data@ エンコードされたリクエストを使ってサーバにファイルをポストしたら、 @java.io.File@ 型を使ってファイルオブジェクトを取得します: + +bc. public static void create(String comment, File attachment) { + String s3Key = S3.post(attachment); + Document doc = new Document(comment, s3Key); + doc.save(); + show(doc.id); +} + +作成されたファイルは、元のファイルと同じ名前になります。ファイルは一時ディレクトリに保存されて、リクエストの完了時に削除されます。このため、作成されたファイルは安全なディレクトリにコピーしなければなりません。そうでなければファイルは無くなってしまいます。 + +通常、アップロードされたファイルの MIME タイプは HTTP リクエストの @Content-type@ ヘッダで指定されます。しかし、一般的でない種類のファイルが web ブラウザからアップロードされた場合、この MIME タイプの指定は行われない場合があります。このような場合、 @play.libs.MimeTypes@ クラスを使ってファイル名の拡張子を MIME タイプにマッピングすることができます。 + +bc. String mimeType = MimeTypes.getContentType(attachment.getName()); + +@play.libs.MimeTypes@ クラスは、与えられたファイル名拡張子の MIME タイプを @$PLAY_HOME/framework/src/play/libs/mime-types.properties@ の中から探します。 + +"カスタム MIME タイプ設定":configuration#mimetype を使って独自の型をアプリケーションの @conf/application.conf@ ファイルに追加することもできます。 + + +h3. サポートされた型の配列またはコレクション + +すべてのサポートされた型は配列またはオブジェクトのコレクションとして取得することができます: + +bc. public static void show(Long[] id) { + … +} + +または: + +bc. public static void show(List id) { + … +} + +または: + +bc. public static void show(Set id) { + … +} + +Play は、以下のような特別なケースの Map のバインディングも取り扱います: + +bc. public static void show(Map client) { + … +} + + +クエリ文字列は以下のようになります: + +bc. ?client.name=John&client.phone=111-1111&client.phone=222-2222 + +クライアントの変数は要素がふたつのマップにバインドされます。ひとつ目の要素はキーが @name@ で値が @John@, そしてふたつ目の要素はキーが @phone@ で値が @111-1111, 222-2222@ です。 + +h3. POJO オブジェクトの紐付け + +Play は簡単な命名規約ルールを使用することで、どんなモデルクラスでも自動的に紐付けることができます。 + +bc. public static void create(Client client ) { + client.save(); + show(client); +} + +このアクションを使って client を作るクエリ文字列は次のようになるでしょう: + +bc. ?client.name=Zenexity&client.email=contact@zenexity.fr + +Play は Client インスタンスを作成し、HTTP パラメータの名前を Client オブジェクトのプロパティに解決します。解決できないパラメータ名は安全に無視されます。型のミスマッチも安全に無視されます。 + +パラメータの紐付けは再帰的に行われるので、完全なオブジェクトグラフを扱うことができます: + +bc. ?client.name=Zenexity +&client.address.street=64+rue+taitbout +&client.address.zip=75009 +&client.address.country=France + +モデルオブジェクトのリストを更新するには、配列記法とオブジェクトを参照する ID を使用してください。例えば、Client モデルが @List Customer customers@ として宣言された Customer モデルのリストを持つと想像してください。Customer のリストを更新するために、以下のようなクエリ文字列を提供するでしょう: + +bc. ?client.customers[0].id=123 +&client.customers[1].id=456 +&client.customers[2].id=789 + + +h2. JPA オブジェクトの紐付け + +HTTP と Java の紐付けを使って、自動的に JPA オブジェクトを紐付けることができます。 + +HTTP パラメータ中に @user.id@ フィールドを提供することできます。Play は @id@ フィールドを見つけると、user を編集する前に、データベースからマッチするインスタンスをロードします。そして、HTTP リクエストで提供された他のパラメータを適用します。このため、直接 user を保存することができます。 + +bc. public static void save(User user) { + user.save(); // ok with 1.0.1 +} + +POJO マッピングの動きと同じやり方で JPA バインディングを使って完全なオブジェクトグラフを変更することができますが、変更するサブオブジェクトごとに ID を供給しなければなりません: + +bc. user.id = 1 +&user.name=morten +&user.address.id=34 +&user.address.street=MyStreet + +h2. カスタムバインディング + +バインディングシステムはより多くのカスタマイズをサポートするようになりました。 + +h3. @play.data.binding.As + +最初に紹介するのは、文脈的にバインディングを構成する新しい @play.data.binding.As アノテーションです。これは例えば、 @DateBinder@ によって使用される日付のフォーマットを指定するために使います: + +bc. public static void update(@As("dd/MM/yyyy") Date updatedAt) { + … +} + +この @As アノテーションは国際化もサポートします。これは、ロケールごとに特定のアノテーションを提供できることを意味しています: + +bc. public static void update( + @As( + lang={"fr,de","en","*"}, + value={"dd/MM/yyyy","dd-MM-yyyy","MM-dd-yy"} + ) + Date updatedAt + ) { + … +} + +この @As アノテーションは、これをサポートするすべてのバインダと共に動作します。以下は、 @ListBinder@ を使用する例です: + +bc. public static void update(@As(",") List items) { + … +} + +このバインダは、単純にカンマで分けられた @String@ を @List@ にバイドします。 + +h3. @play.data.binding.NoBinding + +新たに追加された @play.data.binding.NoBinding は、バインド非対象フィールドをマークし、潜在的なセキュリティ問題を解決します。以下に例を示します: + +bc. public class User extends Model { + @NoBinding("profile") public boolean isAdmin; + @As("dd, MM yyyy") Date birthDate; + public String name; +} + +public static void editProfile(@As("profile") User user) { + … +} + +このようにすると、例え悪意あるユーザが偽のフォームから @user.isAdmin=true@ というフィールドを含めてポストしたとしても、 @isAdmin@ フィールドは決して @editProfile@ アクションからはバインドされません。 + +h3. play.data.binding.TypeBinder + +*@As* アノテーションを使って完全に独自のバインダを定義することができます。独自のバインダは、プロジェクト内にて @TypeBinder@ のサブクラスとして定義されます。以下に例を示します: + +bc. public class MyCustomStringBinder implements TypeBinder { + + public Object bind(String name, Annotation[] anns, String value, + Class clazz) { + return "!!" + value + "!!"; + } +} + +以下のようにして、いずれのアクションにおいてもこのバインダを使用することができます: + +bc. public static void anyAction(@As(binder=MyCustomStringBinder.class) +String name) { + … +} + +h3. @play.data.binding.Global + +対応する型にだけ適用されるグローバルなカスタムバインダを定義することもできます。例えば、以下のようにして @java.awt.Point@ クラスにバインドできるバインダを定義することができます: + +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]) + ); + } +} + +見てのとおり、グローバルバインダは *@play.data.binding.Global* でアノテーションされた古典的なバインダです。外部モジュールは再利用可能な拡張バインダを定義することで、プロジェクトにバインダを提供することができます。 + + +h2. 戻り値の型 + +アクションメソッドは、HTTP レスポンスを生成しなければなりません。HTTP レスポンスを生成するもっとも簡単な方法は、Result オブジェクトを発行することです。Result オブジェクトが発行されると、通常の実行フローは中断され、メソッドはリターンされます。 + +例えば: + +bc. public static void show(Long id) { + Client client = Client.findById(id); + render(client); + System.out.println("This message will never be displayed !"); +} + +@render(…)@ メソッドは Result オブジェクトを発行し、以降のメソッドは実行しません。 + +h3. テキスト内容の返却 + +@renderText(…)@ メソッドは基本的な HTTP レスポンスに何らかのテキストを直接書き込むシンプルな Result イベントを発行します。 + +例えば: + +bc. public static void countUnreadMessages() { + Integer unreadMessages = MessagesBox.countUnreadMessages(); + renderText(unreadMessages); +} + +Java 標準のフォーマット構文を使ってテキストメッセージをフォーマットすることができます: + +bc. public static void countUnreadMessages() { + Integer unreadMessages = MessagesBox.countUnreadMessages(); + renderText("There are %s unread messages", unreadMessages); +} + +h3. JSON 文字列の返却 + +Play には @renderJSON(…)@ メソッドを使ってシンプルに JSON 文字列を返却するメソッドが備わっています。これらのメソッドは JSON 文字列を返却し、レスポンスのコンテントタイプに @application/json@ を設定します。 + +自分で JSON 文字列を指定するか、または @GsonBuilder@ によってシリアライズされる @Object@ を渡すことができます。 + +例えば: + +bc. public static void countUnreadMessages() { + Integer unreadMessages = MessagesBox.countUnreadMessages(); + renderJSON("{\"messages\": " + unreadMessages +"}"); +} + +あるいはオブジェクトがより複雑な構造を持つ場合、 @GsonBuilder@ を使って JSON を組み立てたいと思うかもしれません。 + +bc. public static void getUnreadMessages() { + List unreadMessages = MessagesBox.unreadMessages(); + renderJSON(unreadMessages); +} + +@renderJSON(…)@ メソッドに @Object@ を渡すときに JSON ビルダをより細かく制御する必要がある場合は、GSON シリアライザと @Type@ オブジェクトを渡して出力をカスタマイズすることも可能です。 + +h3. XML 文字列の返却 + +JSON メソッドと同様に、コントローラから直接 XML をレンダリングするいくつかのメソッドがあります。 @renderXml(…)@ メソッドは XML 文字列を返却し、コンテントタイプに @text/xml@ を設定します。 + +ここでは、自分で XML 文字列を指定すること、 @org.w3c.dom.Document@ を渡すこと、あるいは XStream シリアライザによってシリアライズされる POJO を渡すことができます。 + +例えば: + +bc. public static void countUnreadMessages() { + Integer unreadMessages = MessagesBox.countUnreadMessages(); + renderXml(""+unreadMessages+""); +} + +または @org.w3c.dom.Document@ オブジェクトを使うこともできます。 + +bc. public static void getUnreadMessages() { + Document unreadMessages = MessagesBox.unreadMessagesXML(); + renderXml(unreadMessages); +} + +h3. バイナリコンテンツの返却 + +"サーバに保存されたファイル":jpa#file のようなバイナリデータを提供する場合、 @renderBinary@ メソッドを使います。例えば、 @play.db.jpa.Blob photo@ プロパティを持つ @User@ モデルがある場合、このモデルオブジェクトをロードして、格納された MIME タイプで画像をレンダリングするために、コントローラメソッドを追加します: + +bc. public static void userPhoto(long id) { + final User user = User.findById(id); + response.setContentTypeIfNotSet(user.photo.type()); + java.io.InputStream binaryData = user.photo.get(); + renderBinary(binaryData); +} + +h3. 添付ファイルとしてのダウンロード + +一般的に web ブラウザがユーザのコンピュータにそのファイルをダウンロードする ‘添付ファイル’ としてバイナリレスポンスを扱うように、 HTTP ヘッダを設定して web ブラウザに指示することができます。これを行うためには、Playが @Content-Disposition@ レスポンスヘッダに与えられたファイル名を設定するよう、 @renderBinary@ メソッドの引数にファイル名を渡します。例えば、先に挙げた @User@ モデルのプロパティが @photoFileName@ だとした場合: + +bc. renderBinary(binaryData, user.photoFileName); + + +h3. テンプレートの実行 + +生成する内容が複雑である場合、レスポンスの内容を生成するためにテンプレートを使用するべきです。 + +bc. public class Clients extends Controller { + + public static void index() { + render(); + } +} + +テンプレート名は Play の規約から自動的に推測されます。デフォルトのテンプレートのパスは、コントローラとアクションの名前を使って解決されます。 + +この例で呼び出されるテンプレートは以下の通りです: + +bc. app/views/Clients/index.html + +h4. テンプレートスコープへの値の追加 + +テンプレートはしばしばデータを必要とします。 @renderArgs@ オブジェクトを使用することでテンプレートスコープにこれらのデータを追加することができます: + +bc. public class Clients extends Controller { + + public static void show(Long id) { + Client client = Client.findById(id); + renderArgs.put("client", client); + render(); + } +} + +テンプレートが実行される間、この @client@ 変数が定義されます。 + +例えば、以下のようになります。: + +bc.

Client ${client.name}

+ +h4. テンプレートスコープにデータを追加するより簡単な方法 + +@render(…)@ メソッドの引数を使って、直接テンプレートにデータを渡すことができます: + +bc. public static void show(Long id) { + Client client = Client.findById(id); + render(client); +} + +この場合、テンプレートからアクセスする変数は、Java のローカル変数と同じ名前になります。 + +複数の変数を渡すこともできます: + +bc. public static void show(Long id) { + Client client = Client.findById(id); + render(id, client); +} + +p(note). **重要!** + +この方法で渡せるのは **ローカル変数** だけです。 + + +h4. 別のテンプレートの指定 + +デフォルトのテンプレートを使用したくない場合、 @renderTemplate(…)@ メソッドの第一引数にテンプレート名を渡して使うことで、独自のテンプレートファイルを指定することができます。 + +例えば、以下のようにします: + +bc. public static void show(Long id) { + Client client = Client.findById(id); + renderTemplate("Clients/showClient.html", id, client); +} + +h3. 別の URL へのリダイレクト + +@redirect(…)@ メソッドは HTTP Redirect レスポンスを生成する Redirect イベントを発行します。 + +bc. public static void index() { + redirect("http://www.zenexity.fr"); +} + + +h3. アクションチェーン + +Servlet API の @forward@ に該当するものはありません。HTTP リクエストは 1 つのアクションだけを呼び出します。別のアクションを呼び出す必要がある場合は、そのアクションを呼び出すことができる URL にブラウザをリダイレクトさせなければなりません。このようにすることで、ブラウザの URL は常に実行されるアクションと一致し、 **戻る/進む/更新** の管理がはるかに簡単になります。 + +単に Java のやり方でアクションメソッドを実行するだけで、どんなアクションに対しても Redirect レスポンスを送ることができます。Java の呼び出しはフレームワークによってインターセプトされ、適切な HTTP Redirect が生成されます。 + +例えば: + +bc. public class Clients extends Controller { + + public static void show(Long id) { + Client client = Client.findById(id); + render(client); + } + + public static void create(String name) { + Client client = new Client(name); + client.save(); + show(client.id); + } +} + +以下のような routes ファイルの場合: + +bc. GET /clients/{id} Clients.show +POST /clients Clients.create + +* ブラウザは @/clients@ URL に POST を送ります。 +* Router は @Clients@ コントローラの @create@ アクションを起動します。 +* create アクションは @show@ アクションを直接呼び出します。 +* Java 呼び出しはインターセプトされ、Router は id パラメータと共に Clients.show を実行するために必要な URL をリバース生成します。 +* HTTP レスポンスは @302 Location:/clients/3132@ です。 +* ブラウザは @GET /clients/3132@ を発行します。 +* … + +h3. web エンコーディングのカスタマイズ + +Play は UTF-8 の使用を強調しますが、いくつかのレスポンス、またはアプリケーション全体が UTF-8 以外のエンコーディングを使用しなければならない状況があります。 + +h4. 直近レスポンスのエンコーディングのカスタマイズ + +コントローラにおいて次のようにすることで、直近のレスポンスのエンコーディングをカスタマイズすることができます: + +bc. response.encoding = "ISO-8859-1"; + +サーバのデフォルトとは異なるエンコーディングを使ってフォームを post する場合、このフォームにおいて、 @accept-charset@ 属性と、 @_charset_@ という名前の特別な hidden フィールドの両方でエンコーディング/文字セットを二回指定しなければなりません。 @accept-charset@ 属性はブラウザにフォームを post する際にどのエンコーディングを使用するかを指定し、 @_charset_@ フォームフィールドは Play にエンコーディングが何かを伝えます: + +bc.
+ +
+ +h4. アプリケーション全体のエンコーディングのカスタマイズ + +"application.web_encoding":configuration#application.web_encoding を設定して Play がブラウザとコミュニケーションする際に使用するエンコーディングを指定します。 + + +h2. インターセプション + +コントローラにインターセプタを定義することができます。インターセプタは、コントローラクラスとその子孫におけるすべてのアクションに対して実行されます。すべてのアクションに共通する処理: ユーザ認証されていることの確認、リクエストスコープ情報のロード… を定義するのは便利なやり方です。 + +これらのメソッドは、 @static@ ですが、 @public@ である必要はありません。適切なインターセプションマーカでこれらのメソッドを注釈しなければなりません。 + +h3. @Before + +@Before アノテーションで注釈されたメソッドは、このコントローラにおけるすべてのアクション呼び出しの前に実行されます。 + +例えば、セキュリティチェックを行うには以下のようにします: + +bc. public class Admin extends Controller { + + @Before + static void checkAuthentification() { + if(session.get("user") == null) login(); + } + + public static void index() { + List users = User.findAll(); + render(users); + } + … +} + +@Before メソッドにすべてのアクション呼び出しをインターセプトさせたくない場合、除外アクションのリストを指定することができます: + +bc. public class Admin extends Controller { + + @Before(unless="login") + static void checkAuthentification() { + if(session.get("user") == null) login(); + } + + public static void index() { + List users = User.findAll(); + render(users); + } + + … +} + +一連のアクション呼び出しを @Before メソッドでインターセプトさせたい場合は、only パラメータを指定することができます : + +bc. public class Admin extends Controller { + + @Before(only={"login","logout"}) + static void doSomething() { + … + } + … +} + +@unless@ パラメータと @only@ パラメータは @After, @Before@Finally で使うことができます。 + +h3. @After + +@After アノテーションで注釈されたメソッドは、このコントローラにおけるすべてのアクション呼び出しの後に実行されます。 + +bc. public class Admin extends Controller { + + @After + static void log() { + Logger.info("Action executed ..."); + } + + public static void index() { + List users = User.findAll(); + render(users); + } + + … +} + +h3. @Catch + +@Catch アノテーションで注釈されたメソッドは、別のアクションメソッドが特定の例外をスローした場合に実行されます。スローされた例外は @Catch メソッドの引数に渡されます。 + +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); + } +} + +通常の Java の例外を扱うように、より多くの型の例外をキャッチするためにスーパークラスをキャッチできます。ひとつ以上のキャッチメソッドがある場合は **priority** を指定することができるので、キャッチメソッドはこの優先度に従って実行されます (priority 1 が最初に実行されます) 。 + +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 + +@Finally アノテーションで注釈されたメソッドは、このコントローラにおけるすべてのアクション呼び出しの結果が確定された後に実行されます。 +アクションが正常に呼び出された後でも、エラーが発生した場合でも、@Finally メソッドが呼び出されます。 + +bc. public class Admin extends Controller { + + @Finally + static void log() { + Logger.info("Response contains : " + response.out); + } + + public static void index() { + List users = User.findAll(); + render(users); + } + … +} + +@Finally アノテーションで注釈されたメソッドが Throwable 型の引数をひとつだけ受け取る場合、発生した例外が引き渡されます: + +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); + } + } + + public static void index() { + List users = User.findAll(); + render(users); + } + … +} + + +h3. コントローラ階層 + +コントローラクラスが別のコントローラのクラスのサブクラスである場合、インターセプションは完全なクラス階層に対して適用されます。 + +h3. @With アノテーションによる更なるインターセプタの追加 + +Java は多重継承を認めないので、インターセプタの適用はクラス階層に制限されたとても限定的なものになりがちです。しかし、完全に異なるクラス中にいくつかのインターセプタを定義し、 @With アノテーションを使用していかなるコントローラにもこれらをリンクすることができます。 + +例えば、以下のようにします: + +bc. public class Secure extends Controller { + + @Before + static void checkAuthenticated() { + if(!session.containsKey("user")) { + unAuthorized(); + } + } +} + +そして、別のコントローラにおいて、以下のようにします: + +bc. @With(Secure.class) +public class Admin extends Controller { + + … +} + +h2. Session と Flash スコープ + +複数の HTTP リクエストにまたがってデータを保持しなければならない場合、Session または Flash スコープにそれらを保存することができます。Session に保存されたデータは、ユーザセッションにおける全ての間で利用可能であり、Flash スコープに保存されたデータは、次のリクエストにおいてのみ利用可能です。 + +Session と Flash のデータはサーバに保存されず、Cookie メカニズムを使って次の HTTP リクエストに追加されることを理解するのは重要です。このため、そのデータサイズはとても制限 (最大で 4 KB) され、また、文字列しか保存できません。 + +もちろん、クライアントが cookie のデータを変更できない (変更した場合は無効にされる) よう、cookie は秘密鍵で署名されます。Play の Session は、キャッシュとして使用されることを目的としません。特定のセッションに関連するいくつかのデータをキャッシュする必要がある場合は、Play 内蔵のキャッシュ機構と、特定のユーザセッションにそれらを継続して紐付けるための **session.getId()** を使用することができます。 + +例えば、以下のようにします: + +bc. public static void index() { + List messages = Cache.get(session.getId() + "-messages", List.class); + if(messages == null) { + // Cache miss + messages = Message.findByUser(session.get("user")); + Cache.set(session.getId() + "-messages", messages, "30mn"); + } + render(messages); +} + +**application.session.maxAge** に値を設定していない限り、セッションはブラウザを閉じると破棄されます。 + +キャッシュには、古典的な Servlet HTTP セッションオブジェクトとは異なる意味があります。これらのオブジェクトが常にキャッシュにあるとは仮定できません。そのため、キャッシュに失敗した場合について扱わなければなりませんが、アプリケーションは完全にステートレスであり続けます。 + +p(note). **考察を続けます** + +MVC モデルの次の重要な層は、Play が %(next)"テンプレートエンジン":templates% により、効率的なテンプレートシステムを提供する View 層です。 \ No newline at end of file diff --git a/documentation/manual_ja/crud.textile b/documentation/manual_ja/crud.textile new file mode 100644 index 0000000000..557fbf6c01 --- /dev/null +++ b/documentation/manual_ja/crud.textile @@ -0,0 +1,314 @@ +h1. CRUD: 管理機能ジェネレータ + +CRUD (Create, Read, Update, Delete) モジュールは、JPA モデルオブジェクト用の完全に利用可能な web インタフェースを生成します。 + +h2. CRUD モジュールの使い方 + +CRUD モジュールを使ってデータを管理するシンプルな例を見てみましょう。 + +h3. アプリケーションで CRUD モジュールを利用可能にする + +@/conf/dependencies.yml@ ファイルの @require:@ の後に次の行を追加することで、CRUD モジュールを有効にします: + +bc. require: + - play -> crud + +新しいモジュールの依存性を取り込むために、ここで @play dependencies@ コマンドを実行します。 + + +h3. デフォルトの CRUD ルートの取り込み + +@conf/routes@ ファイルに次の行を追加することで、モジュールのデフォルトのルートを取り込みます: + +bc. # Import CRUD routes +* /admin module:crud + +この設定は、このあとに追加することになるいくつかのルートを、それぞれの CRUD コントローラに追加します。 + +p(note). デフォルトのルートファイルの使用は必須ではないことに **注意** してください。独自のルートを定義することも、これら 2 つを混ぜ合わせることも可能です。 + +h3. User クラスの作成 + +CRUD モジュールはアプリケーションのモデルクラスにユーザインタフェースを提供します。 "JPA":jpa エンティティである User モデルクラスの作成から始めましょう。 + +bc. package models; + +import play.*; +import play.db.jpa.*; + +import javax.persistence.*; +import java.util.*; + +@Entity +public class User extends Model { + + public String name; + public String email; + public String address; + +} + +h3. Users コントローラの作成 + +次に、CRUD コントローラを継承するだけのシンプルなコントローラを作成します。これは CRUD モジュールがルートを生成するために使用するある種の ‘マーカー’ です。 + +bc. package controllers; + +public class Users extends CRUD { + +} + +ここで "http://localhost:9000/admin":http://localhost:9000/admin を開くと、User 管理画面が見えるはずです。 + +!images/crud1! + +コントローラクラスの名前は、モデルクラス名の最後に ‘s’ を付けたものでなければなりません。違う名前を付けたい場合は、アノテーションを使うことができます。 + +bc. package controllers; + +import models.User; + +@CRUD.For(User.class) +public class AdminUsers extends CRUD { + +} + +h3. User フォーム + +**Add** ボタンをクリックすると、User フォームが表示されるはずです。 + +!images/crud2! + +User クラスにいくつかバリデーションルールを追加することができます: + +bc. package models; + +import play.*; +import play.db.jpa.*; + +import javax.persistence.*; +import java.util.*; + +import play.data.validation.*; + +@Entity +public class User extends Model { + + @Required + @MinSize(8) + public String name; + + @Required + @Email + public String email; + + @Required + @MaxSize(1000) + public String address; + + public String toString() { + return email; + } + +} + + +User フォームを更新すれば、バリデーションが自動的に適用されていることを確認できるでしょう。 + +!images/crud3! + +h3. フォームラベルの変更 + +アプリケーションの @conf/messages@ ファイルに以下の行を追加してください: + +bc. name=Name +email=Email address +address=Postal address + +そして、User フォームを更新してください: + +!images/crud4! + +h3. User の作成とリストビューのカスタマイズ + +デフォルトのリストビューは、オブジェクトの @toString()@ メソッドの結果を含むただひとつの列を使用します。 + +!images/crud5! + +このビューをカスタマイズするには、アプリケーションに @/app/views/Users/list.html@ テンプレートを作成する必要があります。 + +シェルを開いてアプリケーションディレクトリに移動したら、以下をタイプします: + +bc. play crud:ov --template Users/list + +このコマンドは、CRUD のデフォルトの @list.html@ テンプレートを、アプリケーションの @Users/list.html@ にコピーしますが、もしこのファイルが存在すれば上書きします。 + +このテンプレートを以下のように編集してください: + +bc. #{extends 'CRUD/layout.html' /} + +
+ +

&{'crud.list.title', type.name}

+ +
+ #{crud.search /} +
+ +
+ #{crud.table fields:['email', 'name'] /} +
+ +
+ #{crud.pagination /} +
+ +

+ &{'crud.add', type.modelName} +

+
+ +そして、リストを更新してください。 + +!images/crud6! + + +h3. フィールドレンダリングのカスタマイズ: crud.custom タグ + +それぞれの @User@ エンティティの、リストやフォームビューにおける表示のされ方をカスタマイズすることで、ちょっと前進することができます。 + +フィールドをカスタマイズするには、 @#{crud.custom}@ タグを使用します: + +bc. #{crud.table fields:['name', 'company']} + + #{crud.custom 'company'} + + ${object.company.name} + + #{/crud.custom} + +#{/crud.table} + +カスタムハンドラを定義することで、追加の列や入力フォームを表示することもできます: + +bc. #{crud.table fields:['name', 'company', 'edit']} + + #{crud.custom 'company'} + ${object.company.name} + #{/crud.custom} + + #{crud.custom 'edit'} + Edit + #{/crud.custom} + +#{/crud.table} + + +h2. 文字列のリストと列挙型のリスト + +CRUD モジュールはフィールドを文字として表示します。この文字フィールドにおいて、リストはカンマで区切られた文字列として表示されます。例えば: + +bc. @Entity +public class Account extends Model { + + @CollectionOfElements + public Set contentTypes; + + @CollectionOfElements + public Set usernames; + + public Account(Set usernames) { + super(); + this.usernames = usernames; + } +} + +これは、次のように表示されます: contentTypes は "myEnumId1","myEnumId2" と表示され、usernames は "string1","string2" と表示されます。それぞれの定義において、これが CRUD モジュールで行うべき最初のカスタマイズです。 + +h2. _show_ と _blank_ ビューの一般的なカスタマイズ方法 + +CRUD ビューの振る舞いに主に影響するのは、それぞれの列の @ObjectType@ です。このため、 +例えば @Version アノテーションの付いたフィールドを隠すと言ったように、 CRUD モジュールの振る舞いを一般的な方法で変更したい場合、独自の @ObjectType@ クラスを作成することができます。コントローラまたはコントローラのスーパークラスに static メソッドを宣言しなければなりません。 + +bc. protected static ObjectType createObjectType(Class type) { + return new VersionObjectType(type); +} + +以下に完全な例を示します: + +bc. public class CustomAdminCompany extends CRUD { + protected static ObjectType createObjectType(Class type) { + return new VersionObjectType(type); + } + + public static class VersionObjectType extends ObjectType { + + private final String versionColumn; + + public VersionObjectType(Class modelClass) { + super(modelClass); + versionColumn = getVersionColumnName(modelClass); + } + private String getVersionColumnName(Class modelClass) { + Class c = modelClass; + try { + while (!c.equals(Object.class)) { + for (Field field : c.getDeclaredFields()) { + if (field.isAnnotationPresent(Version.class)) { + return field.getName(); + } + } + c = c.getSuperclass(); + } + } catch (Exception e) { + throw new UnexpectedException("Error while determining the " + + "object @Version for an object of type " + modelClass); + } + return null; + } + + @Override + public List getFields() { + List result = super.getFields(); + for (ObjectField objectField : result) { + if (objectField.name.equals(versionColumn)) { + objectField.type = "hidden"; + } + } + return result; + } + } +} + +これで終わりではありません; @findPage@ やその他のメソッドをカスタマイズすることもできます。ソースコードを覗いてみてください。 + +h2. ローカライズ + +CRUD モジュールのメッセージをアプリケーションの @conf/messages@ ファイルで上書きすることで、ユーザインタフェースをローカライズすることができます。 + +bc. crud.add=Create new &{%s} + +CRUD モジュール自身のメッセージファイル: @$PLAY_HOME/modules/crud/conf/messages@ を見て、どのメッセージキーが使用されているか確認してください。 + + +h2. コマンド + +CRUD モジュールは、コマンドラインから使って、デフォルトのテンプレートを上書きする *crud:override* コマンドを提供します。これが機能するのは、CRUD モジュールはアプリケーションのテンプレートが存在する場合に、自身のテンプレートではなく、アプリケーションのものをロードするためです。 @crud:override@ の代わりに @crud:ov@ を使用することもできます。 + +h3. play crud:override --template [path] + +例えば @Users/list@ のようにパスで指定したテンプレートを、アプリケーションの @app/views/CRUD@ ディレクトリにコピーします。 @--template@ の代わりに @-t@ を使用することもできます。 + +h3. play crud:override --layout + +メインのレイアウトテンプレートである @layout.html@ を上書きします。 + +h3. play crud:override --css + +スタイルシート @crud.css@ を @public/stylesheets/@ ディレクトリにコピーして上書きします。 + + + +h2. 制限事項 + +CRUD モジュールは、双方向の関連を持つふたつのエンティティのうち、ひとつのみ: @mappedBy@ 属性を持たないエンティティを表示します。 \ No newline at end of file diff --git a/documentation/manual_ja/dependency.textile b/documentation/manual_ja/dependency.textile new file mode 100644 index 0000000000..dc8b39cf94 --- /dev/null +++ b/documentation/manual_ja/dependency.textile @@ -0,0 +1,383 @@ +h1. 依存性管理 + +Play の依存性管理システムによって、アプリケーションの外部依存性を単一の @dependencies.yml@ ファイルに記述することができます。 + +Play アプリケーションには三種類の依存性があります: + +* Play フレームワーク自身。Play アプリケーションは常に Play フレームワークに依存します。 +* アプリケーションの @lib/@ ディレクトリにインストールされた **JAR** ファイルとして提供されるあらゆる Java ライブラリ +* アプリケーションの @modules/@ ディレクトリにインストールされた (実際はアプリケーションの断片である) Play モジュール + +これらの依存性をアプリケーションの @conf/dependencies.yml@ ファイルに記述すると、Play はすべての必要な依存性を解決し、ダウンロードしてインストールします。 + +h2. 依存性の書式 + +依存性は組織名とリビジョン番号によって記述されます。例えば、以下のように @dependencies.yml@ ファイルに依存性を記述します: + +bc. organisation -> name revision + +このため、例えば "Play PDF モジュール":http://www.playframework.org/modules/pdf のバージョン 1.0 は以下のように表現されます: + +bc. play -> pdf 1.0 + +"commons-lang":http://commons.apache.org/lang/ のように組織の名称と依存性の名称がぴったり一致する場合があります: + +bc. commons-lang -> commons-lang 2.5 + +この場合、依存性定義から組織名を省略することができます: + +bc. commons-lang 2.5 + +h3. 動的なリビジョン + +リビジョン番号は (例えば 1.2 などのように) 固定にするか、または動的にすることができます。動的なリビジョン番号は許容するリビジョンの範囲を表します。 + +例えば: + +* @[1.0,2.0]@ は 1.0 以上かつ 2.0 以下のすべてのバージョンにマッチします +* @[1.0,2.0[@ は 1.0 以上かつ 2.0 より小さいすべてのバージョンにマッチします +* @]1.0,2.0]@ は 1.0 より大きく 2.0 以下のすべてのバージョンにマッチします +* @]1.0,2.0[@ は 1.0 より大きく 2.0 より小さいすべてのバージョンにマッチします +* @[1.0,)@ は 1.0 以上のすべてのバージョンにマッチします +* @]1.0,)@ は 1.0 より大きいすべてのバージョンにマッチします +* @(,2.0]@ は 2.0 以下のすべてのバージョンにマッチします +* @(,2.0[@ は 2.0 より小さいすべてのバージョンにマッチします + +h2. dependencies.yml + +新しい Play アプリケーションを作成すると、自動的に @dependencies.yml@ 記述子が @conf/@ ディレクトリに作成されます: + +bc. # Application dependencies + +require: + - play 1.2 + +段落 @require@ には、アプリケーションが必要とするすべての依存性が一覧されています。ここでは、この新しいアプリケーションは **Play バージョン 1.2** にのみ依存しています。でも、 "Google Guava":http://code.google.com/p/guava-libraries/" が必要であることにしてみましょう; こんなふうにするでしょう: + +bc. # Application dependencies + +require: + - play 1.2 + - com.google.guava -> guava r07 + +h3. ‘play dependencies’ コマンド + +Play に新しい依存性を解決して、ダウンロードして、インストールするように伝えるには、 @play dependencies@ を実行します: + +bc. $ play dependencies +~ _ _ +~ _ __ | | __ _ _ _| | +~ | '_ \| |/ _' | || |_| +~ | __/|_|\____|\__ (_) +~ |_| |__/ +~ +~ play! 1.2, http://www.playframework.org +~ framework ID is gbo +~ +~ 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) +~ +~ Downloading required dependencies, +~ +~ downloaded http://repo1.maven.org/maven2/com/google/guava/guava/r07/guava-r07.jar +~ downloaded http://repo1.maven.org/maven2/com/google/code/findbugs/jsr305/1.3.7/jsr305-1.3.7.jar +~ +~ Installing resolved dependencies, +~ +~ lib/guava-r07.jar +~ lib/jsr305-1.3.7.jar +~ +~ Done! +~ + +これで Play はふたつの JAR (**guava-r07.jar**, **jsr305-1.3.7.jar**) を Maven 中央リポジトリからダウンロードして @lib/@ ディレクトリにインストールしました。 + +ひとつの依存性しか定義していないのに、なぜふたつの JAR なのでしょうか? それは、Google Buava が透過的な依存性を持つからです。この依存性は実際に必要としているわけではないので外部化したくなるでしょう。 + +h3. 透過的な依存性 + +あらゆる推移的な依存性はデフォルトで自動的に解決されます。しかし、必要であればそれらを無効にする方法がいくつかあります。 + +1. 特定の依存性について、推移的な依存性を無効にすることができます: + +bc. # Application dependencies + +require: + - play 1.2 + - com.google.guava -> guava r07: + transitive: false + +2. プロジェクト全体で推移的な依存性を無効にすることができます: + +bc. # Application dependencies + +transitiveDependencies: false + +require: + - play 1.2 + - com.google.guava -> guava r07 + +3. 特定の依存性を明示的に除外することができます: + +bc. # Application dependencies + +require: + - play 1.2 + - com.google.guava -> guava r07: + exclude: + - com.google.code.findbugs -> * + +h3. lib/ と modules/ ディレクトリの同期 + +ここでもう一度 @play dependencies@ を実行すると、依存性 findbugs は除外されます: + +bc. $ play deps +~ _ _ +~ _ __ | | __ _ _ _| | +~ | '_ \| |/ _' | || |_| +~ | __/|_|\____|\__ (_) +~ |_| |__/ +~ +~ play! 1.2, http://www.playframework.org +~ framework ID is gbo +~ +~ Resolving dependencies using ~/Documents/coco/conf/dependencies.yml, +~ +~ com.google.guava->guava r07 (from mavenCentral) +~ +~ Installing resolved dependencies, +~ +~ lib/guava-r07.jar +~ +~ ******************************************************************** +~ WARNING: Your lib/ and modules/ directories and not synced with +~ current dependencies (use --sync to automatically delete them) +~ +~ Unknown: ~/Documents/coco/lib/jsr305-1.3.7.jar +~ ******************************************************************** +~ +~ Done! +~ + +しかし、以前にダウンロードした **jsr305-1.3.7.jar** アーティファクトは、まだ @lib/@ ディレクトリに存在しています。 + +@lib/@ と @modules/@ ディレクトリを依存性管理システムと同期したまま保持するために、 @dependencies@ コマンドに @--sync@ コマンドを指定することができます: + +bc. play dependencies --sync + +もう一度コマンドを実行すると、不要な **JAR** は削除されます。 + +アプリケーションを本番環境にデプロイする際、モジュールのソースコードとドキュメントを削除することでモジュールの容量を縮小することができます。これはコマンドに @--forProd@ オプションを追加することで実行できます: + +bc. play dependencies --forProd + +これは、各モジュールの @documentation/@, @src/@, @tmp/@, @*sample*/@ そして @*test*/@ ディレクトリを削除します。 + + +h2. 競合の解決 + +ふたつのコンポーネントが同じ依存性の違うリビジョンを必要とする場合は、コンフリクトマネージャがひとつを選びます。デフォルトではもっとも新しいリビジョンを保持し、他のリビジョンを追い出します。 + +しかし、例外があります。Play フレームワークのコア自身が競合に巻き込まれた場合は @$PLAY/framework/lib@ で利用可能なバージョンが優先されます。例えば、Play は @commons-lang 2.5@ に依存します。もしアプリケーションが @commons-lang 3.0@ を要求する場合: + +bc. # Application dependencies + +require: + - play 1.2 + - com.google.guava -> guava r07: + transitive: false + - commons-lang 3.0 + +@play dependencies@ を実行すると、バージョンが新しいにも関わらず @commons-lang 3.0@ は追い出されるでしょう: + +bc. play dependencies +~ _ _ +~ _ __ | | __ _ _ _| | +~ | '_ \| |/ _' | || |_| +~ | __/|_|\____|\__ (_) +~ |_| |__/ +~ +~ play! 1.2, http://www.playframework.org +~ framework ID is gbo +~ +~ 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 +~ +~ Installing resolved dependencies, +~ +~ lib/guava-r07.jar +~ +~ Done! +~ + +@$PLAY/framework/lib@ にて利用可能な依存性はアプリケーションの @lib/@ ディレクトリにインストールされないことにも注意してください。 + +コアの依存性を上書くか、最新バージョンが利用可能となっている別のリビジョンを選択することで、強制的に依存性のバージョンを指定したくなる場合があるかもしれません。 + +このため、いずれの依存性にも @force@ オプションを指定することができます: + +bc. # Application dependencies + +require: + - play 1.2 + - com.google.guava -> guava r07: + transitive: false + - commons-lang 3.0: + force: true + +h2. 新しいリポジトリの追加 + +デフォルトでは、Play は "Maven 中央リポジトリ":http://repo1.maven.org/maven2/ から **JAR** 依存性を探し、 "Play モジュール中央リポジトリ":http://www.playframework.org/modules から **Play モジュール** を探します。 + +もちろん、 @repositories@ 節で新しいカスタムリポジトリを指定することができます: + +bc. # Application dependencies + +require: + - play 1.2 + - com.google.guava -> guava r07: + transitive: false + - commons-lang 3.0: + force: true + - com.zenexity -> sso 1.0 + +# My custom repositories +repositories: + + - zenexity: + type: http + artifact: "http://www.zenexity.com/repo/[module]-[revision].zip" + contains: + - com.zenexity -> * + +この設定を使うと、組織 @com.zenexity@ のすべての依存性はリモート HTTP サーバから検索され、ダウンロードされます。 + +h3. Maven リポジトリ + +以下のように @iBiblio@ タイプを使うことで、maven2 互換のリポジトリを追加することもできます: + +bc. # Application dependencies + +require: + - play + - play -> scala 0.8 + - org.jbpm -> jbpm-persistence-jpa 5.0.0: + exclude: + - javassist -> javassist * + - org.hibernate -> hibernate-annotations * + - javax.persistence -> persistence-api * +repositories: + - jboss: + type: iBiblio + root: "http://repository.jboss.org/nexus/content/groups/public-jboss/" + contains: + - org.jbpm -> * + - org.drools -> * + +h3. ローカルリポジトリ + +最後とおそらく最初に、ローカルモジュールを参照するリポジトリを定義したいかもしれません。ここでは、(今は非推奨の) @application.conf@ でのモジュール解決に非常に近い形で依存関係を解決します。 + +次のようなフォルダ構成だとします。 + +bc. myplayapp/ +myfirstmodule/ +mysecondmodule/ + +次の @myplayapp/conf/depencencies.yml@ は意図したとおりに依存関係を解決します。 + +bc. # Application dependencies + +require: + - play + - myfirstmodule -> myfirstmodule + - mysecondmodule -> mysecondmodule + +repositories: + - My modules: + type: local + artifact: ${application.path}/../[module] + contains: + - myfirstmodule + - mysecondmodule + +注意: @play dependencies myplayapp@ を実行するのを忘れないでください。 + + +h3. カスタム ivy 設定 + +Play は内部で Ivy を使っています。内部の中央 Maven リポジトリの Basic 認証といったプロキシ設定のように特別な設定が必要なら、ivysettings.xml ファイルを編集することができます。ファイルはホームディレクトリの @.ivy2@ フォルダにあります。 + +例 1, Ivy にチェックサムを無視させる: + +bc. + + + + +例 2, Basic 認証を使う: + +bc. + + + + +例 3, ローカル maven リポジトリとリポジトリマネージャを再利用する: + +bc. + + + + + + + + + + + + + + + + + + + + + + + + + + + +設定項目はたくさんあります: "Ivy の設定ドキュメント":http://ant.apache.org/ivy/history/2.1.0/settings.html を参照してください。 + + +h3. Ivy キャッシュをクリアする + +Ivy のキャッシュは、特に @conf/dependencies.yml@ のリポジトリセクションにおいて @http@ タイプを使用すると壊れる場合があります。Ivy キャッシュが壊れて依存性が解決されなくなった場合は、 @--clearcache@ オプションを使ってキャッシュをクリアすることができます。 + +bc. $ play dependencies --clearcache + +これは @rm -r ~/.ivy2/cache@ と等価です。 + + +p(note). **考察を続けます** + +次: %(next)"データベースエボリューション":evolutions% \ No newline at end of file diff --git a/documentation/manual_ja/deployment.textile b/documentation/manual_ja/deployment.textile new file mode 100644 index 0000000000..0deb737f05 --- /dev/null +++ b/documentation/manual_ja/deployment.textile @@ -0,0 +1,195 @@ +h1. デプロイオプション + +事実上、Play アプリケーションはどこにでもデプロイすることができます: サーブレットコンテナの中や、スタンドアロンサーバとして、または Heroku, Google App Engine、Stack、クラウドにもデプロイすることができます。 + +h2. スタンドアロン Play アプリケーション + +もっとも簡単で堅牢な方法は、Play アプリケーションをどのコンテナにも乗せずに実行することです。バーチャルホストのような高度な HTTP の機能が必要な場合は、フロントに Lighttpd や Apache のような HTTP サーバを使用することができます。 + +内蔵の HTTP サーバは 1 秒あたり数千の HTTP リクエストを捌くことができるので、これが性能のボトルネックになることは決してありません。さらに、この HTTP サーバはより効率的なスレッドモデルを使用します (Servlet コンテナは 1 リクエストあたり 1 つのスレッドを使用します) 。"モジュール":http://playframework.org/modules を使うことで、別のサーバ (Grizzly、Netty、その他...) を使用することもできます。 + +これらのサーバは、長時間のポーリングをサポートしており、実行スレッドをブロックせずに非常に長い (長時間タスクの完了を待つ) リクエストや、ファイルオブジェクト (それから、Content-Length を指定すれば、どのような入力ストリームでも) の直接ストリーミングを管理することができます。 + +開発工程で使用したものと同じ環境を使用するので、この方法でアプリケーションを実行する場合、問題はほとんど起こりません。JEE アプリケーションサーバにデプロイするときにだけ、大量のバグ (異なるホームディレクトリ、クラスローダの問題、ライブラリの競合、その他...) が検出されます。 + +より詳しい情報については、 "アプリケーションの本番稼動":production を参照してください。 + +h2. アプリケーションサーバ + +Play アプリケーションは、お好みのアプリケーションサーバで実行することもできます。ほとんどのアプリケーションサーバーは特別な設定なしでサポートされています。 + +h3. サポートするアプリケーションサーバ + +以下ののアプリケーションサーバで Play が動作することが分かっています。この他に動作する配備環境があれば、遠慮なく報告してください。 + +* 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. デプロイ + +アプリケーションを war ファイルとしてパッケージする必要があります。これは、以下のコマンドで容易に実行できます: + +bc. play war myapp -o myapp.war + +p(note). アプリケーションサーバが展開された WAR ファイルの配備をサポートしていることを確認してください。 + +これでアプリケーションをデプロイする準備が整いました。 + +アプリケーションのライブラリ間におけるバージョン不整合を避けるため、Play アプリケーションを他のアプリケーションから ‘隔離’ するようアドバイスされています。この手順はベンダに特化したものであり、標準的な JEE/サーブレット仕様によるものではありません。 + +WAR ファイルを ‘隔離’ するために、アプリケーションサーバのマニュアルを参照することをお勧めします。以下の例は、JBoss アプリケーションサーバにおいて war ファイルを隔離する方法です。これがオプションの手順であることに注意してください: + +アプリケーションの war ディレクトリにある @myapp.war/WEB-INF/jboss-web.xml@ に以下の内容を挿入 (または新規に作成) してください: + +bc. + + + + com.example:archive=unique-archive-name + java2ParentDelegation=false + + + + +com.example:archive=unique-archive-name を一意であり続ける名前に書き換えてください。 + + +h3. データソース + +Play はデータソースとリソースのルックアップもサポートします。JNDI データソースを使うには、application.conf において "データベース設定":configuration#dbconf を以下のように編集してください。 + +bc. db=java:comp/env/jdbc/mydb +jpa.dialect=org.hibernate.dialect.Oracle10gDialect +jpa.ddl=verify + +データソースプラグインは @db=java:@ というパターンを検出し、デフォルトの JDBC システムを非活性化します。 + + +h3. カスタム web.xml + +IBM Websphere などのアプリケーションサーバは、サーブレット API 設定ファイル @web.xml@ 中の @resource-ref@ 要素にデータソースを定義することを要求します。デフォルトでは、 @play war@ コマンドを実行すると Play は @web.xml@ ファイルを生成します。生成された @web.xml@ をカスタマイズするには、展開した WAR バージョンを生成し、 @web.xml@ ファイルをアプリケーションの @war/WEB-INF@ フォルダにコピーします。この次に @play war@ コマンドを実行すると、カスタマイズした @web.xml@ が生成されたフォルダへコピーされます。 + +例えば、 @war/WEB=INF/web.xml@ ファイルの @resource-ref@ に IBM Websphere 7 用のデータソースを定義することができます: + +bc. + + Play! (%APPLICATION_NAME%) + + play.id + %PLAY_ID% + + + play.server.ServletWrapper + + + play + play.server.ServletWrapper + + + play + / + + + Play Datasource for testDatasource + jdbc/mydb + javax.sql.DataSource + Container + + + + +h2. クラウドベースのホスティング + + +h3. AWS Elastic Beanstalk + +"AWS Elastic Beanstalk":http://aws.amazon.com/en/elasticbeanstalk/ は、Amazon Web Services インフラストラクチャに基づいた Amazon の Java ホスティングプラットフォームです。Play アプリケーションのデプロイに関する詳しい情報は "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/ は Java アプリケーションのホスティングプラットフォームです。詳しい情報は "CloudBees module":http://www.playframework.org/modules/cloudbees を参照してください。 + + +h3. Cloud Foundry + +"Cloud Foundry":http://www.cloudfoundry.com/ は VMWare のクラウドプロバイダです。詳しい情報は "Running Play Framework Application on CloudFoundry":http://iambivas.blogspot.com/2011/08/running-play-framework-application-on.html と "CloudFoundry module":http://www.playframework.org/modules/cloudfoundry を参照してください。 + + +h3. Google App Engine (GAE) + +"Google App Engine":http://code.google.com/appengine/ は、一般的なホスティングソリューションと比較して異なった長所と短所を持つ最初のクラウドホスティングプラットフォームの一つです。特に、GAE は JPA 永続性をサポートしません。詳しい情報は "GAE module":http://www.playframework.org/modules/gae を参照してください。 + + +h3. Heroku + +"Heroku cloud application platform":http://www.heroku.com/ は Play に特化したアプリケーションのホスティングサポートを提供しています。Play アプリケーションをデプロイして実行するには、以下の手順を使います。 + +1. Heroku コマンドラインクライアントを "Linux":http://toolbelt.herokuapp.com/linux/readme, "Mac":http://toolbelt.herokuapp.com/osx/download, または "Windows":http://toolbelt.herokuapp.com/windows/download にインストールします。 +2. "git":http://git-scm.com/ をインストールして SSH 鍵を設定します。 +3. "Heroku.com":http://heroku.com/signup アカウントを作成します。 +4. コマンドラインから Heroku にログインします: + +bc. heroku auth:login + +5. git リポジトリを作成します: + +bc. git init + +6. Play によって生成されたファイルを無視するために、以下の内容を含む @.gitignore@ ファイルを作成します: + +bc. /tmp +/modules +/lib +/test-result +/logs + +7. このファイルを git リポジトリに追加してコミットします: + +bc. git add . +git commit -m init + +8. Heroku 上に新しいアプリケーションを作成します: + +bc. heroku create -s cedar + +9. Heroku にアプリケーションを Push します: + +bc. git push heroku master + +10. ブラウザでアプリケーションを開きます: + +bc. heroku open + + +ログを閲覧するためには、以下を実行します: + +bc. heroku logs + +複数の ‘dynos’ にアプリケーションをスケールするためには、以下を実行します: + +bc. heroku scale web=2 + +本番環境で "Heroku Shared Database":http://devcenter.heroku.com/articles/database を使うためには、以下を @conf/application.conf@ ファイルに追加します: + +bc. %prod.db=${DATABASE_URL} +%prod.jpa.dialect=org.hibernate.dialect.PostgreSQLDialect +%prod.jpa.ddl=update + +詳しい情報は "Heroku Dev Center":http://devcenter.heroku.com を参照してください。 + + +h3. playapps.net + +"playapps.net":http://www.playapps.net/ は、Play を作成した Zenexity による PLay に特化したホスティングプラットフォームです。詳しい情報は "playapps.net module":http://www.playframework.org/modules/playapps を参照してください。 \ No newline at end of file diff --git a/documentation/manual_ja/emails.textile b/documentation/manual_ja/emails.textile new file mode 100644 index 0000000000..526d0ab951 --- /dev/null +++ b/documentation/manual_ja/emails.textile @@ -0,0 +1,154 @@ +h1. e メール送信 + +E メール機能は、背後で "Apache Commons Email":http://commons.apache.org/email/userguide.html ライブラリを使用します。 @play.libs.Mail@ ユーティリティクラスを使って、とても簡単に e メールを送信することができます。 + +シンプルな e メール: + +bc. SimpleEmail email = new SimpleEmail(); +email.setFrom("sender@zenexity.fr"); +email.addTo("recipient@zenexity.fr"); +email.setSubject("subject"); +email.setMsg("Message"); +Mail.send(email); + +HTML e メール: + +bc. HtmlEmail email = new HtmlEmail(); +email.addTo("info@lunatech.com"); +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/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, too bad :("); + +詳細は "Commons Email ドキュメント":http://commons.apache.org/email/userguide.html を参照してください。 + +h2. メールと MVC の統合 + +標準的なテンプレート機構と構文を使うことで、複雑で動的なメールを送信することもできます。 + +はじめに、アプリケーション内に **メーラ通知者** を定義してください。メーラ通知者は @play.mvc.Mailer@ のサブクラスであり、 @notifiers@ パッケージに含まれていなければなりません。 + +MVC コントローラにおけるアクションのやり方と同じように、public かつ static なメソッドは、いずれもメールを送信します。以下に例を示します: + +bc. package notifiers; + +import org.apache.commons.mail.*; +import play.*; +import play.mvc.*; +import java.util.*; + +public class Mails extends Mailer { + + public static void welcome(User user) { + setSubject("Welcome %s", user.name); + addRecipient(user.email); + setFrom("Me "); + EmailAttachment attachment = new EmailAttachment(); + attachment.setDescription("A pdf document"); + attachment.setPath(Play.getFile("rules.pdf").getPath()); + addAttachment(attachment); + send(user); + } + + public static void lostPassword(User user) { + String newpassword = user.password; + setFrom("Robot "); + setSubject("Your password has been reset"); + addRecipient(user.email); + send(user, newpassword); + } + +} + +h3. text/html e メール + +@send@ の呼び出しは、メールのメッセージ本文として @app/views/Mails/welcome.html@ テンプレートをレンダリングします。 + +bc.

Welcome ${user.name},

+... + + +lostPassword メソッド用のテンプレートは以下のようになるかもしれません: + +@app/views/Mails/lostPassword.html@ + +bc. +... + +

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

+ + + +h3. text/plain e メール + +HTML テンプレートが定義されていない場合、テキストテンプレートを使った text/plain メールが送信されます。 + +@send@ の呼び出しは、メールのメッセージ本文として @app/views/Mails/welcome.txt@ テンプレートをレンダリングします。 + +bc. Welcome ${user.name}, +... + +lostPassword メソッド用のテンプレートは以下のようになるかもしれません: + +@app/views/Mails/lostPassword.txt@ + +bc. Hello ${user.name}, + +Your new password is ${newpassword}. + +h3. text/plain 代替品を使った text/html e メール + +HTML テンプレートが定義されていて、テキストテンプレートも存在する場合、テキストテンプレートは代替メッセージとして使用されます。先ほどの例において、 @app/views/Mails/lostPassword.html@ と @app/views/Mails/lostPassword.txt@ の両方が定義されている場合、e メールは lostPassword.txt に定義された代替部分を持つ、lostPassword.html で定義された text/html として送信されます。このため、友達にすてきな HTML メールを送ることもできますし、いまだに mutt を使っているギークな友達を喜ばせることもできます ;) + +h3. e メールからアプリケーションにリンクする + +以下のようにして e メールの中にアプリケーションへのリンクを含めることができます: + +bc. @@{application.index} + +ジョブからメールを送る場合、 アプリケーションに有効な外部基礎URLを "application.baseUrl":configuration#application.baseUrl に設定しなければなりません。 + +例えば、playframework.org の webサイト内のジョブから e メールを送信する場合、設定は以下のようになるでしょう: + +bc. application.baseUrl=http://www.playframework.org/ + +h2. SMTP の設定 + +E メール機能は それぞれ "メール設定":configuration#mail properties で設定されます。: + +* SMTP サーバー - "mail.smtp.host":configuration#mail.smtp.host +* SMTP サーバー認証 - "mail.smtp.user":configuration#mail.smtp.user , "mail.smtp.pass":configuration#mail.smtp.pass +* 暗号化チャンネル - "mail.smtp.channel":configuration#mail.smtp.channel +* JavaMail SMTP トランザクションログ - "mail.debug":configuration#mail.debug. + +次の2つの追加設定は、デフォルトの振る舞いを上書きします。 + +* "mail.smtp.socketFactory.class":configuration#mail.smtp.socketFactory.class +* "mail.smtp.port":configuration#mail.smtp.port + + +デフォルト(DEVモード)では、 E メールはコンソールに表示され、 PRODモードでは実際の SMTP サーバーで送信されます。以下の行をコメント化することで、DEVモードでのデフォルトの動作は変更可能です。 + +bc. # デフォルトでmockメーラーを使う +mail.smtp=mock + + +h3. Gmailの使用 + +Gmailサーバーを使用するには、以下の設定を使用してください(例: "playapps":http://www.playframework.org/modules/playapps へのデプロイ): + +bc. mail.smtp.host=smtp.gmail.com +mail.smtp.user=yourGmailLogin +mail.smtp.pass=yourGmailPassword +mail.smtp.channel=ssl + +p(note). **考察を続けます** + +それでは %(next)"アプリケーションのテスト":test% に移りましょう。 \ No newline at end of file diff --git a/documentation/manual_ja/evolutions.textile b/documentation/manual_ja/evolutions.textile new file mode 100644 index 0000000000..da7bdb88df --- /dev/null +++ b/documentation/manual_ja/evolutions.textile @@ -0,0 +1,301 @@ +h1. データベースエボリューションの管理 + +リレーショナルデータベースを使用する場合、データベーススキーマの進展を追跡し、管理する方法が必要になります。データベーススキーマの変更を追跡する、より洗練された方法が必要になるいくつかのシチュエーションがあります。 + +* 開発チームの中で作業をしていて、すべての人があらゆるスキーマの変更を知る必要があるとき。 +* 本番サーバにデプロイをしていて、データベーススキーマを更新する強固な方法が必要なとき。 +* 複数のマシンで作業をしていて、すべてのデータベーススキーマを同期する必要があるとき。 + +p(note). もし JPA を使って作業しているのであれば、Hibernate が自動的にデータベースの変更を取り扱います。JPA を使わない場合、またはより良いチューニングのために手動でデータベーススキーマの面倒を見ることを好む場合、エボリューションが便利です。 + +h2. エボリューションスクリプト + +Play はいくつかの **エボリューションスクリプト** を使ってデータベースの進展を追跡します。これらのスクリプトは古くて平易な SQL で書かれており、アプリケーションの @db/evolutions@ ディレクトリに配置されることになっています。 + +最初のスクリプトは @1.sql@, 二番目のスクリプトは @2.sql@, そしてその次は… と命名されます。 + +いずれのスクリプトもふたつの部分を含みます: + +* 変更の要件を定義する *Ups* パート。 +* 変更を取り消す方法を定義する *Downs* パート。 + +例えば、基本的なアプリケーションを立ち上げる最初のエボリューションスクリプトを見てみましょう: + +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; + +見てのとおり、SQL スクリプトはコメントを使って *UPs* 節と *Downs* 節に分けなければいけません。 + +@application.conf@ にデータベースが設定されていて、エボリューションスクリプトが存在すれば、エボリューションは自動的に活性化します。 application.conf で "evolutions.enabled":configuration#evolutions.enabled を @false@ に設定することでエボリューションを非活性化することができます。 +例えば、テストが自分自身のデータベースをセットアップする場合、テスト環境においてエボリューションを非活性化することができます。 + +**エボリューション** が活性化すると、DEV モードの場合はあらゆるリクエストの前に、あるいは PROD モードの場合はアプリケーションを起動する前に Play はデータベーススキーマの状態を確認します。DEV モードにおいてデータベーススキーマが更新されていない場合、適切な SQL スクリプトを実行してデータベーススキーマを同期することをエラーページが提示します。 + +!images/evolutions! + +この SQL に同意する場合、'Apply evolutions' ボタンをクリックして直接これを適用することができます。 + +p(note). インメモリデータベース (*db=mem*) を使っていてデータベースが空の場合、Play はすべてのエボリューションスクリプトを自動的に実行します。 + +h2. 同時変更の同期 + +さて、ここで二人の開発者が作業するプロジェクトを想像してみましょう。開発者 A は新しいデータベーステーブルが必要な機能に取り掛かっています。そのため、以下のような @2.sql@ エボリューションスクリプトを作成するでしょう: + +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 は開発者 A のデータベースにこのエボリューションスクリプトを適用します。 + +一方で、開発者 B は @User@ テーブルの変更が必要な機能に取り掛かっています。そのため、やはり以下のような @2.sql@ エボリューションスクリプトを作成するでしょう: + +bc. # Update User + +# --- !Ups +ALTER TABLE User ADD age INT; + +# --- !Downs +ALTER TABLE User DROP age; + +開発者 B は機能を終えてコミットします (Git を使っていることにしましょう) 。 ここで開発者 A は作業を続ける前に同僚の成果をマージしなければならないので @git pull@ を実行しますが、このマージでは以下のようなコンフリクトが発生します: + +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. + +いずれの開発者も @2.sql@ エボリューションスクリプトを作成しました。そのため開発者 A はこのファイルの内容をマージする必要があります: + +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 + +このマージは実に簡単に行えます: + +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; + +このエボリューションスクリプトは、開発者 A が既に適用した以前のリビジョン 2 とは違う、このデータベースの新しい **リビジョン 2** を表現しています。 + +このため Play はこれを検知し、まず古いリビジョン 2 を取り消し、新しいリビジョン 2 スクリプトを適用してデータベースを同期するよう開発者 A に依頼します: + +!images/evolutions-conflict! + +h2. 矛盾した状態 + +エボリューションスクリプトを間違えて、エボリューションが失敗することもあるでしょう。この場合、Play はデータベーススキーマを矛盾した状態にあると印付け、作業を続ける前にこの問題を手動で解決するよう依頼します。 + +例えば、このエボリューションの **Ups** スクリプトにはエラーがあります: + +bc. # Add another column to User + +# --- !Ups +ALTER TABLE Userxxx ADD company varchar(255); + +# --- !Downs +ALTER TABLE User DROP company; + +このため、このエボリューションを適用すると失敗し、Play はデータベーススキーマを矛盾していると印付けます: + +!images/evolutions-inconsistent! + +さて、作業を続ける前にこの矛盾を解決しなければなりません。そこで、修正した SQL コマンドを実行します: + +bc. ALTER TABLE User ADD company varchar(255); + +その後、ボタンをクリックすることで、この問題は手動で解決したと印付けます。 + +しかし、エボリューションスクリプトにはエラーがあるので、これを直したいはずです。そこで @3.sql@ スクリプトを修正します: + +bc. # Add another column to User + +# --- !Ups +ALTER TABLE User ADD company varchar(255); + +# --- !Downs +ALTER TABLE User DROP company; + +Play は以前の *3* を置き換えるこの新しいエボリューションを検知し、以下のスクリプトを実行します: + +!images/evolutions-resolve! + +これですべてが解決し、作業を続けることができます。 + +p(note). とは言え、開発モードにおいては、単に開発データベースを捨てて最初からすべてのエボリューションを再度適用するほうが簡単な場合がしばしばあります。 + +h2. エボリューションコマンド + +DEV モードでは対話的にエボリューションを実行します。しかし PROD モードではアプリケーションを実行する前に @evolutions@ コマンドを使ってデータベーススキーマを確定しなければいけません。 + +本番モードのアプリケーションを更新されていないデータベースで実行しようとした場合、アプリケーションは起動しません。 + +bc. ~ _ _ +~ _ __ | | __ _ _ _| | +~ | '_ \| |/ _' | || |_| +~ | __/|_|\____|\__ (_) +~ |_| |__/ +~ +~ play! master-localbuild, http://www.playframework.org +~ 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) + +エラーメッセージは @play evolutions@ コマンドを実行するよう依頼しています: + +bc. $ play evolutions +~ _ _ +~ _ __ | | __ _ _ _| | +~ | '_ \| |/ _' | || |_| +~ | __/|_|\____|\__ (_) +~ |_| |__/ +~ +~ play! master-localbuild, http://www.playframework.org +~ 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` +~ + +このエボリューションを Play が自動的に実行するようにしたい場合は、以下を実行します: + +bc. $ play evolutions:apply + +本番データベースにおいて、このスクリプトを手動で実行したい場合は、次のコマンドを実行してデータベースが更新されたことを Play に伝える必要があります: + +bc. $ play evolutions:markApplied + +エボリューションスクリプトの自動実行中になんらかのエラーが発生した場合、DEV モードにおいては、それらを手動で解決し、次のコマンドを実行することでデータベーススキーマが修正されたと印付ける必要があります: + +bc. $ play evolutions:resolve + +p(note). **考察を続けます** + +%(next)"ロギング":logs% の設定方法を学びましょう。 diff --git a/documentation/manual_ja/faq.textile b/documentation/manual_ja/faq.textile new file mode 100644 index 0000000000..798e5bf94b --- /dev/null +++ b/documentation/manual_ja/faq.textile @@ -0,0 +1,53 @@ +h1. よくある質問 + +h2. ここで答えられていない質問はどこにすればいいですか? + +"コミュニティ":http://www.playframework.org/community ページに Play について読み書きできる様々なサイトへのリンクがあります。一般的に、質問をするのに最適な場所は "play-framework Google グループ":http://groups.google.com/group/play-framework です。 + +h2. X framework と比べて Play はどうですか? + +今日では、web アプリケーションを開発するにあたって非常に多くの選択肢があります。Play は web アプリケーションフレームワークなので、他の Java フレームワークと比較してみましょう。Play は 'share nothing' アーキテクチャに向けて作られた (ステートレスなフレームワークと考えてください) ので、Seam, Tapestry や Wicket のようなステートフルでコンポーネントベースの Java フレームワークとは大きく異なります。Spring MVC や Struts (または Struts2) に近いですが、もっと偏屈です。 + +しかしながら、Play はユニークな Java フレームワークです。Play はいわゆる Java エンタープライズ仕様をまったく当てにしません。Play は Java を使いますが、Ruby On Rails, Django, Pylons, Symfony, Grails, Cake PHP などのスクリプト言語に基づいたフレームワークの良い部分を、すべて Java の世界に取り込もうとしています。私たちは、鈍重な開発サイクル、行き過ぎた抽象化、多過ぎる環境設定と言った伝統的な Java web 開発の痛みを伴わない、最高の Java プラットフォームを本気で手に入れようとしてきました。 + +h2. 'play' パッケージを 'org.playframework' に変更しないのですか?! + +あなたはポイントを押さえていません。Playは、Servlet コンテナに別途追加するようなライブラリではありません。スタンドアロンでアプリケーションを実行するフルスタックな Java フレームワークです。Java パッケージ命名規約は、いくつかのベンダーの異なるライブラリを使うときに、名前が衝突すること避けるために存在します。しかし、我々を信じてください。 @play.jar@ ライブラリを自分のプロジェクトに入れたりすることは決してないでしょう。これは Play を動作させる方法ではありません。Playは **プラットフォーム** です。心配しないでください。 + +h2. なぜ Python が必要なのですか (Maven がいいのですが)? + +新しいアプリケーションを作成したり、実行したり、ブラウザを起動したり、などなど... Play アプリケーションを管理するために沢山のスクリプトが必要です。もちろん、これらを Java で直接書くこともできました。しかし、プロジェクト作成のような簡単な作業のために JavaVM を起動するのはとても遅いです。さらに、OS とのやり取りとなると、Java 自体は非常に制限されています。 + +Python では、これらのスクリプトを完全に可搬性のある方法で書くことができました。Python は高速に動作し、書き易く、ほとんどのシステムにおいて制限を受けずに利用可能です。Python は Windows にはインストールされていないので、私たちは Windows 用の Python バイナリをバンドルしています。 + +h2. play は Groovy フレームワークですか? + +いいえ。たしかに Play のテンプレートシステムの基本的な技術として Groovy を使用していますが、完全に透過的です。アプリケーションのいかなる箇所 (コントローラ、モデル、または他のユーティリティ) も Groovy で書くことはできません。Groovy ベースのフレームワークを探しているなら、Grails を覗いてみてください。 + +h2. あなたたちは Java プログラミングの作法すら分かっていない... + +私たちが Java の世界ではかなり珍しい選択をしていること、Play が Java のいわゆる '良い慣習' に則っていないことは百も承知です。しかし、 Play の全チームメンバーは非常に経験豊富な Java 開発者であり、行った選択と破った規則について完全に認識しています。 + +Java そのものはとても汎用的なプログラミング言語であり、初めから web アプリケーション開発に向けて設計されたものではありません。汎用的で再利用できる Java ライブラリを書くことと、web アプリケーションを作ることは、かなり異なる作業です。web アプリケーションそのものは再利用可能なように設計される必要はありません。行き過ぎない抽象化や少ない設定が必要です。web アプリケーションにおける再利用性は、言語レベルでの組み込みよりも、むしろ web サービス API を通して存在します。 + +開発時間が限りなくゼロに近いとき、未来の開発に向けた抽象化に挑戦するのではなく、速やかにアプリケーションの機能開発や試験に向けて集中するべきです。 + +h2. Play は速いですか? + +はい。Play そのものは高速です。しかし、どれだけ特殊なアプリケーションでも速いという意味ではありません。組み込みの HTTPサーバ、基本的なルーティング、そしてコントローラ起動スタックは、とてもとても高速に動作します。昨今の JVM と JIT コンパイルを使用することで、毎秒数千リクエストを容易に捌くことができます。残念ながら、あなたがたのアプリケーションは、常にボトルネックとなるデータベースを使用することが多いでしょう。 + +現在、Play スタックで最も CPU を消費しているのは、Groovy ベースのテンプレートエンジンです。しかし、もしあなたが大量のトラフィックを捌かなければならないとしても、Play アプリケーションが容易にスケールする限り、大した問題にはなりません: 複数台のサーバで負荷を分散することが可能です。私たちは JDK7 と、その動的言語のこれまで以上のサポートによるパフォーマンスの向上を期待しています。 + +h2. Play は製品としてのアプリケーションに使用できる段階にあるでしょうか? + +はい、既に "多くの方々":http://www.playframework.org/community/testimonials が Play を製品アプリケーションに使用しています。1.0 ブランチは現在、メンテナンスモードであり、バグ修正のみが行われ、API の互換性は保ち続けられます。 + +h2. X ライブラリをサポートしていますか? + +Play は標準的な Java アプリケーションであり、一般的な Java ライブラリであればどのようなものでも容易に使用できます。ただし、Play がサーブレット API を使用しないことは忘れないでください (WAR エクスポート機能を使って標準的なサーブレットコンテナ上で動かせば、それすらも可能ですが) 。使用したいライブラリがサーブレット API に依存していなければ、とくに問題ないでしょう。 + +h2. どうすれば支援/貢献できますか? + +私たちは主な開発ツールとして "GitHub":http://github.com/playframework/play を使用しており、GitHub 自体がとてもオープンです。GitHub のアカウントを登録するだけでコードベースをフォークすることができます。"貢献者ガイド":http://play.lighthouseapp.com/projects/57987/contributor-guide を確認してください。 + +ドキュメント自体も "Textile":http://en.wikipedia.org/wiki/Textile_(markup_language%29 ファイルとしてフレームワークのソースコードリポジトリ中に管理されており、ソースコードと同じように、編集して寄稿することができます。 diff --git a/documentation/manual_ja/firstapp.textile b/documentation/manual_ja/firstapp.textile new file mode 100644 index 0000000000..252a6ea126 --- /dev/null +++ b/documentation/manual_ja/firstapp.textile @@ -0,0 +1,328 @@ +h1. はじめてのアプリケーション -- 'Hello World' チュートリアル + +よくある 'Hello World' の例を使って Play フレームワークの良さを理解する、とてもシンプルなチュートリアルです。 + +h2. 前提条件 + +何よりも、まず Java がインストールされていることを確認してください。Play は **Java 5 以降** を必要とします。 + +私たちはコマンドラインを多用するので、Unix ライクな OS のほうが良いでしょう。もし Windows 上で動かす場合でも、問題なく動作します。ただコマンドプロンプトでいくつかのコマンドを実行しなければならないだけです。 + +もちろんテキストエディタも必要です。もし Eclipse や Netbeans のようなフル機能の Java IDE に慣れているなら、もちろんそれを使用できます。しかし、Play であれば、Textmate, Emacs または VI のようにシンプルなテキストエディタでも楽しく作業できます。これは、フレームワーク自身がコンパイルとデプロイを管理するからです。間もなくその様子をご覧に入れます... + + +h2. Play フレームワークのインストール + +インストールは非常に簡単です。 ダウンロードページから最新のバイナリパッケージをダウンロードして、任意のパスに展開してください。 + +p(note). Windows を使用している場合、パスにスペースを含めないのは一般的に良い案です。例えば、 @c:\Play@ は @c:\Documents And Settings\user\play\@ よりも良い選択です。 + +効率的に作業するためには、作業パスに Play ディレクトリを加える必要があります。パスを追加することで、コマンドプロンプトでただ @play@ とタイプすれば、Play ユーティリティを使用できるようになります。正常にインストールが完了したことを確認するには、新しいコマンドラインを開いて @play@ とタイプしてください; このコマンドは Play の基本的なヘルプを表示します。 + +h2. プロジェクトの作成 + +Play が正しくインストールされたので、いよいよ hello world アプリケーションを作成します。Play アプリケーションの作成は、Play コマンドラインユーティリティによって完全に管理されており、とても簡単です。Play コマンドラインユーティリティは、すべての Play アプリケーション間において標準的なプロジェクトレイアウトを割り当てます。 + +新しいコマンドラインを開いて、以下をタイプしてください: + +bc. ~$ play new helloworld + +アプリケーションの完全な名前を入力するプロンプトが表示されます。 @Hello world@ とタイプしてください。 + +!images/firstapp-1! + +@play new@ コマンドは @helloworld/@ ディレクトリを新規に作成し、一連のファイルやディレクトリをここに追加します。以下は、もっとも重要なディレクトリです: + +@app/@ ディレクトリはアプリケーションの中心部であり、モデル、コントローラ、およびビュー用のディレクトリに分けられています。 他の Java パッケージを含むこともできます。 app/ ディレクトリは @.java@ ソースファイルを保存するディレクトリです。 + +@conf/@ ディレクトリは、特に重要な @application.conf@ ファイル、ルーティングを定義する @routes@ ファイル、国際化のための @messages@ ファイルなど、アプリケーションを設定する全てのファイルを保存します。 + +@lib/@ は標準的な .jar ファイルとしてパッケージされたオプションの Java ライブラリを保存します。 + +@public/@ は JavaScript、スタイルシート、および画像ディレクトリを含む公的に利用可能なリソースを保存します。 + +@test/@ には、すべてのアプリケーションテストを保存します。テストは Java の JUnit として書かれるか、または Selenium テストとして書かれます。 + +p(note). **Play は UTF-8** を唯一のエンコーディングとして使用するので、これらのディレクトリでホスティングされたすべてのテキストファイルが UTF-8 でエンコードされていることは非常に重要です。テキストエディタの設定がこれに従っていることを確認してください。 + +もし、あなたが熟練した Java 開発者であれば、すべての .class ファイルがどこに行ってしまったのか不思議に思うことでしょう。実は、どこにもありません: Play はいかなる class ファイルも使用せず、java ソースファイルを直接読み込みます。舞台裏では、実行中の Java ソースをコンパイルするために Eclipse コンパイラを使用しています。 + +これは、開発工程において 2 つの非常に重要なことを可能にします。最初の 1 つは、Play はあなたが Java ソースファイルに加えたいかなる変更をも検出して、実行時にそれを自動的にリロードするということです。 2 番目は、Java の例外が発生したとき、Play がソースコードそのものを示しながらより良いエラーレポートを作成するということです。 + +h2. アプリケーションの実行 + +ここまで来れば、新しく作成したアプリケーションを試してみることができます。コマンドラインに戻り、新たに作成された @helloworld/@ ディレクトリに移動したら、 @play run@ とタイプしてください。Play がアプリケーションをロードし、9000 番ポートで Web サーバを起動します。 + +ブラウザから "http://localhost:9000":http://localhost:9000 を開くことで、新しいアプリケーションを閲覧することができます。新しいアプリケーションは、それが首尾よく作成されたこと示す標準のウェルカムページを表示します。 + +!images/guide1-2! + +新しいアプリケーションがどのようにしてこのページを表示するのかを見てみましょう。 + +アプリケーションのエントリーポイントは @conf/routes@ ファイルです。このファイルはアプリケーション上のアクセス可能な URL をすべて定義します。 生成された routes ファイルを開くと、最初の 'route' が確認できるでしょう: + +bc. GET / Application.index + +この設定は、web サーバが @/@ パスに対する @GET@ リクエストを受け取った場合には @Application.index@ という Java メソッドをコールしなければならないことを Play に伝えています。この場合、controllers パッケージが暗黙的に指定されるので、 @Application.index@ は @controllers.Application.index@ のショートカットです。 + +スタンドアロンな Java アプリケーションを作成するとき、一般的に以下のようなメソッドを定義することで単一のエントリーポイントを設けます: + +bc. public static void main(String[] args) { + ... +} + +Play アプリケーションには、各 URL あたり 1 つのエントリーポイントがあります。 私たちは、これらのメソッドを **アクション** メソッドと呼びます。アクションメソッドは **コントローラ** と呼ぶ特別なクラスで定義されます。 + +@controllers.Application@ コントローラがどのようなものか見てみましょう。 @helloworld/app/controllers/Application.java@ ソースファイルを開いてください: + +bc. package controllers; + +import play.mvc.*; + +public class Application extends Controller { + + public static void index() { + render(); + } + +} + +コントローラクラスは @play.mvc.Controller@ クラスを継承します。このクラスは index アクションで使用した @render()@ メソッドのような、コントローラ向けの便利なメソッドを提供します。 + +index アクションは @public static void@ なメソッドとして定義されています。アクションメソッドはこのようにして定義されます。コントローラクラスは決してインスタンス化されないので、アクションメソッドは static です。アクションメソッドは、フレームワークがリクエストされた URL にレスポンスできるよう public として宣言されます。アクションメソッドは常に void を返します。 + +デフォルトの index アクションはシンプルです: Play にテンプレートをレンダリングするよう指示する @render()@ メソッドをコールします。テンプレートの使用は HTTP レスポンスを生成する最も一般的な (しかし、唯一ではない) 方法です。 + +テンプレートは @/app/views@ ディレクトリに存在するシンプルなテキストファイルです。今回はテンプレートを指定しなかったので、このアクション用のデフォルトのテンプレートが使用されます: @Application/index.html@ です。 + +テンプレートがどのようなものかを見るには、 @helloworld/app/views/Application/index.html@ ファイルを開いてください: + +bc. #{extends 'main.html' /} +#{set title:'Home' /} + +#{welcome /} + +テンプレートの内容はとても簡単に見えます。実際、ここにあるものはすべて Play のタグです。Play タグは JSP taglib にとても似ています。 @#{welcome /}@ が、ブラウザに表示されたウェルカムメッセージを生成しています。 + +@#{extends /}@ タグは、このテンプレートが @main.html@ と呼ばれる別のテンプレートを継承することを Play に伝えます。テンプレートの継承は、共通化された部分を再利用することで複雑なウェブページを作成することができる強力な概念です。 + +@helloworld/app/views/main.html@ テンプレートを開いていてください: + +bc. + + + #{get 'title' /} + + + + + + #{doLayout /} + + + +@#{doLayout /}@ タグに気付きましたか?ここに @Application/index.html@ の内容が挿入されます。 + +h2. フォームの作成 + +名前を入力できるフォームを作成するところから 'Hello World' アプリケーションを始めましょう。 + +@helloworld/app/views/Application/index.html@ テンプレートを編集してください: + +bc. #{extends 'main.html' /} +#{set title:'Home' /} + +
+ + +
+ +p(note). フォームの投稿はいかなる副作用も持たず、べき等なので、フォームを投稿するメソッドとして GET を使用することに注意してください。 + +@Application.sayHello@ アクションを起動できる URL を自動的に生成するよう Play に問い合わせるには @{...} 記法を使用します。さあ、ブラウザ内のトップページをリフレッシュしましょう。 + +!images/firstapp-2! + +おっと、エラーです。存在しない @Application.sayHello@ アクションを参照していることが原因です。 @helloworld/app/controllers/Application.java@ ファイル内にアクションを作成しましょう: + +bc. package controllers; + +import play.mvc.*; + +public class Application extends Controller { + + public static void index() { + render(); + } + + public static void sayHello(String myName) { + render(myName); + } + +} + +アクションメソッドのシグネチャに @myName@ パラメータを宣言したので、ここにはフォームから投稿される HTTP @myName@ パラメータの値が自動的に設定されます。その後、テンプレートを表示するために render をコールします。 @myName@ 変数を @render()@ の呼び出しに渡しているので、この変数はテンプレートから利用できます。 + +!images/firstapp-3! + +この段階で名前を入力してフォームをサブミットすると、別のエラーが発生します: + +!images/firstapp3! + +エラーの内容はかなり明確です。Play はこのアクションメソッドのデフォルトのテンプレートをレンダリングしようとしますが、それは存在しません。 @helloworld/app/views/Application/sayHello.html@ を作成しましょう: + +bc. #{extends 'main.html' /} +#{set title:'Home' /} + +

Hello ${myName ?: 'guest'}!

+ +Back to form + +ページをリフレッシュします。 + +!images/firstapp-4! + +Groovy の @?:@ 演算子の使い方を見てください。 @myName@ 変数に値が設定されていない場合は、デフォルト値を表示します。このため、名前を入力せずにフォームをサブミットすると、'Hello guest' を表示します。 + +h2. より良いURLの提供 + +さて、投稿した URL を見てみると、このような感じのものになっていることでしょう: + +bc. http://localhost:9000/application/sayhello?myName=guillaume + +これはあまりきれいではありません。これは Play がデフォルトの 'catch all' ルートを使用したからです。 + +bc. * /{controller}/{action} {controller}.{action} + +@Application.sayHello@ アクションにカスタムパスを設定する事でより良い URL を得ることができます。 @helloworld/conf/routes@ を編集して、はじめの一行の次にこのルーティングを追記してください: + +bc. GET /hello Application.sayHello + +フォームに戻ってもう一度サブミットし、新しい URL パターンが使用されることを確認してください。 + +h2. レイアウトのカスタマイズ + +アプリケーションが使用する両方のテンプレートが、同じ @main.html@ テンプレートを継承する場合、容易にカスタムレイアウトを加えることができます。 @helloworld/app/views/main.html@ を編集してください: + +bc. ... + + The Hello world app. +
+ + #{doLayout /} + +... + +これで両方のページに共通のヘッダを持たせることができます。 + +!images/firstapp-5! + +h2. バリデーションの追加 + +名前欄を入力必須とするちょっとしたバリデーションをフォームに追加しましょう。Play のバリデーションフレームワークを使用できます。 + +@helloworld/app/controllers/Application.java@ コントローラの @sayHello@ アクションを編集してください: + +bc. ... +public static void sayHello(@Required String myName) { + if(validation.hasErrors()) { + flash.error("Oops, please enter your name!"); + index(); + } + render(myName); +} +... + +@Required アノテーションを使用するために @play.data.validation.@ をインポートするのを忘れないでください。Play は自動的に myName フィールドに値が設定されているかを確認し、設定されていない場合は **errors** スコープにエラーオブジェクトを追加します。何かエラーがある場合は、 **flash** スコープにメッセージを追加して @index@ アクションにリダイレクトするようにします。 + +flash スコープは、アクションがリダイレクトされる間もメッセージを保持します。 + +さて、何かエラーがあれば **error** メッセージを表示しなければなりません。 @helloworld/app/views/Application/index.html@ を編集してください: + +bc. #{extends 'main.html' /} +#{set title:'Home' /} + +#{if flash.error} +

+ ${flash.error} +

+#{/if} + +
+ + +
+ +これが動作する事を確認します: + +!images/firstapp-6! + +h2. 自動化されたテストスイートの作成 + +このアプリケーションのためのいくつかのテストを書いて、このチュートリアルを終えましょう。今はテストすべき Java のロジックが無いので、Web アプリケーションそのものをテストする必要があります。そこで、Selenium テストを書いていきましょう。 + +まず最初に、アプリケーションを **test モード** で実行する必要があります。アプリケーションを停止し、 @test@ コマンドで再起動してください: + +bc. $ play test + +@play test@ コマンドは、ブラウザから直接テストスイートを実行することのできるテストランナーモジュールをロードすること以外は @play run@ とほとんど同じです。 + +URL "http://localhost:9000/@tests":http://localhost:9000/@tests をブラウザで開き、テストランナーを見てみてください。デフォルトのテストをすべて選択して実行してみてください。すべてのテストはグリーンであるべきですが、実際のところ、これらのデフォルトのテストは何もテストしません。 + +!images/guide2-1! + +通常、selenium のテストスイートは HTML ファイルとして記述されます。selenium が必要とする HTML の構文は (HTML のテーブル要素を使うようフォーマットされており) 少々退屈です。Play のテンプレートエンジンと selenium シナリオの構文を簡易にするタグのセットを使うことで、Play がこれを手助けしてくれるのは良いニュースです。 + +新しく作成された Play アプリケーションのデフォルトのテストスイートには selenium テストが含まれています。 @helloworld/test/Application.test.html@ ファイルを開いてください: + +bc. *{ You can use plain selenium command using the selenium tag }* + +#{selenium} + // Open the home page, and check that no error occurred + open('/') + assertNotTitle('Application error') +#{/selenium} + +現時点でこのテストは何も問題なく実行されるはずです。このテストは、ホームページを開いて、このページが 'Application error' というテキストを含んでいないことを確認するだけです。 + +アプリケーションのためのテストを書きましょう。テストの内容を編集してください: + +bc. #{selenium} + // Open the home page, and check that no error occurred + open('/') + assertNotTitle('Application error') + + // Check that it is the Hello World application + assertTextPresent('The Hello world app.') + + // Submit the form + clickAndWait('css=input[type=submit]') + + // Check the error + assertTextPresent('Oops, please enter your name!') + + // Type the name and submit + type('css=input[type=text]', 'bob') + clickAndWait('css=input[type=submit]') + + // Check the result + assertTextPresent('Hello bob!') + assertTextPresent('The Hello world app.') + + // Check the back link + clickAndWait('link=Back to form') + + // Home page? + assertTextNotPresent('Hello bob!') +#{/selenium} + +これでこのアプリケーションを完全にテストできました。テストランナーでこのテストを選択して 'Start' をクリックしてください。すべてがグリーンになるはずです! + +!images/firstapp-7! + + +h2. さらに詳しく + +これは非常にばかばかしいアプリケーションに関するとても簡単なチュートリアルでした。Play についてもっと学びたいなら、 %(next)"Play guide":guide1% をチェックしてください。 \ No newline at end of file diff --git a/documentation/manual_ja/guide1.textile b/documentation/manual_ja/guide1.textile new file mode 100644 index 0000000000..8a21da464c --- /dev/null +++ b/documentation/manual_ja/guide1.textile @@ -0,0 +1,252 @@ +h1. プロジェクトの立ち上げ + +h2. はじめに + +このチュートリアルでは、実際の web アプリケーションを始めから終わりまでコーディングすることによって、Play フレームワークについて学びます。このアプリケーションでは、Play アプリケーション開発の優れた実践を紹介する間、実際のプロジェクトで必要とするあらゆるものを使っていきたいと思います。 + +このチュートリアルは、いくつかの独立している部分に分かれています。それぞれの部分は、より複雑な特徴を紹介して、実際のプロジェクトが必要とするすべてのことを提供します: バリデーション、エラー処理、完全にセキュアなフレームワーク、自動化されたテストスイート、輝く web インタフェース、管理領域など。 + +p(note). このチュートリアルに含まれる **すべてのコード** は、あなたのプロジェクトに使用することができます。コードの断片をコピーアンドペーストするか、または全体をまるごと流用することを推奨します。 + +h2. プロジェクト + +チュートリアルには、よくあるブログエンジンの作成を選びました。これは、とても想像的な選択ではありませんが、モダンな web アプリケーションで必要な機能のほとんどの部分を調査することができます。 + +チュートリアルをちょっと面白くするために、数人のユーザを異なる役割 (投稿者、管理者) で管理します。 + +このブログエンジンプロジェクトを **yabe** と呼ぶことにします。 + +!images/guide1-0! + +p(note). このチュートリアルはサンプルアプリケーションとしても配布されています。最終的なコードは Play インストール先の @samples-and-tests/yabe@ ディレクトリにあります。 + +h2. 前提条件 + +何よりも、まず Java がインストールされていることを確認してください。Play は **Java 5 以降** を必要とします。 + +私たちはコマンドラインを多用するので、Unix ライクな OS のほうが良いでしょう。もし Windows 上で動かす場合でも、問題なく動作します。ただコマンドプロンプトでいくつかのコマンドを実行しなければならないだけです。 + +このチュートリアルは、あなたが Java と Web 開発 (特に HTML, CSS, およびJavaScript) に関する知識を既に持つものとします。しかしながら、Java Enterprise Edition (JEE) のあらゆるコンポーネントに関する深い知識は必要としません。Play は 'フルスタック' な Java フレームワークであり、必要な Java API のすべての部分を提供するか、またはカプセル化します。JPA エンティティマネージャを設定する方法や、あるいは JEE コンポーネントをデプロイする方法を知っている必要はありません。 + +もちろんテキストエディタも必要です。もし Eclipse や Netbeans のようなフル機能の Java IDE に慣れているなら、もちろんそれを使用できます。しかし、Play であれば、Textmate, Emacs または VI のようにシンプルなテキストエディタでも楽しく作業できます。これは、フレームワーク自身がコンパイルとデプロイを管理するからです。間もなくその様子をご覧に入れます... + +このチュートリアルの後半で、Lighttpd と MySql を使い、'production' モードの Play アプリケーションをデプロイする方法を示します。しかし、Play はこれらのコンポーネントなしでも動作することができるので、もしこれらをインストールできなくても問題ありません。 + +h2. Play フレームワークのインストール + +インストールは非常に簡単です。 ダウンロードページから最新のバイナリパッケージをダウンロードして、任意のパスに展開してください。 + +p(note). Windows を使用している場合、パスにスペースを含めないのは一般的に良い案です。例えば、 @c:\play@ は @c:\Documents And Settings\user\play\@ よりも良い選択です。 + +効率的に作業するためには、作業パスに Play ディレクトリを加える必要があります。パスを追加することで、コマンドプロンプトでただ @play@ とタイプすれば、play ユーティリティを使用できるようになります。正常にインストールが完了したことを確認するには、新しいコマンドラインを開いて @play@ とタイプしてください; このコマンドは Play の基本的なヘルプを表示します。 + +h2. プロジェクトの作成 + +Play が正しくインストールされたので、いよいよブログアプリケーションを作成します。Play アプリケーションの作成は、play コマンドラインユーティリティによって完全に管理されており、とても簡単です。play コマンドラインユーティリティは、すべての Play アプリケーション間において標準的なプロジェクトレイアウトを割り当てます。 + +新しいコマンドラインを開いて、以下をタイプしてください: + +bc. ~$ play new yabe + +アプリケーションの完全な名前を入力するプロンプトが表示されます。 **Yet Another Blog Engine** とタイプしてください。 + +!images/guide1-1! + +@play new@ コマンドは @yabe/@ ディレクトリを新規に作成し、一連のファイルやディレクトリをここに追加します。以下は、もっとも重要なディレクトリです: + +@app/@ ディレクトリはアプリケーションの中心部であり、モデル、コントローラ、およびビュー用のディレクトリに分けられています。 他の Java パッケージを含むこともできます。 app/ ディレクトリは @.java@ ソースファイルを保存するディレクトリです。 + +@conf/@ ディレクトリは、特に重要な @application.conf@ ファイル、ルーティングを定義する @routes@ ファイル、国際化のための @messages@ ファイルなど、アプリケーションを設定する全てのファイルを保存します。 + +@lib/@ は標準的な @.jar@ ファイルとしてパッケージされたオプションの Java ライブラリを保存します。 + +@public/@ は JavaScript, スタイルシート、および画像ディレクトリを含む公的に利用可能なリソースを保存します。 + +@test/@ には、すべてのアプリケーションテストを保存します。テストは Java の JUnit として書かれるか、または Selenium テストとして書かれます。 + +p(note). **Play は UTF-8** 唯一のエンコーディングとして使用するので、これらのディレクトリでホスティングされたすべてのテキストファイルが UTF-8 でエンコードされていることは非常に重要です。テキストエディタの設定がこれに従っていることを確認してください。 + +もし、あなたが熟練した Java 開発者であれば、すべての .class ファイルがどこに行ってしまったのか不思議に思うことでしょう。実は、どこにもありません: Play はいかなる class ファイルも使用せず、Java ソースファイルを直接読み込みます。舞台裏では、実行中の Java ソースをコンパイルするために Eclipse コンパイラを使用しています。 + +これは、開発工程において 2 つの非常に重要なことを可能にします。最初の 1 つは、Play があなたが Java ソースファイルに加えたいかなる変更をも検出して、実行時にそれを自動的にリロードするということです。 2 番目は、Java の例外が発生したとき、Play がソースコードそのものを示しながらより良いエラーレポートを作成するということです。 + +p(note). 実際のところ、Play はアプリケーションの @/tmp@ ディレクトリにバイトコードのキャッシュを保持しますが、これは大きなアプリケーションの再起動を高速化するためだけのものです。必要であれば、 @play clean@ コマンドを使うことでこのキャッシュを破棄することができます。 + +h2. アプリケーションの実行 + +ここまで来れば、新しく作成したアプリケーションを試してみることができます。コマンドラインに戻り、新たに作成された @yabe/@ ディレクトリに移動したら、 @play run@ とタイプしてください。Play がアプリケーションをロードし、9000 番ポートで Web サーバを起動します。 + +ブラウザから "http://localhost:9000":http://localhost:9000 を開くことで、新しいアプリケーションを閲覧することができます。新しいアプリケーションは、それが首尾よく作成されたこと示す標準のウェルカムページを表示します。 + +!images/guide1-2! + +新しいアプリケーションがどのようにしてこのページを表示するのかを見てみましょう。 + +アプリケーションのエントリーポイントは @conf/routes@ ファイルです。このファイルはアプリケーション上のアクセス可能な URL をすべて定義します。 生成された routes ファイルを開くと、最初の 'route' が確認できるでしょう: + +bc. GET / Application.index + +この設定は、web サーバが @/@ パスに対する **GET** リクエストを受け取った場合には @Application.index@ という Java メソッドをコールしなければならないことを Play に伝えています。この場合、 @controllers@ パッケージが暗黙的に指定されるので、 @Application.index@ は @controllers.Application.index@ のショートカットです。 + +スタンドアロンな Java アプリケーションを作成するとき、一般的に以下のようなメソッドを定義することで単一のエントリーポイントを設けます: + +bc. public static void main(String[] args) { + ... +} + +Play アプリケーションには、各 URL あたり 1 つのエントリーポイントがあります。 私たちは、これらのメソッドを @Action@ メソッドと呼びます。アクションメソッド @controller@ と呼ぶ特別なクラスで定義されます。 + +@controllers.Application@ コントローラがどのようなものか見てみましょう。 @yabe/app/controllers/Application.java@ ソースファイルを開いてください: + +bc. package controllers; + +import play.mvc.*; + +public class Application extends Controller { + + public static void index() { + render(); + } + +} + +コントローラクラスは @play.mvc.Controller@ クラスを継承します。このクラスは index アクションで使用した @render()@ メソッドのような、コントローラ向けの便利なメソッドを提供します。 + +index アクションは @public static void@ なメソッドとして定義されています。アクションメソッドはこのようにして定義されます。コントローラクラスは決してインスタンス化されないので、アクションメソッドは static です。アクションメソッドは、フレームワークがリクエストされた URL にレスポンスできるよう public として宣言されます。アクションメソッドは常に void を返します。 + +デフォルトの index アクションはシンプルです: Play にテンプレートをレンダリングするよう指示する @render()@ メソッドをコールします。テンプレートの使用は HTTP レスポンスを生成する最も一般的な (しかし、唯一ではない) 方法です。 + +テンプレートは @/app/views@ ディレクトリに存在するシンプルなテキストファイルです。今回はテンプレートを指定しなかったので、このアクション用のデフォルトのテンプレートが使用されます: @Application/index.html@ です。 + +テンプレートがどのようなものかを見るには、 @yabe/app/views/Application/index.html@ ファイルを開いてください: + +bc. #{extends 'main.html' /} +#{set title:'Home' /} + +#{welcome /} + +テンプレートの内容はとても簡単に見えます。実際、ここにあるものはすべて Play のタグです。Play タグは JSP タグに似ています。 @#{welcome /}@ が、ブラウザに表示されたウェルカムメッセージを生成しています。 + +@#{extends /}@ タグは、このテンプレートが @main.html@ と呼ばれる別のテンプレートを継承することを Play に伝えます。テンプレートの継承は、共通化された部分を再利用することで複雑なウェブページを作成することができる強力な概念です。 + +@yabe/app/views/main.html@ テンプレートを開いていてください: + +bc. + + + #{get 'title' /} + + + #{get 'moreStyles' /} + + + #{get 'moreScripts' /} + + + #{doLayout /} + + + +下にある **#{doLayout /}** タグに気付きましたか?ここに **Application/index.html** の内容が挿入されます。 + +コントローラファイルを編集することで、Play がどのようにして自動的にこれをロードするのか試してみることができます。 @yabe/app/controllers/Application.java@ ファイルをテキストエディタで開いて、 @render()@ 呼び出しの後に続くセミコロンを削除することで間違いを埋め込んでください: + +bc. public static void index() { + render() +} + +ブラウザに戻りページをリフレッシュしてください。Play が変更を検出して Application コントローラをリロードしようとする様子を見ることができます。しかし、間違いがあるためコンパイルエラーが発生します。 + +!images/guide1-3! + +OK, エラーを修正して、実際の変更を加えましょう: + +bc. public static void index() { + System.out.println("Yop"); + render(); +} + +今度は、Play は適切にコントローラをリロードし、JVM 上の古いコードを置き換えました。 @/@ URL への各リクエストは、コンソールに 'Yop' メッセージを出力します。 + +この役に立たない行を取り除いて、歓迎のメッセージを置き換えるために @yabe/app/views/Application/index.html@ テンプレートを編集することができます: + +bc. #{extends 'main.html' /} +#{set title:'Home' /} + +

A blog will be there

+ +Java コードの変更と同じように、変更内容を確認するには、ただブラウザでページをリフレッシュするだけです。 + +p(note). いよいよブログアプリケーションのコーディングを始めました。このままテキストエディタで作業し続けることも、Eclipse や NetBeans のような Java IDE でプロジェクトを開くことも可能です。Java IDE をセットアップする場合は、 "お好みの IDE を設定しよう":ide をチェックしてください。 + +h2. データベースのセットアップ + +コーディングを始める前にもうひとつ。ブログエンジンにはデータベースが必要です。開発目的のために Play には H2 と呼ばれるスタンドアロンの SQL データベース管理システムを同梱しています。より堅牢なデータベースが必要になって切り替える前までは、これが開発を開始するにはベストな方法です。インメモリデータベースか、またはアプリケーションを再起動してもデータを保持するファイルシステムデータベースのいずれかを選ぶこともできます。 + +初めのうちは、アプリケーションモデルを何度もテストし、変更します。このため、常にフレッシュなデータセットで起動するインメモリデータベースはより良い方法です。 + +データベースを設定するためには、 @yabe/conf/application.conf@ ファイルを開き、次の行を非コメント化します: + +bc. db=mem + +見て分かる通り、どのような JDBC 対応データベースについても、そしてコネクションプールの設定すらも容易に構成することができます。 + +p(note). このチュートリアルはインメモリデータベースを使って動作するように設計されています。他のデータベースを JPA を使って操作する手順はこのチュートリアルには含まれていません。 + +さて、ブラウザに戻ってページをリフレッシュしてください。Play は自動的にデータベースを起動します。アプリケーションログに次の行があることを確認してください: + +bc. INFO ~ Connected to jdbc:h2:mem:play + +h2. バージョン管理システムを使った変更管理 + +プロジェクトに従事する場合、ソースコードをバージョン管理システム (VCS) に保存することを強くお勧めします。VCS は、変更が何かを壊した場合に以前のバージョンに戻ることや、複数人で作業すること、そしてアプリケーションの連続したバージョンすべてにアクセスすることを可能にします。 + +Play アプリケーションを VCS に格納する場合、 @tmp/@, @modules/@, @lib/@, @test-result/@ そして @logs/@ ディレクトリを除外することは重要です。 + +Eclipse と @play eclipsify@ コマンドを使っている場合、 @.classpath@ と @eclipse/@ も除外すべきです。 + +h3. Bazaar + +ここでは例として Bazaar を使用します。"Bazaar":http://bazaar-vcs.org/ は分散バージョン管理システムです。 + +Bazaar のインストールはこのチュートリアルの範囲を超えていますが、どのようなシステム上においても非常に簡単です。Bazaar をインストールしたら、ブログディレクトリに戻り、以下をタイプしてアプリケーションのバージョン管理を初期化してください: + +bc. $ bzr init +$ bzr ignore tmp +$ bzr ignore modules +$ bzr ignore lib +$ bzr ignore test-result +$ bzr ignore logs + +これでブログエンジンの最初のバージョンをコミットできます: + +bc. $ bzr add +$ bzr commit -m "YABE initial version" + +h3. Git + +"Git":http://git-scm.com は、もうひとつの分散バージョンコントロールシステムです。詳細は Git のドキュメントを参照してください。 + +アプリケーションのルートディレクトリに git 作業リポジトリを作成します: + +bc. $ git init + +以下の内容を含む @.gitignore@ ファイルを作成します: + +bc. /tmp +/modules +/lib +/test-result +/logs + +アプリケーションのコンテンツを追加し、コミットします: + +bc. $ git add . +$ git commit -m "YABE initial version" + +バージョン 1 がコミットされ、我々のプロジェクトのための信頼できる基盤ができました。 + +p(note). %(next)"はじめてのモデル":guide2% に進みましょう。 diff --git a/documentation/manual_ja/guide10.textile b/documentation/manual_ja/guide10.textile new file mode 100644 index 0000000000..508a0e1e29 --- /dev/null +++ b/documentation/manual_ja/guide10.textile @@ -0,0 +1,204 @@ +h1. テストの完了 + +ここまでで、このチュートリアルで作成したかったブログエンジンを完成させました。ですが、プロジェクトそのものはまだ完全には終わっていません。すべてのコードに自信を持つためには、プロジェクトにより多くのテストを追加する必要があります。 + +もちろん、yabe のすべてのモデル層の機能をテストするための単体テストは既に作成しています。この単体テストが、ブログエンジンの主要な機能がよくテストされていることを保証するのは素晴らしいことです。しかし、web アプリケーションは 'モデル' の部分だけではありません。web インタフェースが期待通り動作することを保証する必要があります。これは、yabe ブログエンジンのコントローラ層をテストすることを意味します。そして、例えば JavaScript コードのように、UI 自体もテストしなければなりません。 + +h2. コントローラのテスト + +Play は、JUnit を使ってアプリケーションのコントローラ部分を直接テストする方法を提供します。これは **'機能テスト'** と呼ばれます。なぜなら、アプリケーションの機能性を完全にテストするからです。 + +基本的には、機能テストは HTTP リクエストをシミュレートする Play の @ActionInvoker@ を直接呼び出します。このため、HTTP メソッドと URI、そして HTTP パラメータを指定します。その後 Play は、リクエストをルーティングして対応するアクションを起動し、値を詰めたレスポンスを返します。このレスポンスを分析し、その内容が期待したものであるかを確認することができます。 + +最初の機能テストを書いてみましょう。 @yabe/test/ApplicationTest.java@ 単体テストを開いてください: + +bc. import org.junit.*; +import play.test.*; +import play.mvc.*; +import play.mvc.Http.*; +import models.*; + +public class ApplicationTest extends FunctionalTest { + + @Test + public void testThatIndexPageWorks() { + Response response = GET("/"); + assertIsOk(response); + assertContentType("text/html", response); + assertCharset("utf-8", response); + } + +} + +今のところ、これは標準的な JUnit テストのように見えます。便利なユーティリティヘルパを取得するために、Play の @FunctionalTest@ スーパークラスを使用することに注意してください。このテストに誤りは無く、ただ単にアプリケーションのトップページ (典型的な '/' という URL は、ステータスコード **'200 OK'** と共に HTML レスポンスをレンダリングすること) を確認します。 + +ここで、管理領域のセキュリティ機能が期待通りに動作することを確認してみましょう。 @ApplicationTest.java@ ファイルに新しいテストを追加してください: + +bc. … +@Test +public void testAdminSecurity() { + Response response = GET("/admin"); + assertStatus(302, response); + assertHeaderEquals("Location", "/login", response); +} +… + +@play test@ コマンドを使って yabe アプリケーションを test モードで実行したら、 "http://localhost:9000/@tests":http://localhost:9000/@tests を開き、 @ApplicationTest.java@ を選択して実行します。 + +グリーンになりましたか? + +!images/guide10-1! + +さて、このやり方でアプリケーションの全機能をテストし続けることもできますが、HTML ベースの web アプリケーションをテストする方法としては、あまり良いやり方ではありません。このブログエンジンは web ブラウザで実行するつもりなので、 **本物の web ブラウザ** で直接テストする方がより良いでしょう。そして、Play の **'Selenium テスト'** は、まさにこれを行います。 + +これらの JUnit ベースの **'機能テスト'** は、HTTP を使って JSON や XML と言った 非 HTML なレスポンスを返す Web サービスの典型的なテストには依然として有用です。 + +h2. Selenium テストの作成 + +"Selenium":http://seleniumhq.org は web アプリケーションのテストに特化したテストツールです。Selenium は、既存のあらゆるブラウザで直接テストスイートを実行することができる点においてクールです。Selenium は 'ブラウザシミュレータ' は一切使わないので、ユーザが使用するブラウザでテストしていることを確信することができます。 + +通常、Selenium のテストスイートは HTML ファイルとして記述されます。Selenium が必要とする HTML の構文を書くのは (HTML のテーブル要素を使うようフォーマットされており) 少々退屈です。Play のテンプレートエンジンと Selenium シナリオの構文を簡易にするタグのセットを使うことで、Play がこれを手助けしてくれるのは良いニュースです。複雑なテストを書くために (くり返しや条件ブロックなどの) Play テンプレートの機能を使うことで、どのような '静的なシナリオ' にも縛られなくなるのは、テンプレートを使用することの興味深い副作用です。 + +p(note). とは言え、もし必要であれば特別な Selenium タグのことは忘れて、テンプレート内に素の HTML 構文を書くことも可能です。"Selenium IDE":http://seleniumhq.org/projects/ide のような、いくつかのテストシナリオ生成ツールのうちのひとつを使ってみるのも面白いかもしれません。 + +新規作成した Play アプリケーションのデフォルトのテストスイートには、すでに Selenium テストが含まれています。 @yabe/test/Application.test.html@ ファイルを開いてください: + +bc. *{ You can use plain Selenium commands using the selenium tag }* + +#{selenium} + // Open the home page, and check that no error occurred + open('/') + waitForPageToLoad(1000) + assertNotTitle('Application error') +#{/selenium} + +yabe アプリケーションでは、このテストは問題なく実行できるはずです。このテストは、単にトップページを開き、その内容に文字列 'Application error' が含まれていないことを確認します。 + +一方で、複雑なテストなどの場合には、アプリケーションを操作してテストを実行する前に、分かりきったデータセットを設定する必要があります。もちろん、以前に使用したフィクスチャの概念と @yabe/test/data.yml@ ファイルを再利用することができます。テストスイートの前にこのデータセットをインポートするためには、ただ @#{fixtures /}@ タグを使用してください: + +bc. #{fixture delete:'all', load:'data.yml' /} + +#{selenium} + // Open the home page, and check that no error occurred + open('/') + waitForPageToLoad(1000) + assertNotTitle('Application error') +#{/selenium} + +確認すべきもうひとつの重要なことは、テスト開始時に新しいユーザセッションを使用することです。セッションはブラウザの永続化クッキーに保存され、ふたつの成功するテストを実行している間、同じセッションが保持されるでしょう。 + +そこで、特別なコマンドを使ってテストを開始するようにしましょう: + +bc. #{fixture delete:'all', load:'data.yml' /} + +#{selenium} + clearSession() + + // Open the home page, and check that no error occurred + open('/') + waitForPageToLoad(1000) + assertNotTitle('Application error') +#{/selenium} + +これを実行して何も問題が無いことを確認してください。このテストはグリーンになるはずです。 + +これでもっと特別なテストを書くことができます。トップページを開いてデフォルトの投稿が存在することを確認しましょう: + +bc. #{fixture delete:'all', load:'data.yml' /} + +#{selenium 'Check home page'} + clearSession() + + // Open the home page + open('/') + + // Check that the front post is present + assertTextPresent('About the model layer') + assertTextPresent('by Bob, 14 Jun 09') + assertTextPresent('2 comments , latest by Guest') + assertTextPresent('It is the domain-specific representation') + + // Check older posts + assertTextPresent('The MVC application') + assertTextPresent('Just a test of YABE') +#{/selenium} + +ここでは、"Selenese":http://seleniumhq.org/docs/02_selenium_ide.html#selenium-commands-selenese と呼ばれる標準的な Selenium 構文を使っています。 + +このテストを実行してください (単にリンクを新しいウィンドウで開けば、その別のウィンドウでテストを実行することができます) 。 + +!images/guide10-2! + +今度はコメントフォームをテストします。テンプレートに新しい @#{selenium /}@ タグを追加してください: + +bc. #{selenium 'Test comments'} + + // Click on 'The MVC application post' + clickAndWait('link=The MVC application') + assertTextPresent('The MVC application') + assertTextPresent('no comments') + + // Post a new comment + type('content', 'Hello') + clickAndWait('css=input[type=submit]') + + // Should get an error + assertTextPresent('no comments') + assertTextPresent('Author is required') + type('author', 'Me') + clickAndWait('css=input[type=submit]') + + // Check + assertTextPresent('Thanks for posting Me') + assertTextPresent('1 comment') + assertTextPresent('Hello') +#{/selenium} + +そして実行してください。はい、失敗します; ここには深刻な問題があります。 + +!images/guide10-3! + +キャプチャ機能を本当の意味で正しくテストすることはできないので、ずるをしなければなりません。テストモードではどんなコードでも正しいキャプチャとして妥当性を検証するようにしましょう。ご存知のように、テストモードの場合のフレームワーク id は @test@ です。そこで、テストモードの場合は妥当性検証をスキップするよう @yabe/app/controllers/Application.java@ ファイル中の @postComment@ アクションを変更しましょう: + +bc. … +if(!Play.id.equals("test")) { + validation.equals(code, Cache.get(randomID)).message("Invalid code. Please type it again"); +} +… + +今度は、以下のように適当なコードをタイプするようテストケースを変更します: + +bc. … +type('author', 'Me') +type('code', 'XXXXX') +clickAndWait('css=input[type=submit]') +… + +これでテストを再度実行すれば、動作するはずです。 + +h2. コードカバレッジ計測 + +もちろん、アプリケーションに必要なすべてのテストケースを書いたわけではありません。しかし、このチュートリアルにはこれで充分です。ところで、実世界のプロジェクトの場合では、どうすれば充分なテストを書いたと知ることができるのでしょうか? これは **'コードカバレッジ'** などと呼ばれます。 + +"Cobertura モジュール":http://www.playframework.org/modules/cobertura は "Cobertura":http://cobertura.sourceforge.net/ ツールを使ってコードカバレッジレポートを生成します。 @install@ コマンドを使ってこのモジュールをインストールしてください: + +bc. play install cobertura-{version} + +このモジュールはテストモードのときだけ有効にする必要があります。そこで、以下の行を @application.conf@ に追加し、アプリケーションをテストモードで再起動します。 + +bc. # Import the cobertura module in test mode +%test.module.cobertura=${play.path}/modules/cobertura + +ここで、ブラウザから "http://localhost:9000/@tests":http://localhost:9000/@tests URL を再度開き、すべてのテストを選択して実行してください。すべてグリーンになるはずです。 + +!images/guide10-5! + +すべてのテストをパスしてからアプリケーションを停止すると、cobertura はコードカバレッジレポートを生成します。ブラウザで @yabe/test-result/code-coverage/index.html@ を開き、このレポートを確認することができます。 + +!images/guide10-4! + +アプリケーションを再起動する場合、 "http://localhost:9000/@cobertura":http://localhost:9000/@cobertura でそれを参照することも可能です。 + +見ての通り、アプリケーションの全ケースをテストするには程遠い状態です。すべてのコードを確認することがほとんど不可能だとしても、よく出来たテストスイートのカバレッジ率は 100% に近付くはずです。通常このために、キャプチャについて行ったようなテストモードにおけるハックをしばしば行うことになります。 + +p(note). 次: %(next)"本番環境への準備":guide11% \ No newline at end of file diff --git a/documentation/manual_ja/guide11.textile b/documentation/manual_ja/guide11.textile new file mode 100644 index 0000000000..4f54104280 --- /dev/null +++ b/documentation/manual_ja/guide11.textile @@ -0,0 +1,78 @@ +h1. 本番環境への準備 + +今回はブログエンジンを作成しました。Play アプリケーションを本番環境へセットアップするために一般的に必要な数ステップを見てみましょう。 + +h2. フレームワーク ID の定義 + +通常、開発版を作成するために使用していたコンピュータとは違うコンピュータ (サーバ) にアプリケーションをデプロイします。つまり、そこには別の Play インストール環境があります。 + +Play では、フレームワークのインストール環境ごとに異なる ID を割り当て、同じ @application.conf@ ファイルで異なる設定を管理することができます。ここでは @server01@ が本番環境アプリケーションをホストするとしましょう。 + +このサーバに Play フレームワークをインストールしたら、 @paly id@ コマンドを使用してフレームワーク ID を定義しましょう。以下のようにタイプしてください: + +bc. $ play id + +そして ID として @server01@ を割り当ててください。これで、アプリケーションがこのサーバ上で実行されるときにだけ使用される特別なキーを yabe 設定ファイルに定義することができます。 + +h2. PROD モードアプリケーションの設定 + +サーバデプロイ版に特別に設定したい最初のキーは @application.mode@ プロパティです。これまでは、動的に再ロードし、java ファイルを再コンパイルし、エラーが起こったときには詳細なメッセージを表示する **DEV** モードを使用しました。一方、 **PROD** モードでは Play はスタートアップ時にすべての java ソースとテンプレートをコンパイルし、二度と変更を確認しません。 + +@yabe/conf/application.conf@ ファイルにて、以下のように定義してください: + +bc. %server01.application.mode=PROD + +これで、yabe アプリケーションをこのサーバ上で実行した場合、アプリケーションは自動的に **PROD** モードで起動します。 + +h2. MySQL サーバの設定 + +本番環境では、これまで使用してきたインメモリ H2 データベースに代えて、MySQL をデータベースサーバとして使用します。Play には MySQL 用の JDBC ドライバが含まれているので、何もインストールする必要はありません。 + +@yabe/conf/application.conf@ ファイル中のデータベース設定を編集してください: + +bc. %server01.db=mysql:root:secret@yabe + +ここで、Hibernate がデータベーススキーマを管理する方法を少し調整します。Java モデルオブジェクトが変更されたときに Hibernate が自動的にデータベーススキーマを更新するのは、とても便利です。 + +@jpa.ddl@ 設定キーを以下のように変更してください: + +bc. %server01.jpa.ddl=update + +しかしながら、これは予期しない類のものであり、本番環境のデータベースにおいて摩訶不思議なことが実行されるのは良いことではありません。そのため、新しくデプロイする前には常にバックアップを取るべきです。Hibernate にデータベースを更新させたくない場合は、 @jpa.ddl@ 設定キーを @validate@ に変更してください: + +bc. %server01.jpa.ddl=validate + +h2. フロント HTTP サーバの設定 + +さて、現実的な本番サーバを立てるために、単に組込み HTTP サーバのデフォルトポートを 80 番に変更することができます。しかし、これには提供されたサーバにたったひとつの Play アプリケーションしかインストールできないという制限があります。通常、同じサーバにいくつかのアプリケーションを (ただし、異なる IP ホスト名を使って) インストールするので、リバースプロキシとしてフロント HTTP サーバを使う必要があります。 + +どのような HTTP サーバを選択し、リバースプロキシとして設定することができますが、"LIGHTTPD":http://www.lighttpd.net/ のような軽量で速いものを使用するのが一般的に良い選択です。 + +LIGHTTPD の正確な設定はこのチュートリアルの対象範囲外ですが、それは以下のようなものになるはずです: + +bc. server.modules = ( + "mod_access", + "mod_proxy", + "mod_accesslog" +) +… +$HTTP["host"] =~ "www.yabe.com" { + proxy.balance = "round-robin" proxy.server = ( "/" => + ( ( "host" => "127.0.0.1", "port" => 9000 ) ) ) +} + +そして、 @application.conf@ ファイルに以下のキーを追加することで、このローカルリバースプロキシが Play アプリケーションに接続することを許可します: + +bc. %server01.XForwardedSupport=127.0.0.1 + +h2. これは始まりに過ぎません + +p(note). このチュートリアルを読んで理解したのであれば、今やあなたは熟練した Play 開発者です。あなたは Play アプリケーション開発をドライブする概念のほとんどを知っています。 + +ここまでくると、 %(next)"国際化と地域化":guide12% にてチュートリアルを続けたいと思うかもしれません。 + +まだまだ説明していない機能、特に Web サービスに関連する JSON もしくは XML と言った機能があります。Play には、より多くの機能を提供するたくさんのモジュールも含まれています。そして Play そのものが日々進化しています。 + +もし Play があなたの次の Java web アプリケーションにかかる時間を節約する助けになると確信しているのであれば、あなたはもう Play を始める準備ができています。そして、"Play Google Group":http://groups.google.com/group/play-framework 上で、どんな質問でも躊躇せずに私たちに尋ねてください。 + +**どうもありがとう!** \ No newline at end of file diff --git a/documentation/manual_ja/guide12.textile b/documentation/manual_ja/guide12.textile new file mode 100644 index 0000000000..9542bc74ef --- /dev/null +++ b/documentation/manual_ja/guide12.textile @@ -0,0 +1,330 @@ +h1. 国際化と地域化 + +完全に機能するブログエンジンを作り終えたので、オプション機能について考えてみます: web アプリケーションの国際化と言語の地域化です。初めからアプリケーションを国際化することもできたかもしれませんが、アプリケーションの最初のバージョンは単一の言語で作成し、後から複数の言語を追加するほうが、より現実的です。 + +h2. 国際化と地域化 + +実行する二つのステップがあります: "国際化":http://en.wikipedia.org/wiki/Internationalization_and_localization と "地域化":http://en.wikipedia.org/wiki/Language_localisation です。両方とも、ほとんどテキストに関するものです。 + +プログラミングにおける *国際化* とは、アプリケーションコードからロケールに特化したコードを取り除くリファクタリングです。web アプリケーションにおける国際化とは、ほぼ完全に、ビューテンプレート内のユーザインタフェース文字列をメッセージの参照へ置き換えることです。国際化は、文字列でないデータ型: 日付、通貨、その他の数字の整形も含みます。 + +*地域化* とは、ロケールに特化したバージョンのアプリケーションを作成することです。アプリケーションが国際化されているならば、地域化とは、ひとつ以上の選択可能なロケールに特化したバージョンがあることを意味します。web アプリケーションの場合、地域化とは、ユーザインタフェース文字列を選択された自然言語に翻訳することです。言語の選択は、典型的には、web ブラウザにセットされた言語設定と、アプリケーションそのものの言語選択インタフェースの組み合わせです。 + +実際には、これら二つのステップを一緒に進めます: アプリケーションの一部を同時に国際化し、地域化します。 + +h2. Yet Another Blog Engine + +p(note). このセクションの出発点は、Play の配布物の @samples-and-tests/yabe@ ディレクトリにある、完成したチュートリアルコードです。このセクションのゴールは、アプリケーションを完全に国際化して、フランス語とオランダ語の地域化を追加することです。 + +始めるには、最初に @conf/application.conf@ を編集し、三つの言語をサポートするよう、(デフォルト設定ファイルの場合は) 行を非コメント化するか、または追加します: + +bc. # Localisations for English, Dutch and French. +application.langs=en,nl,fr + +ここでアプリケーションのページを読み込むと、まだロケールに特化したメッセージファイルがひとつもないので、Play コンソールに三つの警告が表示されるでしょう: + +bc. 16:19:04,728 WARN ~ Messages file missing for locale en +16:19:04,729 WARN ~ Messages file missing for locale nl +16:19:04,729 WARN ~ Messages file missing for locale fr + +h2. UTF-8 メッセージファイル + +上記の警告は、既存の @conf/messages@ ファイルを、それぞれの言語ごとに一つのメッセージファイルとして置き換える必要があることを意味しています: + +* @messages.en@ +* @messages.nl@ +* @messages.fr@ + +ここで、標準の Java のやり方に対する最初の改良に出会います。これらのメッセージファイルは Java のプロパティファイルと同じ構文を使用しますが、UTF-8 エンコーディングを使用しなければならないので、プロパティファイルではありません。一方、Java "プロパティ":http://java.sun.com/javase/6/docs/api/java/util/Properties.html は、テキストファイルとストリームをやり取りするためのエンコーディングに ISO-8859-1 'Latin-1' 文字符号を指定しています。 + +UTF-8 のメッセージファイルを使用できることは、地域化された言語メッセージを 'プレーンテキスト' で書くことができることを意味しているため、言語の地域化にとって、とても大事なことです。例えば、ギリシャ語の地域化において: + +bc. hello.morning = \u0152\u222b\u0152\u00b1\u0152\u00aa\u0152\u2211\u0152\u00ba\u0152\u2260\u0153\u00c5\u0152\u00b1 +hello.informal = \u0152\u2265\u0152\u00b5\u0152\u03c0\u0152\u00b1 \u0153\u00c9\u0152\u00f8\u0153\u00d6 + +これらのユニコード文字エスケープの代わりに、ギリシャ文字を使用することができます: + +bc. hello.morning = καλημホュρα +hello.informal = γεια σου + +このチュートリアルの残りの部分では、コードサンプルは、これらのファイルのうちの一つでメッセージを定義するか、または HTML ビューテンプレートのうちの一つにおいて、国際化されたマークアップを示します。 + +h2. シンプルなメッセージ + +シンプルなケースとしては、変化せず、他のマークアップから邪魔されないテキスト文字列があります。例えば、 @yabe/app/views/main.html@ テンプレートの @tools@ リストにある最初のシンプルなテキストです: + +bc. + +これを国際的にするために、 @&{'key'}@ 構文を使って文字列をメッセージの参照に置き換えます: + +bc. + +地域化するために、対応する行を三つのメッセージファイルそれぞれに追加します。 @conf/messages.en@ は + +bc. views.main.tools.login = Log in to write something + +@conf/messages.nl@ は + +bc. views.main.tools.login = Inloggen om iets te schrijven + +@conf/messages.fr@ は + +bc. views.main.tools.login = Connectez-vous pour テゥcrire quelque chose + +メッセージキーはどのようなものでも構いません; この例の場合、 @views/main.html#tools@ の位置を示すキーを私用しました。 + +これらの変更を保存したら、 @Accept-Language@ HTTP リクエストヘッダを異なるものにするよう web ブラウザの設定を変更することで、別の言語のバージョンを見ることができます。Firefox の場合、オプション ≫ コンテンツ ≫ 言語 ≫ 言語設定 を選択し、まだリストに無ければ _フランス語 \[fr]_ と _オランダ語 \[nl]_ を追加し、どちらか一つをリストのトップに変更して、ダイアログボックスを閉じたらページをリロードします。 + +!images/guide12-1! + + +h2. アプリケーションモデルの地域化 + +リンクをクリックしてブログの 'admin' ページにログインすると、ポスト、タグ、コメント、およびユーザのリストにアクセスすることができます。これらのページは "CRUD":http://www.playframework.org/documentation/1.0.1/crud によって提供されるものです。これらそれぞれのページにおいて、その (薄いピンク色の) タイトルと列ヘッダは、アプリケーションのモデル、すなわち JavaBean のクラス名と属性名から来る文字列です。 + +CRUD モジュールは、これら JavaBean のクラス名または属性名をメッセージキーとして使用することで、これらの名前を国際化し、これは以下のようにすることでメッセージを地域化できることを意味します。 + +@conf/messages.nl@ は以下のようにします。 + +bc. post = artikel +Post = Artikel +posts = artikelen +Posts = Artikelen +comment = reactie +Comment = Reactie +comments = reacties +Comments = Reacties +user = gebruiker +User = Gebruiker +users = gebruikers +Users = Gebruikers + +@conf/messages.fr@ は以下のようにします。 + +bc. post = article +Post = Article +posts = articles +Posts = Articles +comment = commentaire +Comment = Commentaire +comments = commentaires +Comments = Commentaires +user = utilisateur +User = Utilisateur +users = utilisateur +Users = Utilisateurs + +これだけでは、紫色で囲まれたナビゲーションリンクが変わらないことに気付くでしょう: + +!images/guide12-2! + +これらのリンクは、以下のようにして既存の文字列を単純に @&{'…'}@ で囲むことによる地域化を同様に使用するよう国際化できる @views/admin.html@ に定義されています: + +bc. &{'Posts'} +… +&{'Tags'} +… +&{'Comments'} +… +&{'Users'} + +h2. パラメータ化されたメッセージ + +単純なメッセージと同様に、アプリケーションは _Play によってタグ付けをされた Posts_ などの、変数を持つメッセージを含んでいます。 + +パラメータを一つだけ含むメッセージを地域化するには、パラメータの値をメッセージに挿入する "Java フォーマット文字列":http://java.sun.com/javase/6/docs/api/java/util/Formatter.html#syntax を使用してください: + +bc. views.Application.listTagged.title = Posts tagged with %s + +そして、テンプレートでは、以下のようにしてパラメータを追加します: + +bc. &{'views.Application.listTagged.title', tag} + +メッセージが複数のパラメータを含んでいるときは、別の言語では単語の順番が変わることを考慮して、フォーマット文字列にインデックスを追加します: + +bc. views.Admin.index.welcome = Welcome %1$s, you have written %2$s posts so far + +…リストがテンプレートにある状態で: + +bc. &{'views.Admin.index.welcome', user, posts.size()} + +また、この例では 'post' という単語に正しい複数形を使用したいので、この単語もパラメータ化します: + +bc. views.Admin.index.welcome = Welcome %1$s, you have written %2$s %3$s so far + +…そしてテンプレートで @pluralize@ エクステンションを使用します。 + +bc. &{'views.Admin.index.welcome', user, posts.size(), posts.pluralize(messages.get('post'), messages.get('posts'))} + +地域化された単数形と複数形を参照するために @messages.get@ を使用する必要があることに注意してください。 + + +h2. Play モジュールの地域化 + +Play モジュールの地域化はアプリケーションの地域化と同じように動作します。このアプリケーションは CRUD モジュールと Secure モジュールを使用するので、これはアプリケーションが使用する @play/modules/crud/conf/messages@ と @play/modules/secure/conf/messages@ にあるメッセージを地域化しなければならないことを意味します。 + +@conf/messages.nl@ は以下のようにします。 + +bc. # play/modules/crud (administration) +crud.title = Beheer +crud.home = Home +crud.blank = Nieuw +crud.index.title = Kies het te bewerken object +crud.index.objectType = Type object +crud.index.action = +crud.index.add = Voeg toe +crud.add = &{%s} toevoegen +crud.list.title = &{%s} +crud.list.size = %d &{%s} +crud.list.totalSize = %d totaal +crud.pagination.previous = ≪ Vorige +crud.pagination.next = Volgende ≫ +crud.pagination.last = Laatste ≫≫ +crud.pagination.first = ≪≪ Eerste +crud.show.title = &{%s} bewerken +crud.save = Opslaan +crud.saveAndContinue = Opslaan en verder bewerken +crud.cancel = Annuleren +crud.hasErrors = Corrigeer fouten a.u.b. +crud.blank.title = &{%s} toevoegen +crud.saveAndAddAnother = Opslaan en nogmaals creeren +crud.delete = &{%s} verwijderen +crud.created = &{%s} is aangemaakt +crud.saved = &{%s} is opgeslagen +crud.deleted = &{%s} is verwijderd +crud.delete.error = Kan dit object niet verwijderen +crud.search = Zoeken +crud.none = (Geen) +crud.help.required = Verplicht. +crud.help.minlength = Min. lengte is %d. +crud.help.maxlength = Max. lengte is %d. +crud.help.email = Geldig e-mailadres +crud.help.dateformat = In de vorm YYYY-MM-DD. +crud.help.numeric = Numeriek. +crud.help.min = Moet groter daan %d zijn. +crud.help.future = In de toekomst. +crud.help.past = In het verleden. +crud.help.after = Na %s. +crud.help.before = Voor %s. +crud.help.range = Tussen %d en %d + +# play/modules/secure +secure.username = Uw e-mailadres: +secure.password = Uw wachtwoord: +secure.signin = Nu inloggen + +@conf/messages.fr@ は以下のようにします。 + +bc. # play/modules/crud (administration) +crud.title = Administration +crud.home = Home +crud.blank = Nouveau +crud.index.title = Choisissez l'objet a modifier +crud.index.objectType = Type objet +crud.index.action = XXX +crud.index.add = Ajouter +crud.add = Ajouter &{%s} +crud.list.title = &{%s} +crud.list.size = %d &{%s} +crud.list.totalSize = %d total +crud.pagination.previous = ≪ Precedent +crud.pagination.next = Suivant ≫ +crud.pagination.last = Dernier ≫≫ +crud.pagination.first = ≪≪ Premier +crud.show.title = Modifier &{%s} +crud.save = Enregistrer +crud.saveAndContinue = Enregistrer et continuez a modifier +crud.cancel = Annuler +crud.hasErrors = Corrigez les erreurs s.v.p. +crud.blank.title = Ajouter &{%s} +crud.saveAndAddAnother = Enregistrer et ajouter un autre +crud.delete = Supprimer &{%s} +crud.created = &{%s} a ete cree +crud.saved = &{%s} est enregistre +crud.deleted = &{%s} est supprime +crud.delete.error = Ne peut pas supprimer l’objet +crud.search = Chercher +crud.none = (aucun) +crud.help.required = Obligatoire. +crud.help.minlength = Longeur minimum est %d. +crud.help.maxlength = Longeur maximum est %d. +crud.help.email = Adresse e-mail valide +crud.help.dateformat = En format YYYY-MM-DD. +crud.help.numeric = Numerique. +crud.help.min = Doit etre plus grand que %d. +crud.help.future = Dans le futur. +crud.help.past = Dans le passe. +crud.help.after = Apres %s. +crud.help.before = Avant %s. +crud.help.range = Entre %d et %d + +# play/modules/secure +secure.username = Votre adresse e-mail: +secure.password = Votre mot de passe: +secure.signin = Connectez-vous maintenant + +もちろん、この地域化を行ったあと、これをモジュールにフィードバックして貢献することは良い考えです。 + + +h2. 特別なクラス + +web アプリケーションを地域化していく中には、例えば JavaServer Faces のようなコンポーネントベースの web アプリケーションフレームワークを使用していた場合、実装しづらいいくつかの特別なケースがあります: + +# 属性値に使用されるパラメータ化されたメッセージ +# フォーマットされたメッセージパラメータ +# メッセージ中のリンク + +三つのケースすべてが Play では簡単であることが分かります。 + +最初のケースは、以下のようにテンプレート中の属性値にパラメータを伴う句を使いたい場合です: + +bc. + +これは JSF における問題です。通常、パラメータを置換するために XML タグを使用しますが、属性値ではこのような置換を行うことはできません。Play の構文は単純にこれを回避するので、そのまま実行することができます: + +bc. + +二番目のケースは、 @By Bob on 2009-06-14@ のような句においてメッセージパラメータを使うことで、例えば日付などの値をフォーマットしたい場合です。ここで、値のフォーマット結果を XML 属性値として使用できる必要があるにも関わらず、値をフォーマットするために XML タグが必要であるという JSF の問題が再び発生します。Play のフォーマット拡張はメッセージパラメータ構文を妨げないので、以下のように実行することができます: + +bc. &{'views.tags.display.author', _post.author.fullname, comment.postedAt.format('yyyy-MM-dd')}"} + +もちろん、フォーマットパターンを地域化することもできます: + +bc. &{'views.tags.display.author', _post.author.fullname, comment.postedAt.format(messages.get('views.dateFormat'))}"} + +三番目のケースは、 Log in to write something のようなメッセージにおいて、地域化されたメッセージの一部をハイパーリンクにしたい場合に起こります。ハイパーリンクは、そのマークアップをメッセージファイルに含められないことを意味するやり方でレンダリングされる JSF のコンポーネントであるため、この問題は JSF で起こります。一方、Play はテンプレートにプレーンな HTML を使用させるため、URL 用のパラメータを持つマークアップをそのままメッセージに指定することができます: + +bc. logIn = Log in to write something + +bc. &{'logIn', '/admin'} + +ブログエンジンアプリケーションでは、ハイパーリンクにおいて routes ファイルに基づく URL をフレームワークに生成させる構文を使用していました。 + +bc. + +メッセージパラメータで同じことをするためには、以下のようにしてください: + +bc. &{'logIn', actionBridge.Admin.index()} + + +h2. 地域化された 'Yet Another Blog Engine' + +上記の手順を適用した最終的な結果は、英語、オランダ語、およびフランス語で動作する、地域化されたバージョンの 'Yet Another Blog Engine' です。 + +!images/guide12-3! + +'Yet Another Blog Engine' のオランダ語版 (上) とフランス語版 (下) 管理インタフェースです。 + +!images/guide12-4! + +p(note). 次: チュートリアルはこれで終わりです。本質的なドキュメント - %(next)"主要な概念":main% に進んでください。 + +p(note). オリジナルは "Lunatech Research":http://www.lunatech-research.com/archives/2010/04/12/how-localise-play-framework-web-application ブログにて、"Peter Hilton":http://hilton.org.uk/about_ph.phtml により公開されました。 diff --git a/documentation/manual_ja/guide2.textile b/documentation/manual_ja/guide2.textile new file mode 100755 index 0000000000..b3d26ffe7d --- /dev/null +++ b/documentation/manual_ja/guide2.textile @@ -0,0 +1,430 @@ +h1. はじめてのモデル + +ここから、ブログエンジンのモデルを書いていきます。 + +h2. JPA 概論 + +モデル層は、Play アプリケーション (そして、実際のところはよくデザインされたすべてのアプリケーション) において中心的な位置を占めます。モデルは、アプリケーションが操作する情報のドメインに特化した表現です。ここではブログエンジンを作りたいので、モデル層は確実に User, Post そして Comment といったクラスを含むことになるでしょう。 + +モデルオブジェクトはアプリケーションを再起動する間も存続する必要があるので、これを永続化データストアに保存しなければなりません。一般的にはリレーショナルデータベースを使うことを選択します。しかしながら Java はオブジェクト指向言語なので、インピーダンスミスマッチの減少を手助けする "オブジェクト-リレーショナルマッピングツール":http://en.wikipedia.org/wiki/Object-relational_mapping を使用します。 + +"Java Persistence API":http://en.wikipedia.org/wiki/Java_Persistence_API (JPA) は O/R マッピングの標準的な API を定義する Java の仕様です。JPA 実装として、Play はよく知られた "Hibernate":http://www.hibernate.org フレームワークを使用します。Hibernate API を通して JPA を使うことの 1 つの利点は、すべての 'マッピング' が直接 Java オブジェクトに定義されることです。 + +もし Hibernate か JPA を以前に使ったことがあるなら、Play によって追加された簡潔さに驚くことでしょう。なにも設定する必要はありません; JPA は Play とともにすぐに使うことができます。 + +もし JPA を知らないのであれば、以下を続ける前に "いくつかの簡単なプレゼンテーション":http://java.sun.com/javaee/5/docs/tutorial/doc/bnbpz.html を読むことができます。 + +h2. User クラス + +User クラスを作成するところからブログエンジンのコーディングを始めましょう。新しく @/yabe/app/models/User.java@ ファイルを作成し、User クラスの最初の実装を定義します: + +bc. package models; + +import java.util.*; +import javax.persistence.*; + +import play.db.jpa.*; + +@Entity +public class User extends Model { + + public String email; + public String password; + public String fullname; + public boolean isAdmin; + + public User(String email, String password, String fullname) { + this.email = email; + this.password = password; + this.fullname = fullname; + } + +} + +@Entity アノテーションは、このクラスが管理された JPA エンティティであることを印付けし、 @Model@ スーパークラスは後述する便利な JPA ヘルパを自動的に提供します。このクラスのすべてのフィールドは、自動的にデータベースに永続化されます。 + +p(note). デフォルトでは、テーブルの名前は‘User’です。‘user’が予約語とされているデータベースを使うためにこの設定を変更する場合は、JPA マッピングに別のテーブル名を指定する必要があります。これをするためには、 @User@ クラスを @Table(name="blog_user") でアノテーションしてください。 + +p(note). モデルオブジェクトは @play.db.jpa.Model@ クラスを継承しなければならないわけではありません。素の JPA を使うこともできます。しかし、このクラスは JPA 周りの多くの部分を簡易にするので、多くのケースにおいて、これを継承するのは良い選択です。 + +もし以前に JPA を使ったことがあるなら、すべての JPA エンティティは @Id プロパティを提供しなければならないことを知っているでしょう。ここでは、Model スーパークラスが自動的に生成された数値型の id を提供しており、ほとんどの場合はこれで必要充分です。 + +p(note). @id@ フィールドを **機能的な識別子** として考えず、 **技術的な識別子** として考えてください。これらの概念を分けて扱い、自動的に生成された数値型の ID を技術的な識別子として保持するのは、一般的に良い考えです。 + +どのような経験があろうと、Java 開発者であれば public 変数を目にした途端に警告ベルがじゃんじゃん鳴り出すかもしれません。Java においては (他のオブジェクト指向言語と同様に) すべてのフィールドを private にして、アクセサとミューテータを提供するのを最も良い習慣としています。これは、オブジェクト指向デザインにおいて重要な概念であるカプセル化を促進するためのものです。実際のところ、Play はこれに対応しており、getter と setter を自動生成してカプセル化を保護します; これがどのようにして動作するのかについては、このチュートリアルの後半で紹介します。 + +これでアプリケーションのホームページをリフレッシュして結果を確認することができます。なにか間違いがない限り、実は何の変化もありません: Play は User クラスを自動的にコンパイルしてロードしていますが、これはアプリケーションに対して何の新機能も追加しません。 + +h2. はじめてのテストの作成 + +新規に作成した User クラスをテストする良い方法は、JUnit テストケースを書くことです。これによりアプリケーションをくり返し仕上げ、すべてがばっちりであることを確信できるようになります。 + +テストケースを実行するには、アプリケーションを特別な 'test' モードで起動する必要があります。いま実行しているアプリケーションを停止し、コマンドラインを開いて次のようにタイプしてください: + +bc. ~$ play test + +!images/guide2-0! + +@play test@ コマンドは、ブラウザから直接テストスイートを実行できるテストランナーモジュールをロードすること以外は @play run@ とほとんど同じです。 + +p(note). Paly アプリケーションを @test モード@ で実行すると、Play は自動的にフレームワーク ID を @test@ に切り替え、これに従った @application.conf@ ファイルをロードします。詳しくは "フレームワーク ID ドキュメント":ids を確認してください。 + +ブラウザから "http://localhost:9000/@tests":http://localhost:9000/@tests を開いてテストランナーを見てみてください。すべてのデフォルトテストを選択して実行してみてください; すべてグリーンになるはずです... しかし、これらのデフォルトテストは実際には何もテストしません。 + +!images/guide2-1! + +アプリケーションのモデル部分をテストするには、JUnit テストを使います。ご覧の通り @BasicTests.java@ は既に存在しますので、これ (@/yabe/test/BasicTest.java@) を開いてください: + +bc. import org.junit.*; +import play.test.*; +import models.*; + +public class BasicTest extends UnitTest { + + @Test + public void aVeryImportantThingToTest() { + assertEquals(2, 1 + 1); + } + +} + +役に立たないデフォルトテスト (@aVeryImportantThingToTest@) を削除して、新規にユーザを作成して検索するテストを作成してみてください: + +bc. @Test +public void createAndRetrieveUser() { + // Create a new user and save it + new User("bob@gmail.com", "secret", "Bob").save(); + + // Retrieve the user with e-mail address bob@gmail.com + User bob = User.find("byEmail", "bob@gmail.com").first(); + + // Test + assertNotNull(bob); + assertEquals("Bob", bob.fullname); +} + +ご覧の通り Model スーパークラスは 2 つの便利なメソッドを提供してくれています: @save()@ と @find()@ です + +p(note). Model クラスメソッドの更なる情報は、Play マニュアルの "JPA サポート":jpa の章で読むことができます。 + +テストランナーで @BasicTests.java@ を選択し、スタートをクリックしてすべてがグリーンであることを確認します。 + +User クラスには指定されたユーザ名とパスワードを持つユーザが存在するかチェックするメソッドが必要です。さっそく書いてテストしましょう。 + +@User.java@ ソースに @connect()@ メソッドを追加します: + +bc. public static User connect(String email, String password) { + return find("byEmailAndPassword", email, password).first(); +} + +テストケースは以下のようになります: + +bc. @Test +public void tryConnectAsUser() { + // Create a new user and save it + new User("bob@gmail.com", "secret", "Bob").save(); + + // Test + assertNotNull(User.connect("bob@gmail.com", "secret")); + assertNull(User.connect("bob@gmail.com", "badpassword")); + assertNull(User.connect("tom@gmail.com", "secret")); +} + +変更を行うたびに Play テストランナーですべてのテストを実行し、なにも壊れていないことを確認することができます。 + +h2. Post クラス + +@Post@ クラスはブログの投稿を表現します。最初の実装を書いてみましょう: + +bc. package models; + +import java.util.*; +import javax.persistence.*; + +import play.db.jpa.*; + +@Entity +public class Post extends Model { + + public String title; + public Date postedAt; + + @Lob + public String content; + + @ManyToOne + public User author; + + public Post(User author, String title, String content) { + this.author = author; + this.title = title; + this.content = content; + this.postedAt = new Date(); + } + +} + +ここでは @Lob アノテーションを使って、投稿の内容を保持する大きな文字列型データベースを使用することを JPA に告げています。 @User@ クラスとの関連を @ManyToOne を使って宣言しました。これはそれぞれの @Post@ は 1 つの @User@ によって所有され、各 @User@ は複数の @Post@ インスタンスを所有することができることを意味します。 + +p(note). 最近の PostgreSQL のバージョンは @Type(type = "org.hibernate.type.TextType") というアノテーションをフィールドにつけていない限り、 @Log アノテーションがついた @String@ フィールドをテキストとしてストアしません。 + +@Post@ クラスが期待通りに動作することを確認する新しいテストケースを作成しましょう。ただし、より多くのテストを書く前に JUnit クラスでやらなければならないことがあります。現在のテストでは、データベースの内容は決して削除されませんので、新たに実行するごとにオブジェクトがどんどん作成されます。これは、すべてのオブジェクトが適切であることを確認するためにオブジェクトを数え上げるようなより高度なテストを始めた途端に問題となるでしょう。 + +このため、各テストの前にデータベースを削除する JUnit @setup()@ メソッドを書きましょう: + +bc. public class BasicTest extends UnitTest { + + @Before + public void setup() { + Fixtures.deleteDatabase(); + } + + … +} + +p(note). この @Before は JUnit テストツールの主要な概念です。 + +見ての通り、 @Fixtures@ クラスはテスト中にデータベースを扱うヘルパです。再度テストを実行して何もおかしくなっていないことを確認し、次のテストを書き始めましょう: + +bc. @Test +public void createPost() { + // Create a new user and save it + User bob = new User("bob@gmail.com", "secret", "Bob").save(); + + // Create a new post + new Post(bob, "My first post", "Hello world").save(); + + // Test that the post has been created + assertEquals(1, Post.count()); + + // Retrieve all posts created by Bob + List bobPosts = Post.find("byAuthor", bob).fetch(); + + // Tests + assertEquals(1, bobPosts.size()); + Post firstPost = bobPosts.get(0); + assertNotNull(firstPost); + assertEquals(bob, firstPost.author); + assertEquals("My first post", firstPost.title); + assertEquals("Hello world", firstPost.content); + assertNotNull(firstPost.postedAt); +} + +p(note). @java.util.List@ をインポートすることを **決して忘れない** でください。さもないとコンパイルエラーが発生します。 + +h2. コメントの追加 + +このはじめてのモデルの設計に最後に追加するのは、投稿にコメントを添付する機能です。 + +@Comment@ クラスの作成はとても単純です。 + +bc. package models; + +import java.util.*; +import javax.persistence.*; + +import play.db.jpa.*; + +@Entity +public class Comment extends Model { + + public String author; + public Date postedAt; + + @Lob + public String content; + + @ManyToOne + public Post post; + + public Comment(Post post, String author, String content) { + this.post = post; + this.author = author; + this.content = content; + this.postedAt = new Date(); + } + +} + +最初のテストケースを書きましょう: + +bc. @Test +public void postComments() { + // Create a new user and save it + User bob = new User("bob@gmail.com", "secret", "Bob").save(); + + // Create a new post + Post bobPost = new Post(bob, "My first post", "Hello world").save(); + + // Post a first comment + new Comment(bobPost, "Jeff", "Nice post").save(); + new Comment(bobPost, "Tom", "I knew that !").save(); + + // Retrieve all comments + List bobPostComments = Comment.find("byPost", bobPost).fetch(); + + // Tests + assertEquals(2, bobPostComments.size()); + + Comment firstComment = bobPostComments.get(0); + assertNotNull(firstComment); + assertEquals("Jeff", firstComment.author); + assertEquals("Nice post", firstComment.content); + assertNotNull(firstComment.postedAt); + + Comment secondComment = bobPostComments.get(1); + assertNotNull(secondComment); + assertEquals("Tom", secondComment.author); + assertEquals("I knew that !", secondComment.content); + assertNotNull(secondComment.postedAt); +} + +**Post** と **Comments** 間のナビゲーションがとても簡単ではないことが分かると思います: @Post@ に添付されたすべてのコメントを検索するクエリが必要です。これは @Post@ クラスに関連の逆端を設定することで、もっとうまくやることができます。 + +@Post@ クラスに @comments@ フィールドを追加します: + +bc. ... +@OneToMany(mappedBy="post", cascade=CascadeType.ALL) +public List comments; + +public Post(User author, String title, String content) { + this.comments = new ArrayList(); + this.author = author; + this.title = title; + this.content = content; + this.postedAt = new Date(); +} +... + +@mappedBy@ 属性を使って、 @Comment@ クラスの post フィールドが関連を管理するという側面を持っていることを JPA に伝えていることに注意してください。JPA で双方向の関連を定義する場合、どちらの端が関連を管理するかを JPA に伝えるのはとても重要です。この場合、 @Comments@ は @Post@ に従属するので、 @Comment@ クラスが関連を管理するほうがベターです。 + +@cascade@ プロパティを設定して、JPA に @Post@ の削除を @comments@ にも連鎖するよう告げています。このようにすると、投稿を削除した場合、すべての関連するコメントが同様に削除されます。 + +この新しい関連に伴って、コメントを追加するシンプルなヘルパメソッドを @Post@ クラスに追加します: + +bc. public Post addComment(String author, String content) { + Comment newComment = new Comment(this, author, content).save(); + this.comments.add(newComment); + this.save(); + return this; +} + +これが動作することを確認する別のテストケースを書きましょう: + +bc. @Test +public void useTheCommentsRelation() { + // Create a new user and save it + User bob = new User("bob@gmail.com", "secret", "Bob").save(); + + // Create a new post + Post bobPost = new Post(bob, "My first post", "Hello world").save(); + + // Post a first comment + bobPost.addComment("Jeff", "Nice post"); + bobPost.addComment("Tom", "I knew that !"); + + // Count things + assertEquals(1, User.count()); + assertEquals(1, Post.count()); + assertEquals(2, Comment.count()); + + // Retrieve Bob's post + bobPost = Post.find("byAuthor", bob).first(); + assertNotNull(bobPost); + + // Navigate to comments + assertEquals(2, bobPost.comments.size()); + assertEquals("Jeff", bobPost.comments.get(0).author); + + // Delete the post + bobPost.delete(); + + // Check that all comments have been deleted + assertEquals(1, User.count()); + assertEquals(0, Post.count()); + assertEquals(0, Comment.count()); +} + +グリーンになりましたか? + +!images/guide2-2! + +h2. Fixture を使ったより複雑なテスト + +より複雑なテストを書き始める場合、しばしばテストに使うデータセットが必要になります。Fixtures は、モデルを "YAML":http://en.wikipedia.org/wiki/Yaml ファイルに記述して、テストを実行する前にはいつでもロードできるようにします。 + +@/yabe/test/data.yml@ ファイルを編集して User を定義してみましょう: + +bc. +User(bob): + email: bob@gmail.com + password: secret + fullname: Bob + +... + + +ああ、 @data.yml@ ファイルは少々大きいので、"ここからダウンロード":files/data.yml することができます。 + +このデータをロードして、それについていくつかのアサーションを実行するテストを作成してみましょう: + +bc. @Test +public void fullTest() { + Fixtures.loadModels("data.yml"); + + // Count things + assertEquals(2, User.count()); + assertEquals(3, Post.count()); + assertEquals(3, Comment.count()); + + // Try to connect as users + assertNotNull(User.connect("bob@gmail.com", "secret")); + assertNotNull(User.connect("jeff@gmail.com", "secret")); + assertNull(User.connect("jeff@gmail.com", "badpassword")); + assertNull(User.connect("tom@gmail.com", "secret")); + + // Find all of Bob's posts + List bobPosts = Post.find("author.email", "bob@gmail.com").fetch(); + assertEquals(2, bobPosts.size()); + + // Find all comments related to Bob's posts + List bobComments = Comment.find("post.author.email", "bob@gmail.com").fetch(); + assertEquals(3, bobComments.size()); + + // Find the most recent post + Post frontPost = Post.find("order by postedAt desc").first(); + assertNotNull(frontPost); + assertEquals("About the model layer", frontPost.title); + + // Check that this post has two comments + assertEquals(2, frontPost.comments.size()); + + // Post a new comment + frontPost.addComment("Jim", "Hello guys"); + assertEquals(3, frontPost.comments.size()); + assertEquals(4, Comment.count()); +} + +p(note). Play と YAML については、 "YAML マニュアルページ":yaml で詳しく読むことができます。 + +h2. 作業内容の保存 + +ここまででブログエンジンの大きな部分をやり終えました。これらを作成し、すべてテストしており、web アプリケーションそれ自身の開発を始めることができます。 + +しかし、開発を続ける前に作業内容を Bazaar を使って保存しましょう。コマンドラインを開いて @bzr st@ とタイプし、最後のコミットから行われた変更内容を確認してください: + +bc. $ bzr st + +見ての通り、いくつかのファイルはバージョン管理されていません。 @test-result@ フォルダはバージョン管理する必要がないので、これは無視しましょう。 + +bc. $ bzr ignore test-result + +その他のファイルは @bzr add@ を使って追加します。 + +bc. $ bzr add + +これでプロジェクトをコミットすることができます。 + +bc. $ bzr commit -m "The model layer is ready" + +p(note). %(next)"はじめての画面":guide3% に進みましょう。 \ No newline at end of file diff --git a/documentation/manual_ja/guide3.textile b/documentation/manual_ja/guide3.textile new file mode 100755 index 0000000000..dcb716a18a --- /dev/null +++ b/documentation/manual_ja/guide3.textile @@ -0,0 +1,328 @@ +h1. はじめての画面 + +はじめてのデータモデルを作ったので、いよいよこのアプリケーションのはじめての画面を作り始めます。この画面はもっとも新しい投稿のみを表示し、古い投稿はリストとして表示します。 + +以下は、実現したい画面のモックです: + +!images/guide-mock1! + +h2. デフォルトデータでの起動 + +実は、はじめての画面をコーディングする前にやらなければならないことが、もうひとつあります。テストデータなしに web アプリケーションに関する作業をするのは楽しくありません。テストすることすらできません。しかし、まだ投稿画面を開発していないので、自分自身でブログに投稿を追加することもできません。 + +ブログにデフォルトデータを投入するひとつの方法は、アプリケーション起動時にフィクスチャファイルを読み込むことです。これを行うために、Bootstrap ジョブを作りましょう。Play のジョブは一切の HTTP リクエスト無しに、例えばアプリケーション起動時や、CRON ジョブを使用した指定間隔毎に、自分自身で起動します。 + +@Fixtures@ を使ってデフォルトデータをロードする @/yabe/app/Bootstrap.java@ ジョブを作成しましょう: + +bc. import play.*; +import play.jobs.*; +import play.test.*; + +import models.*; + +@OnApplicationStart +public class Bootstrap extends Job { + + public void doJob() { + // Check if the database is empty + if(User.count() == 0) { + Fixtures.loadModels("initial-data.yml"); + } + } + +} + +このジョブをアプリケーションの起動と同期して実行したい旨を Play に伝えるために、 @OnApplicationStart アノテーションでこのジョブを注釈しました。 + +p(note). 実際のところ、このジョブは DEV モードと PROD モードでは異なる動作をします。DEV モードの場合、Play は最初のリクエストがあるまでアプリケーションの起動を待機します。このため、このジョブは最初のリクエストと同期して実行されます。この方法では、ジョブが失敗した場合、ブラウザにエラーメッセージが表示されます。一方、PROD モードでは、このジョブはアプリケーションの起動時に ( @play run@ コマンドと同期して) 実行され、エラーが発生した場合はアプリケーションの起動を停止します。 + +@/yabe/conf@ ディレクトリに @initial-data.yml@ を作成する必要があります。もちろん、以前にテストで使用した @data.yml@ の内容を再利用することもできます。 + +それでは @play run@ を使ってアプリケーションを実行し、ブラウザでページ "http://localhost:9000":http://localhost:9000 を表示してみましょう。 + +h2. ブログトップページ + +今度こそ本当にトップページのコーディングを始められます。 + +最初の画面がどのようにして表示されるのか覚えていますか? まず最初に、routes ファイルで @/@ という URL が @controllers.Application.index()@ というアクションメソッドを起動するよう指定します。次に、このメソッドが @render()@ を呼び出して @/yabe/app/views/Application/index.html@ テンプレートを実行します。 + +これらのコンポーネントを使いつつ、投稿リストをロードして表示するコードを追加します。 + +@/yabe/app/controllers/Application.java@ コントローラを開いて、投稿リストをロードするよう @index()@ アクションを以下のように変更します: + +bc. package controllers; + +import java.util.*; + +import play.*; +import play.mvc.*; + +import models.*; + +public class Application extends Controller { + + public static void index() { + Post frontPost = Post.find("order by postedAt desc").first(); + List olderPosts = Post.find( + "order by postedAt desc" + ).from(1).fetch(10); + render(frontPost, olderPosts); + } + +} + +どのようにして @render@ メソッドにオブジェクトを渡すか分かりましたか? これでテンプレートからこれらのオブジェクトに同じ名前でアクセスすることができます。この場合はテンプレートで変数 @frontPost@ と @olderPosts@ が利用できます。 + +@/yabe/app/views/Application/index.html@ を開いて、これらのオブジェクトを表示するよう変更します: + +bc. #{extends 'main.html' /} +#{set title:'Home' /} + +#{if frontPost} +
+

+ ${frontPost.title} +

+ +
+ ${frontPost.content.nl2br()} +
+
+ + #{if olderPosts} +
+

Older posts from this blog

+ + #{list items:olderPosts, as:'oldPost'} +
+

+ ${oldPost.title} +

+ +
+ #{/list} +
+ + #{/if} + +#{/if} + +#{else} +
+ There is currently nothing to read here. +
+#{/else} + +テンプレートがどのように動作しているのかについて、"テンプレートの章":templates で読むことができます。基本的に、テンプレートを使うと java オブジェクトに動的にアクセスすることができます。その背後では Groovy を使います。( **?:** 演算子のような) かわいらしい構成要素のほとんどは Groovy から来たものです。しかし Play のテンプレートを書くために本気で Groovy の勉強をする必要はありません。JSP や JSTL のようなテンプレート言語に既に慣れているのであれば、迷うことはないでしょう。 + +OK, それではブログトップページをリフレッシュしてみましょう。 + +!images/guide3-0! + +きれいな画面ではありませんが、動いています! + +しかし、もう既にコードの重複が始まっているのが見られます。投稿をいくつかの方法 (全文、全文とコメント、前文) で表示したいので、いくつかの画面から呼び出すことのできる関数のようなものを作るべきです。これはまさに Play タグでできることです! + +タグを作るためには、ただ @/yabe/app/views/tags/display.html@ ファイルを作成してください。タグは単なる別のテンプレートです。タグは (関数のように) 引数を持ちます。 @#{display /}@ タグはふたつの引数を持ちます: 表示する Post オブジェクトと、'home', 'teaser', または 'full' のうちいずれかひとつの表示モードです。 + +bc. *{ Display a post in one of these modes: 'full', 'home' or 'teaser' }* + +
+

+ ${_post.title} +

+ + #{if _as != 'teaser'} +
+
Detail:
+ ${_post.content.nl2br()} +
+ #{/if} +
+ +#{if _as == 'full'} +
+

+ ${_post.comments.size() ?: 'no'} + comment${_post.comments.size().pluralize()} +

+ + #{list items:_post.comments, as:'comment'} +
+ +
+
Detail:
+ ${comment.content.escape().nl2br()} +
+
+ #{/list} + +
+#{/if} + +これで、このタグを使うことでトップページをコードの重複無しに書き直すことができます: + +bc. #{extends 'main.html' /} +#{set title:'Home' /} + +#{if frontPost} + + #{display post:frontPost, as:'home' /} + + #{if olderPosts.size()} + +
+

Older posts from this blog

+ + #{list items:olderPosts, as:'oldPost'} + #{display post:oldPost, as:'teaser' /} + #{/list} +
+ + #{/if} + +#{/if} + +#{else} +
+ There is currently nothing to read here. +
+#{/else} + +ページをリロードして、すべてがうまく行っていることを確認してください。 + +h2. レイアウトの改善 + +見ての通り、 @index.html@ テンプレートは @main.html@ を継承します。すべてのブログページに共通の、ブログタイトルと認証リンクを含んだレイアウトを提供したいので、このファイルを編集する必要があります。 + +@/yabe/app/views/main.html@ ファイルを次のように編集します: + +bc. + + + #{get 'title' /} + + + + + + + + +
+ #{doLayout /} +
+ + + + + + +ページを更新して結果を確認してください。 @blogTitle@ と @blogBaseLine@ 変数が表示されないことを除けば、動作しているように見えます。これは、 @render(...)@ 呼び出しにこれらオブジェクトを引き渡していないことが原因です。もちろん @index@ アクションで @render()@ 呼び出しにこれらのオブジェクトを追加することもできます。しかし、 @main.html@ ファイルはアプリケーションのすべてのアクションにおいて主要なテンプレートとして使われるので、これらのオブジェクトを毎回追加したくありません。 + +あるコントローラ (もしくはコントローラ階層) のそれぞれのアクションで同じコードを実行する 1 つの方法は、 @Before インターセプタを定義することです。 + +Application コントローラに @addDefaults()@ メソッドを追加しましょう: + +bc. @Before +static void addDefaults() { + renderArgs.put("blogTitle", Play.configuration.getProperty("blog.title")); + renderArgs.put("blogBaseline", Play.configuration.getProperty("blog.baseline")); +} + +p(note). @Application.java@ ファイル内で @play.Play@ をインポートする必要があります。 + +すべての変数はテンプレートから利用することができる @renderArgs@ スコープに追加されます。そして、ご覧の通り、このメソッドは @Play.configuration@ オブジェクトから変数の値を読み込みます。このオブジェクトは @/yabe/conf/application.conf@ ファイルにあるすべての設定キーを保持します。 + +設定ファイルにふたつのキーを追加します: + +bc. # Blog engine configuration +# ~~~~~ +blog.title=Yet another blog +blog.baseline=We will write about nothing + +トップページをリロードして、これが動いていることを確認しましょう! + +!images/guide3-1! + +h2. いくつかのスタイルの追加 + +ここまでで、ブログのトップページはほとんど出来上がりましたが、あまりきれいではありません。これをより輝かせるいくつかのスタイルを追加します。見てのとおり、中心的なテンプレートファイルである main.html は @/public/stylesheets/main.css@ スタイルシートを読み込みます。このファイルをそのまま使い、いくつかスタイルを追加します。 + +スタイルは "ここからダウンロード":files/main.css して @/public/stylesheets/main.css@ ファイルにコピーすることもできます。 + +トップページを更新すると、今度はスタイルの適用されたページが表示されます。 + +!images/guide3-2! + +h2. 作業内容のコミット + +ブログのトップページは完成しました。いつもどおりこのバージョンのブログを bazaar にコミットすることができます: + +bc. $ bzr st +$ bzr add +$ bzr commit -m "Home page" + +p(note). 次: %(next)"コメント投稿ページ":guide4% diff --git a/documentation/manual_ja/guide4.textile b/documentation/manual_ja/guide4.textile new file mode 100755 index 0000000000..014ce168e7 --- /dev/null +++ b/documentation/manual_ja/guide4.textile @@ -0,0 +1,244 @@ +h1. コメントの閲覧と投稿 + +ここまででブログトップページが設置されたので、続けて投稿の詳細ページを書いていきます。このページはその投稿に関するすべてのコメントを表示し、また、新しいコメントを投稿するためのフォームを含みます。 + +h2. 'show' アクションの作成 + +投稿の詳細ページを表示するためには、 @Application@ コントローラに新しいアクションが必要です。これを @show()@ アクションと呼びましょう: + +bc. public static void show(Long id) { + Post post = Post.findById(id); + render(post); +} + +見ての通り、このアクションはとてもシンプルです。HTTP の @id@ パラメータを Java の @Long@ オブジェクトとして自動的に検索できるよう、 @id@ メソッド引数を宣言します。このパラメータは、クエリ文字列か URL パス、またはリクエストボディから抽出されます。 + +p(note). 妥当な数値でない HTTP パラメータ @id@ を送信しようとした場合、変数 @id@ は @null@ になり、Play は自動的に @errors@ コンテナにバリデーションエラーを追加します。 + +このアクションは @/yabe/app/views/Application/show.html@ テンプレートを表示します: + +bc. #{extends 'main.html' /} +#{set title:post.title /} + +#{display post:post, as:'full' /} + +すでに @display@ タグを作成してあるので、このページはとてもシンプルに書くことができます。 + +h2. 詳細ページへのリンクの追加 + +display タグのすべてのリンクは ( @#@ を使うことで) 空のままにしてあります。ここで、これらのリンクが @Application.show@ アクションを指すようにします。Play ではテンプレートにおいて @{…} 識別子 を使うことで容易にリンクを構築することができます。この構文は、ルータを使って指定したアクションを呼び出すために必要な URL を 'リバース' します。 + +@/yabe/app/views/tags/display.html@ タグを編集しましょう: + +bc. … +

+ ${_post.title} +

+… + +トップページを更新したら、投稿のタイトルをクリックして詳細ページを表示してください。 + +!images/guide4-0! + +よく出来ていますが、トップページへ戻るためのリンクがありません。 @/yabe/app/views/main.html@ テンプレートを編集して、タイトルへのリンクを完成させます: + +bc. … +
+ About this blog +

${blogTitle}

+

${blogBaseline}

+
+… + +これで、トップページと投稿詳細ページ間を遷移することができるようになりました。 + +h2. より良い URL の指定 + +見ての通り、投稿詳細画面の URL は以下のようになっています: + +bc. /application/show?id=1 + +これは Play が 'catch all' ルートを使ったためです。 + +bc. * /{controller}/{action} {controller}.{action} + +@Application.show@ アクションのためのカスタムパスを指定することで、より良い URL にすることができます。 @/yabe/conf/routes@ ファイルを編集して、先頭行の後に以下のルートを追加してください: + +bc. GET /posts/{id} Application.show + +p(note). この方法では、 @id@ パラメータは URL パスから抽出されます。URI パターンについては、 "ルートファイル構文":routes#syntax に関するマニュアルページで、より詳しく読むことができます。 + +ブラウザを更新して、今度は適切な URL が使用されることを確認してください。 + +h2. ページングの追加 + +ユーザが投稿を通じて容易に遷移できるようにするために、ページング機能を追加します。必要に応じて前や後の投稿を取って来られるよう Post クラスを拡張します: + +bc. public Post previous() { + return Post.find("postedAt < ? order by postedAt desc", postedAt).first(); +} + +public Post next() { + return Post.find("postedAt > ? order by postedAt asc", postedAt).first(); +} + +あるリクエストにおいて、これらのメソッドを何度か呼ぶことになるため、これらを最適化するのもいいのですが、今のところはこれで充分です。さて、 @show.html@ テンプレートの上部 ( @#{display/}@ タグの前) にページングのリンクを追加してください: + +bc. + +ぐっと良くなりました。 + +h2. コメントフォームの追加 + +いよいよコメントフォームを設置します。Application コントローラに @postComment@ アクションメソッドを追加することから始めましょう。 + +bc. public static void postComment(Long postId, String author, String content) { + Post post = Post.findById(postId); + post.addComment(author, content); + show(postId); +} + +見ての通り、以前に Post クラスに追加した @addComment()@ メソッドを再利用しただけです。 + +@show.html@ テンプレート ( @#{display /}@ タグのすぐ後) に HTML フォームを書きましょう: + +bc.

Post a comment

+ +#{form @Application.postComment(post.id)} +

+ + +

+

+ + +

+

+ +

+#{/form} + +これで、新しいコメントの投稿を試すことができるようになりました。ばっちり動くはずです。 + +!images/guide4-1! + +h2. バリデーションの追加 + +現状、コメントを作成する前にフォームの内容の妥当性を確認していません。いずれのフィールドも入力必須にしたいと思います。Play のバリデーション機能を使って、HTTP パラメータが適切に入力されていることを簡単に保証することができます。 @postComment@ アクションに @Required アノテーションを追加して、エラーが発生していないことを確認するよう変更します: + +bc. public static void postComment(Long postId, @Required String author, @Required String content) { + Post post = Post.findById(postId); + if (validation.hasErrors()) { + render("Application/show.html", post); + } + post.addComment(author, content); + show(postId); +} + +p(note). @play.data.validation.*@ をインポートすることも **忘れないでください** 。 + +見ての通り、バリデーションエラーが発生した場合、投稿詳細画面を再度表示します。フォームのコードを、エラーメッセージを表示するよう変更しなければなりません: + +bc.

Post a comment

+ +#{form @Application.postComment(post.id)} + + #{ifErrors} +

+ All fields are required! +

+ #{/ifErrors} + +

+ + +

+

+ + +

+

+ +

+#{/form} + +HTML input の値を設定するために、投稿されたパラメータを再利用していることに注意してください。 + +投稿者により親切な UI フィードバックを提供するために、エラーが発生した場合には、自動的にコメントフォームにフォーカスをセットする、ちょっとした JavaScript を追加します。このスクリプトは "JQuery":files/jquery-1.4.2.min.js と "JQuery Tools Expose":http://cdn.jquerytools.org/1.2.5/full/jquery.tools.min.js を補助ライブラリとして使用するので、これらを include する必要があります。これら 2 つのライブラリを @/yabe/public/javascripts@ にダウンロードしたら、これらを include するよう @main.html@ を変更してください: + +bc. … + + + + +p(note). 現在のバージョンの Play には、このチュートリアルで使用しているものより新しいバージョンの jQuery がバンドルされていることに注意してください。 + +これで以下のスクリプトを @show.html@ テンプレートに追加することが出来ます。 (ページの最後に追加してください): + +bc. + +!images/guide4-2! + +コメントフォームがずいぶんクールになりました。あと二つ追加しましょう。 + +最初に、コメントが正常に投稿されたら成功メッセージを表示するようにします。これをするために、ひとつのアクション呼び出しから次の呼び出しにメッセージを引き渡すことができる flash スコープを使います。 + +成功メッセージを追加するように @postComment@ アクションを修正します: + +bc. public static void postComment(Long postId, @Required String author, @Required String content) { + Post post = Post.findById(postId); + if(validation.hasErrors()) { + render("Application/show.html", post); + } + post.addComment(author, content); + flash.success("Thanks for posting %s", author); + show(postId); +} + +そして、もし成功メッセージが存在する場合は @show.html@ にこれを表示します (ページの最上部に追加します) 。 + +bc. … +#{if flash.success} +

${flash.success}

+#{/if} + +#{display post:post, as:'full' /} +… + +!images/guide4-3! + +最後は @postComment@ アクションに使われる URL を整えます。特にルートを定義していないので、このアクションはいつもどおり全てをキャッチするルートを使用します。そこで、以下のルートをアプリケーションのルートファイルに追加します: + +bc. POST /posts/{postId}/comments Application.postComment + +以上です。いつものようにこのバージョンを bazaar にコミットします。 + +p(note). 次: %(next)"キャプチャの設定":guide5%. diff --git a/documentation/manual_ja/guide5.textile b/documentation/manual_ja/guide5.textile new file mode 100644 index 0000000000..28e816b8b2 --- /dev/null +++ b/documentation/manual_ja/guide5.textile @@ -0,0 +1,138 @@ +h1. キャプチャの設定 + +我々のブログエンジンは誰でもコメントを投稿することができるので、自動化されたスパムを避けるためにちょっと保護してあげるべきです。スパムからフォームを守るシンプルな方法は、"captcha":http://en.wikipedia.org/wiki/Captcha 画像を追加することです。 + +h2. キャプチャ画像の生成 + +Play を使うと、どれだけ簡単にキャプチャ画像を生成できるかを見るところから始めましょう。これまでのように HTML レスポンスを返す代わりに、バイナリストリームを返すことを除けば、新たに別のアクションを使用するだけです。 + +Play は **フルスタック** な web フレームワークなので、web アプリケーションで一般的に必要なものは組込みで含めています; キャプチャの生成はそのうちの 1 つです。 @play.libs.Images@ ユーティリティを使って容易にキャプチャ画像を生成することが可能であり、その後、HTTP レスポンスにそれを書き出します。 + +いつも通り、シンプルな実装から始めましょう。 @Application@ コントローラに @captcha@ アクションを追加してください: + +bc. public static void captcha() { + Images.Captcha captcha = Images.captcha(); + renderBinary(captcha); +} + +Images.Captcha クラスは java.io.InputStream を実装するため、キャプチャオブジェクトを直接 renderBinary() メソッドに渡せることに注意してください。 + +p(note). @play.libs.*@ をインポートすることを **忘れないでください** 。 + +ここで、 @/yabe/conf/routes@ ファイルに新しいルートを追加します: + +bc. GET /captcha Application.captcha + +そして、"http://localhost:9000/captcha":http://localhost:9000/captcha を開いて @captcha@ アクションを試してください。 + +!images/guide5-1! + +画面をリフレッシュする度に、ランダムな文字列を生成するはずです。 + +h2. 状態を管理する方法 + +これまでは簡単でしたが、もっとも複雑な部分がやって来ます。キャプチャの妥当性を検証するためには、キャプチャ画像に書き込まれたランダム文字列をどこかに保存して、フォームが投稿されたタイミングでこの文字列を確認する必要があります。 + +もちろん、単純に画像を生成するときに文字列をユーザセッションに設定し、あとから検索することも可能です。しかし、この方法には 2 つの欠点があります: + +**はじめに** 、Play のセッションは cookie として保存されます。これはアーキテクチャ上の多くの問題を解決しますが、様々な影響も及ぼします。cookie に書き込まれたデータは、署名はされています (そのため、ユーザはこれを変更できません) が、暗号化はされていません。キャプチャのコードをセッションに書き込んでしまうと、セッション cookie を読むことで誰でも簡単にこれを解読することができてしまいます。 + +**続いて** 、Play が **ステートレス** なフレームワークであることを思い出してください。Play は、物事を純粋にステートレスな方法で管理します。典型的な例としては、もしユーザが同時に 2 つの異なるキャプチャを持つ 2 つの異なるブログページを開いた場合、何が起こるでしょうか? フォームごとにキャプチャコードを追跡しなければならなくなります。 + +このため、この問題を解決するために 2 つのことをしなければなりません。キャプチャの秘密鍵をサーバ側に保存します。これは一時的なデータなので、Play **キャッシュ** を容易に使用することができます。さらに、キャッシュされたデータの生存期間は限られているので、セキュリティ機能が 1 つ追加されることになります (キャプチャコードは 10 分間のみ有効になるようにしましょう) 。そして、このコードを後から解決するために、 **ユニーク ID** を生成する必要があります。このユニーク ID は、それぞれのフォームに hidden フィールドとして追加され、生成されたキャプチャコードを暗黙的に参照します。 + +この方法で、状態に関する問題をエレガントに解決することができます。 + +@captcha@ アクションを以下のように変更してください: + +bc. public static void captcha(String id) { + Images.Captcha captcha = Images.captcha(); + String code = captcha.getText("#E4EAFD"); + Cache.set(id, code, "10mn"); + renderBinary(captcha); +} + +@getText()@ メソッドがどのような色でも引数として受け取ることに注意してください。この色は文字列を描画するのに使用されます。 + +p(note). @play.cache.*@ をインポートするのを **忘れないでください** 。 + +h2. コメントフォームへのキャプチャ画像の追加 + +それでは、コメントフォームを表示する前にユニーク ID を生成しましょう。その後、この ID を使ってキャプチャ画像を統合するよう HTML フォームを変更し、ID は別の hidden フィールドに追加します。 + +@Application.show@ アクションを書き換えましょう: + +bc. public static void show(Long id) { + Post post = Post.findById(id); + String randomID = Codec.UUID(); + render(post, randomID); +} + +@/yable/app/views/Application/show.html@ テンプレートに含まれるフォームは、以下のようになります: + +bc. ... +

+ + +

+

+ + +
+ + +

+

+ +

+... + +よいスタートです。これでコメントフォームにキャプチャ画像が付きました。 + +!images/guide5-2! + +h2. キャプチャのバリデーション + +あとはキャプチャの妥当性を検証するだけです。 @randomID@ を hidden フィールドとして追加しましたよね? このため、 @postComment@ アクションからこれを検索して、その後 Cache から実際のコードを検索して、これを投稿されたコードと最終的に比較することができます。 + +そんなに難しくありません。 @postComment@ アクションを変更しましょう。 + +bc. public static void postComment( + Long postId, + @Required(message="Author is required") String author, + @Required(message="A message is required") String content, + @Required(message="Please type the code") String code, + String randomID) +{ + Post post = Post.findById(postId); + validation.equals( + code, Cache.get(randomID) + ).message("Invalid code. Please type it again"); + if(validation.hasErrors()) { + render("Application/show.html", post, randomID); + } + post.addComment(author, content); + flash.success("Thanks for posting %s", author); + Cache.delete(randomID); + show(postId); +} + +エラーメッセージが増えたので、 @show.html@ テンプレートへのエラー表示方法を変更してください (ええ、今のところ最初のエラーだけを表示します。これで充分です): + +bc. .. +#{ifErrors} +

+ ${errors[0]} +

+#{/ifErrors} +... + +p(note). 一般的に、もっと複雑なフォームの場合、エラーメッセージはこのようなやり方では管理されず、 @messages@ ファイルに外出しにされ、それぞれのエラーは関連するフィールドの隣に出力されます。 + +今や完全に機能するキャプチャを確認してください。 + +!images/guide5-3! + +いいですね! + +p(note). 次: %(next)"タグ機能のサポート":guide6% diff --git a/documentation/manual_ja/guide6.textile b/documentation/manual_ja/guide6.textile new file mode 100644 index 0000000000..675d7e8b08 --- /dev/null +++ b/documentation/manual_ja/guide6.textile @@ -0,0 +1,290 @@ +h1. タグ機能のサポート + +ブログがたくさんの投稿を含むにつれて、投稿を探すことはどんどん難しくなります。投稿をテーマごとに分類できるようにするために、タグ機能のサポートを追加しましょう。 + +h2. Tag モデルオブジェクト + +ブログのモデル定義にもうひとつオブジェクトを追加します。この @Tag@ クラス自体は本当にとてもシンプルです: + +bc. package models; + +import java.util.*; +import javax.persistence.*; + +import play.db.jpa.*; + +@Entity +public class Tag extends Model implements Comparable { + + public String name; + + private Tag(String name) { + this.name = name; + } + + public String toString() { + return name; + } + + public int compareTo(Tag otherTag) { + return name.compareTo(otherTag.name); + } +} + +遅延タグ生成のようなものが欲しいので、タグの取得は常に @findOrCreateByName(String name)@ ファクトリメソッドを使うことにします。これを @Tag@ クラスに追加しましょう: + +bc. public static Tag findOrCreateByName(String name) { + Tag tag = Tag.find("byName", name).first(); + if(tag == null) { + tag = new Tag(name); + } + return tag; +} + +h2. 投稿のタグ付け + +それでは、いよいよ新しい @Tag@ モデルを @Post@ に紐付けます。 @Post@ クラスに適切な関連を追加しましょう: + +bc. … +@ManyToMany(cascade=CascadeType.PERSIST) +public Set tags; + +public Post(User author, String title, String content) { + this.comments = new ArrayList(); + this.tags = new TreeSet(); + this.author = author; + this.title = title; + this.content = content; + this.postedAt = new Date(); +} +… + +p(note). タグリストを予測できる順序 (実際は、上記の compareTo の実装によってアルファベット順) に保つために @TreeSet@ を使用することに注意してください。 + +この関連は単方向のままにします。 + +タグ管理をシンプルにするため、ヘルパメソッドをひと揃え追加しましょう。最初の 1 つは、 @Post@ をタグ付けする機能です: + +bc. … +public Post tagItWith(String name) { + tags.add(Tag.findOrCreateByName(name)); + return this; +} +… + +次のメソッドは指定したタグで全ての投稿を検索するメソッドです。: + +bc. … +public static List findTaggedWith(String tag) { + return Post.find( + "select distinct p from Post p join p.tags as t where t.name = ?", tag + ).fetch(); +} +… + +そろそろこれらをテストする新しいテストケースを書く頃合です。以下をタイプして、サーバを @test@ モードで再起動してください: + +bc. $ play test + +そして @BasicTest@ クラスに新しい @Test を追加してください: + +bc. @Test +public void testTags() { + // Create a new user and save it + User bob = new User("bob@gmail.com", "secret", "Bob").save(); + + // Create a new post + Post bobPost = new Post(bob, "My first post", "Hello world").save(); + Post anotherBobPost = new Post(bob, "Hop", "Hello world").save(); + + // Well + assertEquals(0, Post.findTaggedWith("Red").size()); + + // Tag it now + bobPost.tagItWith("Red").tagItWith("Blue").save(); + anotherBobPost.tagItWith("Red").tagItWith("Green").save(); + + // Check + assertEquals(2, Post.findTaggedWith("Red").size()); + assertEquals(1, Post.findTaggedWith("Blue").size()); + assertEquals(1, Post.findTaggedWith("Green").size()); +} + +きちんと動作することを確認してください。 + +h2. ちょっと難しくなってきました + +まあ、今すぐこれをブログで使うつもりはありませんが、いくつかの指定されたタグでタグ付けをされた投稿を検索するとしたら、どうするのでしょうか? それは見かけより難しいです。 + +いくつかの web プロジェクトで使うことになると思うので、必要な JPQL を以下に提供します: + +bc. … +public static List findTaggedWith(String... tags) { + return Post.find( + "select distinct p from Post p join p.tags as t where t.name in (:tags) group by p.id, p.author, p.title, p.content,p.postedAt having count(t.id) = :size" + ).bind("tags", tags).bind("size", tags.length).fetch(); +} +… + +トリッキーなのは、ジョインしたビューから **すべてのタグ** を持つ投稿をフィルタするために @having count@ 構文を使う必要がある点です。 + +p(note). @Post.find("...", tags, tags.count)@ というシグネチャをここでは使えないことに **注意してください** 。 @tags@ はすでに **引数** として使われています。 + +先ほどのテストにチェック項目を追加してこの JPQL をテストすることができます: + +bc. … +assertEquals(1, Post.findTaggedWith("Red", "Blue").size()); +assertEquals(1, Post.findTaggedWith("Red", "Green").size()); +assertEquals(0, Post.findTaggedWith("Red", "Green", "Blue").size()); +assertEquals(0, Post.findTaggedWith("Green", "Blue").size()); +… + +h2. タグクラウド + +タグがあるところにはタグクラウドが必要です。タグクラウドを生成するメソッドを @Tag@ クラスに追加しましょう: + +bc. public static List getCloud() { + List result = Tag.find( + "select new map(t.name as tag, count(p.id) as pound) from Post p join p.tags as t group by t.name order by t.name" + ).fetch(); + return result; +} + +ここで、JPA クエリから任意のオブジェクトを返せるようにする hibernate の便利な機能を使います。このメソッドは、2 つのキー: タグの名前を示す @tag@ と、タグの数を示す @pound@ を持つ、それぞれのタグの @Map@ を含む @List@ を返します。 + +タグのテストケースにチェック項目をもう 1 つ追加して、このメソッドをテストしましょう: + +bc. … +List cloud = Tag.getCloud(); +assertEquals( + "[{tag=Blue, pound=1}, {tag=Green, pound=1}, {tag=Red, pound=2}]", + cloud.toString() +); + +h2. ブログ UI へのタグの追加 + +これで、ブログを閲覧するもう 1 つの方法として、新しいタグ付け機能を追加することができるようになりました。いつも通り効率的に作業するために、初期データセットにテスト用のタグをひと揃え追加します。 + +テスト用の投稿にいくつかのタグを追加するよう @/yabe/conf/initial-data.yml@ を変更してください。例えば、以下のようにします: + +bc. … +Tag(play): + name: Play + +Tag(architecture): + name: Architecture + +Tag(test): + name: Test + +Tag(mvc): + name: MVC +… + +さらにそれらのタグに対して post の宣言を追加します: + +bc. … +Post(jeffPost): + title: The MVC application + postedAt: 2009-06-06 + author: jeff + tags: + - play + - architecture + - mvc + content: > + A Play +… + +p(note). どの Post が参照するよりも前に作成されている必要があるので、Tag の宣言は YAML の上部で行ってください。 + +新しい初期データセットを強制的に読み込ませるためにアプリケーションを再起動する必要があります。Play は YAML ファイルにおいてすら、以下のようにして問題を報告することを確認してください: + +!images/guide6-1! + +続いて、 **full** モードで投稿を閲覧する場合に、タグのセットを表示するよう @#{display /}@ タグを変更します。 @/yabe/app/views/tags/display.html@ ファイルを以下のように編集してください: + +bc. … +#{if _as != 'full'} + +  |  ${_post.comments.size() ?: 'no'} + comment${_post.comments.size().pluralize()} + #{if _post.comments} + , latest by ${_post.comments[0].author} + #{/if} + +#{/if} +#{elseif _post.tags} + +#{/elseif} +… + +!images/guide6-2! + +h2. 新しい 'tagged with' ページ + +ここまでで、タグによって投稿をリスト表示する新たな方法を追加できるようになりました; #{display /} タグの中に上で空のままにしたリンクを、新しい @listTagged@ アクションへのリンクで置き換えてみましょう: + +bc. … +- Tagged +#{list items:_post.tags, as:'tag'} + ${tag}${tag_isLast ? '' : ', '} +#{/list} +… + +そして、このアクションを @Application@ コントローラに作成します: + +bc. … +public static void listTagged(String tag) { + List posts = Post.findTaggedWith(tag); + render(tag, posts); +} +… + +いつもの通り、URI をクリーンに保つために特定のルートを作成します: + +bc. GET /posts/{tag} Application.listTagged + +うーん、新しいルートと競合するルートがすでに存在するため、問題です。これら 2 つのルートは同じ URI にマッチします: + +bc. GET /posts/{id} Application.show +GET /posts/{tag} Application.listTagged + +しかし、 @id@ は数値、 @tag@ は数値以外を想定しているので、正規表現を使って初めのルートを制限することで、この状況を容易に解決することができます: + +bc. GET /posts/{<[0-9]+>id} Application.show +GET /posts/{tag} Application.listTagged + +最後に、新しい @listTagged@ アクションから使用される @/yabe/app/views/Application/listTagged.html@ テンプレートを作る必要があります: + +bc. #{extends 'main.html' /} +#{set title:'Posts tagged with ' + tag /} + +*{********* Title ********* }* + +#{if posts.size() > 1} +

There are ${posts.size()} posts tagged '${tag}'

+#{/if} +#{elseif posts} +

There is 1 post tagged '${tag}'

+#{/elseif} +#{else} +

No post tagged '${tag}'

+#{/else} + +*{********* Posts list *********}* + +
+ #{list items:posts, as:'post'} + #{display post:post, as:'teaser' /} + #{/list} +
+ +!images/guide6-3! + +p(note). 次: %(next)"CRUD モジュールによる基本的な管理機能":guide7% \ No newline at end of file diff --git a/documentation/manual_ja/guide7.textile b/documentation/manual_ja/guide7.textile new file mode 100755 index 0000000000..bb54a92ccb --- /dev/null +++ b/documentation/manual_ja/guide7.textile @@ -0,0 +1,374 @@ +h1. CRUD による基本的な管理機能のセットアップ + +今のところ、ブログ UI を使って新しいブログや控えめなコメントを作る方法はありません。Play は、すぐに使えて、基本的な管理機能を素早く生成する **CRUD** モジュールを提供しています。 + +h2. CRUD モジュールの有効化 + +Play アプリケーションはいくつかのアプリケーションモジュールから組み立てることができます。これにより、複数のアプリケーションをまたいでアプリケーションコンポーネントを再利用したり、大きなアプリケーションをいくつかの、より小さなアプリケーションに分割することが可能です。 + +CRUD モジュールは、シンプルなリストとフォームを作るためにモデルクラスに対してイントロスペクションを行う、汎用アプリケーションです。 + +CRUD モジュールを有効にするには、 @/yabe/conf/application.conf@ ファイルの require の後に次の行を追加します: + +bc. require: + - play -> crud + +新たなモジュール依存を解決するために play dependencies コマンドを実行してください。IDE を使っている場合は、新しいモジュールの依存性を取り込むためにプロジェクトも更新するべきです: 例えば `play eclipsify` を再度実行し、Eclipse でプロジェクトをリフレッシュします。 + +そして、このモジュールには、今回、再利用できる一般的な **routes** のセットが含まれています。これらのルートをインポートするには @/yabe/conf/routes@ に次の行を追加するだけです: + +bc. # Import CRUD routes +* /admin module:crud + +これで、URL パスの接頭辞として @/admin@ を使用する、すべての CRUD ルートがインポートされます。 + +新しいモジュールを認識させるために、アプリケーションを再起動する必要があります。 + +h2. CRUD コントローラの宣言 + +管理エリアに統合するどのモデルオブジェクトについても、 @controllers.CRUD@ スーパーコントローラを継承するコントローラを宣言しなければなりません。これはとても簡単です。 + +モデルオブジェクトごとに 1 つのコントローラを作成します。例えば、 @Post@ オブジェクト用に、 @/yabe/app/controllers/Posts.java@ ファイル中に @Posts@ コントローラを作成します。 + +bc. package controllers; + +import play.*; +import play.mvc.*; + +public class Posts extends CRUD { +} + +p(note). 対応するコントローラを作成するためには、モデルオブジェクトの名前を複数形にすることが規約で定められています。こうすることで、Play はそれぞれのコントローラに関連するモデルオブジェクトを自動的に見つけます。もし違う名前をつける必要がある場合は、 @CRUD.For アノテーションを使うこともできます。"マニュアルページ":crud を確認してください。 + +すべてのモデルオブジェクトについて同じように作りましょう: + +bc. package controllers; + +import play.*; +import play.mvc.*; + +public class Users extends CRUD { +} + +bc. package controllers; + +import play.*; +import play.mvc.*; + +public class Comments extends CRUD { +} + +bc. package controllers; + +import play.*; +import play.mvc.*; + +public class Tags extends CRUD { +} + +これで、ただ URL "http://localhost:9000/admin/":http://localhost:9000/admin/ を開くだけで、管理機能を使用することができるはずです。 + +!images/guide7-1! + +管理機能をざっと見てみると、リストされたオブジェクトの名前が少し雑なことに気付くと思います。これは、デフォルトではモデルオブジェクトの判読できる表現を得るために、単純に @toString()@ を使用することが原因です。 + +すべてのモデルオブジェクトについて、 @toString()@ の適切な実装を提供することで、簡単にこれを修正することができます。例えば、User クラスについて、以下のようにします: + +bc. … +public String toString() { + return email; +} +… + +h2. バリデーションの追加 + +生成された管理機能の主な問題は、フォームにどのようなバリデーションルールも含まれていないことです。しかし、実際には CRUD モジュールは、モデルクラスが適切にアノテーションで注釈されている場合、バリデーションルールを抽出することができます。 + +@User@ クラスにいくつかアノテーションを追加しましょう: + +bc. package models; + +import java.util.*; +import javax.persistence.*; + +import play.db.jpa.*; +import play.data.validation.*; + +@Entity +public class User extends Model { + + @Email + @Required + public String email; + + @Required + public String password; + + public String fullname; + public boolean isAdmin; +… + +これで、 @User@ モデルオブジェクトの編集または作成フォームにアクセスしてみると、このフォームに自動的に、魔法のようにバリデーションルールが追加されていることを見ることができます: + +!images/guide7-2! + +@Post@ クラスにも同じことをしましょう: + +bc. package models; + +import java.util.*; +import javax.persistence.*; + +import play.db.jpa.*; +import play.data.validation.*; + +@Entity +public class Post extends Model { + + @Required + public String title; + + @Required + public Date postedAt; + + @Lob + @Required + @MaxSize(10000) + public String content; + + @Required + @ManyToOne + public User author; + + @OneToMany(mappedBy="post", cascade=CascadeType.ALL) + public List comments; + + @ManyToMany(cascade=CascadeType.PERSIST) + public Set tags; +… + +結果を確認してみます: + +!images/guide7-3! + +ここに興味深い副作用が見られます: @MaxSize バリデーションルールは、Play が Post フォームを表示する方法を変更しています。今では content フィールド用に大きなテキストエリアを使用しています。 + +最後に、 @Comment@ と @Tag@ クラスにも同様にバリデーションルールを追加しましょう: + +bc. package models; + +import java.util.*; +import javax.persistence.*; + +import play.db.jpa.*; +import play.data.validation.*; + +@Entity +public class Tag extends Model implements Comparable { + + @Required + public String name; +… + +bc. package models; + +import java.util.*; +import javax.persistence.*; + +import play.db.jpa.*; +import play.data.validation.*; + +@Entity +public class Comment extends Model { + + @Required + public String author; + + @Required + public Date postedAt; + + @Lob + @Required + @MaxSize(10000) + public String content; + + @ManyToOne + @Required + public Post post; +… + +h2. より良いフォームラベル + +見ての通り、フォームのラベルが少し雑です。Play はフォームのラベルに Java のフィールド名を使用します。これをカスタマイズするのに必要なのは、単に @/yabe/conf/messages@ により良いラベルを提供することだけです。 + +p(note). ちなみに、アプリケーションがサポートする言語ごとに @messagaes@ ファイルを分けて作成することができます。例えば、フランス語のメッセージは @yabe/conf/messages.fr@ ファイルに書くことができます。12 章で地域化された言語を追加する方法をご覧に入れます: "国際化と地域化":guide12. + +これらのラベルを @messages@ ファイルに追加しましょう: + +bc. title=Title +content=Content +postedAt=Posted at +author=Author +post=Related post +tags=Tags set +name=Common name +email=Email +password=Password +fullname=Full name +isAdmin=User is admin + +どれかフォームをリフレッシュすれば、新しいフォームラベルが表示されます: + +!images/guide7-4! + +h2. 'Comments' データリストのカスタマイズ + +CRUD モジュールは完全にカスタマイズ可能なように作られています。例えば、コメントリストのページを見てみると、データを一覧するやり方は良くありません。もっと列を、特に容易に一覧をフィルタできるようにする 'related post' 列を追加したくなります。 + +実際のところ、アプリケーションがこのマスタを保持する限り、CRUD モジュールによって提供された **アクション** または **テンプレート** のどれでも完全に上書きすることができます。例えば、'comment list' ビューをカスタマイズする場合にしなければならないのは、別の @/yabe/app/views/Comments/list.html@ テンプレートを用意することだけです。 + +**CRUD** モジュールが有効化されている場合、追加の Play コマンドが提供されます。 @crud:ov@ コマンドはテンプレートを上書きする手助けとなります。コマンドラインから、以下のようにタイプしてください: + +bc. $ play crud:ov --template Comments/list + +@/yabe/app/views/Comments/list.html@ に、新しいテンプレートが作成されます: + +bc. #{extends 'CRUD/layout.html' /} + +
+ +

&{'crud.list.title', type.name}

+ +
+ #{crud.search /} +
+ +
+ #{crud.table /} +
+ +
+ #{crud.pagination /} +
+ +

+ &{'crud.add', type.modelName} +

+ +
+ +まず、 @{'crud.list.title', type.name}@ が新しく追加されています、これはメッセージパラメータとして @type.name@ を使用して @crud.list.title@ をキーにローカライズされたメッセージを出力します。 CRUD モジュールの @conf/messages@ ファイルは @crud.list.title=&{%s}@ エントリを含みます。そのエントリは他のメッセージを検索するためのキーとして使用されます、 ( 今回の場合は、 @&{'Comments'}@ です。これは @type@ が @models.Comments@ のための @CRUD.Object.Type@ のインスタンスであることがその理由です。 ) @Comments@ に一致するメッセージがファイルに定義されていなかった場合、メッセージキーをデフォルトで出力します。ローカライズされたメッセージについてより詳細に知りたい場合、本チュートリアルの最後、 guide12 の "国際化とローカライズ" に記載されています。 + +まさに @#{crud.table /}@ タグがテーブルを生成します。 @fields@ パラメータを使うことで、さらに列を追加するようカスタマイズすることができます。以下のようにしてみてください: + +bc. #{crud.table fields:['content', 'post', 'author'] /} + +これでテーブルに列が 3 つ表示されます: + +!images/guide7-5! + +いくつかのコメントの @content@ フィールドはとても長くなることがあり、問題です。必要な場合はこれを切り詰められるよう、 @#{crud.table /}@ タグがこれを扱う方法を特別にしてみましょう: + +以下のようにして、 @#{crud.custom /}@ タグを使うあらゆるフィールドの表示方法をカスタマイズすることができます: + +bc. #{crud.table fields:['content', 'post', 'author']} + #{crud.custom 'content'} + + ${object.content.length() > 50 ? object.content[0..50] + '…' : object.content} + + #{/crud.custom} +#{/crud.table} + +p(note). はい、ここでの作業には groovy の糖衣構文が使われています。 + +h2. 'Post' フォームのカスタマイズ + +生成されたフォームについても同様にカスタマイズ可能です。例えば、Post フォームにタグを入力する方法は、とても簡単ではありません。もっと良くすることができます。 @Posts/show@ テンプレートを上書きしましょう: + +bc. $ play crud:ov --template Posts/show + +@/yabe/app/views/Posts/show.html@ に、新しいテンプレートが作成されます: + +bc. #{extends 'CRUD/layout.html' /} + +
+ +

&{'crud.show.title', type.modelName}

+ +
+#{form action:@save(object.id), enctype:'multipart/form-data'} + #{crud.form /} +

+ + +

+#{/form} +
+ +#{form @delete(object.id)} +

+ +

+#{/form} + +
+ +@tags@ フィールドをカスタマイズするために、 @Posts/show.html@ にある @#{crud.form /}@ タグのボディに @crud.custom@ タグを追加して @#{crud.form /}@ タグを変更することができます: + +bc. #{crud.form} + #{crud.custom 'tags'} + + +
+ #{list items:models.Tag.findAll(), as:'tag'} + + ${tag} + + + #{/list} +
+ #{/crud.custom} +#{/crud.form} + +これはちょっと hacky であり、もっとうまくやることもできますが、ちょっとした JavaScript を使うことでタグセレクタはよりシンプルになりました: + +!images/guide7-6! + +タグリストの外観をカスタマイズするために、下記のように @public/stylesheets/tags.css@ 内に新しい CSS ファイルを作成してください: + +bc. .tags-list .tag { + cursor: pointer; + padding: 1px 4px; +} +.crudField .tags-list .selected { + background: #222; + color: #fff; +} + +さらに、 @views/CRUD/layout.html@ 内の @#{set 'moreStyles'}@ ブロックを下記のように変更してください: + +bc. #{set 'moreStyles'} + + +#{/set} + +管理機能のスタートは上々です! + +p(note). 次: %(next)"認証機能の追加":guide8% \ No newline at end of file diff --git a/documentation/manual_ja/guide8.textile b/documentation/manual_ja/guide8.textile new file mode 100644 index 0000000000..f23f963f84 --- /dev/null +++ b/documentation/manual_ja/guide8.textile @@ -0,0 +1,315 @@ +h1. 認証機能の追加 + +管理機能には何らかの認証プラグインが必要です。幸いなことに、Play にはそのためのモジュールがあります。そのモジュールは **Secure** モジュールと呼ばれます。 + +h2. secure モジュールの有効化 + +@yabe/conf/application.conf@ ファイルにおいて **Secure** モジュールを有効化し、アプリケーションを再起動してください。 + +bc. # Import the secure module +module.secure=${play.path}/modules/secure + +再起動後、Play はこのモジュールがロードされたことを告げるはずです。 + +**Secure** モジュールにはデフォルトルートがひと揃え含まれており、これは @yabe/conf/routes@ ファイルで容易に利用することができます (あるいは、独自のルートを定義することも同様に可能です): + +bc. # Import Secure routes +* / module:secure + +h2. admin コントローラの保護 + +このモジュールは、必要な全てのインターセプタを宣言する @controllers.Secure@ コントローラを提供します。もちろん、単にこのコントローラを継承することもできるのですが、Java では単一継承に制限されるので、問題となります。 + +直接 @Secure@ を継承する代わりに、同様に関連するインターセプタを起動するよう Play に指示する @With アノテーションを使って管理コントローラを注釈することができます: + +bc. package controllers; + +import play.*; +import play.mvc.*; + +@With(Secure.class) +public class Posts extends CRUD { +} + +@Comments@ 、 @Users@ 、そして @Tags@ コントローラも同様に注釈してください。 + +これで、どの管理アクションにアクセスしようとしても、ログインページが表示されるはずです: + +!images/guide8-1! + +実は、このままではどのようなログイン/パスワードのペアも使うことができてしまいます。ただ動いているだけです。 + +h2. 認証プロセスのカスタマイズ + +アプリケーションは、認証プロセスをカスタマイズするために @controllers.Secure.Security@ のインスタンスを提供する必要があります。このクラスの独自のバージョンを作成することで、ユーザはどのように認証されるべきなのかを指定できるようになります。 + +@yabe/controllers/Security.java@ ファイルを作成して、 @authenticate()@ メソッドをオーバーライドしてください: + +bc. package controllers; + +import models.*; + +public class Security extends Secure.Security { + + static boolean authenticate(String username, String password) { + return true; + } + +} + +ブログシステムのモデルの一部として既に User オブジェクトを用意しているので、このメソッドの動作するバージョンを実装することは簡単です: + +bc. static boolean authenticate(String username, String password) { + return User.connect(username, password) != null; +} + +これで "http://localhost:9000/logout":http://localhost:9000/logout へ行き、ログアウトしてから、 bob@gmail.com/secret のように、 @initial-data.yml@ からインポートしたユーザのどれかひとつを使ってログインを試してみることができます。 + +h2. 管理機能のリファクタリング + +CRUD モジュールを使った管理機能を開始しましたが、まだブログの UI によく統合されていません。新しい管理機能について作業を始めましょう。この管理機能は、それぞれの投稿者に、彼/彼女自身の投稿にアクセスできるようにします。CRUD を使ったフル機能の管理機能は、引き続き管理ユーザが利用できるようにしましょう。 + +この管理機能のための @Admin@ コントローラを作成してください: + +bc. package controllers; + +import play.*; +import play.mvc.*; + +import java.util.*; + +import models.*; + +@With(Secure.class) +public class Admin extends Controller { + + @Before + static void setConnectedUser() { + if(Security.isConnected()) { + User user = User.find("byEmail", Security.connected()).first(); + renderArgs.put("user", user.fullname); + } + } + + public static void index() { + render(); + } + +} + +そして、 @yabe/conf/routes@ 内のルート定義を以下のようにリファクタリングしてください: + +bc. # Administration +GET /admin/? Admin.index +* /admin module:crud + + routes ファイル内にある順序を忘れないようにしてください。最初の行からマッチしている HTTP リクエストが使用されます。これは @Admin@ コントローラへのマップに対するエントリは CRUD モジュールページに @/admin@ のリクエストを行うため、2行目よりも前に来ないといけないことを意味している。しかし、 @/admin/@ は @Admin.index@ の代わりに @CRUD.index@ にマッピングされている。 + +@yabe/app/views/main.html@ テンプレートの 'Log in to write something' 文字列から、このコントローラにリンクを貼ってください: + +bc. … + +… + +この新しい管理機能を動作させるための最後の作業は @yabe/app/views/Admin/index.html@ テンプレートを作成することです。シンプルなものから始めましょう: + +bc. Welcome ${user}! + +これで、ブログのトップページへ行き、'Log in to write something' リンクをクリックすると、新しい管理機能が表示されるはずです: + +!images/guide8-2! + +いいスタートです! ただ、この管理機能には複数のページを用意するつもりなので、親テンプレートが必要です。 @yabe/app/views/admin.html@ ファイルに、このテンプレートを作成しましょう: + +bc. + + + Administration + + #{get 'moreStyles' /} + + + + + + + + + +
+ #{doLayout /} +
+ + + + + + +見ての通り、これはブログエンジンのフロント部分に使用したテンプレートととても似ています。 **Log in** リンクを、secure モジュールが提供する @Secure@ コントローラの @logout@ アクションをコールする **Log out** リンクで置き換えました。 + +それでは、これを @yabe/app/views/Admin/index.html@ テンプレート中で使用してみましょう: + +bc. #{extends 'admin.html' /} + +Welcome ${user}! + +そして、画面を更新してください! + +!images/guide8-3! + +**log out** リンクをクリックしてみると、ログインフォームに送り返されます: + +!images/guide8-4! + +これが secure モジュールがログアウトイベントを扱うデフォルトの方法です。しかし、これは @controllers.Security@ クラスで @onDisconnected()@ メソッドを単にオーバーライドすることで容易にカスタマイズすることができます: + +bc. static void onDisconnected() { + Application.index(); +} + +@onAuthenticated()@ イベントについても同様にカスタマイズすることができます: + +bc. static void onAuthenticated() { + Admin.index(); +} + +h2. 役割の追加 + +実際には、2 つの管理領域が必要です: 1 つは普通の投稿者向けのもので、もう 1 つは上位管理者向けのものです。これまで見てきたように、 @User@ モデルオブジェクトは、そのユーザが上位管理者権限を持つか否かを示す @isAdmin@ フィールドを持ちます。 + +secure モジュールは @認証@ だけではなく、 @認可@ 管理も提供します。この認可管理は @プロファイル@ と呼ばれます。 @admin@ プロファイルを作成するために必要なのは、 @controllers.Security@ クラスの @check()@ メソッドをオーバーライドすることだけです: + +bc. static boolean check(String profile) { + if("admin".equals(profile)) { + return User.find("byEmail", connected()).first().isAdmin; + } + return false; +} + +これで、ユーザが admin 権限を持つ場合は管理メニューを表示することができるようになります。トップレベルメニューを統合するよう @app/views/admin.html@ を更新してください: + +bc. … +
+ + + + #{doLayout /} +
+… + +どのように @#{secure.check /}@ タグを使って、このメニュー部分を @admin@ ユーザにのみ表示しているかを確認してください。 + +!images/guide8-5! + +ただし、まだこのサイトの CRUD 部分のセキュリティは充分ではありません! ユーザが URL を知っていれば、依然としてアクセスすることができます。コントローラも同様に保護しなければなりません。これは @Check アノテーションを使うことでとても簡単に行うことができます。例えば、 @Posts@ コントローラを保護するには、以下のようにします: + +bc. package controllers; + +import play.*; +import play.mvc.*; + +@Check("admin") +@With(Secure.class) +public class Posts extends CRUD { +} + +@Tags@ 、 @Comments@ 、そして @Users@ コントローラも同じように保護してください。ここで、ログアウトして (jeff@gmail.com/secret のような) 一般ユーザで再度ログインしてください。管理者向け CRUD リンクが表示されないはずです。そして、URL "http://localhost:9000/admin/users":http://localhost:9000/admin/users に直接アクセスしてみると、 **403 Forbidden** レスポンスが返ります。 + +!images/guide8-6! + +h2. CRUD レイアウトのカスタマイズ + +楽しくなってきましたが、管理領域の CRUD 部分へ行くと管理用レイアウトが無くなってしまいます。これは CURD モジュールが自身のレイアウトを提供するからです。しかし、もちろんこれをオーバーライドすることができます。以下の Play コマンドを使用してください: + +bc. play crud:ov --layout + +新しい @/yabe/app/views/CRUD/layout.html@ テンプレートが作成されます。このテンプレートの内容を @admin.html@ レイアウトとよく馴染むように置き換えましょう: + +bc. #{extends 'admin.html' /} +#{set 'moreStyles'} + +#{/set} + +
+ + #{if flash.success} +
+ ${flash.success} +
+ #{/if} + #{if flash.error || error} +
+ ${error ?: flash.error} +
+ #{/if} + +
+ #{doLayout /} +
+ +
+ +見ての通り、テンプレート変数 **get/set** の仕組みを使って @crud.css@ を @admin.html@ に取り込み、再利用します。これで、管理領域の CRUD 部分にアクセスしてみると、管理領域のレイアウトと適切に統合されて表示されます: + +!images/guide8-7! + +!images/guide8-9! + +h2. ログインページのスタイル変更 + +ここまでで、管理領域は視覚的にほぼ統一されました。スタイルについて最後にすることは、ログインページに対する作業です。いつもどおり、デフォルト CSS をオーバーライドすることで容易にカスタマイズすることができます: + +bc. play secure:ov --css + +この CSS はこのまま維持しますが、先頭部分で @main.css@ をインポートします。単に以下の最初の一行を @yabe/public/stylesheets/secure.css@ に追加してください: + +bc. @import url(main.css); +… + +そして、 @yabe/conf/messages@ ファイルに以下の行を追加してログイン画面のメッセージをカスタマイズします: + +bc. secure.username=Your email: +secure.password=Your password: +secure.signin=Log in now + +!images/guide8-8! + +p(note). 次: %(next)"カスタムエディタの作成":guide9% \ No newline at end of file diff --git a/documentation/manual_ja/guide9.textile b/documentation/manual_ja/guide9.textile new file mode 100644 index 0000000000..e8eb36014a --- /dev/null +++ b/documentation/manual_ja/guide9.textile @@ -0,0 +1,276 @@ +h1. カスタムエディタの作成 + +これまでの部分で yabe の管理機能を作成し、'My posts' 画面の準備をしました。このページは、それぞれの投稿者に自身の投稿のリストと、これらを編集する、または新規に作成する機能も提供します。 + +このページを作り始めるのに CRUD モジュールを再利用することもできますが、ここではスクラッチで作成してみましょう。これらのページには、パーソナライズする箇所がたくさんあります。 + +h2. ユーザの投稿一覧から始めましょう + +まずは接続しているユーザが書いた投稿を検索し、表示しなければなりません。とても簡単です。 @Admin.index@ アクションを拡張するところから始めましょう: + +bc. public static void index() { + String user = Security.connected(); + List posts = Post.find("author.email", user).fetch(); + render(posts); +} + +そして @yabe/app/views/Admin/index.html@ を以下のように仕上げます: + +bc. #{extends 'admin.html' /} + +

Welcome ${user}, you have written ${posts.size() ?: 'no'} +${posts.pluralize('post', 'posts')} so far

+ +#{list items:posts, as:'post'} +

+ ${post.title} +

+#{/list} + +

+ + write a new post +

+ +最初の画面が整いました: + +!images/guide9-1! + +h2. '新規投稿作成' 画面 + +新しい投稿を作成するフォームを作ります。基本的に、フォームには 2 つのアクションがあります: 1 つはフォームを表示し、もう 1 つはフォームのサブミットを扱います。フォームの表示とサブミットを取り扱う新しい @Admin.form@ アクションと @Admin.save@ アクションを作成しましょう。 + +@yabe/conf/routes@ ファイルにルートを追加します: + +bc. GET /admin/new Admin.form +POST /admin/new Admin.save + +そして @Admin.java@ コントローラに @form()@ と @save()@ アクションを追加します: + +bc. public static void form() { + render(); +} + +public static void save() { + // Not implemented yet +} + +ここで @yabe/app/views/Admin/form.html@ テンプレートを作成しなければなりません: + +bc. #{extends 'admin.html' /} + +

Write, a new post

+ +#{form @save()} + + #{ifErrors} +

+ Please correct these errors. +

+ #{/ifErrors} + +

+ #{field 'title'} + + + #{error 'post.title' /} + #{/field} +

+ +

+ #{field 'content'} + + + #{error 'post.content' /} + #{/field} +

+ +

+ #{field 'tags'} + + + #{/field} +

+ +

+ +

+ +#{/form} + +最後に、 **Write a new post** ボタンがこのフォームにリンクするよう @yabe/app/views/Admin/index.html@ テンプレートを編集します: + +bc. … +

+ + write a new post +

+… + +結果を確認することができます: + +!images/guide9-2! + +ここで、フォームのサブミットを適切に取り扱うよう @Admin.save@ アクションを完成させなければなりません。このアクションは、新しい @Post@ オブジェクトを作成し、タグリストを実際の @Tags@ オブジェクトのセットに変換し、全てのフィールドの妥当性を検証し、そして保存します。何らかのエラーがあった場合には、エラーを示すためにフォームを再表示します。 + +bc. public static void save(String title, String content, String tags) { + // Create post + User author = User.find("byEmail", Security.connected()).first(); + Post post = new Post(author, title, content); + // Set tags list + for(String tag : tags.split("\\s+")) { + if(tag.trim().length() > 0) { + post.tags.add(Tag.findOrCreateByName(tag)); + } + } + // Validate + validation.valid(post); + if(validation.hasErrors()) { + render("@form", post); + } + // Save + post.save(); + index(); +} + +p(note). ここでは ==render("Admin/form.html")== のショートカットとして ==render("@form")== を使用しています。このショートカットは、単に form アクションのデフォルトテンプレートを使用するよう Play に告げています。 + +確認してみましょう! + +h2. 投稿の '編集' へ再利用しましょう + +新しいブログを投稿できるよう HTML フォームと Java アクションを定義しました。しかし、既に存在する投稿も編集できるようにする必要があります。ほんの少しの変更でまったく同じコードを容易に再利用することができます。 + +初めに、 @Admin.form@ で存在する @Post@ を検索する必要があります: + +bc. public static void form(Long id) { + if(id != null) { + Post post = Post.findById(id); + render(post); + } + render(); +} + +見てのとおり、この検索はオプション扱いにしたので、 @id@ パラメータが入力されている場合にのみ、この同じアクションは存在する投稿を検索します。これで、メイン画面の投稿リストを編集フォームにリンクすることができます。 @yabe/app/views/Admin/index.html@ テンプレートを以下のように編集してください: + +bc. #{extends 'admin.html' /} + +

Welcome ${user}, you have written ${posts.size() ?: 'no'} ${posts.pluralize('post', 'posts')} so far

+ +#{list items:posts, as:'post'} +

+ ${post.title} +

+#{/list} + +

+ + write a new post +

+ +とても簡単ですが、問題があります。Router によって生成されたこれらのリンクの実際の URL を見てみてると、以下のようになっているはずです: + +bc. /admin/new?id=3 + +これは動作しますが美しくありません。 @id@ パラメータがサブミットされた場合は違う URL を使う別のルートを定義しましょう: + +bc. GET /admin/myPosts/{id} Admin.form +GET /admin/new Admin.form + +見てのとおり、このルートを古いものの前に定義したので、これはより高い優先度を持ちます。これは、もし @id@ パラメータがサブミットされた場合には、Play はこの URL を優先するということです。id パラメータがサブミットされない場合は、古いルートがそのまま使用されます。 + +**My posts** ページを更新すれば、これらのリンクにより良い URL が設定されるはずです。 + +ここで @yabe/app/views/Admin/form.html@ テンプレートも同様に変更しなければなりません: + +bc. #{extends 'admin.html' /} + +#{ifnot post?.id} +

Write, a new post

+#{/ifnot} +#{else} +

Edit, this post

+#{/else} + +#{form @save(post?.id)} + + #{ifErrors} +

+ Please correct these errors. +

+ #{/ifErrors} + +

+ #{field 'title'} + + + #{error 'post.title' /} + #{/field} +

+ +

+ #{field 'content'} + + + #{error 'post.title' /} + #{/field} +

+ +

+ #{field 'tags'} + + + #{/field} +

+ +

+ +

+ +#{/form} + +見てのとおり、投稿の ID が存在する場合、これをアクションの第一引数として追加するよう、フォームの送り先となるアクションを更新しました。このため、投稿の id フィールドが設定されている (つまり、システム中に投稿が既に存在する) 場合、その id は @Admin.save@ アクションに送られます。 + +これで、作成と編集の両方のケースを取り扱うよう、 @save()@ メソッドを少し変更することができます: + +bc. public static void save(Long id, String title, String content, String tags) { + Post post; + if(id == null) { + // Create post + User author = User.find("byEmail", Security.connected()).first(); + post = new Post(author, title, content); + } else { + // Retrieve post + post = Post.findById(id); + // Edit + post.title = title; + post.content = content; + post.tags.clear(); + } + // Set tags list + for(String tag : tags.split("\\s+")) { + if(tag.trim().length() > 0) { + post.tags.add(Tag.findOrCreateByName(tag)); + } + } + // Validate + validation.valid(post); + if(validation.hasErrors()) { + render("@form", post); + } + // Save + post.save(); + index(); +} + +そして、より良い URL のために、 @id@ パラメータが存在する場合に優先されるルートを追加する、先ほどと同じトリックを使用します: + +bc. POST /admin/myPosts/{id} Admin.save +POST /admin/new Admin.save + +できました! これからは同じアクションを新しいブログの投稿の作成と古い投稿の編集に使用します。これで管理機能は完成です! + +p(note). 次: %(next)"テストの完了":guide10% \ No newline at end of file diff --git a/documentation/manual_ja/home.textile b/documentation/manual_ja/home.textile new file mode 100644 index 0000000000..39229a09e7 --- /dev/null +++ b/documentation/manual_ja/home.textile @@ -0,0 +1,197 @@ +h1. ドキュメント + +Play framework 1.2.5 ドキュメントへようこそ。"バージョン 1.2.5 リリースノート":releasenotes-1.2.5 を確認してください。 + +h2. はじめよう + +Play framework のはじめの一歩を学ぶ楽しい 5 分間です。 + +# "Play framework の概要":overview +# "動画を観てみよう":http://vimeo.com/7087610 +# "Play でできる 5 つのすごいこと":5things +# "利便性 - 細部は機能と同様に重要":usability +# "よくある質問":faq +# %(next)"インストールガイド":install% +# "お好みの IDE を設定しよう":ide +# "はじめてのアプリケーション - 'Hello World' チュートリアル":firstapp +# "サンプルアプリケーション":samples + + +h2. チュートリアル - リアルな Play アプリケーションを一歩ずつ作成するガイド + +**'Yet Another Blog Engine'** を最初から最後までコーディングすることで Play について学びます。各章は Play の更なるクールな機能について学ぶ機会となるでしょう。 + +# "プロジェクトの立ち上げ":guide1 +# "はじめてのモデル":guide2 +# "はじめての画面":guide3 +# "コメント投稿ページ":guide4 +# "キャプチャの設定":guide5 +# "タグ機能のサポート":guide6 +# "CRUD モジュールによる基本的な管理機能":guide7 +# "認証機能の追加":guide8 +# "カスタムエディタの作成":guide9 +# "テストの完了":guide10 +# "本番環境への準備":guide11 +# "国際化と地域化":guide12 + +h2. 重要なドキュメンテーション + +Play について学ばなければならない全てです。 + +# "主要な概念":main +## "MVC アプリケーションモデル":main#mvc +## "リクエストライフサイクル":main#request +## "アプリケーションのレイアウトと編成":main#application +## "開発ライフサイクル":main#lifecycle +# "HTTP ルーティング":routes +## "routes ファイルの構文":routes#syntax +## "ルーティングの優先順位":routes#priority +## "静的リソースの配信":routes#static +## "リバースルーティング : URL の生成":routes#reverse +## "content type の設定":routes#content-types +## "コンテントネゴシエーション":routes#content-negotiation +# "コントローラ":controllers +## "コントローラの概要":controllers#overview +## "HTTP パラメータの取得":controllers#params +## "結果の返却":controllers#result +## "アクションチェーン":controllers#chaining +## "インターセプション":controllers#interceptions +## "セッションとフラッシュのスコープ":controllers#session +# "テンプレートエンジン":templates +## "テンプレートの構文":templates#syntax +## "テンプレートの継承":templates#inheritance +## "タグの作成":templates#tags +## "テンプレート言語の拡張":templates#extensions +## "暗黙オブジェクト":templates#implicits +# "HTTP フォームデータのバリデーション":validation +## "どのように動作するのでしょうか?":validation#how +## "エラーメッセージの取得":validation#messages +## "テンプレートへのエラーの表示":validation#display +## "アノテーションの使用":validation#annotations +## "オブジェクトのバリデーション":validation#objects +## "カスタムバリデーション":validation#custom +# "ドメインオブジェクトモデル":model +## "プロパティのシミュレーション":model#properties +## "モデルを永続化するデータベースの設定":model#database +## "Hibernate によるモデルの永続化":model#hibernate +## "ステートレスモデル":model#stateless +# "JPA 永続化":jpa +## "JPA エンティティマネージャの開始":jpa#starting +## "JPA エンティティマネージャの取得":jpa#obtaining +## "トランザクション管理":jpa#transactions +## "The play.db.jpa.Model サポートクラス":jpa#support +## "オブジェクトの検索":jpa#finding +## "オブジェクトのカウント":jpa#counting +## "明確な保存":jpa#save +## "ジェネリック型の問題についてもっと詳しく":jpa#typing +# "Play ライブラリ":libs +## "XPath を使った XML 解析":libs#ParsingXMLusingXPath +## "Web サービスクライアント":libs#WebServiceclient +## "Java を使った関数プログラミング":libs#FunctionalprogrammingwithJava +## "OAuth":libs#OAuth +## "OpenID":libs#OpenID +# "ジョブによる非同期処理":jobs +## "起動時に実行するジョブ":jobs#concepts +## "スケジューリングされたジョブ":jobs#scheduling +## "トリガータスクジョブ":jobs#tasks +# "HTTP を使った非同期プログラミング":asynchronous +## "HTTP リクエストの中断":asynchronous#SuspendingHTTPrequests +## "HTTP レスポンスストリーム":asynchronous#HTTPresponsestreaming +## "WebSockets を使う":asynchronous#UsingWebSockets +# "Ajax リクエスト":ajax +## "JQuery を使った Ajax リクエストの作成":ajax#jsaction +# "国際化":i18n +## "使用するのは UTF-8 だけ":i18n#utf8 +## "メッセージの外部化":i18n#messages +## "アプリケーションがサポートする言語の定義":i18n#languages +## "ロケールに依るデータフォーマットの定義":i18n#dates +## "ローカライズされたメッセージの検索":i18n#retrieve +# "キャッシュ":cache +## "キャッシュ API":cache#api +## "セッションをキャッシュとして使わない":cache#session +## "memcached の設定":cache#memcached +# "eメール送信":emails +## "メールと MVC の統合":emails#mvc +## "SMTP の設定":emails#smtp +## "設定についてもっと詳しく":emails#configuration +## "Gmail の使用":emails#gmail +# "アプリケーションのテスト":test +## "テストの記述":test#writing +## "フィクスチャ":test#fixtures +## "テストの実行":test#running +## "継続的統合":test#continus +# "セキュリティガイド":security +## "セッション":security#sessions +## "クロスサイトスクリプティング":security#xss +## "SQL インジェクション":security#sql +## "クロスサイトリクエストフォージェリ":security#csrf +# "モジュールとモジュールリポジトリ":modules +## "モジュールとは ?":modules#what +## "アプリケーションからモジュールをロードする方法":modules#loading +## "モジュールからデフォルトルートをロードする":modules#routes +## "モジュールリポジトリの使用":modules#repository +## "モジュールリポジトリへの新しいモジュールの寄付":modules#contributing +# "依存性管理":dependency +## "依存性フォーマット":dependency#format +## "dependencies.yml":dependency#yml +## "競合の解決":dependency#conflicts +## "新しいリポジトリの追加":dependency#repositories +# "データベースの変更管理":evolutions +## "変更スクリプト":evolutions#script +## "同時に発生した変更の同期":evolutions#Synchronizingconcurrentchanges +## "矛盾した状態":evolutions#Inconsistentstates +## "変更コマンド":evolutions#Evolutionscommands +# "ログの設定":logs +## "アプリケーションからのロギング":logs#logging +## "ログレベルの設定":logs#levels +# "複数環境における設定":ids +## "フレームワーク ID":ids#id +## "コマンドラインからのフレームワーク ID の設定":ids#command +# "本番環境":production +## "application.conf の設定":production#application +## "ログの設定":production#logging +## "フロントエンド HTTP サーバのセットアップ":production#server +# "デプロイオプション":deployment +## "スタンドアロン Play アプリケーション":deployment#standalone +## "JEE アプリケーションサーバ上での実行":deployment#appservers +## "Google App Engine":deployment#gae +## "Stax クラウドホスティングプラットフォーム":deployment#stax + + +h2. リファレンス + +日々のハックのための広範囲なリファレンスです。 + +# クイックインデックス +# API ドキュメント (Javadoc) +# "チートシート":cheatsheet/commandLine +## "コマンドライン":cheatsheet/commandLine +## "コントローラ":cheatsheet/controllers +## "テンプレート":cheatsheet/templates +## "モデル":cheatsheet/model +## "テスト":cheatsheet/tests +## "複数環境":cheatsheet/multiEnvironment +# "組み込み拡張":tags +## "テンプレートタグ":tags +## "Java エクステンション":javaextensions +## "バリデーション":validation-builtin +# 設定リファレンス + +h2. 配布モジュール + +これらのオプションモジュールは標準の配布物に含まれています。"その他のモジュールはこちら":/modules 。 + +# "CRUD":crud +# "Secure":secure + +h2. リリースノート + +新しいバージョンの Play には、いくつかの変更点が含まれます。古いリリースノートを確認してください: + +# "Play 1.2.5":releasenotes-1.2.5 +# "Play 1.2.4":releasenotes-1.2.4 +# "Play 1.2":releasenotes-1.2 +# "Play 1.1":releasenotes-1.1 +# "Play 1.0.3":releasenotes-1.0.3 +# "Play 1.0.2":releasenotes-1.0.2 +# "Play 1.0.1":releasenotes-1.0.1 \ No newline at end of file diff --git a/documentation/manual_ja/i18n.textile b/documentation/manual_ja/i18n.textile new file mode 100644 index 0000000000..1cf0c35356 --- /dev/null +++ b/documentation/manual_ja/i18n.textile @@ -0,0 +1,135 @@ +h1. Internationalization + +Internationalisation (I18N) is a means of adapting your application to different languages to allow for regional differences. Follow these steps to enable internationalisation in your application. + +h2. Only use UTF-8! + +Play supports only one encoding: UTF-8. Since encoding problems can be weird and difficult to deal with, we made the choice to support only one encoding. UTF-8 allows you to display all characters for all languages. + +Be sure to be consistent with UTF-8 usage throughout your application: + +* Edit all your source files as UTF-8 +* Define proper encoding headers in HTTP +* Set your HTML meta tags to UTF-8 +* If you use a database, configure it to use UTF-8, and always connect to it using UTF-8 + +p(note). **Note** + +The UTF-8 encoding issue is the reason why most of the Play configuration files, even though they are Java properties files, are not named @*.properties@. Java imposes the requirement that properties files must be encoded with the **ISO-8859-1** encoding. Play configuration files must be UTF-8 encoded. Need we say more? + +h2. Externalize your messages + +To support I18N you have to externalize all messages in your application. + +Create a file named @messages@ in the application’s @conf/@ directory. This file is really just a Java properties file. + +bc. hello=Hello! +back=Back + +Then you can define a specific @message@ file for each language used in your application. Just add the ISO language code as a file extension. + +For example, the message file containing the corresponding French translations is @conf/messages.fr@: + +bc. hello=Bonjour! +back=Retour + +h2. Define languages supported by the application + +Define a list of supported languages in the "application.langs configuration":configuration#application.langs. + +On the first request from a new user, Play will guess the default language to use. It does so by parsing the HTTP @Accept-language@ header. It will then save the chosen language in a @PLAY_LANG@ cookie. So the next request will use the same language. + +You can use language/country pair if you want to distinguish between variant, such as en_US and en_GB, or zh_CN and zh_TW. However, be aware that some users may only expose a language and not a country in their Accept-language. For that reason, you should always provide the "naked" language (e.g. en). + +For example, if most of your users are from US but you also want to support British English, it is recommended to use simply "en" of US English and "en_GB" for British English. + +From your application code your can retrieve the current language for the user by accessing the @play.i18n.Lang@ object: + +bc. String lang = Lang.get(); + +If you want to permanently change the user language, use the change() method: + +bc. Lang.change("ja"); + +The new value will be saved back to the user’s language cookie. + + +h2. Define date format according to your locale + +Configure "date.format":configuration#date.format to specify the default date format to use. + + +h2(#retrieve). Retrieve localized messages + + +h3(#argument). Message arguments + +From the application code, you can retrieve messages defined in message files. From Java, use the @play.i18n.Messages@ object. + +bc. public static void hello() { + renderText(Messages.get("hello")); +} + +We support message formatting through the standard @java.util.Formatter@ ‘Format string syntax’. You can also define dynamic content in your messages: + +bc. hello=Hello %s! + +where @%s@ represents a message argument that will be output as a @String@. Message arguments are provided by additional (varargs) arguments to @Messages.get@: + +bc. public static void hello(String user) { + renderText(Messages.get("hello", user)); +} + +h3(#template). Template output + +From a template you can use the special @&{…}@ syntax to display localized messages: + +bc.

&{'hello'}

+ +or using dynamic content in message arguments: + +bc.

&{'hello', params.user}

+ + +h3(#arguments). Multiple arguments + +You can define multiple message arguments, such as this message which refers to two ‘decimal integer’ arguments: + +bc. guess=Please pick a number between %d and %d + +which you display by specifying the message arguments in the right order: + +bc.

&{'guess', low, high}

+ + +h3(#indices). Argument indices + +You can also specify the message argument explicitly, to use a different order. For example, suppose a message in English has two parameters: + +bc. guess.characteristic=Guess %s’s %s. + +with message output like: + +bc.

&{'guess.characteristic', person.name, 'age'}

+ +The French localisation has the two message in the opposite order, so in the French localisation we specify the argument indices: + +bc. guess.characteristic=Devinez %2$s de %1$s. + +where @%2$s@ outputs the **second** argument as a decimal integer. + +Finally, we want to localise the characteristic name ‘age’ as well, so we would change the output to use the message key @person.age@, and change the message definitions to: + +bc. guess.characteristic=Guess %s’s &{%s}. +person.age = age + +and + +bc. guess.characteristic=Devinez &{%2$s} de %1$s. +person.age = l’age + +where @&{%s}@ is itself a message look-up, with the argument value as the message key. + +p(note). **Continuing the discussion** + +Next: %(next)"Cache":cache%. diff --git a/documentation/manual_ja/ide.textile b/documentation/manual_ja/ide.textile new file mode 100644 index 0000000000..c07cde27c3 --- /dev/null +++ b/documentation/manual_ja/ide.textile @@ -0,0 +1,156 @@ +h1. お好みの IDE を設定しよう + +Play と共に作業することは簡単です。Play が自動的にソースファイルに加えられた変更をコンパイルしてリフレッシュするので、高性能な IDE は必要ありません。シンプルなテキストエディタを使って簡単に作業することができます。 + +とは言え、近頃の Java IDE は自動補完、逐次コンパイル、リファクタリングやデバッグのアシストと言ったクールで生産的な機能を提供します。Play は "Netbeans":http://www.netbeans.org, "IntelliJ IDEA":http://www.jetbrains.com/idea/index.html と "Eclipse":http://www.eclipse.org プラットフォームをサポートします。 + + +h2(#eclipse). Eclipse + +h3. 設定ファイルの生成 + +Play は Eclipse の設定を簡易化するコマンドを提供しています。Play アプリケーションを動作する Eclipse プロジェクトに変換するには、 @eclipsify@ コマンドを使用してください: + +bc. # play eclipsify myApp + +その後、 **File/Import/General/Existing project...** メニューから Workspace にアプリケーションをインポートする必要があります。 + +!images/eclipse! + +classpath の変更のような重要な変更をアプリケーションへ加えるときは、設定ファイルを再生成するために @eclipsify@ を再度実行します。 + +p(note). **チームで作業しているときは Eclipse 設定ファイルをコミットしないこと!** + +生成した設定ファイルにはフレームワークをインストールした場所への絶対参照が含まれています。これはあなた自身のインストール作業に特化したものです。チームで作業するときは、それぞれの開発者は自分の Eclipse 設定ファイルを個人で管理しなければなりません。 + +h3. ランチャ + +@eclipsify@ コマンドはアプリケーション用にいくつかのランチャを生成します。例えば、 **Nice app** と呼ばれるアプリケーション (上記スクリーンショット参照) 上で @eclipsify@ を実行すると、@eclipse/@ folder 内に以下のランチャを作成します: + +* @Nice app.launch@ - 右クリックから **Run As > Nice app** を選択することでアプリケーションを実行するために使用できる、 @play test@ コマンドと等価なメインのランチャ +* @Test Nice app.launch@ - やはり右クリックから **Run As > Test Nice app** を選択して実行する、 @play test@ と等価なテストランチャ +* @Connect JPDA Nice app.launch@ - ランチャファイルを右クリックして **Debug As > Connect JPDA Nice app** を選択することで、実行済みの Play インスタンスに対するEclipse デバッガに接続し、デバッグセッションを開始する JPDA デバッグセッションコネクタです。デバッグセッションを停止してもサーバは停止されません。 + +h3. Play Eclipse プラグイン + +更に、Play には HTML ビューテンプレート、 @application.conf@ そして @routes@ ファイル用のエディタを提供する Eclipse プラグインが含まれています。 + +プラグインをインストールするには、 @$PLAY_HOME/support/eclipse@ から @$ECLIPSE_HOME/dropins@ に JAR ファイルをコピーします。 + + +h2(#netbeans). NetBeans + +Play は Netbeans の設定を簡易化するコマンドを提供しています。既存のアプリケーションを妥当な Netbeans プロジェクトに変換するには、 @netbeansify@ コマンドを使用してください: + +bc. # play netbeansify myApp + +その後は、そのアプリケーションをそのまま Netbeans プロジェクトとして開くことができます。 + +!images/netbeans! + +アプリケーションを開始するには、標準の *Run* ボタンを使用してください。アプリケーションが起動されていれば、いつでも *Debug* ボタンを使ってデバッグセッションをアタッチすることができます。デバッグセッションを終了してもサーバは止まりません。 + +classpath の変更のような重要な変更をアプリケーションへ加えるときは、設定ファイルを再生成するために @netbeansify@ を再度実行します。 + +p(note). **チームで作業しているときは @nbproject/@ ディレクトリをコミットしないこと!** + +生成した設定ファイルにはフレームワークをインストールした場所への絶対参照が含まれています。これはあなた自身のインストール作業に特化したものです。チームで作業するときは、それぞれの開発者は自分の Netbeans 設定ファイルを個人で管理しなければなりません。 + +h2(#intellij). IntelliJ IDEA + +Play は IntelliJ IDEA の設定を簡易化するコマンドを提供しています。既存のアプリケーションを妥当な IntelliJ IDEA モジュールに変換するには、 @idealize@ コマンドを使用してください: + +bc. # play idealize myApp + +単一モジュールプロジェクトを作成する場合は、IntelliJ IDEA にて以下を実行してください。 + +# コマンドラインから @play new@ コマンドを使って Play プロジェクトを作成する。 +# コマンドラインから @play idealize@ コマンドを使って IntelliJ IDEA モジュールを作成する。 +# IntelliJ IDEA の *File* メニューから *Open Project…* を選択して、生成された @.ipr@ ファイルを選択する。 + +!images/intellij! + +実行設定を追加するには: + +# IntelliJ IDEA の *Run* メニューから *Edit Configurations* を選択する。 +# *Defaults* 内の *Application* で右クリックし、 *Add New Configuration* を選択する。 +# *Main class* 内に @play.server.Server@ を入力する。 +# *VM parameters* 内に @-Dapplication.path="."@ を入力する。 +# *Working directory* 内にアプリケーションパスを入力する。 + +IntelliJ において Play をテストモードで実行するには: + +# 実行設定を編集し、 *VM parameters* に *-Dplay.id=test* を追加する。 +# モジュールを右クリックして *Open Module Settings* を選択する。 +# *Dependencies* タブを選択する。 +# *Add…* をクリックして *Single-Entry Module Library* を選択する。 +# @$PLAY_HOME/modules/testrunner/lib/play-testrunner.jar@ を選択して *OK* をクリックする。 + +p(note). **チームで作業しているときは .iml ファイルをコミットしないこと!** + +生成した設定ファイルにはフレームワークをインストールした場所への絶対参照が含まれています。これはあなた自身のインストール作業に特化したものです。チームで作業するときは、それぞれの開発者は自分の IntelliJ IDEA 設定ファイルを個人で管理しなければなりません。 + + +h2(#textmate). Textmate + +シンタックスハイライトと自動補完を有効にするために @$PLAY_HOME/support/textmate.zip@ にある "Textmate":http://macromates.com/ バンドルをインストールします。このバンドルはコントローラとビューの行き来も容易にします。 + +!images/editor! + + +h2(#vim). Vim + +Textmate に影響された "snipMate":http://www.vim.org/scripts/script.php?script_id=2540 プラグインは、 "Vim":http://www.vim.org/ にキーワード自動補完を提供します。Play は HTML と Java のスニペットファイルを提供しています: これらを使用するためには、snipMate をインストールして @$PLAY_HOME/support/vim/*.snippets@ を @~/.vim/snippets/@ にコピーします。 + + +h2(#custom). カスタム設定 + +Play アプリケーションは標準的な Java アプリケーションであり、お好みのエディタを使って作業をするために特定のプラグインも必要としません。ただし、その場合は Play がどのように動作しているかについて、ほんのちょっとの知識を必要とします: + +h3. Classpath 設定 + +Play アプリケーションのクラスパスは以下の通り (この順で) 構築されます: + +* アプリケーションの @conf/@ ディレクトリ +* The @$PLAY_PATH/framework/play-$version.jar@ +* @lib/@ ディレクトリ内のすべての JAR ファイル +* @$PLAY_PATH/framework/lib/@ ディレクトリ内のすべての JAR ファイル + +p(note). *Tip* + +モジュールを利用する場合は、すべてのモジュールライブラリも同様に ( @$module/lib/@ ディレクトリから) クラスパスに追加する必要があります。 からモジュールライブラリをクラスパスに追加する必要があります。 + + +h3. 実行する Main クラス + +Play アプリケーションを開始するには、 @play.server.Server@ クラスを実行するだけです。Play は実行するアプリケーションの場所を見つけるために @"application.path"@ システムプロパティを使用します。通常、以下のようにしてこの値を渡します: + +bc. java -Dapplication.path="/app/path"… + + +h3. Java エージェント + +HotSwap リロードを有効にするためには、Java agent を @play.jar@ ライブラリに組み込む必要があります。通常は以下のようにします: + +bc. java -javaagent:"$PLAY_PATH/framework/play.jar" … + +これは必須ではありませんが、これが利用可能であるとき、クラスのリロードが高速になるでしょう。 + + +h2. デバッグ問題 + +Java ソースが変更されたとき、Play は自動的に Java クラスをリロードします。しかし、Java はクラスのリロードを完全にサポートしていないので、JDPA デバッグは簡単に混乱します: コードをステップインしたときに、ブレークポイントの設定に失敗したり、デバッガが間違った行で止まるかもしれません。 + +これを避けるよりよい方法は、コードを変更した後にデバッグセッションを新規に開始することです。幸運なことに、JPDA は JVM を再起動することなく、いつでもデバッガを接続したり、切断することができます。 + +以上より、デバッグにおける正しいワークフローは次のようになります: + +* ソースコードを変更します。 +* 結果を見るためにブラウザをリフレッシュします。 (このとき Play は変更内容をリロードして JVM 上のクラスを再定義します) +* 何かがおかしくデバッグする必要がある場合は、新しいデバッグセッションを開始します。 +* コードをデバッグして、修正します。 +* デバッガを切断します。 + +このワークフローを使用することによって、JVM にロードされたコードとデバッガを常に同期させることができます。 + +p(note). 次は: %(next)" 'Hello World' チュートリアル":firstapp% です。 \ No newline at end of file diff --git a/documentation/manual_ja/ids.textile b/documentation/manual_ja/ids.textile new file mode 100644 index 0000000000..c6aec51701 --- /dev/null +++ b/documentation/manual_ja/ids.textile @@ -0,0 +1,52 @@ +h1. 複数環境用 application.conf の管理 + +チームで作業をする場合、様々な開発者がそれぞれの @application.conf@ で異なる設定キーを使用します。例えば、ログレベルやいくつかのデータベース設定… これは一般的に、使用する VCS にファイルをコミットするときのコンフリクトの再発を招きます。 + +さらに、様々なデプロイ環境 - 例えば、開発、テスト、ステージング、本番環境 - は、それぞれ異なる設定を必要とします。 + +h2. フレームワーク ID + +この問題を解決するため、Play ではインストールしたフレームワーク毎に **ID** を与えることができます。この ID を定義するためには、play ツールの @id@ コマンドを使用してください: + +bc. play id + +!images/id! + +こうすることで、選択可能にしたい設定項目のキーにフレームワーク ID を接頭辞として付けることができます: + +bc. application.name=Cool app +application.mode=dev +application.log=INFO + +# Configuration for gbo +%gbo.application.log=DEBUG +%gbo.db=mem + +# Configuration for src +%scr.http.port=9500 + +# Production configuration +%production.http.port=80 +%production.application.log=INFO +%production.application.mode=prod + +h2. コマンドラインによるフレームワーク ID の設定 + +使用するフレームワーク ID を、特定のコマンドに対してコマンドラインから直接指定することができます。例えば、アプリケーションを production モードで実行するためには、以下のように使用することができます: + +bc. play run --%production + +@application.conf@ ファイルに定義されている設定行は以下のとおりです: + +bc. application.mode=dev +%production.application.mode=prod + +これは、フレームワーク ID 情報を使用するすべてのコマンドと互換性があるはずです。デフォルトの ID は既に @play id@ コマンドを使用して定義されています。 + +ちなみに、 @play test@ は以下と等価です: + +bc. play run --%test + +p(note). **考察を続けます** + +今度は %(next)"本番環境へのデプロイ":production% に移りましょう。 \ No newline at end of file diff --git a/documentation/manual_ja/index.textile b/documentation/manual_ja/index.textile new file mode 100644 index 0000000000..7af45e2ea1 --- /dev/null +++ b/documentation/manual_ja/index.textile @@ -0,0 +1,124 @@ +h1. インデックス + +Play framework の特定の機能を素早く見つけるためにこのページを使ってください。 + +h2. テンプレート中で暗黙的に使用できる変数 + +
+# "errors":/@api/play/data/validation/Validation.html#errors%28%29 +# "flash":/@api/play/mvc/Scope.Flash.html +# "lang":/@api/play/i18n/Lang.html +# "messages":/@api/play/i18n/Messages.html +# "out":http://download.oracle.com/javase/1.5.0/docs/api/java/io/PrintWriter.html +# "params":/@api/play/mvc/Scope.Params.html +# "play":/@api/play/Play.html +# "request":/@api/play/mvc/Http.Request.html +# "session":/@api/play/mvc/Scope.Session.html +
+ +h2. 組み込みタグ + +
+# "a":tags#a +# "authenticityToken":tags#authenticityToken +# "cache":tags#cache +# "doLayout":tags#dolayout +# "else":tags#else +# "elseif":tags#elseif +# "error":tags#error +# "errorClass":tags#errorClass +# "errors":tags#errors +# "extends":tags#extends +# "field":tags#field +# "form":tags#form +# "get":tags#get +# "i18n":tags#i18n +# "if":tags#if +# "ifError":tags#iferror +# "ifErrors":tags#iferrors +# "ifnot":tags#ifnot +# "include":tags#include +# "jsAction":tags#jsaction +# "list":tags#list +# "option":tags#option +# "script":tags#script +# "render":tags#render +# "select":tags#select +# "set":tags#set +# "stylesheet":tags#stylesheet +# "verbatim":tags#verbatim +
+ +h2. 組み込み Java エクステンション + +
+# "Collection extensions":javaextensions#collection +# "join(separator)":javaextensions#join +# "pluralize()":javaextensions#pluralize +# "pluralize(plural)":javaextensions#pluralize-plural +# "pluralize(singular, plural)":javaextensions#pluralize-singular-plural +# "Date extensions":javaextensions#date +# "format(format)":javaextensions#format +# "format(format, lang)":javaextensions#format-language +# "since()":javaextensions#since +# "since(condition)":javaextensions#since-condition +# "Long extensions":javaextensions#long +# "asdate(format)":javaextensions#asdate +# "asdate(format, lang)":javaextensions#asdate-language +# "formatSize()":javaextensions#formatSize +# "Map extensions":javaextensions#map +# "asAttr()":javaextensions#asAttr +# "asAttr(condition)":javaextensions#asAttr-condition +# "Number extensions":javaextensions#number +# "divisibleBy(divisor)":javaextensions#number-divisibleBy +# "format(format)":javaextensions#number-format +# "formatCurrency(code)":javaextensions#number-formatCurrency +# "page(pageSize)":javaextensions#number-page +# "pluralize()":javaextensions#number-pluralize +# "pluralize(plural)":javaextensions#number-pluralize-plural +# "pluralize(singular, plural)":javaextensions#number-pluralize-singular-plural +# "Object extensions":javaextensions#object +# "addSlashes()":javaextensions#addSlashes +# "capAll()":javaextensions#capAll +# "capFirst()":javaextensions#capFirst +# "cut(substring)":javaextensions#cut +# "escape()":javaextensions#escape +# "nl2br()":javaextensions#nl2br +# "raw()":javaextensions#raw +# "raw(condition)":javaextensions#raw-condition +# "yesNo(‘yes’, ‘no’)":javaextensions#yesNo +# "String extensions":javaextensions#string +# "asXml()":javaextensions#asXml +# "camelCase()":javaextensions#camelCase +# "capitalizeWords()":javaextensions#capitalizeWords +# "escapeHtml()":javaextensions#escapeHtml +# "escapeJavaScript()":javaextensions#escapeJavaScript +# "escapeXml()":javaextensions#escapeXml +# "last()":javaextensions#last +# "noAccents()":javaextensions#noAccents +# "pad(length)":javaextensions#pad +# "slugify()":javaextensions#slugify +# "urlEncode()":javaextensions#urlEncode +# "String array extensions":javaextensions#string-array +# "add(value)":javaextensions#add +# "contains(string)":javaextensions#contains +# "remove(string) ":javaextensions#remove +
+ +h2. 組み込みバリデーション + +
+# "email":validation-builtin#email +# "equals":validation-builtin#equals +# "future":validation-builtin#future +# "isTrue":validation-builtin#isTrue +# "match":validation-builtin#match +# "max":validation-builtin#max +# "maxSize":validation-builtin#maxSize +# "min":validation-builtin#min +# "minSize":validation-builtin#minSize +# "past":validation-builtin#past +# "range":validation-builtin#range +# "required":validation-builtin#required +# "url":validation-builtin#url +
diff --git a/documentation/manual_ja/install.textile b/documentation/manual_ja/install.textile new file mode 100644 index 0000000000..e6e0de11d2 --- /dev/null +++ b/documentation/manual_ja/install.textile @@ -0,0 +1,91 @@ +h1. インストールガイド + + +h2. 前提条件 + +Play framework を実行するには、"Java5 以降":http://java.sun.com が必要です。Play をソースからビルドしたいのであれば、ソースコードを取得するために "Git ソースコントロールクライアント":http://git-scm.com/ が必要であり、ビルドするために "Ant":http://ant.apache.org/ が必要になります。 + +カレントパスで java コマンドが認識できることを確認してください (@java -version@ を入力して確認してみてください) 。Play はデフォルトの java コマンドか、 @$JAVA_HOME@ が定義されている場合は、このパスで実行可能な java コマンドを使用します。 + +*play* のコマンドラインユーティリティは Python を使用します。ですから、どのような UNIX システムでもそのまま使うことができます (ただし Python 2.5 以上が必要です) 。 Windows で実行する場合でも、フレームワークに Python ランタイムをバンドルしているので、心配する必要はありません。 + + +h2. Installation from the binary packageバイナリパッケージからのインストール + +h3. 一般的な手順 + +通常、インストール手順は次のとおりです。 + +# Java をインストールします。 +# 最新の "Play binary package":http://download.playframework.org/ ダウンロードし、アーカイブを展開します。 +# ‘play’ コマンドをシステムパスに追加し、実行できることを確認します。 + + +h3. Mac OS X + +Java は組み込まれているか、自動的にインストールされるので、最初の手順は省略することができます。 + +# 最新の "Play binary package":http://download.playframework.org/ をダウンロードし、 @/Applications@ に展開します。 +# @/etc/paths@ を編集し、(例えば) @/Applications/play-1.2.5@ 行を追加します。 + +OS X における代替手順は次のとおりです: + +# "HomeBrew":http://mxcl.github.com/homebrew/ をインストールします。 +# @brew install play@ を実行します。 + +h3. Linux + +Javaをインストールするには、(多くの Linux 上でデフォルトの Java コマンドである GCJ ではなく)Sun-JDK または OpenJDK のいずれかを使用してください。 + + +h3. Windows + +Java をインストールするには、最新の JDK パッケージをダウンロードしてインストールします。 Python ランタイムはフレームワークに同梱されているので、別途 Python をインストールする必要はありません。 + + +h2. 最新ソースからのビルド + +最新の機能やバグ修正の恩恵を得るために、Play をソースからコンパイルしたくなるかもしれません。ソースコードを取得するためには "Git クライアント":http://git-scm.com/ が必要であり、フレームワークをビルドするためには "Ant":http://ant.apache.org/ が必要です。 + +コマンドラインから下記を実行します: + +bc. # git clone git://github.com/playframework/play.git +# cd play/framework +# ant + +これで Play framework を使用する準備が整いました。 + + +h2. play コマンドを使用する + +フレームワークを正しくインストールしたら、シェルを開いて *play* コマンドを実行してください。 + +bc. $ play + +play のデフォルトメッセージが見えるはずです: + +!images/help! + +*play help コマンド名* を実行することで、特定のコマンドのより詳細なヘルプを見ることができます。次の例を試してみてください: + +bc. # play help run + +h2. 新しいアプリケーションの作成 + +@new@ コマンドを使って新しいアプリケーションを作成してください。アプリケーションの作成先として、存在しないパスを指定する必要があります。 + +bc. # play new myApp + +!images/guide1-1! + +新しいアプリケーションが作成されます。以下のコマンドでアプリケーションを開始します: + +bc. # play run myApp + +ブラウザで "http://localhost:9000":http://localhost:9000 を開いて、アプリケーションのデフォルトページを見ることができます。 + +!images/guide1-2! + +p(note). **Play を利用する環境が整いました** + +次は %(next)"お好みの IDE を設定しましょう":ide% \ No newline at end of file diff --git a/documentation/manual_ja/javaextensions.textile b/documentation/manual_ja/javaextensions.textile new file mode 100644 index 0000000000..3b80237f70 --- /dev/null +++ b/documentation/manual_ja/javaextensions.textile @@ -0,0 +1,400 @@ +h1. Java エクステンション + +Java エクステンションは、ビューテンプレートで使う便利なメソッドをオブジェクトに追加し、式をより表現力豊かにします。 + +コードサンプルは、テンプレートにおける式の例と、その結果を次の行に示しています。 + + + +h2. コレクションの拡張 + + +h3. join(separator) + +コレクションの要素それぞれの間を与えられたセパレータで連結します。返却型: @String@ + +bc. ${['red', 'green', 'blue'].join('/')} +red/green/blue + + +h3. last() + +リストの最後のアイテムを返します。返却型: @Object@ + +bc. ${['red', 'green', 'blue'].last()} +blue + + +h3. pluralize() + +コレクションのサイズが 1 でない場合に ‘s’ を返します。返却型: @String@ + +bc. colour${['red', 'green', 'blue'].pluralize()} +colours + + +h3. pluralize(plural) + +コレクションのサイズが 1 でない場合に、与えられた複数形を返します。返却型: @String@ + +bc. box${['red', 'green', 'blue'].pluralize('es')} +boxes + + +h3. pluralize(singular, plural) + +コレクションのサイズが 1 でない場合に与えられた複数形を返します; コレクションのサイズが 1 の場合は与えられた単数形を返します。返却型: @String@ + +bc. journ${['red'].pluralize('al', 'aux')} +journal + +journ${['red', 'green', 'blue'].pluralize('al', 'aux')} +journaux + + + +h2. 日付の拡張 + + +h3. format(format) + +与えられたフォーマットパターンで日付をフォーマットします。返却型: @String@ + +bc. ${new Date(1275910970000).format('dd MMMM yyyy hh:mm:ss')} +07 June 2010 01:42:50 + + +h3. format(format, language) + +与えられたフォーマットパターンと言語で日付をフォーマットします。返却型: @String@ + +bc. ${new Date(1275910970000).format('dd MMMM yyyy hh:mm:ss', 'fr')} +07 juin 2010 01:42:50 + + +h3. since() + +日付を現在と比較した相対値、例えば **3 minutes ago** としてフォーマットします。返却型: @String@ + +bc. ${new Date(new Date().getTime() - 1000000).since()} +16 minutes ago + +次のメッセージを使って出力をカスタマイズすることができます: @since.seconds@, @since.minutes@, @since.hours@, @since.days@, @since.months@ and @since.years@. + + +h3. since(condition) + +日付を現在と比較した相対値としてフォーマットします。条件が true の場合、一ヶ月より前の日付は日付としてフォーマットされます。返却型: @String@ + +@since()@ と同じメッセージを使って出力をカスタマイズすることができるのと同様に、一ヶ月より前の日付について @since.format@ を使うことができます。 + +bc. ${new Date(1262350000000).since(false)} +5 months ago + +${new Date(1262350000000).since(true)} +Jan 1, 2010 + + + +h2. Long の拡張 + + +h3. asdate(format) + +タイムスタンプを日付としてフォーマットします。返却型: @String@ - フォーマットされた日付 + +bc. ${1275910970000.asdate('dd MMMM yyyy hh:mm:ss')} +07 June 2010 01:42:50 + + +h3. asdate(format, language)formatSize() + +バイト数を単位の付いたファイルサイズとしてフォーマットします。 + +bc. ${726016L.formatSize()} +709KB + + + + +h2. Map の拡張 + + +h3. asAttr() + +マップのキーと値を HTML の属性としてフォーマットします。返却型: @play.templates.Template.ExecutableTemplate.RawData@. + +bc. ${[id:'42', color:'red'].asAttr()} +id="42" color="red" + + +h3. asAttr(condition) + +条件が ture の場合、マップのキーと値を HTML の属性としてフォーマットします。返却型: @play.templates.Template.ExecutableTemplate.RawData@. + +bc. ${[id:'42', color:'red'].asAttr(true)} +id="42" color="red" + + + +h2. Number の拡張 + + +h3. divisibleBy(divisor) + +与えられた数字で割り切れる場合に true を返します。返却型: @boolean@ + +bc. ${42.divisibleBy(7)} +true + + +h3. format(format) + +与えられたフォーマットパターンで数字をフォーマットします。返却型: @String@ + +bc. ${42.format('000.00')} +042.00 + + +h3. formatCurrency(currencyCode)page(pageSize) + +インデックスとして解釈した数字の、与えられたページ数に対するページ数を返します。返却型: @String@ + +bc. ${42.page(10)} +5 + + +h3. pluralize() + +数字が 1 でない場合に ‘s’ を返します。返却型: @String@ + +bc. colour${['red', 'green', 'blue'].pluralize()} - colour${3.pluralize()} +colours - colours + + +h3. pluralize(plural) + +数字が 1 でない場合に与えられた複数形を返します。返却型: @String@ + +bc. box${3.pluralize('es')} +boxes + + +h3. pluralize(singular, plural) + +数字が 1 でない場合に与えられた複数形を返します; 数字が 1 の場合は与えられた単数形を返します。返却型: @String@ + +bc. journ${1.pluralize('al', 'aux')} +journal + +journ${3.pluralize('al', 'aux')} +journaux + + + +h2. オブジェクトの拡張 + + +h3. addSlashes() + +Java においてエスケープされたオブジェクトの @文字列@ 表現に含まれるシングルクォーテーションとダブルクォーテーションを、バックスラッシュでエスケープします。返却型: @String@ + +bc. ${"single quote (')".addSlashes().raw()} ${'double quote (")'.addSlashes().raw()} +single quote (\') double quote (\") + + +h3. capAll() + +オブジェクトの @文字列@ 表現に含まれるすべての単語の先頭を大文字にします。返却型: @String@ + +bc. ${"lorum ipsum dolor".capAll()} +Lorum Ipsum Dolor + + +h3. capFirst() + +オブジェクトの @文字列@ 表現に含まれる最初の単語の先頭を大文字にします。返却型: @String@ + +bc. ${"lorum ipsum dolor".capFirst()} +Lorum ipsum dolor + + +h3. cut(substring) + +与えられた部分文字列を取り除きます。返却型: @String@ + +bc. ${"lorum ipsum dolor".cut('um')} +lor ips dolor + + +h3. escape() + +オブジェクトの @文字列@ 表現に含まれる HTML 文字をエスケープします。返却型: @String@ + +bc. ${"The tag is evil".escape().raw()} +The <blink>tag</blink> is evil + + +h3. nl2br() + +改行文字を HTML の @br@ タグで置き換えます。返却型: @String@ + +bc. ${"one\ntwo".nl2br()} +one
two + +この出力は HTML-エスケープされないので、 出力中の @br@ タグ がエスケープされていないことに注意してください。これは、おそらくユーザの入力に対して "escape()":#escape を使用したくなることを意味します: + +bc. ${userInput.escape().nl2br()} + + +h3. raw() + +オブジェクトをテンプレートエスケープせずに返します。返却型: @play.templates.Template.ExecutableTemplate.RawData@ + +bc. ${'<'} +< + +${'<'.raw()} +< + + +h3. raw(condition) + +条件が true の場合、オブジェクトをテンプレートエスケープせずに返します。返却型: @play.templates.Template.ExecutableTemplate.RawData@ + +bc. ${'<'.raw(true)} +< + + +h3. yesNo('yes', 'no') + +オブジェクトが true と評価される場合は最初のパラメータ (‘yes’) を、そうでない場合は二番目のパラメータ (‘no’) を返します。返却型: @String@ + +bc. ${"".yesno('yes', 'no')} +no + +${"not empty".yesno('yes', 'no')} +yes + + +h2. String の拡張 + + +h3. asXml() + +与えられた XML 文字列を解析します。返却型: @groovy.util.slurpersupport.GPathResult@ + +h3. camelCase() + +文字列を Java クラス名のようにキャメルケースとしてフォーマットします。返却型: @String@ + +bc. ${"lorum ipsum dolor".camelCase()} +LorumIpsumDolor + + +h3. capitalizeWords() + +文字列に含まれるすべての単語の先頭を大文字にします。返却型: @String@ + +bc. ${"lorum ipsum dolor".capitalizeWords()} +Lorum Ipsum Dolor + + +h3. escapeHtml() + +与えられた HTML 文字をエスケープします。返却型: @String@ + +bc. ${"The tag is evil".escapeHtml().raw()} +The <blink>tag</blink> is evil + + +h3. escapeJavaScript() + +与えられた JavaScript 文字をエスケープします。返却型: @String@ + +bc. ${"single quote (') double quote (\")".escapeJavaScript().raw()} +single quote (\') double quote (\") + + +h3. escapeXml() + +与えられた XML 文字をエスケープします。返却型: @String@ + +bc. ${"<>\"&".escapeXml().raw()} +<>"& + + +h3. noAccents() + +文字列に含まれる文字からアクセントを取り除きます。返却型: @String@ + +bc. ${"Stéphane Épardaud".noAccents()} +Stephane Epardaud + + +h3. pad(length) + +与えられた長さだけ文字列に @nbsp;@ を付け足します。返却型: @String@ + +bc. ${"x".pad(4).raw()} +x    + + +h3. slugify() + +URL パスとして予約されている文字に沿うように、文字列を URL に使用できる ‘俗語’ としてフォーマットします。返却型: @String@ + +bc. ${"The Play! framework’s manual".slugify()} +the-play-framework-s-manual + + +h3. urlEncode() + +与えられた URL クエリ文字列をエスケープします。返却型: @String@ + +bc. ${"!@#\$%^&()".urlEncode()} +%21%40%23%24%25%5E%26%28%29 + + +h2. String 配列の拡張 + + +h3. add(value) + +配列の終端に値を追加します。返却型: @String[]@ + +bc. ${(["red", "green", "blue"] as String[]).add('pink').join(' ')} +red green blue pink + + +h3. contains(string) + +配列が与えられた文字列を含む場合は true を返します。返却型: @boolean@ + +bc. ${(['red', 'green', 'blue'] as String[]).contains('green')} +true + + +h3. remove(string) + +与えられた文字列を取り除いた配列を返します。返却型: @String[]@ + +bc. ${(['red', 'green', 'blue'] as String[]).remove('green').join(' ')} +red blue + diff --git a/documentation/manual_ja/jobs.textile b/documentation/manual_ja/jobs.textile new file mode 100644 index 0000000000..b25e014255 --- /dev/null +++ b/documentation/manual_ja/jobs.textile @@ -0,0 +1,138 @@ +h1. 非同期ジョブ + +Play はウェブアプリケーションフレームワークなので、アプリケーションロジックの大部分は HTTP リクエストに応じるコントローラによって行われます。 + +しかし、どのような HTTP リクエストとも関係のないところで何らかのアプリケーションロジックを実行しなければならないことも時々あります。初期化タスク、保守タスク、あるいは HTTP リクエストの実行プールをブロックせずに実行する長いタスクなどには、役に立つ場合があります。 + +ジョブはフレームワークによって完全に管理されます。これは、Play が JPA エンティティマネージャの同期と、トランザクション管理といったすべてのデータベース接続関連のものを管理することを意味します。ジョブを作成するのに必要なのは、 @play.jobs.Job@ スーパークラスを継承することだけです。 + +bc. package jobs; + +import play.jobs.*; + +public class MyJob extends Job { + + public void doJob() { + // execute some application logic here ... + } + +} + +結果を返すジョブを作成しなければならない場合があります。そのときは @doJobWithResult()@ メソッドをオーバーライドします。 + +bc. package jobs; + +import play.jobs.*; + +public class MyJob extends Job { + + public String doJobWithResult() { + // execute some application logic here ... + return result; + } + +} + +この例では @String@ を使用しましたが、もちろん、ジョブはどんなオブジェクト型でも返すことができます。 + +h2. 起動時に実行するジョブ + +ブートストラップジョブはアプリケーション始動時に Play によって実行されます。ブートストラップジョブとしてマークするために必要なのは、 @OnApplicationStart アノテーションを追加することだけです。 + +bc. import play.jobs.*; + +@OnApplicationStart +public class Bootstrap extends Job { + + public void doJob() { + if(Page.count() == 0) { + new Page("root").save(); + Logger.info("A root page has been created."); + } + } + +} + +結果を返す必要はありません。結果を返しても、それは失われます。 + +デフォルトでは、 *@OnApplicationStart* でアノテーションされたすべてのジョブは順番に実行されます。すべてのジョブが完了したところで、アプリケーションはリクエストを受け取り、処理する準備を整えます。 + +アプリケーションの起動時にジョブを開始したいけれど、直ちにリクエストを受け取って処理したい場合、ジョブを @OnApplicationStart(async=true) と註釈することができます。これでジョブはアプリケーション起動時にバックグラウンドで開始するようになります。すべての非同期ジョブは同時に開始します。 + +p(note). **警告** + +DEV モードでアプリケーションを実行する場合、アプリケーションは最初の HTTP リクエストがあるまで始動を控えます。さらには、DEV モードの場合、アプリケーションは必要に応じて自動的に再起動することがあります。 + +PROD モードで実行するとき、アプリケーションはサーバの起動と同時に開始します。 + +h2. スケジューリングされたジョブ + +スケジューリングされたジョブは、フレームワークによって定期的に実行されます。 @Every アノテーションを使用することで、特定の間隔でジョブを実行するよう Play に指示することができます。 + +bc. import play.jobs.*; + +@Every("1h") +public class Bootstrap extends Job { + + public void doJob() { + List newUsers = User.find("newAccount = true").fetch(); + for(User user : newUsers) { + Notifier.sayWelcome(user); + } + } + +} + +上記は時間用アノテーションであり、分または秒で感覚を指定したい場合は、以下の方法で指定することができます:- +i) 分 - @Every("3mn"),@Every("30mn") +ii) 秒 - @Every("35s"),@Every("56s") + +@Every アノテーションで事足りない場合は、CRON 式を使用してジョブを実行する @On アノテーションを使用することができます。 + +bc. import play.jobs.*; + +/** Fire at 12pm (noon) every day **/ +@On("0 0 12 * * ?") +public class Bootstrap extends Job { + + public void doJob() { + Logger.info("Maintenance job ..."); + ... + } + +} + +p(note). **Tip** + +Play は "Quartz ライブラリ":http://www.quartz-scheduler.org/docs/tutorials/crontrigger.html の CRON 式パーサを使用します。 + +結果を返す必要はありません。結果を返しても、それは失われます。 + +h2. Triggering task jobs + +Job インスタンスにおいて単に @now()@ をコールすることで、特別なタスクを執り行うジョブをトリガーすることもできます。こうすると、ジョブは何もブロックせずに、速やかに実行されます。 + +bc. public static void encodeVideo(Long videoId) { + new VideoEncoder(videoId).now(); + renderText("Encoding started"); +} + +Job において @now()@ をコールすると、そのタスクの終了後に結果を読み出すために使用できる @Promise@ を返します。 + +h2. アプリケーションの停止 + +アプリケーションがシャットダウンする前にアクションを実行する必要がある場合があるので、Playは @OnApplicationStop アノテーションを提供します。 + +bc. import play.jobs.*; + +@OnApplicationStop +public class Bootstrap extends Job { + + public void doJob() { + Fixture.deleteAll(); + } +} + +p(note). **考察を続けます** + +より強力な %(next)"HTTP による非同期プログラミング":asynchronous% とジョブを組み合わせる方法について学びましょう。 diff --git a/documentation/manual_ja/jpa.textile b/documentation/manual_ja/jpa.textile new file mode 100644 index 0000000000..87423817e7 --- /dev/null +++ b/documentation/manual_ja/jpa.textile @@ -0,0 +1,285 @@ +h1. JPA 永続化 + +Play は、JPA エンティティの管理を容易にするとても便利な一連のヘルパを提供します。 + +p(note). いつでも素の JPA API に立ち返ることができることに **注意** してください。 + +h2. JPA エンティティマネージャの開始 + +Playは、 @javax.persistence.Entity アノテーションで注釈されたクラスをひとつ以上見つけた場合、自動的に Hibernate エンティティマネージャを開始します。とは言え、JDBC データソースを正しく設定していない場合、エンティティマネージャの開始は失敗してしまうので、確実に設定してください。 + +h2. JPA エンティティマネージャの取得 + +JPA エンティティマネージャが開始されている場合、JPA ヘルパを使用してアプリケーションコードからそれを取得することができます。例えば、以下のようにします: + +bc. public static index() { + Query query = JPA.em().createQuery("select * from Article"); + List
articles = query.getResultList(); + render(articles); +} + +h2. トランザクション管理 + +Play は自動的にトランザクションを管理します。Play は、HTTPリクエスト毎にトランザクションを開始し、HTTP レスポンスを送信するときにコミットします。アプリケーションコードが例外を創出した場合、トランザクションは自動的にロールバックされます。 + +アプリケーションコードから強制的にトランザクションをロールバックする必要が場合、 @JPA.setRollbackOnly()@ メソッドを使用することができ、JPAは現在のトランザクションにコミットしません。 + +アノテーションを使って、トランザクションをどのように扱うべきか指定することもできます。 + +コントローラ内のメソッドを @play.db.jpa.Transactional(readOnly=true) でアノテーションした場合、トランザクションは読み出し専用になります。 + +Play にトランザクションを一切起動させないようにしたい場合は、メソッドを @play.db.jpa.NoTransaction でアノテーションします。 + +すべてのメソッドでトランザクションを利用しない場合は、コントローラクラスを @play.db.jpa.NoTransaction でアノテーションすることができます。 + +@play.db.jpa.NoTransaction を使うと、Play はコネクションプールから一切のコネクションを取得しません - これは速度を向上させます。 + + +h2. **play.db.jpa.Model** サポートクラス + +このクラスは JPA の中心的なヘルパクラスです。JPA エンティティのうちの 1 つに @play.db.jpa.Model@ クラスを継承させると、ヘルパクラスは JPA アクセスを容易にする多くのヘルパトメソッドを提供します。 + +例えば、次の Post モデルオブジェクトを見てください: + +bc.. @Entity +public class Post extends Model { + + public String title; + public String content; + public Date postDate; + + @ManyToOne + public Author author; + + @OneToMany + public List comments; +} + +@play.db.jpa.Model@ クラスは自動的に 自動生成された @Long 型の id@ フィールドを提供します。自動生成された @Long 型の id@ を JPA モデルのための主キー (技術的な主キー) として保持し、別のフィールドを機能的な主キーとして管理することは一般的に良い案だと考えます。 + +Play が、Post クラスの **public** メンバを自動的に **プロパティ** と見なすという事実に注意してください。このため、このオブジェクトのためにすべての setter/getter メソッドを書く必要はありません。 + +h2. GenericModel によるカスタム id のマッピング + +エンティティは @play.db.jpa.Model@ に基づかなければならないわけではありません。エンティティは @play.db.jpa.GenericModel@ クラスを継承することもできます。これは、エンティティのプライマリキーに @Long 型の id@ を使いたくない場合に必要になります。 + +例えば、以下はとてもシンプルな **User** エンティティのマッピングです。 **id** は UUID であり、属性 **name** と **mail** は必須、そして Play のバリデーションを使ってシンプルなビジネスルールを強制することができます。 + +bc.. @Entity +public class User extends GenericModel { + @Id + @GeneratedValue(generator = "system-uuid") + @GenericGenerator(name = "system-uuid", strategy = "uuid") + public String id; + + @Required + public String name; + + @Required + @MaxSize(value=255, message = "email.maxsize") + @play.data.validation.Email + public String mail; +} + +h2. オブジェクトの検索 + +@play.db.jpa.Model@ はデータを検索するいくつかの方法を提供します。例えば、以下のようにします: + +h3. ID による検索 + +オブジェクトを検索する最も簡単な方法です。 + +bc. Post aPost = Post.findById(5L); + +h3. 全件検索 + +bc. List posts = Post.findAll(); + +これは、 **すべての** ポストを検索する最も簡単な方法ですが、同じことは次のようにしても可能です: + +bc. List posts = Post.all().fetch(); + +次のようにして、ページングした結果を得ることができます: + +bc. // 100 max posts +List posts = Post.all().fetch(100); + +または、次のようにすることも可能です, + +bc. +// 100 max posts start at 50 +List posts = Post.all().from(50).fetch(100); + +h3. 簡易なクエリによる検索 + +クエリは、とても表現豊かなファインダを作成することを可能にしますが、シンプルなクエリについてのみ動作します。 + +bc. Post.find("byTitle", "My first post").fetch(); +Post.find("byTitleLike", "%hello%").fetch(); +Post.find("byAuthorIsNull").fetch(); +Post.find("byTitleLikeAndAuthor", "%hello%", connectedUser).fetch(); + +簡易なクエリは、Comparator が以下に従う箇所において、 ==[Property][Comparator]And?== という文法に従います。 + +* @LessThan@ - 与えられた値より小さい +* @LessThanEquals@ - 与えられた値と等しいか小さい +* @GreaterThan@ - 与えられた値より大きい +* @GreaterThanEquals@ - 与えられた値と等しいか大きい +* @Like@ - 属性値が常に小文字に変換されることを除いて、SQL の like 式と等価 +* @Ilike@ - 大文字小文字を無視することを除いて Like と同じであり、属性値はやはり小文字に変換される +* @Elike@ - SQL の like 式と等価であり、変換は行われない +* @NotEqual@ - 等しくない +* @Between@ - ふたつの値の間 (ふたつの引数が必要) +* @IsNotNull@ - null でない (引数は必要なし) +* @IsNull@ - null (引数は必要なし) + +h3. JPQL クエリによる検索 + +JPQL クエリを使用することができます: + +bc. Post.find( + "select p from Post p, Comment c " + + "where c.post = p and c.subject like ?", "%hop%" +); + +一部だけ使用することも可能です: + +bc. Post.find("title", "My first post").fetch(); +Post.find("title like ?", "%hello%").fetch(); +Post.find("author is null").fetch(); +Post.find("title like ? and author is null", "%hello%").fetch(); +Post.find("title like ? and author is null order by postDate", "%hello%").fetch(); + +@order by@ 構文を指定することも可能です: + +bc. Post.find("order by postDate desc").fetch(); + +h2. オブジェクトのカウント + +簡単にオブジェクトをカウントすることができます。 + +bc. long postCount = Post.count(); + +クエリを使ってカウントすることも可能です: + +bc. long userPostCount = Post.count("author = ?", connectedUser); + + +h2. play.db.jpa.Blob によるアップロードされたファイルの保存 + +@play.db.jpa.Blob@ 型を使ってアップロードされたファイルを (データベースではなく) ファイルシステムに保存することができます。サーバ側では、Play はアップロードされた画像をアプリケーションフォルダの中にある @attachments/@ フォルダのファイルに保存します。このファイル名 ( "UUID":http://en.wikipedia.org/wiki/Uuid) と MIME タイプは、データベースの属性に @VARCHAR@ SQL 型として保存されます。 + +Play におけるファイルのアップロードと保存、そして提供の基本的な使い方はとても簡単です。これは、バインディングフレームワークが HTML フォームからアップロードされたファイルを自動的に JPA モデルにバインドするためであり、また Play がプレーンなテキストデータと同じくらい簡単にバイナリデータを配信できる便利なメソッドを提供しているためです。モデルにアップロードされたファイルを保存するには、 @play.db.jpa.Blob@ 型のプロパティを追加します。 + +bc. import play.db.jpa.Blob; + +@Entity +public class User extends Model { + + public String name; + public Blob photo; +} + +ファイルをアップロードするためには、ビューにフォームを追加し、モデルの @Blob@ プロパティ用にファイルをアップロードするフォームコントロールを使用します: + +bc. #{form @addUser(), enctype:'multipart/form-data'} + + +#{/form} + +それから、アップロードされたファイルを新規モデルオブジェクトに保存するアクションをコントローラに追加します: + +bc. public static void addUser(User user) { + user.save(); + index(); +} + +Play が自動的にファイルアップロードをハンドリングするので、このコードは JPA エンティティを保存すること以外は何もしないように見えます。まず最初に、アクションメソッドを開始する前にアップロードされたファイルは @tmp/uploads/@ のサブフォルダに保存されます。次に、エンティティが保存されると、このファイルは UUID をファイル名として @attachments/@ フォルダにコピーされます。最後に、アクションが完了するとこの一時ファイルは削除されます。 + +同じ user に対して別のファイルをアップロードした場合、新しい UUID の名前を持つ新規ファイルとしてサーバに保存されます。これは、元のファイルが孤立することを意味しています。無限のディスクスペースを持っていないのであれば、おそらく "非同期ジョブ":jobs による独自のクリーンアップスキーマを実装しなければならないことでしょう。 + +もし HTTP リクエストがファイルの正確な MIME タイプを指定していない場合は、 "コントローラファイルアップロード":controllers#file の章で述べたとおり、ファイル名の拡張子マッピングを使用することができます。 + +添付ファイルを別のフォルダに保存するためには、 "attachments.path":configuration#attachments.path を設定してください。 + +保存されたファイルを提供するためには、 "コントローラバイナリレスポンス":controllers#binary の章で述べたとおり、コントローラの @renderBinary@ メソッドに @Blob.get()@ を渡します。 + + + +h2. 明示的な保存 + +Hibernate は、データベースから問い合わせたオブジェクトのキャッシュを保持します。これらオブジェクトは、これを取得するために使用された EntityManager がアクティブである限り、永続的なオブジェクトとして参照されます。これは、トランザクション境界内で行われる、これらのオブジェクトに対するいかなる変更についても、トランザクションがコミットされるときに自動的に永続化されることを意味します。JPA の標準では、これらの更新はトランザクション境界内において暗黙的です; これらの値を永続化するためにメソッドをコールする必要はありません。 + +主に不都合な点は、すべてのオブジェクトを手動で管理しなければならないということです。EntityManager に (もっと直感的であるべき) オブジェクトの更新を指示する代わりに、どのオブジェクトを更新しないのかを EntityManager に指示しなければなりません。これを行うために、本質的にはひとつのエンティティをロールバックする @refresh()@ メソッドをコールします。トランザクションのコミットを呼び出す前、またはオブジェクトを更新すべきでないことを認識している場合に、これを行います。 + +以下は、フォームがサブミットされた後に永続オブジェクトを編集する一般的なユースケースです: + +bc. public static void save(Long id) { + User user = User.findById(id); + user.edit("user", params.all()); + validation.valid(user); + if(validation.hasErrors()) { + // Here we have to explicitly discard the user modifications... + user.refresh(); + edit(id); + } + show(id); +} + +これまで見てきたところ、ほとんどの開発者がこのことに気付いておらず、save() を明示的にコールしなければオブジェクトは保存されないと仮定し、エラーが発生した場合にオブジェクトの状態を破棄し忘れます。 + +このため、Play ではまさにこれを変えました。JPASupport/JPAModel を継承するすべての永続オブジェクトは save() メソッドを明示的にコールしなければ保存されません。このため、実際のところ前述のコードを次のように書き直すことができます: + +bc. public static void save(Long id) { + User user = User.findById(id); + user.edit("user", params.all()); + validation.valid(user); + if(validation.hasErrors()) { + edit(id); + } else{ + user.save(); // explicit save here + show(id); + } +} + +これははるかに直感的です。さらには、大きなオブジェクトグラフについて明示的に save() をコールすることは退屈でなので、 **cascade=CascadeType.ALL** 属性で注釈された関連について、 @save()@ は連鎖的にコールされます。 + +h2. ジェネリック型の問題についてもっと詳しく + +@play.db.jpa.Model@ はひと揃えのジェネリックメソッドを定義します。これらのジェネリックメソッドは、メソッドの戻り値の型を指定するために型パラメータを使用します。これらのメソッドを使用する場合、戻り値として使用される具体的な型は実行コンテキストから型推論を使用することで決定されます。 + +例えば、 @findAll@ メソッドは次のように定義されています: + +bc. List findAll(); + +これは次のように使用します: + +bc. List posts = Post.findAll(); + +この場合、Javaコンパイラは、メソッドの結果に ==List<Post>== が割り当てられているという事実を使用することで、実際の型 @T@ を解決します。このため、T は Post 型として解決されます。 + +残念ながら、ジェネリックメソッドの戻り値を、別のメソッドの実行引数に直接使用する場合や、ループ処理で使用する場合は、型推論による型の決定は行われません。このため、以下のコードは "型の不一致: 要素タイプ Object から Post には変換できません" というコンパイルエラーが発生します: + +bc. for(Post p : Post.findAll()) { + p.delete(); +} + +もちろん、次のように一時的なローカル変数を使用することで、この問題を解決することができます: + +bc. List posts = Post.findAll(); // type inference works here! +for(Post p : posts) { + p.delete(); +} + +でも、ちょっと待ってください。もっといいやり方があります。Java 言語には、一度にコードを短く、しかしながら読み易くする、実用的だけれど、あまり知られていない機能があります: + +bc. for(Post p : Post.findAll()) { + p.delete(); +} + + +p(note). Play は XA (two-phase commits) をサポートしていません。 同じリクエストでそれぞれの違ったJPA設定を使う場合、 **Play はできる限り多くのトランザクションでコミットしようとします** 。 最初のデータベースでコミットに成功し、次に別データベースでコミットに失敗しても、最初のコミットはロールバックされません。同一リクエストの中で複数のJPA設定を使用する場合には、このことを覚えておいてください。 + +p(note). **考察を続けます** + +今度はいくつかの %(next)"Play ライブラリ":libs% を確認しましょう。 \ No newline at end of file diff --git a/documentation/manual_ja/lambdaj.textile b/documentation/manual_ja/lambdaj.textile new file mode 100644 index 0000000000..f1b13b6173 --- /dev/null +++ b/documentation/manual_ja/lambdaj.textile @@ -0,0 +1,129 @@ +h1. Some functional programming techniques + +Play supports the Scala programming language. One great thing (but not the only one) about Scala is that it is a mixed imperative/functional language that makes it possible to solve a lot of problems in a functional way. + +But now, what if you prefer keep using Java with Play? Could we bring some of the nice features of Scala to Java as well ? This section describes Play’s "lambdaj":http://code.google.com/p/lambdaj/ support. + +The main purpose of lambdaj is to partially eliminate the burden to write (often nested and poorly readable) loops while iterating over collections. From the lambdaj website: + +bq. How many times have you read or written the same two or three lines of code that frequently seem to go together, and even though they operate on different objects, feel like the same thing? And how often these repetitions involve some sort of collections iteration or more generically manipulation? These repetitions in the code is something that developers eventually learn to filter out and ignore when reading code, once they figure out where the interesting parts are placed. But even if the developers get used to it, it slows them down. Code like that is clearly written for computers to execute, not for developers to read. + +lambdaj is a library that makes easier to address this issue by allowing to manipulate collections in a pseudo-functional and statically typed way. In our experience to iterate over collection, especially in nested loops, is often error prone and makes the code less readable. The purpose of this library is to alleviate these problems employing some functional programming techniques but without losing the static typing of java. We impose this last constraint to make refactoring easier and safer and allow the compiler to do its job. + +h2. Let’s use lambdaj + +We will start with a fresh application that displays a Car catalogue. The @Car@ model class is: + +bc. package models; + +import play.*; +import play.db.jpa.*; + +import javax.persistence.*; +import java.util.*; + +@Entity +public class Car extends Model { + + public String name; + public String brand; + public int numberOfViews; + public Date lastViewed; + + @Transient + public double price; + + public Car viewed() { + lastViewed = new Date(); + numberOfViews++; + return this; + } + +} + +And let’s write a simple action that retrieve all these cars: + +bc. public static index() { + List cars = Car.find().fetch(); + render(cars); +} + +Now in the page it would be great to be able to order all these cars by brand, so we need to extract the brands from the car list. Let’s do it the lambdaj way: + +bc. List brands = collect(cars, on(Car.class).brand); + +This line will iterate over all the retrieved cars, collect all the brands and feed them to the returned list. The cool thing is that we are able to express that stuff in a pure statically-typed way. + +Now we need to filter this list to remove brand duplication: + +bc. Collection brands = selectDistinct( + collect(cars, on(Car.class).brand) +); + +Very easy. + +h2. Batching method calls + +We want to cound each time a car has been viewed, and remember the last viewed time. As we already have the @viewed()@ method that updates the @Car@ objects we just need to call this method on each retrieved car. Again, let’s do it the lambdaj way: + +bc. forEach(cars).viewed(); + +This line will iterate over each car object of the cars list and call the @viewed()@ method on each. + +And because we modified the state of the persistent objects, we need to save them. So rewrite it as: + +bc. forEach(cars).viewed().save(); + +h2. Using closures + +Huh? But Java doesn’t have closures! Wait, lambdaj partially fills this omission with a feature that allows you to define, in its traditional DSL style, first-class functions with free variables. + +Let’s say we have a @PriceWatcher@ utility able to fetch in real time the price of each car. + +bc. package models; + +public class PriceWatcher { + + public void setPrice(Car car) { + // retrieve the correct price here + car.price = currentPrice; + } + +} + +Because this data need to be **in real time** we don’t want store it in the database. Before displaying the car list we need to create a PriceWatcher object and ask it to resolve the current price of each car. Again, let’s do it the lambdaj way: + +bc. PriceWatcher priceWatcher = new PriceWatcher(); +Car.forEach(cars); { + of(priceWatcher).setPrice(var(Car.class)); +} + +We define a function with a free variable, and then ask to the @Car@ class to call them for each element of the @cars@ list. + +h2. The final action code + +Using all these good lambdaj stuff, we can finally write the @index@ action in a very expressive fashion: + +bc. public static void index() { + List cars = Car.find().fetch(); + + // Collect brands + Collection brands = selectDistinct( + collect(cars, on(Car.class).brand) + ); + + // Set all these cars viewed + forEach(cars).viewed().save(); + + // Update the prices + PriceWatcher priceWatcher = new PriceWatcher(); + Car.forEach(cars); { + of(priceWatcher).update(var(Car.class)); + } + + render(cars, brands); +} + + + + diff --git a/documentation/manual_ja/libs.textile b/documentation/manual_ja/libs.textile new file mode 100644 index 0000000000..a3fc4e1f11 --- /dev/null +++ b/documentation/manual_ja/libs.textile @@ -0,0 +1,333 @@ +h1. Play ライブラリ + +@play.libs@ パッケージには、一般的なプログラミングタスクを達成する手助けとなる便利ないくつかのライブラリが含まれています。 + +これらライブラリのほとんどは本当に直感的に使うことのできるシンプルなヘルパです: + +* "Codec":/@api/play/libs/Codec.html: データをエンコードまたはデコードするユーティリティ +* "Crypto":/@api/play/libs/Crypto.html: 暗号化に関するユーティリティ +* "Expression":/@api/play/libs/Expression.html: 動的な式の評価 +* "F":/@api/play/libs/F.html: Java による関数プログラミング +* "ファイル":/@api/play/libs/Files.html: ファイルシステム操作ヘルパ +* "I18N":/@api/play/libs/I18N.html: 国際化ヘルパ +* "IO":/@api/play/libs/IO.html: ストリーム操作ヘルパ +* "Images":/@api/play/libs/Images.html: イメージ操作ヘルパ +* "Mail":/@api/play/libs/Mail.html: e メール関数 +* "MimeTypes":/@api/play/libs/MimeTypes.html: MIME タイプの取扱い +* "OAuth":/@api/play/libs/OAuth.html: OAuth クライアントプロトコル +* "OAuth2":/@api/play/libs/OAuth2.html: OAuth2 クライアントプロトコル +* "OpenID":/@api/play/libs/OpenID.html: OpenID クライアントプロトコル +* "Time":/@api/play/libs/Time.html: 時間と期間のユーティリティ +* "WS":/@api/play/libs/WS.html: 強力な Web サービスクライアント +* "XML":/@api/play/libs/XML.html: XML 構造のロード +* "XPath":/@api/play/libs/XPath.html: XPath による XML の解析 + +以下の節では、特に重要なライブラリについて、より詳細な情報を提供します。 + +h2. XPath による XML の解析 + +"XPath":http://en.wikipedia.org/wiki/XPath は、コード生成ツールを使わずに XML ドキュメントを解析する、おそらくもっとも簡単な方法です。 @play.libs.XPath@ ライブラリは、このタスクを効率的に達成するために必要な基本機能を提供します。 + +@XPath@ 演算子はすべての @org.w3.dom.Node@ 型を操作します: + +bc. org.w3.dom.Document xmlDoc = … // retrieve a Document somewhere + +for(Node event: XPath.selectNodes("events//event", xmlDoc)) { + + String name = XPath.selectText("name", event); + String data = XPath.selectText("@date", event); + + for(Node place: XPath.selectNodes("//place", event)) { + String place = XPath.selectText("@city", place); + … + } + + … +} + +h2. Web サービスクライアント + +@play.libs.WS@ は、強力な HTTP クライアントを提供します。内部では "Async HTTP client":https://github.com/AsyncHttpClient/async-http-client を使用しています。 + +リクエストの作成は簡単です: + +bc. HttpResponse res = WS.url("http://www.google.com").get(); + +いったん @HttpResponse@ オブジェクトを手に入れてしまえば、すべてのレスポンス属性にアクセスすることができます。 + +bc. int status = res.getStatus(); +String type = res.getContentType(); + +複数の content-type においてボディの内容を検索することもできます: + +bc. String content = res.getString(); +Document xml = res.getXml(); +JsonElement json = res.getJson(); +InputStream is = res.getStream(); + +*async* API を使ってノンブロッキングな HTTP リクエストを作成することもできます。この場合は @Promise@ を受け取ります。いったん非同期状態から回復すれば、通常通り @HttpResponse@ を使うことができます: + +bc. Promise futureResponse = WS.url( + "http://www.google.com" +).getAsync(); + +h2. Java による関数プログラミング + +@play.libs.F@ ライブラリは関数プログラミング由来の便利な構成物を提供します。これらの構成物は複雑に抽象化された事象を取り扱うために使用されます。関数プログラミングに慣れている人のために、以下が提供されています: + +* @Option@ (設定できる、または設定できない T 値) +* @Either@ (どちらかが含まれる A 値または B 値) +* @Tuple@ (どちらとも含まれる A 値と B 値) + +h3. @Option@, @Some@ と @None@ + +いくつかのケースでは結果を返さないかもしれない関数 (例えば find) を記述する場合において、一般的な(悪い)Java のパターンは結果がない場合に @null@ を返すことです。この方法は、関数の戻り値の型がオブジェクトを返さない可能性があることを明確に示していないし、null 参照の発明者によって "“billion-dollar mistake”":http://en.wikipedia.org/wiki/Pointer_(computing)#Null_pointer として認識されているおり、危険です。 +@Option@ はこの問題に対するエレガントなソリューションです: @T@ 型のオブジェクトを返す代わりに、この関数は @Option@ を返します。この関数は成功した場合、(実際の結果をラップする) @Some@ 型、そうでない場合は @None@ 型、いずれも @Option@ のサブタイプであるオブジェクトを返します。 +以下に例を示します: + +bc. /* Safe division (will never throw a runtime ArithmeticException) */ +public Option div(double a, double b) { + if (b == 0) + return None(); + else + return Some(a / b); +} + +この関数のこのように使います: + +bc. Option q = div(42, 5); +if (q.isDefined()) { + Logger.info("q = %s", q.get()); // "q = 8.4" +} + +しかし、これを使うために @Option@ が @Iterable@ を実装していることを利用したもっと便利な構文があります: + +bc. for (double q : div(42, 5)) { + Logger.info("q = %s", q); // "q = 8.4" +} + +この for ループの中身は @div@ 関数が成功した場合にのみ、一回だけ実行されます。 + +h3. @Tuple@ + +便利な @Tuple@ は、 @A@ および @B@ 型の二つのオブジェクトをラップします。これらのオブジェクトは、それぞれ @_1@ および @_2@ フィールドを使って取得することができます。例えば: + +bc. public Option> parseEmail(String email) { + final Matcher matcher = Pattern.compile("(\\w+)@(\\w+)").matcher(email); + if (matcher.matches()) { + return Some(Tuple(matcher.group(1), matcher.group(2))); + } + return None(); +} + +そして: + +bc. for (Tuple email : parseEmail("foo@bar.com")) { + Logger.info("name = %s", email._1); // "name = foo" + Logger.info("server = %s", email._2); // "server = bar.com" +} + +p(note). @T2@ クラスは @Tuple@ のエイリアスです。三要素のタプルを扱う場合は @T3@ クラスを使い、 @T5@ まで同様です。 + +h3. パターンマッチング + +Java にはパターンマッチングが必要だと感じることがありました。残念ながら Java は組み込みのパターンマッチングを持ち合わせておらず、関数構造の欠如により、パターンマッチングをライブラリとして追加することも困難でした。とにかく、私たちはそれほど悪くない解決策に取り組みました。 + +私たちのアイディアとは、Java において基礎的なパターンマッチングを達成するために最新の‘for ループ’文法を使うことでした。パターンマッチングは、オブジェクトが求められる条件に合致することを確認し、関心のある値を抽出しなければなりません。Play のパターンマッチングライブラリは @play.libs.F@ ライブラリの一部です。 + +簡単な例を見てみましょう; Object 型の参照があり、これが "command:" から始まる文字列であることを確認したいとします。 + +標準的な方法は以下のようになるでしょう: + +bc. Object o = anything(); + +if(o instanceof String && ((String)o).startsWith("command:")) { + String s = (String)o; + System.out.println(s.toUpperCase()); +} + +Play のパターンマッチングを使うと、これを次のように書くことができます: + +bc. for(String s: String.and(StartsWith("command:")).match(o)) { + System.out.println(s.toUpperCase()); +} + +この for ループは条件に合致する場合に一度だけ実行され、キャストをする必要なく自動的に String の値を抽出します。明示的なキャストが存在しないので、すべてがタイプセーフであり、コンパイラによって型チェックが行われます。 + +h3. Promises + +@Promise@ は Play による @Future@ 型のカスタマイズです。実際のところ、 @Promise@ は @Future@ でもあるので、標準的な @Future@ として使用することもできます。しかし、 @Promise@ にはとても興味深い属性: @onRedeem(…)@ を使って、期待する値が利用可能になるとできるだけ早く呼び出されるコールバックを登録する機能が備わっています。 + +@Promise@ インスタンスは Play のいたるところ (Jobs や @WS.async@, その他…) で @Future@ の代わりに使用されています。 + +Promise はいくつかの方法で紐付けることができます。例えば: + +bc. Promise p = Promise.waitAll(p1, p2, p3) +Promise p = Promise.waitAny(p1, p2, p3) +Promise p = Promise.waitEither(p1, p2, p3) + +h2. OAuth + +"OAuth":http://oauth.net/ は、デスクトップアプリケーションや web アプリケーションに向けた、シンプルで標準的なアプローチを使ったセキュアな認証 API 用オープンプロトコルです。 + +二つの異なる仕様が存在します: OAuth 1.0 と OAuth 2.0 です。Play は、これらのうちどちらかの仕様を提案するサービスにコンシューマとして接続するライブラリを提供します。 + +一般的な手順は次のようになります: + +* ユーザをプロバイダの認証ページへリダイレクトします +* ユーザが認証されると、プロバイダはユーザを未認証トークンと共にあなたのサーバへ再度リダイレクトします +* あなたのサーバは、サービスに対するリクエストを処理するために保存されなければならない、現在のユーザ向けに特化したアクセストークンと、この未認証トークンを交換します。この手順はサーバ間のコミュニケーションとして実行されます + +Play フレームワークは、この手順の大部分を取り計らいます。 + +h3. OAuth 1.0 + +OAuth 1.0 の機能は "oauth-signpost":http://code.google.com/p/oauth-signpost/ に基づく @play.libs.OAuth@ クラスによって提供されます。OAuth 1.0 は "Twitter":http://apiwiki.twitter.com/ や "Google":http://code.google.com/apis/accounts/docs/OAuth.html のようなサービスで使用されています。 + +サーバへ接続するためには、サービスプロバイダから得られる以下の情報を使ってOAuth.ServiceInfo インスタンスを作成する必要があります: +* リクエストトークン URL +* アクセストークン URL +* 認証 URL +* コンシューマ・キー +* コンシューマ・シークレット + +アクセストークンはこのようにして読み出すことができます: + +bc. public static void authenticate() { + // TWITTER is a OAuth.ServiceInfo object + // getUser() is a method returning the current user + if (OAuth.isVerifierResponse()) { + // We got the verifier; + // now get the access tokens using the request tokens + OAuth.Response resp = OAuth.service(TWITTER).retrieveAccessToken( + getUser().token, getUser().secret + ); + // let's store them and go back to index + getUser().token = resp.token; getUser().secret = resp.secret; + getUser().save() + index(); + } + OAuth twitt = OAuth.service(TWITTER); + Response resp = twitt.retrieveRequestToken(); + // We received the unauthorized tokens + // we need to store them before continuing + getUser().token = resp.token; getUser().secret = resp.secret; + getUser().save() + // Redirect the user to the authorization page + redirect(twitt.redirectUrl(resp.token)); +} + +これで、トークンペアを使って署名したリクエストによって API を呼び出すことができるようになります: + +bc. mentions = WS.url(url).oauth(TWITTER, getUser().getTokenPair()).get().getString(); + +この例ではエラーをチェックしていませんが、本番環境ではチェックすべきです。 OAuth.Response オブジェクトは、エラーが起こった場合には null でないエラーフィールドを保持します。それらの多くは、ユーザがあなたにアクセス権を与えていないか、プロバイダがダウンしているか、あるいはプロバイダにバグがある場合です。 + +完全な使用例が @samples-and-tests/twitter-oauth@ にあります。 + +h3. OAuth 2.0 + +OAuth 2.0 は、リクエストの署名を伴わないので、OAuth 1.0 よりもかなりシンプルです。OAuth 2.0 は "Facebook":http://developers.facebook.com/docs/authentication/ と "37signals":http://37signals.com で使用されています。 + +OAuth 2.0 の機能は @play.libs.OAuth2@ として提供されています。 + +サーバへ接続するためには、サービスプロバイダから得られる以下の情報を使って OAuth2 インスタンスを作成する必要があります: +* アクセストークン URL +* 認証 URL +* クライアント ID +* シークレット + +bc. public static void auth() { + // FACEBOOK is a OAuth2 object + if (OAuth2.isCodeResponse()) { + // authUrl must be the same as the retrieveVerificationCode call + OAuth2.Response response = FACEBOOK.retrieveAccessToken(authUrl); + // null if an error occurred + String accessToken = response.accessToken; + // null if the call was a success + OAuth2.Error = response.error; + // Save accessToken, you will need it to request the service + index(); + } + // authUrl is a String containing an absolute URL where the service + // should redirect the user back + // This will trigger a redirect + FACEBOOK.requestVerificationCode(authUrl); +} + +一旦このアクセストークンをユーザと関連づけたあとは、ユーザに代わってサービスへ問い合わせを行うためにこのトークンを使うことができます: + +bc. WS.url( + "https://graph.facebook.com/me?access_token=%s", access_token +).get().getJson(); + +完全な使用例が @samples-and-tests/facebook-oauth2@ にあります。 + + +h2. OpenID + +"OpenID":http://openid.net/ オープンで分散した識別システムです。アプリケーションに特化したユーザ情報を保持することなしに、容易に新しいユーザを受け入れることができます。ユーザの **OpenID** を通して認証されたユーザを追跡し続けなければならないだけです。 + +この例では、Play アプリケーションにおいて OpenID 認証がどのように使用されるかを高いレベルで見ることができます: + +* あらゆるリクエストにおいて、ユーザが接続されているか確認する +* もしユーザが接続されていなければ、ユーザが OpenID を投稿することのできるページを表示する +* ユーザを OpenID プロバイダにリダイレクトする +* ユーザが戻ってきたら、検証された OpenID を取得して HTTP セッションに保存する + +OpenID の機能は @play.libs.OpenID@ クラスとして提供されています。 + +bc. @Before(unless={"login", "authenticate"}) +static void checkAuthenticated() { + if(!session.contains("user")) { + login(); + } +} + +public static void index() { + render("Hello %s!", session.get("user")); +} + +public static void login() { + render(); +} + +public static void authenticate(String user) { + if(OpenID.isAuthenticationResponse()) { + UserInfo verifiedUser = OpenID.getVerifiedID(); + if(verifiedUser == null) { + flash.error("Oops. Authentication has failed"); + login(); + } + session.put("user", verifiedUser.id); + index(); + } else { + if(!OpenID.id(user).verify()) { // will redirect the user + flash.error("Cannot verify your OpenID"); + login(); + } + } +} + +そして、以下が @login.html@ テンプレートです: + +bc. #{if flash.error} +

${flash.error}

+#{/if} + +
+ + + +
+ + +最後に、 @routes@ の定義は以下のようになります: + +bc. GET / Application.index +GET /login Application.login +* /authenticate Application.authenticate + +p(note). **考察を続けます** + +それでは %(next)"非同期ジョブ":jobs% を使って、あらゆる HTTP リクエストの外側で操作を実行する方法を確認しましょう。 \ No newline at end of file diff --git a/documentation/manual_ja/logs.textile b/documentation/manual_ja/logs.textile new file mode 100644 index 0000000000..b83b517a1d --- /dev/null +++ b/documentation/manual_ja/logs.textile @@ -0,0 +1,49 @@ +h1. ログの設定 + +Play のロガーは組み込みの "Log4j":http://logging.apache.org/log4j/1.2/index.html です。ほとんどの Java ライブラリが Log4j か、または Log4j をバックエンドとして使えるラッパを使用するので、アプリケーションに適合するよう、容易にロギングを設定することができます。 + +h2. アプリケーションからのロギング + +Play はデフォルトのロガーとして @play.Logger@ クラスを提供します。このクラスは @play@ という名前のロガーにメッセージや例外を書き込むために Log4j を使用します。 + +アプリケーションからのロギングは簡単です: + +bc. Logger.info("A log message"); +Logger.error(ex, "Oops"); + +@play.Logger@ クラスのメソッドは、標準の Java フォーマット構文に通じる容易なフォーマット化をサポートします: + +bc. Logger.debug("The param was %s", param); +Logger.info("I want to log %s and %s and %s", a, b, c); + +特別な要件のために Log4j を直接使って別のロガーを作成することもできます: + +bc. org.apache.log4j.Logger.getLogger("another.logger"); + +h2. ログレベルの設定 + +"application.log":configuration#application.log を設定することでPlay loggerのログレベルを設定できます。このレベルはアプリケーションによって生成されたメッセージにのみ適用されることに注意してください。 + +完全に Log4j を制御する必要のある場合は、 @conf/@ ディレクトリに @log4j.properties@ ファイルを作成します。このディレクトリはクラスパスの最初の要素なので、このファイルは全てのライブラリからデフォルトとして使用されます。 + +log4j のデフォルト設定は以下のとおりです: + +bc. log4j.rootLogger=ERROR, Console + +log4j.logger.play=INFO + +# Console +log4j.appender.Console=org.apache.log4j.ConsoleAppender +log4j.appender.Console.layout=org.apache.log4j.PatternLayout +log4j.appender.Console.layout.ConversionPattern=%d{ABSOLUTE} %-5p ~ %m%n + +このファイルをコピーして、特別な要件に向けて更新してください! + + +h3. Production 設定 + +参考: "configure logging for production":production#logging にも例があります。 + +p(note). **考察を続けます** + +次は、 %(next)"複数環境における設定":ids% に続きます。 diff --git a/documentation/manual_ja/main.textile b/documentation/manual_ja/main.textile new file mode 100644 index 0000000000..bddfcf101b --- /dev/null +++ b/documentation/manual_ja/main.textile @@ -0,0 +1,156 @@ +h1. 主要な概念 + +h2. MVC アプリケーションモデル + +Play アプリケーションは web に適用された MVC アーキテクチャパターンに従います。 + +このパターンはアプリケーションを別々の層: Presentation 層 と Model 層 に分割します。Presentation 層 はさらに View 層 と Controller 層 に分けられます。 + +* **Model** は、アプリケーションが扱う情報をドメインに特化して表現したものです。ドメインのロジックは生のデータに '意味' (例えば、今日がユーザの誕生日かとか、あるいはショッピングカートの合計や税金や送料などを計算して) を追加します。ほとんどのアプリケーションが、データを保存するためにデータベースなどの永続的なストレージを使用します。Model によって隠蔽されるかカプセル化されることが分かっているので、MVC ではデータアクセス層について明確には言及しません。 + +* **View** は、通常はユーザインタフェースである双方向のやり取りに適した形式にモデルをレンダリングします。異なる目的に応じて、ひとつのモデルに対して複数のビューが存在することもあります。Web アプリケーションでは、通常、ビューは HTML、XML または JSON のような 'Web フォーマット' としてレンダリングされます。しかし、例えば動的にレンダリングされたチャートダイヤグラムのようにバイナリ形式としてビューを表現する場合もいくつかあります。 + +* **Controller** は、イベント (通常はユーザのアクション) に反応してそれらを処理します。このとき、モデルの変更も実行するかもしれません。Web アプリケーションでは、通常、イベントは HTTP リクエストです: コントローラは HTTP リクエストを待ち受けて、'イベント' からクエリ文字列パラメータやリクエストヘッダ… と言った関連データを抽出します。そして、下層のモデルオブジェクトに変更を適用します。 + +!images/diagrams_mvc! + +Play アプリケーションでは、これらの 3 つの層は @app@ ディレクトリの中に、それぞれ別々の Java パッケージとして定義されます。 + +h3. app/controllers + +コントローラは、public かつ static なメソッドはいずれも **action** となるクラスです。アクションは HTTP リクエストが受信されたときに起動される Java のエントリポイントです。コントローラクラスの Java コードはまったくオブジェクト指向的ではありません: 主に手続き型のコードです。アクションメソッドは、HTTP リクエストから関連データを抽出して、モデルを検索するか更新して、HTTP レスポンスにラップされた結果を返します。 + +h3. app/models + +ドメインモデルオブジェクト層は、Java 言語から利用可能なすべてのオブジェクト指向特徴を使用する Java クラスの集合です。ドメインモデルオブジェクト層はアプリケーションが扱うデータ構造と操作を含んでいます。モデルオブジェクトを永続的なストレージに保存する必要がある場合は、ドメインモデルオブジェクト層には JPA アノテーションや SQL ステートメントのような、接着剤的な部品を含むこともあるかもしれません。 + +h3. app/views + +アプリケーションのビューのほとんどは、Play によって提供された効率的なテンプレートシステムを使用して生成されます。コントローラは、モデル層からいくつかのデータを取得し、これらのオブジェクトを装飾するためにテンプレートを適用します。このパッケージは HTML、XML、JSON やその他の、動的にモデルの値を表現する特別なディレクティブを持つテンプレートファイルを保持します。 + + +h2. リクエストライフサイクル + +Play フレームワークは完全にステートレスであり、リクエスト/レスポンスのみを重視します。すべての HTTP リクエストは同じ処理フローを通ります: + +# HTTP リクエストがフレームワークによって受信されます。 +# Router コンポーネントは、このリクエストに適合する最も特殊なルーティングを探します。そして、対応するアクションメソッドが起動されます。 +# アプリケーションコードが実行されます。 +# 複雑なビューを生成する必要がある場合は、テンプレートファイルがレンダリングされます。 +# アクションメソッドの結果 (HTTP レスポンスコードと内容) は HTTP レスポンスとして出力されます。 + +HTTP リクエストの処理フローを下図にまとめます: + +!images/diagrams_path! + + +h2. 標準的なアプリケーションレイアウト + +Play アプリケーションのレイアウトは、できるだけ物事を簡単に保つために標準化されています。 + + +h3. **app** ディレクトリ + +このディレクトリはすべての実行可能な部品: Java ソースコードとビューテンプレートを含みます。 + +p(note). **.classファイルはどこ?** + +コンパイルされた Java クラスを探さないでください。フレームワークは実行時に Java ソースコードをコンパイルして、 @tmp@ ディレクトリ配下にバイトコードキャッシュとしてコンパイルされたクラスを保持するだけです。Play アプリケーションにおける主な実行可能な成果物は、コンパイルされたクラスではなく、 *.java* ソースファイルです。 + +app ディレクトリには、MVC アーキテクチャパターンのそれぞれの層にあたる 3 つの標準的なパッケージがあります。もちろん、例えば **utils** のような独自のパッケージを追加することも可能です。 + +加えて、view パッケージの内容は複数のサブパッケージに整理されます: + +* @tags@ ディレクトリは、例えば再利用できるテンプレート部品のようなアプリケーションタグを管理します。 +* コントローラごとにひとつの @view@ ディレクトリが割り当てられます。慣例により、コントローラに関連するテンプレートはそれぞれのサブパッケージ内に保持されます。 + + +h3. **public** ディレクトリ + +@public@ ディレクトリに保存されたリソースは、静的な資産であり、Web サーバによって直接配信されます。 + +このディレクトリは 3 つの標準的なサブディレクトリ: 画像、CSS スタイルシート、および JavaScript ファイル用のディレクトリに分けられます。すべての Play アプリケーションにおいて一貫性を保つために、静的な資産はこのように管理されるべきです。 + +p(note). デフォルトでは @/public@ ディレクトリは @/public@ という URL にマッピングされますが、これは容易に変更可能であり、静的な資産のために複数のディレクトリを使用することもできます。 + + +h3(#conf). **conf** ディレクトリ + +@conf@ ディレクトリはアプリケーションのためのすべての構成ファイルを含んでいます。 + +2 つの必要な構成ファイルがあります: + +* @application.conf@ は、アプリケーションのための主な構成ファイルです。標準的な "設定パラメータ":configuration を含んでいます。 +* @routes@ は、ルーティングを定義するファイルです。 + +アプリケーションに特化した設定オプションをいくつか追加する必要がある場合、 @application.conf@ にさらにオプションを追加することは良い考えです。このファイル内の設定オプションは @Play.configuration.get("propertyName")@ によってプラグラムに読み込まれます。新しいアプリケーションを作成したとき @play new@ コマンドは @$PLAY_HOME/resources/application-skel/conf@ ディレクトリから、作業を始めるためにいくつかオプションをコメントアウトしたデフォルトの設定ファイルをコピーします。 + +ライブラリが特別な設定ファイルを必要とする場合、この @conf@ ディレクトリ配下に置いてみてください: **このディレクトリは Java クラスパスに含まれます** 。 + +@include を付けたオプション設定値として @application.conf@ にファイル名を指定することで、Play の設定に更に設定ファイルを追加することができます。 + +p(note). 実験的な機能であり、適切に動作しません。特に、プレースホルダー、および、フレームワーク ID は適切に扱われません。 + +例えば、 **conf/mime-types.conf** に追加の MIME タイプを定義する場合 + +bc. # Web fonts +mimetype.eot = application/vnd.ms-fontobject +mimetype.otf = application/octet-stream +mimetype.ttf = application/octet-stream +mimetype.woff = application/x-font-woff + +@application.conf@ に以下の行を追加することで、これらを取り込むことができます: + +bc. @include.mime = mime-types.conf + +h3. lib ディレクトリ + +このディレクトリはアプリケーションに必要なすべての標準的な Java ライブラリを含みます。これらのライブラリは、自動的に Java クラスパスに追加されます。 + + +h2. 開発ライフサイクル + +Play を使って作業している間、コンパイル、パッケージング、そしてデプロイのいずれのフェーズも存在しません。一方で、Play には 2 つの異なった環境が用意されています: 開発フェーズ中の DEV モードと、アプリケーションがデプロイされるときの PROD モードです。 + +p(note). **DEV/PROD モードについて** + +アプリケーションは DEV か PROD いずれかのモードで実行できます。 "application.mode 設定"::configuration#application.mode プロパティを使ってこのモードを切り換えます。DEV モードで動作している場合、Play はファイルの変更をチェックし、必要な場合は動的にリロードします。 + +PROD モードは本番稼動向けに完全に最適化されます: Java ソースとテンプレートは、一度だけコンパイルされ、複数の用途のためにキャッシュされます。 + +Java ソースコードは、実行時にコンパイルされ、ロードされます。アプリケーションが動作している間に Java ソースファイルが変更された場合、ソースコードは再コンパイルされて JVM 上に動的にリロードされます。 + +コンパイルエラーが発生した場合、問題が正確にブラウザに表示されます (DEV モードの場合のみ) 。 + +!images/guide1-3! + +テンプレートも動的に再コンパイルされ、リロードされます。 + +h3. Java デバッガへの接続 + +アプリケーションを DEV モードで実行している場合、Java デバッガをポート 8000 に接続することができます。 + +例えば、NetBeans デバッガはこのように使用します: + +!images/screenshot_attach-debugger! + +h2. クラス拡張 + +Play プラグイン (すなわち @play.PlayPlugin@ のサブクラス) は、機能を追加するために、実行時にアプリケーションのクラスファイルに手を入れる ‘enhancer’ を含むかもしれません。ここでは、いくつかの Play の魔法がどのようにして動作しているかを示します。 + +組み込みの @play.CorePlugin@ は、アプリケーションのクラスに動的にコードを追加するために @play.classloading.enhancers@ パッケージの enhancer を使用します: + +* @ContinuationEnhancer@ - コントローラクラスに継続サポートを追加する +* @ControllersEnhancer@ - コントローラのアクションメソッドをスレッドセーフにする。メソッド呼び出しに HTTP リダイレクトを追加する +* @LocalvariablesNamesEnhancer@ - コントローラ内のローカル変数名を追跡する +* @MailerEnhancer@ - @play.mvc.Mailer@ サブクラスを設定する +* @PropertiesEnhancer@ - フィールドに基づいたプロパティによってアプリケーションのすべてのクラスを妥当な JavaBean にする +* @SigEnhancer@ - 自動リロードを行うために、すべてのクラスの署名となる一意なハッシュを計算する + +これに加えて、 @play.db.jpa.JPAPlugin@ は JPA クエリ用のメソッド規約に従って @play.db.jpa.JPABase@ を拡張します。通常、この拡張は @play.db.jpa.Model@ のサブクラスであるアプリケーションのモデルクラスに適用されます。質問にある JPA クエリは @play.db.jpa.GenericModel@ に定義されているものです。 + +独自の enhancer を追加するには、プラグインの @enhance(ApplicationClass)@ メソッドで @play.classloading.enhancers.Enhancer@ のサブクラスを使用します。 + +p(note). **考察を続けます** + +ここまでは、Play アプリケーションがどういったものであるかを見てきたので、 %(next)"HTTP ルーティング":routes% がどのように動作するかを見ていきましょう。Router は送り込まれた HTTP リクエストのアクションへの対応付けを担当します。 diff --git a/documentation/manual_ja/model.textile b/documentation/manual_ja/model.textile new file mode 100644 index 0000000000..09c798ed9f --- /dev/null +++ b/documentation/manual_ja/model.textile @@ -0,0 +1,229 @@ +h1. ドメインオブジェクトモデル + +モデルは Play アプリケーションの中心的な存在です。モデルは、アプリケーションが操作する情報のドメインに特化した表現です。 + +マーチンファウラーはこれを次のように定義しています: + +bq. ビジネスのコンセプト、ビジネスの状況に関する情報、およびビジネスルールを表現する責務を負います。これを保存する技術的な詳細はインフラストラクチャに委譲されるものの、ビジネスの状況を反映する状態はここで制御され、使用されます。この層は業務ソフトウェアの心臓部です。 + +一般的な Java のアンチパターンは、モデルをシンプルな Java Beans のセットに保ち、モデルオブジェクトを操作する "service" 層にアプリケーションロジックを戻すことです。 + +マーチンファウラーはこのアンチパターンを "ドメインモデル貧血症":http://www.martinfowler.com/bliki/AnemicDomainModel.html と名付けました: + +bq. ドメインモデル貧血症の基本的な症状は、一見、それが本物のドメインモデルに見えるという点です。オブジェクトがいくつかあり、それらはドメイン空間にある名詞から名前をつけられています。それから、オブジェクト同士がしっかりとしたリレーションで結びついており、本物のドメインモデルと同じような構造を持っているのです。ただし、オブジェクトの振る舞いを見れば違いが分かります。それらのオブジェクトにはわずかな振る舞いしかない、ということに気づくと思います。ドメインのロジックをドメインオブジェクトの中に入れないという設計ルールに従っているのでしょう。その代わり、すべてのドメインロジックを含むサービスオブジェクト群が存在しているのです。こういったサービスはドメインモデルの上位に居座り、データのためだけにドメインモデルを使うのです。 + +このアンチパターンが根本的に怖いのは、オブジェクト指向設計の基本概念 (データと処理を一緒にする) の真逆だということです。ドメインモデル貧血症とは、単なる手続き型設計のことなのです。まさに、私 (そして Eric) のようなオブジェクト信望者が、Smalltalk の初期からずーーーーーっと戦ってきたもの、そのものなのです。さらに困ったことに、貧血オブジェクト (Anemic Object) が本物のオブジェクトだと思っているひとがたくさんいます。つまり、オブジェクト指向設計が何たるかをまったく分かっていないということなんです。 + +h2. プロパティのシミュレーション + +Play のサンプルアプリケーションを見てみると、クラスが public 変数を宣言しているのをしばしば目にするでしょう。どのような経験があろうと、Java 開発者であれば public 変数を目にした途端に警告サイレンがじゃんじゃん鳴り出すかもしれません。Java においては (他のオブジェクト指向言語と同様に) すべてのフィールドを private にして、アクセサとミューテータを提供するのを最も良い習慣としています。これは、オブジェクト指向デザインにおいて重要な概念であるカプセル化を促進するためのものです。 + +Java には、本当の意味でプロパティを定義する組み込みのシステムはありません。Java Beans と名付けられた規約を使用します: Java オブジェクトのプロパティは getXxx/setXxx メソッドのペアと共に定義するという規約です。プロパティが読み取り専用の場合、getter しかありません。 + +システムはうまく動きますが、書くのは非常に退屈です。いずれのプロパティについても、private で宣言して 2 つのメソッドを書かなければなりません。ほとんどの場合、getter と setter の実装はいつも同じです: + +bc. private String name; + +public String getName() { + return name; +} + +public void setName(String value) { + name = value; +} + +Play フレームワークの Model 部分は、自動的にこのパターンを生成し、コードを簡潔に保ちます。事実上、すべての public 変数はインスタンスのプロパティになります。規約では、クラスの **public** で **static ではなく** 、 **final でない** フィールドはすべてプロパティと見なされます。 + +例えば、このようにクラスを定義する場合: + +bc. public class Product { + + public String name; + public Integer price; +} + +ロードされたクラスは以下のようになります: + +bc. public class Product { + + public String name; + public Integer price; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getPrice() { + return price; + } + + public void setPrice(Integer price) { + this.price = price; + } +} + +プロパティにアクセスしたい場合は、以下のように書くだけです: + +bc. product.name = "My product"; +product.price = 58; + +ロード時には以下のように変換されます: + +bc. product.setName("My product"); +product.setPrice(58); + +p(note). **警告!** + +自動生成に頼る場合、プロパティにアクセスするために getter および setter メソッドを直接使用することはできません。これらのメソッドは実行時に生成されます。このため、それらを参照するコードを書くと、コンパイラはメソッドを見つられず、エラーを発生させます。 + +もちろん、自分で getter および setter メソッドを定義することができます。メソッドが存在している場合、Play は既存のアクセサを使用します。 + +これらから、Product クラスの price プロパティを保護するには、以下のように書くことができます: + +bc. public class Product { + + public String name; + public Integer price; + + public void setPrice(Integer price) { + if (price < 0) { + throw new IllegalArgumentException("Price can’t be negative!"); + } + this.price = price; + } +} + +このプロパティに負の値を設定しようとすると、例外が投げられます: + +bc. product.price = -10: // Oops! IllegalArgumentException + +Play は getter または setter が存在すれば、常にそれを使用します。次のコードを見てください: + +bc. @Entity +public class Data extends Model { + + @Required + public String value; + public Integer anotherValue; + + public Integer getAnotherValue() { + if(anotherValue == null) { + return 0; + } + return anotherValue; + } + + public void setAnotherValue(Integer value) { + if(value == null) { + this.anotherValue = null; + } else { + this.anotherValue = value * 2; + } + } + + public String toString() { + return value + " - " + anotherValue; + } + +} + +別のクラスから、以下のようなアサーションを試すことができます: + +bc. Data data = new Data(); +data.anotherValue = null; +assert data.anotherValue == 0; +data.anotherValue = 4 +assert data.anotherValue == 8; + +これはちゃんと動きます。拡張されたクラスは Java Beans の規約に従うので、JavaBean を期待するようなライブラリからオブジェクトを使用するような場合でも、それは完璧に動作します! + + +h2. モデルを永続化するデータベースの設定 + +ほとんどの場合、モデルオブジェクトのデータを永続化して保存する必要があるでしょう。データをデータベースに保存するのは、もっとも自然なやり方です。 + +開発中、インメモリ、または "db 設定":configuration#db を使ってアプリケーション中のサブディレクトリにデータを保存する組み込みデータベースを素早くセットアップすることができます。 + +Play は @$PLAY_HOME/framework/lib/@ 内にH2とMySQLのJDBCドライバーを含みます。PostgreSQL か Oracleデータベースを使いたければ、例えば、JDBCドライバーをそこに、または、アプリケーションの @lib/@ ディレクトリに追加します。 + +任意のJDBCに従ったデータベースに接続するには、ドライバーライブラリを加え、またJDBCプロパティ "db.url":configuration#db.url , "db.driver":configuration#db.driver , "db.user":configuration#db.user , "db.pass":configuration#db.pass を定義する。: + +bc. db.url=jdbc:mysql://localhost/test +db.driver=com.mysql.jdbc.Driver +db.user=root +db.pass= + +"jpa.dialect":configuration#jpa.dialect で JPA の方言を設定することもできます: + +ソースコードにおいて、 @play.db.DB@ から @java.sql.Connection@ を取得して、通常の方法で使用することができます。 + +bc. Connection conn = DB.getConnection(); +conn.createStatement().execute("select * from products"); + +h2. Hibernate によるモデルの永続化 + +Java オブジェクトを自動的にデータベースに永続化するために (JPA を通して) Hibernate を使用することができます。 + +どんな Java オブジェクトでも、 @javax.persistence.Entity アノテーションを追加して JPA エンティティとして定義した場合、Play は自動的に JPA EntityManager を起動します。 + +bc. @Entity +public class Product { + + public String name; + public Integer price; +} + +p(note). **警告!** + +JPA のものではなく、Hibernate の @Entity アノテーションを使用するのはよくある誤りです。Play は JPA API を通して Hibernate を使用することを思い出してください。 + +@play.db.jpa.JPA@ オブジェクトから EntityManager を取得することができます: + +bc. EntityManager em = JPA.em(); +em.persist(product); +em.createQuery("from Product where price > 50").getResultList(); + +Play は、JPA の扱いを手助けする気の利いたサポートクラスを提供します。 @play.db.jpa.Model@ を継承するだけです。 + +bc. @Entity +public class Product extends Model { + + public String name; + public Integer price; +} + +その後、Product インスタンスのシンプルなメソッドを使用して Product オブジェクトを操作してください: + +bc. Product.find("price > ?", 50).fetch(); +Product product = Product.findById(2L); +product.save(); +product.delete(); + +h2. ステートレスモデル + +Play は、‘何も共有しない’ アーキテクチャで機能するように設計されています。この考えは、アプリケーションを完全にステートレスに保ちます。これをすることによって、アプリケーションは同時に必要とされる複数のサーバノード上で動作するようになります。 + +モデルをステートレスに保つために避けるべき一般的な落とし穴はどのようなものでしょうか? **いかなるオブジェクトも、複数リクエストのために Java ヒープに保存してはならないということです。** + +h4. 複数リクエストをまたいでデータを保持するには、いくつかの選択肢があります: + +# データが小さくて、十分に簡単である場合は、フラッシュかセッションスコープに保存してください。ただし、これらのスコープに保存できるデータはそれぞれ 4KB に制限され、かつ String 型のデータしか認められません。 +# (データベースのような) 永続的なストレージに、データを恒久的に保存してください。例えば "ウィザード" を使ってオブジェクトを作成するような場合、データは複数のリクエストに渡ります: +#* 最初のリクエストでオブジェクトを初期化し、データベースに保存します。 +#* 新規に作成したオブジェクトの ID をフラッシュスコープに保存します。 +#* 連続するリクエストの間、オブジェクトの id を使ってデータベースからオブジェクトを取り出し、更新し、再度保存します。 +# (キャッシュのような) 一時的なストレージに、データを一時的に保存してください。例えば "ウィザード" を使ってオブジェクトを作成するような場合、データは複数のリクエストに渡ります: +#* 最初のリクエストでオブジェクトを初期化し、キャッシュに保存します。 +#* 新規に作成したオブジェクトのキーをフラッシュスコープに保存します。 +#* 連続するリクエストの間、キャッシュから (正しいキーを持った) オブジェクトを取り出し、更新し、再度保存します。 +#* 一連の処理の最後のリクエストの終わりに、(例えばデータベースに) オブジェクトを恒久的に保存します。 + +キャッシュは信頼できるストレージではありませんが、キャッシュにオブジェクトを入れられるなら、それは検索できて然るべきです。要件によって、キャッシュは Java Servlet セッションを置き換えるとても良い選択肢となり得ます。 + +p(note). **考察を続けます** + +今度は、 %(next)"JPA 永続化":jpa% を使ってモデルを永続化する方法について確認しましょう。 \ No newline at end of file diff --git a/documentation/manual_ja/modules.textile b/documentation/manual_ja/modules.textile new file mode 100644 index 0000000000..fd98b12a88 --- /dev/null +++ b/documentation/manual_ja/modules.textile @@ -0,0 +1,140 @@ +h1. Play モジュール + +Play アプリケーションは、いくつかのアプリケーションモジュールから組み立てることができます。これにより、複数のアプリケーションをまたいでアプリケーションコンポーネントを再利用したり、大きなアプリケーションをいくつかの、より小さなアプリケーションに分割することが可能です。 + +h2. モジュールとは? + +モジュールは、ただの play アプリケーションです; しかし、アプリケーションモジュールのリソースがロードされる方法にいくつかの違いがあります: + +* モジュールには @conf/application.conf@ ファイルがありません。 +* モジュールは @conf/routes@ ファイルを持つことができますが、これらのルートは自動的にはロードされません。 +* すべてのファイルは、まずメインのアプリケーションパスから検索され、その後、すべてのロードされたモジュールから検索されます。 +* モジュールは module/lib ディレクトリ に JAR ファイルとしてパッケージされたピュア Java コードを含むことができます。 +* モジュールはドキュメントを含むことができます。 +* モジュールにおいて、すべてが任意です。 + +@play new-module@ コマンドでモジュールを作成することができます。 + +h2. アプリケーションからモジュールをロードするには + +モジュールは、アプリケーションの @/modules@ ディレクトリから自動的にロードされます。"依存性管理システム":dependency を使って自動的にアプリケーションのモジュールを管理することができます。 + +h2. モジュールからデフォルトルートをロードする + +モジュールはデフォルト @ルート@ ファイルを提供することができます。特別なルート宣言を使用することで、メインアプリケーションの @routes@ ファイルにこれをロードすることができます: + +bc. # Import the default CRUD routes +GET /admin module:crud + +すべての利用可能なモジュールからルートをロードすることもできます: + +bc. GET / module:* + +h2. モジュールにドキュメントを追加する + +モジュールに @documentation/manual/home.textile@ ファイルを追加するだけで、モジュールにドキュメントを追加することができます。 @${play.path}/documentation/manual/@ にある Play 自身のドキュメントと同じ Textile 文法を使ってください。 + +ドキュメントを持つひとつまたはそれ以上のモジュールを使って Play アプリケーションを実行している場合、 "http://localhost:9000/@documentation":http://localhost:9000/@documentation にあるローカルの Play ドキュメントは、サイドバーの *Installed Modules* の下にそれらドキュメントへのリンクを含みます。 + +h2. モジュールリポジトリの使用 + +"モジュールリポジトリ":http://www.playframework.org/modules は、コミュニティによって寄付されたすべてのモジュールを識別します。モジュールはいくつかのバージョンを持つことができます。使用するフレームワークのバージョンに必要なのはどのバージョンのモジュールなのか、モジュールのドキュメントをチェックする必要があります。 + +@play list-modules@ コマンドを使ってモジュールリポジトリを閲覧することもできます。 + +bc. gbo-mac:‾ guillaume$ play list-modules +‾ _ _ +‾ _ __ | | __ _ _ _| | +‾ | '_ ¥| |/ _' | || |_| +‾ | __/|_|¥____|¥__ (_) +‾ |_| |__/ +‾ +‾ play! 1.2, http://www.playframework.org +‾ +‾ You can also browse this list online at http://www.playframework.org/modules +‾ +‾ [bespin] +‾ Bespin online editor +‾ http://www.playframework.org/modules/bespin +‾ Versions: 1.0, 1.0.1 +‾ +‾ [cobertura] +‾ Cobertura +‾ http://www.playframework.org/modules/cobertura +‾ Versions: 1.0 +... + +@play install {module}-{version}@ コマンドを使ってローカルにモジュールをインストールすることができます。ローカルにモジュールをインストールすると、いくつかのアプリケーションがそれぞれ個別のコピーをインストールすることなく、このモジュールを使えるようになります。これは、アプリケーションの拡張というよりも、フレームワークを拡張するような大きなモジュールについて有効です。 + +例えば、フレームワークに Scala サポートをインストールする場合、以下を使います: + +bc. play install scala-head + +規約では、 @head@ バージョンのモジュールは不安定です。バージョン情報を省略することで、デフォルトバージョンのモジュールをインストールすることもできます。例えば、以下のようにします: + +bc. play install scala + +この方法の場合、モジュールはフレームワークをインストールした場所の @/modules@ ディレクトリにインストールされます。 + +@--path@ オプションを使ってこのインストールパスを変更することができます: + +bc. play install gwt --path=my-project + +h2. モジュールリポジトリに新しいモジュールを寄付する + +h3. 前提条件 + +新しいモジュールを登録するには、次のものが必要です。 + +# Google グループに投稿するための Google アカウント。 +# 開発者ログインするための OpenID (このために Google アカウントを使用することができます) 。 +# 正規表現 [a-zA-Z]+ にマッチするモジュール名。 +# モジュールは "ドキュメント":#documentation を含む必要があります。 +# 例えば GitHub, Google Code または Launchpad プロジェクトのような、ソースコードが利用できてバグを報告する方法を備えたモジュールのホームページ。 + +h3. モジュールの登録 + +Google アカウントを OpenID として使用するためには、以下のようにしてその完全な URL を見つける必要があります。 + +# "http://www.playframework.org/modules":http://www.playframework.org/modules の _Developer login_ の下に @https://www.google.com/accounts/o8/id@ を入力し、 _Login_ ボタンをクリックします。 +# Google アカウントにログインします。 +# 例えば @https://www.google.com/accounts/o8/id?id=BItOawk7q69CFhRarQIo@ のように、 _Developer login_ の下の完全な Google OpenID URL をメモします。 + +"play-framework Google Group":https://groups.google.com/forum/#!forum/play-framework にモジュール登録リクエストを投稿します。例えば: + +bc. Subject: Module registration request: {module name} + +Module name: +jqueryui + +Display name: +jQuery UI + +Description: +Working examples of jQuery UI widgets, integrated with a Play application. + +Project home page: +https://github.com/hilton/jqueryui-module + +OpenID: +https://www.google.com/accounts/o8/id?id=BItOawk7q69CFhRarQIo + +モジュールが登録されたら、リリースを公開することができます。 + + +h3. モジュールのリリース + +モジュールをリリースするためには: + +# 例えば @self: play -> jqueryui 1.0@ のように、 @conf/dependency.yml@ の最初の行にモジュールのバージョン番号を設定します +# @play build-module@ コマンドでモジュールをビルドします +# _Developer login_ の下から "http://www.playframework.org/modules":http://www.playframework.org/modules にログインします +# "http://www.playframework.org/modules/developers":http://www.playframework.org/modules/developers の _Your modules_ の下にあるリンクからあなたのモジュールのページを閲覧します +# _Manage module releases_ の下にあるフォームを使って、あなたのモジュールの @dist/@ ディレクトリから生成した ZIP ファイルをアップロードします + +もちろん、ヘルプを提供したり情報を共有するために公式の Google Group を使用することができます。 + + +p(note). **考察を続けます** + +%(next)"依存性管理":dependency% を使ってモジュールを効率的に管理する方法を学びましょう。 \ No newline at end of file diff --git a/documentation/manual_ja/overview.textile b/documentation/manual_ja/overview.textile new file mode 100644 index 0000000000..92c83913b8 --- /dev/null +++ b/documentation/manual_ja/overview.textile @@ -0,0 +1,122 @@ +h1. Play framework の概要 + +Play framework は、肥大化したエンタープライズ Java を代替する洗練されたフレームワークです。開発者の生産性に注目し、RESTful アーキテクチャを目指します。Play は *アジャイルソフトウェア開発* のための完璧なガイドです。 + +Play framework のゴールは Java による Web アプリケーション開発を容易にすることです。これをどのようにして達成するのかを見ていきましょう。 + +p(note). **コードを見てみたいですか?** + +"Play でできる 5 つのすごいこと":5things を確認するか、いきなり "hello world チュートリアル":firstapp を始めてみてください。 + + +h2. 痛みの伴わない Java フレームワーク + +Play は純粋な Java フレームワークであり、あなた好みのツールやライブラリで開発を続けることができます。開発プラットフォームとして既に Java を使用しているのならば、別の言語や IDE、ライブラリに切り替える必要はありません。 *もっと生産的な Java 開発環境に切り替えるだけです!* + +h2. バグを直してリロード + +web アプリケーションの開発生産性が低いことで悪名高い Java EE プラットフォームですが、その主な原因は何度もくり返される退屈なコンパイル-パッケージング-デプロイのサイクルです。 + +そこで我々は Play による開発プロセスが効率的になるよう開発サイクルを見直しました。 + +play フレームワークは Java のソースコードを直接コンパイルし、サーバを再起動する必要なしに JVM へ動的にリロードします。ちょうど LAMPRails のように、コードを編集してリロードすれば直ちに変更点を見ることができます。 + +お望みであれば、フル機能の Java IDE を使わなくてもシンプルなテキストエディタさえあれば作業できます。これはとても楽しいことです。 + +!images/editor! + +どのようなエラーが発生した場合でも、フレームワークはできる限り問題の原因を特定して表示します。 + +!images/guide1-3! + +スタックトレースからは余分な情報が取り除かれ、より簡単に問題を解決できるよう最適化されています。テンプレートの実行についても、いかに Java スタックトレースとうまく統合されているか見てみてください。 + +!images/stacktrace! + +h2. シンプルなステートレス MVC アーキテクチャ + +一方にはデータベースがあり、もう一方には Web ブラウザがあります。これらの間で状態を保持すべきなのでしょうか? + +ステートフルでコンポーネントベースの Java Web フレームワークは、自動的にページの状態を保持するのを容易にしますが、他に多くの問題をもたらします: ユーザがふたつ目のウィンドウを開いたら何が起こるでしょう? ブラウザの戻るボタンを押したら? + +"Share Nothing":http://zef.me/883/the-share-nothing-architecture アーキテクチャは、PHP に始まり、Ruby on Rails や Django など数多くの Web フレームワークで奨励されています。ブラウザがどんどん強力になっていくにつれて、今や Ajax やオフラインストレージを使って、クライアントサイドで状態保持の問題を解決することが容易になっています。 + +もう Web に紛い物の状態を再構築するために HTTP モデルをハックする必要はありません。'share nothing' には、画面の部品を同時にレンダリングすることや、画面の一部を更新 (または段階的に表示する) ことを容易にする側面もあります。 + + +h2. HTTP-コードマッピング + +もしあなたが Servlet API や Strtus のような Java の Web フレームワークを使っているならば、HTTP プロトコルを Java の奇妙な API やコンセプトで抽象化したビューを使ってきたことになります。私たちは違う考え方をします。Web アプリケーションフレームワークは HTTP とそのコンセプトに対して完全かつ容易なアクセス手段を提供すべきです。これが Play とその他の Java web フレームワークの根本的な違いです。 + +HTTP, Request/Response パターン, REST アーキテクチャ, content-type ネゴシエーション, URI はいずれも Play framework の主要な概念です。 + +例えば、URI パターンと Java の呼び出しの紐付けはたった一行です: + +bc. GET /clients/{id} Clients.show + +もし日々の web 開発で Ajax や REST, Web ページ間の戻る/進むといった問題の管理に直面しているのであれば、是非 Play を試してみてください。 + + +h2. 効率的なテンプレートエンジン + +私たちは JSP と式言語の考え方を気に入っています。しかし、なぜタグライブラリを作成するのに大量の設定ファイルが必要なのでしょうか? なぜ表示元となるオブジェクトモデルに完全にアクセスできないのでしょう? JSP には多くの制限があり、フラストレーションが募ります。これが、我々が JSP の影響を受け、JSP のような制約のないテンプレートエンジンを作成した理由です! + +このような書き方にはみんなもう飽き飽きなはずです: + +bc. <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> + + + + You have ${fn:size(emails.unread)} unread email(s)! + + + You have no unread emails! + + + +こんな書き方のほうが気に入ると思います: + +bc. You have ${emails.unread ?: 'no'} ${emails.unread?.pluralize('email')} ! + +Play のテンプレートエンジンで使用する式言語は Java の構文と一貫性を持つ "Groovy":http://groovy.codehaus.org/ です。Play は主に HTML レスポンスをレンダリングするためにテンプレートシステムを使用しますが、email メッセージや JSON レスポンスなどのドキュメントを生成するのにも使えます。 + + +h2. 強化された JPA + +Java Persistence API (JPA) は、Java で利用できる、もっともきれいなオブジェクト-リレーショナルマッピング (ORM) API です。もし JPA を知っているなら、Play で JPA がいとも簡単に利用できることに驚くでしょう。なんの設定も無しに Play は自動的に JPA のエンティティマネージャを起動し、コードがリロードされたときはまるで魔法のように同期を行います。 + +さらに、スーパークラス @play.db.jpa.Model@ を使うことでコードをよりきれいにすることもできます。こんな感じです: + +bc. public static void messages(int page) { + User connectedUser = User.find("byEmail", connected()).first(); + List messages = Message.find( + "user = ? and read = false order by date desc", + connectedUser + ).from(page * 10).fetch(10); + render(connectedUser, messages); +} + +h2. テスト駆動開発 (お好みで) + +組み込みのテストランナーは テスト駆動開発 による作業を容易にします。シンプルなユニットテストから完全な受入テストまで、あらゆる種類のテストを記述し、"Selenium":http://seleniumhq.org/ を使用してブラウザでこれらを実行することができます。コードのカバレッジ率も計測されます。 + +!images/guide2-2! + +h2. フルスタック・アプリケーションフレームワーク + +Play framework は我々自身の Java アプリケーションから閃きを得ていました。Play は最近の web アプリケーションを作成するのに必要な、以下に示す全てのツールを備えています: + +* JDBC を介したリレーショナルデータベースのサポート +* (JPA API と共に) Hibernate を利用したオブジェクト-リレーショナルマッピング +* 必要に応じて分散 memchashed システムを簡単に使える組み込みキャッシュサポート +* JSON または XML による簡単な web サービス利用 (SOAP のようなガラクタではない、 *本当* の web サービスの話です) +* 分散認証のための OpenID サポート +* どこ (アプリケーションサーバ, Google App Engine, Cloud, その他...) にでもデプロイできる web アプリケーション +* イメージ操作 API + +モジュールアーキテクチャは、色々なものを web アプリケーションに組み込むことを可能にします。"アプリケーションモジュール":modules のおかげで、Java コードやテンプレート、そして (JavaScript や CSS ファイルなどの) 静的なリソースを簡単に再利用する事ができます。 + +p(note). **やってみよう** + +"Play framework をインストール":install して最初のアプリケーション開発を始めてみましょう。 \ No newline at end of file diff --git a/documentation/manual_ja/production.textile b/documentation/manual_ja/production.textile new file mode 100644 index 0000000000..89d2095a59 --- /dev/null +++ b/documentation/manual_ja/production.textile @@ -0,0 +1,239 @@ +h1. アプリケーションの本番稼動 + +アプリケーションを本番環境向けに最適化するシンプルな tips を紹介します。 + +h2. 本番稼動向け application.conf の設定 + +まず最初に、本番モードを指定するベストな方法は、本番環境のフレームワークに特別な ID を与えることです。例として @production@ という ID にしてみましょう。どのようにして ID を与えるかについては、"複数環境用 application.conf の管理":ids を参照してください。 + +h3. フレームワークを本番モードに設定する: + +bc. %production.application.mode=prod + +このモードでは、フレームワークは全ての Java ソースとテンプレートをプリコンパイルします。この段階でエラーが見つかった場合、アプリケーションは開始されません。ソースの変更は動的にリロードされません。 + +h3. 実際に利用するデータベースを定義する: + +もし (@db=mem@ または @db=fs@ いずれかの) 開発用データベースを使っていたのであれば、より堅牢なデータベースエンジンを設定すべきです: + +bc. %production.db.url=jdbc:mysql://localhost/prod +%production.db.driver=com.mysql.jdbc.Driver +%production.db.user=root +%production.db.pass=1515312 + +h3. JPA の自動スキーマ更新を無効にする: + +もし Hibernate が提供する自動スキーマ更新機能を使っていたのであれば、本番環境ではこの機能を無効にすべきです。 + +本番環境サーバにおいて、Hibernate に自動的に本番環境スキーマやデータを ALTER させるというのは、一般的には悪いアイディアです… + +最初のデプロイについては、別の問題として考えることもできます。この場合のみを指定するには、以下のようにします: + +bc. %production.jpa.ddl=create + +h3. 安全な秘密鍵を定義する: + +Play の秘密鍵は、セッション署名のような暗号機能を保証するために使用されます。アプリケーションは、この秘密鍵を厳重に管理しなければなりません。 + +bc. %production.application.secret=c12d1c59af499d20f4955d07255ed8ea333 + +@play secret@ コマンドを使って、安全で (少なくとも ‘本物の’ OS 上では) ランダムな新しい秘密鍵を生成することができます。アプリケーションを複数台のサーバに分散させる場合は、すべてのアプリケーションインスタンスに @同じ秘密鍵@ を使用することを忘れないでください! + + +h2. ログの設定 + +本番環境において、ローテーションするログファイルを使用することは良い考えです。コンソールログは @logs/system.out@ ファイルに書き込まれ、このファイルは制限無しに肥大化するので、ログをコンソールに出力してはいけません! + +カスタマイズした @log4j.properties@ を @conf/@ ディレクトリに作成してください: + +bc. log4j.rootLogger=ERROR, Rolling + +log4j.logger.play=INFO + +# Rolling files +log4j.appender.Rolling=org.apache.log4j.RollingFileAppender +log4j.appender.Rolling.File=application.log +log4j.appender.Rolling.MaxFileSize=1MB +log4j.appender.Rolling.MaxBackupIndex=100 +log4j.appender.Rolling.layout=org.apache.log4j.PatternLayout +log4j.appender.Rolling.layout.ConversionPattern=%d{ABSOLUTE} %-5p ~ %m%n + +h2. フロントエンド HTTP サーバ + +アプリケーションの HTTP ポートを @80@ 番に設定することで、簡単にアプリケーションをスタンドアロンサーバとしてデプロイすることができます: + +bc. %production.http.port=80 + +しかし、同一サーバ上で複数のアプリケーションを管理したり、拡張性や耐障害性のために複数のアプリケーションインスタンスを負荷分散する場合、フロントエンド HTTP サーバを使用することができます。 + +**フロントエンド HTTP サーバを使用する場合は、直接 Play サーバを使用する場合よりも決して良いパフォーマンスが得られないことに注意してください!** + +h3. lighttpd をセットアップする + +この例では、"lighttpd":http://www.lighttpd.net/ をフロントエンド web サーバとして設定する方法を紹介します。Apache でも同じことができますが、仮想ホストまたは負荷分散だけが必要な場合、lighttpd はとても良い選択であり、設定はとても簡単です! + +@/etc/lighttpd/lighttpd.conf@ ファイルは、以下のように定義されるでしょう: + +bc. server.modules = ( + "mod_access", + "mod_proxy", + "mod_accesslog" +) +… +$HTTP["host"] =~ "www.myapp.com" { + proxy.balance = "round-robin" proxy.server = ( "/" => + ( ( "host" => "127.0.0.1", "port" => 9000 ) ) ) +} + +$HTTP["host"] =~ "www.loadbalancedapp.com" { + proxy.balance = "round-robin" proxy.server = ( "/" => ( + ( "host" => "127.0.0.1", "port" => 9000 ), + ( "host" => "127.0.0.1", "port" => 9001 ) ) + ) +} + +h3. Apache をセットアップする + +以下の例は、 標準的設定の Play の前面で動作する "Apache httpd server":http://httpd.apache.org/ のシンプルな設定を示しています。 + +bc. LoadModule proxy_module modules/mod_proxy.so +… + + ProxyPreserveHost On + ServerName www.loadbalancedapp.com + ProxyPass / http://127.0.0.1:9000/ + ProxyPassReverse / http://127.0.0.1:9000/ + + + +h3. アプリケーションの透過的な更新を可能にするプロキシサーバとしての Apache + +基本的なアイディアとして、web アプリケーションの 2 つの Play インスタンスを実行し、前面のプロキシにこれらを負荷分散させます。1 つのインスタンスが使用できない場合、すべてのリクエストを使用できるもう 1 つのインスタンスにフォワードします。 + +同一の Play アプリケーションを 2 つ起動しましょう: ひとつは 9999 番ポートで、もうひとつは 9998 番ポートで実行します。 + +アプリケーションをコピーして、 @conf@ ディレクトリにある @application.conf@ を編集してポート番号を変更してください。 + +それぞれのアプリケーションディレクトリで、以下のように実行します: + +bc. play start mysuperwebapp + +ここで、Apache web サーバに負荷分散させるよう設定しましょう。 + +Apache を次のように設定します: + +bc. + ServerName mysuperwebapp.com + + SetHandler balancer-manager + Order Deny,Allow + Deny from all + Allow from .mysuperwebapp.com + + + BalancerMember http://localhost:9999 + BalancerMember http://localhost:9998 status=+H + + + Order Allow,Deny + Allow From All + + ProxyPreserveHost On + ProxyPass /balancer-manager ! + ProxyPass / balancer://mycluster/ + ProxyPassReverse / http://localhost:9999/ + ProxyPassReverse / http://localhost:9998/ + + +balancer://mycluster が重要な箇所です。これが負荷分散を宣言します。+H オプションは、二番目の Play アプリケーションがスタンバイ状態にあることを意味します。しかし、負荷分散するよう指示することもできます。 + +mysuperwebapp をアップグレードしたい場合は、毎回、以下を実行する必要があります: + +bc. play stop mysuperwebapp1 + +すると、ロードバランサは全てを mysuperwebapp2 にフォワードします。その間に mysuperwebapp1 を更新してください。更新が完了したら、以下を実行してください: + +bc. play start mysuperwebapp1 + +これで mysuperwebapp2 を安全に更新することができます。 + +Apache はクラスタの状態を閲覧する方法も提供します。クラスタの現在の状態を確認するには、単純にブラウザに /balancer-manager を指定してください。 + +Play は完全にステートレスなので、2 つのクラスタ間でセッションの管理をする必要はありません。とても簡単に 2 つ以上の Play インスタンスにスケールすることが可能です。 + +h3. 高度なプロキシ設定 + +フロント HTTP サーバを使用する場合、リクエストアドレスは HTTP サーバからやって来るように見えます。Play アプリケーションとプロキシを同一筐体で実行する、一般的なセットアップにおいては、Play アプリケーションからは、リクエストが 127.0.0.1 からやって来るように見えるでしょう。 + +プロキシサーバは、プロキシされたアプリケーションにリクエストがどこからやって来たかを伝える特別なヘッダを、リクエストに追加することができます。ほとんどの web サーバは、第一引数をリモートクライアントの IP アドレスとした X-Forwarded-For ヘッダを追加します。 "XForwardedSupport 設定":configuration#XForwardedSupportPlay でのフォワードサポートを有効にするには、Play は request.remoteAddress をプロキシの IP からクライアントの IP に変更します。これが動作するよう、プロキシの IP アドレスをリストする必要があります。 + +しかし、ホストヘッダが付加されない場合、プロキシの問題は依然として残ってしまいます。Apache 2.x を使用する場合、ディレクティブに以下のように追加することができます: + +bc. ProxyPreserveHost on + +host: ヘッダはクライアントから発行されたオリジナルのホストリクエストヘッダになります。これらふたつのテクニックを連携することで、アプリケーションはスタンドアロンと同じようになります。 + +h2. HTTPS の設定 + +本番環境では、組み込みサーバーはHTTPSプロトコルをサポートしています。 +古典的なJava **キーストア** 、または、 @cert@ と @kye@ のファイルでの証明書管理をサポートしています。 +アプリケーションへのHTTPS接続が始まると、 @application.conf@ ファイルでの @https.port@ 設定プロパティを表します。 + +bc. http.port=9000 +https.port=9443 + +@conf@ ディレクトリに証明書をおく必要があります。Play は X509 証明書とキーストア証明書をサポートします。X509証明書は 証明書は *host.cert* , キーは *host.key* で指定しなければなりません。キーストアを使っていれば、デフォルトで *certificate.jks* を指定します。 + +X509証明書を使っていれば、 @application.conf@ ファイルで以下の値を設定します。 + +bc. # X509 certificates +certificate.key.file=conf/host.key +certificate.file=conf/host.cert +# In case your key file is password protected +certificate.password=secret +trustmanager.algorithm=JKS + +キーストアを使っていれば: + +bc. keystore.algorithm=JKS +keystore.password=secret +keystore.file=conf/certificate.jks + +上記の値はデフォルト値であることに注意してください。 + +*openssl* を使って自己署名証明書を生成できます。: + +bc. openssl genrsa 1024 > host.key +openssl req -new -x509 -nodes -sha1 -days 365 -key host.key > host.cert + +Java キーストアを使っていれば、 @application.conf@ ファイルで以下のプロパティを設定します。 + +bc. # Keystore +ssl.KeyManagerFactory.algorithm=SunX509 +trustmanager.algorithm=JKS +keystore.password=secret +keystore.file=certificate.jks + +上記の値はデフォルト値です。 + +h2. Python を使わないデプロイ + +ほとんどの Unix マシンにはデフォルトで Python がインストールされており、Windows 版の Play には Python が同梱されています。しかしながら、Python を一切実行することなしにアプリケーションをデプロイしなければならないケースがあるかもしれません。 + +このため、機能を制限した build.xml が Play アプリケーションと共に提供されています。 + +アプリケーションフォルダから、以下のようにしてサーバを開始することができます: + +bc. ant start -Dplay.path=/path/to/playdirectory + +警告: @play@ コマンドを使用した場合、出力は @System.out@ にリダイレクトされます; 一方で、ant を使用した場合は標準出力にアクセスすることができません。追記されるファイルを指定する log4j プロパティを提供する必要があります。 + +サーバを停止するには、以下のようにします: + +bc. ant stop -Dplay.path=/path/to/playdirectory + +Play フレームワークへのパスを、環境変数、またはアプリケーションの @build.xml@ に直接指定することができることも覚えておいてください。 + +p(note). **考察を続けます** + +次: %(next)"デプロイオプション":deployment% diff --git a/documentation/manual_ja/releasenotes-1.0.1.textile b/documentation/manual_ja/releasenotes-1.0.1.textile new file mode 100644 index 0000000000..f074120e11 --- /dev/null +++ b/documentation/manual_ja/releasenotes-1.0.1.textile @@ -0,0 +1,230 @@ +h1. Play 1.0.1 — リリースノート + +**Play 1.0.1** は Play 1.0 ブランチのメンテナンスリリースです。このリリースは大量の小さなバグを修正し、JPA オブジェクトのより良いバインディング、XSS セキュリティ問題を回避する HTML および Javascript コードの自動エスケープを提供します。 + +p(note). **Play 1.0.1** はメンテナンスリリースであり、バージョン 1.0 との完全な互換性があります。何か問題にぶつかったら Google Group にて質問してください。 + +"1.0.1 ロードマップページ":http://www.playframework.org/roadmap/1.0.1 にて、修正されたバグについて読むことができます。もっとも重要な変更は、このページにてハイライトされています: + +h2. テンプレートにおける HTML コードの自動エスケープ + +全ての動的な式は、アプリケーションにおける XSS セキュリティ問題を回避するために、テンプレートエンジンによってエスケープされます。このため、 ==<h1>Title</h1>== を含む @title@ 変数は、以下のようにエスケープされます: + +bc. ${title} --> <h1>Title</h1> + +これらを本当にエスケープせずに表示したい場合は、 @raw()@ メソッドを明示的に呼び出す必要があります: + +bc. ${title.raw()} -->

Title

+ +素の HTML の大部分を表示したい場合は、 @#{verbatim /}@ タグを使うこともできます: + +bc. #{verbatim} + ${title} -->

Title

+#{/verbatim} + +もちろん、この機能は既存のアプリケーションを壊してしまうかもしれないので、デフォルトでは有効ではありません。これは以下の行を @application.conf@ ファイルに追加することで有効にすることができます: + +bc. future.escapeInTemplates=true + +この行は、リリース 1.0.1 で作られた新規アプリケーションには追加されます。 + +h2. @javax.inject.Inject サポート + +"Spring module":spring モジュールで提供されるような DI コンテナを使っている場合、 @Inject アノテーションを使うことができます。この @Inject アノテーションは、定義された Java Beans をコントローラ、ジョブ、またはメーラに自動的にインジェクションします。このアノテーションは static フィールドに作用します。 + +例えば、Spring に定義された @PriceWatcher@ サービスをコントローラにインジェクションするには、以下のようにするだけです: + +bc. public class Application extends Controller { + + @Inject + static PriceWatcher prices; + + public static void index() { + prices.getAll(); // prices is defined here + } + +} + +自動リロードは期待通り動作するはずです。 + +h2. JPA オブジェクトのより良いバインディング + +以前は、HTTP を使って JPA オブジェクトを自動的に紐付ける方法はありませんでした。例えば: + +bc. public static void save(User user) { + user.save(); // fail in 1.0 +} + +@User@ は JPA エンティティクラスであり、user は Binder によって作成された一時的なオブジェクトで、まだ Hibernate によって管理されていないので、このコードは失敗します。 + +古いやり方は、以下のように @edit()@ メソッドを使うものでした: + +bc. public static void save(Long id) { + User user = User.findById(id); + user.edit(params); + user.save(); // ok +} + +今では、HTTP パラメータに @user.id@ フィールドを渡すことができます。Play は @id@ フィールドを見つけると、HTTP パラメータを編集する前に、データベースからマッチするインスタンスをロードします。その後、HTTP リクエストの他のパラメータが適用されます。こうして、HTTP パラメータを直接保存することができます。 + +bc. public static void save(User user) { + user.save(); // ok with 1.0.1 +} + +もちろん、この機能は既存のアプリケーションを壊してしまうかもしれないので、デフォルトでは有効ではありません。これは以下の行を @application.conf@ ファイルに追加することで有効にすることができます: + +bc. future.bindJPAObjects=true + +この行は、リリース 1.0.1 で作られた新規アプリケーションには追加されます。 + +h2. コマンドラインからのフレームワーク ID の設定 + +フレームワーク ID によって、同じ Play アプリケーションを異なる設定 (例えば dev, test, staging, production, など…) で実行することができます。 + +コマンドラインを使うことで、アプリケーションが使用する "フレームワーク ID":ids を指定することができます。例えば、アプリケーションを production モードで実行するには、以下のコマンドを: + +bc. play run --%production + +以下の行を @application.conf@ ファイルに定義して実行します: + +bc. application.mode=dev +%production.application.mode=prod + +これは、フレームワーク id 情報を使用する全ての既存のコマンドと互換性があります。今でも、デフォルト ID は @play id@ コマンドを使って定義します。 + +ちなみに @play test@ は以下と等価です: + +bc. play run --%test + +h2. カスタムバリデーション + +必要なバリデータを @play.data.validation@ パッケージから探すことができませんか? 自分で書きましょう。一般的な @CheckWith アノテーションを使って、独自の @Check@ 実装を紐付けることができます。 + +例: + +bc. public class User { + + @Required + @CheckWith(MyPasswordCheck.class) + public String password; + + static class MyPasswordCheck extends Check { + + public abstract boolean isSatisfied(Object user, Object password) { + return notMatchPreviousPasswords(password); + } + + } +} + +h2. テストランナーの更新 + +selenium をバージョン 1.0.1 final に更新し、UI を改良しました。Selenium テストは全画面で実行されます。さらに "Run all tests" のような新機能がいくつか追加されています。 + +!images/selenium-fullscreen! + +アプリケーションを test モードで実行した場合、ローカルのドキュメントを利用することもできます。 + +h2. HTML5 をデフォルトの doctype へ、そして JQuery を新規アプリケーションへ + +新規アプリケーションにはデフォルトの HTML スケルトンが含まれます。以前、このスケルトンは **XHTML 1.0 Transitional** でフォーマットされた HTML でした。今では、デフォルトで **HTML5** doctype となります: + +bc. + + + + #{get 'title' /} + + + #{get 'moreStyles' /} + + + #{get 'moreScripts' /} + + + #{doLayout /} + + + +これは単なるデフォルト設定であり、当然、お望みであればどのような doctype にも変更可能です。しかしながら、"HTML5":http://html5.org は間違いなく次世代的であり、簡単です。 + +デフォルトテンプレートには **JQuery** Javascript ライブラリが含まれます。これは、モダンな web アプリケーションには優れた Javascript フレームワークが必要であり、かつ我々が "JQuery":http://www.jquery.com を愛しているからです。こちらも単なるデフォルト設定であり、お望みのどんな Javascript ライブラリで置き換えることもできます。 + +ああ、そして、デフォルトテンプレートには追加のスクリプトと/またはスタイルを挿し込む 2 つのデフォルトのプレースホルダが含まれます。例えば、以下のようにしてビューに追加します: + +bc. #{set 'moreScripts'} + +#{/set} + +これで、このビューにて gear Javascript ライブラリにアクセスすることができるようになります。 + +h2. #{list} タグの改善 + +@items@ パラメータはオプションとなり、デフォルトの @arg@ 引数で置き換えることが可能です。 + +このため: + +bc. #{list items:users, as:'user'} +
  • ${user}
  • +#{/list} + +を、以下のように書き換えることができます: + +bc. #{list users, as:'user'} +
  • ${user}
  • +#{/list} + +@as@ パラメータもまたオプションです。デフォルト変数名として @_@ を使用します: + +bc. #{list users} +
  • ${_}
  • +#{/list} + +h2. 新しい #{jsAction /} タグ + +この @#{jsAction /}@ タグは、サーバ側で定義されたルートを Javascript 関数として取り込めるようにします。これは、自由な引数を使う AJAX によって URL を呼び出す必要がある場合に、とても便利です。 + +例を見てみましょう: + +bc. GET /users/{id} Users.show + +このルートをクライアント側に取り込むことができます: + +bc. + +見ての通り、自由な引数を定義するために @:name@ 構文を使用します。可変引数と固定引数を一緒に使用することも可能です。この @#{jsAction /}@ タグは Javascript 関数を出力します。この関数は全ての可変引数を定義する Javascript オブジェクトを引数として受け取ります。 + +h2. 新しいサンプルアプリケーション: ‘booking’ + +この @予約@ アプリケーションは、有名な "JBoss Seam フレームワークの予約アプリケーション":http://docs.jboss.com/seam/latest/reference/en-US/html/tutorial.html#booking を移植したものです。 + +この予約アプリケーションは、ステートレスなフレームワークを使って、クライアント側で RESTFul な方法で複雑な状態をどのように管理するかを示します。 + +!images/booking! + +h2. Eclipse プラグインは順調です + +Eclipse 専用プラグインを提供するためにがんばって作業しています。現状はアルファ版ですが、すでに利用可能です。このプラグインは Play インストールパスの @support/eclipse@ ディレクトリにあります。 + +!images/eclipse-plugin! + +h2. IntelliJ IDEA のサポート + +今や Play フレームワークは、そのままで "IntelliJ IDEA":http://www.jetbrains.com/idea/ をサポートします! + +Play アプリケーションを IntelliJ IDEA プロジェクトに変換するには、 @idealize@ コマンドを使います: + +bc. # play idealize myApp + +!images/intellij! + +コンテキストメニューから実行やデバッグを行うことができます。 + +p(note). 次のリリース: %(next)"Play 1.0.2 リリースノート":releasenotes-1.0.2% diff --git a/documentation/manual_ja/releasenotes-1.0.2.textile b/documentation/manual_ja/releasenotes-1.0.2.textile new file mode 100644 index 0000000000..4d55bc129e --- /dev/null +++ b/documentation/manual_ja/releasenotes-1.0.2.textile @@ -0,0 +1,139 @@ +h1. Play 1.0.2 — リリースノート + +**Play 1.0.2** は play 1.0 ブランチのメンテナンスリリースです。主な新機能は、モジュールリポジトリのサポートと、組込みの CSRF 攻撃対策です。大量の小さなバグも修正されています。 + +p(note). **Play 1.0.2** はメンテナンスリリースであり、バージョン 1.0 シリーズとの完全な互換性があります。何か問題にぶつかったら "Google Group":http://groups.google.com/group/play-framework にて質問してください。 + +"1.0.2 ロードマップページ":http://www.playframework.org/roadmap/1.0.2 にて、修正されたバグについて読むことができます。もっとも重要な変更は、このページにてハイライトされています。 + +h2. Module repository + +モジュールリポジトリのゴールは、Play フレームワークの寄贈されたモジュールを集約して、簡単にインストールできるようにすることです。以下は、モジュールに関連する新しいコマンドです: + +* @play list-modules@, リポジトリの内容を一覧します。 +* @play install@, モジュールの特定のバージョンをローカルにインストールします。 +* @play new-module@, 新規モジュールのスケルトンを作成します。 +* @play build-module@, モジュールをパッケージし、リポジトリに公開します。 + +ほとんどすべてのモジュールが削除されていることに気付くかと思います。すぐに利用できるのは、一連の ‘中心的な’ モジュール: testrunner, docviewer, crud そして secure だけです。 + +その他のモジュールはオプションです。このため、例えば GWT サポートをインストールしたい場合は、単に @play install gwt@ とタイプして、このモジュールの最新バージョンを取得します。 + +なぜモジュールを移動したのでしょうか? それは、フレームワークの核となる部分に集中する必要があり、プロジェクト管理をシンプルにする必要があるからです。また、多くの人がいくつかの寄贈されたモジュールを欲しがっており、これは次のやり方で容易に行うことができます: それぞれのモジュールは、専用の管理者を持つスタンドアロンのプロジェクトとするのです。このため、モジュールに特化したバグを報告する際は、そのプロジェクトのホームページと専用のバグトラッカを使用してください。 + +即座に得られる利点として、これ以降、モジュールのライフサイクルはフレームワークのライフサイクルに縛られなくなります。モジュールは、フレームワークそのものよりも高い頻度でリリースすることができます。そして最後に、フレームワークにはオプションモジュールを一切含めないので、Play の配布サイズは半分になります。 + +モジュールリポジトリに関する詳細は "専用のページ":modules を読んでください。 + +h2. 組込みのクロスサイトリクエストフォージェリ対策 + +CSRF は、ウェブアプリケーションにおいて真の問題になる場合があります: + +bq. この攻撃手法は、ユーザが認証されていると信じている web アプリケーションにアクセスするページに悪意あるコードかリンクを含めることで実行されます。この web アプリケーションのセッションがタイムアウトされていない場合、攻撃者は認証されていない命令を実行できる場合があります。 + +この攻撃を防ぐために最初にすることは、GET と POST メソッドを適切に使用することです。これは、アプリケーションの状態を変更に使用されるのは POST メソッドだけであるべきということを意味します。 + +POST リクエストをセキュアで重要なアクションとして適切に保証する唯一の方法は、認証トークンを発行することです。現在の Play 1.0.2 にはこれを扱う組み込みのヘルパがあります: + +* 新しい @checkAuthenticity()@ メソッドがコントローラで利用可能であり、これはリクエストパラメータ中に有効な認証トークンが存在するかどうかチェックし、何かが正しくない場合は、認証不可のレスポンスを送信します +* @session.getAuthenticityToken()@ は、現在のセッションにおいてのみ有効な認証トークンを生成します +* @#{authenticityToken /}@ は、どのようなフォームにも加えることができる hidden 入力フィールドを作成します + +例えば、以下のようにすると: + +bc. public static destroyMyAccount() { + checkAuthenticity(); + … +} + +適切な認証トークンを含むフォームから呼ばれた場合にのみ動作します: + +bc. #{form @ destroyMyAccount()} + #{authenticityToken /} + +#{/form} + +もちろん、コントローラ階層におけるすべてのアクションを保護したい場合は、これを事前フィルタとして追加することができます。より詳しくは "セキュリティガイド":security を読んでください。 + +h2. デフォルトで HEAD メソッドをサポート + +Play は、GET メソッド用のルートが存在する場合、自動的に HEAD リクエストに応答します。これは、HTTP RFC によって、いかなるリソースも HEAD リクエストに同様に応答することが求められているからです。 + +"http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html":http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html + +bq. HEAD メソッドは、サーバがレスポンスにおいてメッセージボディを返してはならない事を除けば GET と同一である。HEAD リクエストへのレスポンスにおける HTTP ヘッダに含まれる外部情報は、GET リクエストへのレスポンスで送られる情報と同一であるべきである。このメソッドは、エンティティボディ自身を転送する事なしにリクエストによって意味されるエンティティに付いての外部情報を得るために使用される。このメソッドは、ハイパーテキストリンクの正当性、アクセス可能性、最近の修正のテストのために、しばしば使用される。 + +このため、いかなる HEAD リクエストもアクションメソッドを起動しますが、レスポンスの内容はクライアントに送信されません。もちろん、HEAD リクエストに応答するカスタムルートを routes ファイルに追加することで特化することも可能です。例えば、以下のようにします: + +bc. GET /orders/{id} Orders.show +HEAD /orders/{id} Orders.showHead + +h2. 新しいサンプルアプリケーション、‘zencontact’ + +"Wicket ベースの連絡先管理アプリケーション":http://blog.zenika.com/index.php?post/2009/03/10/Concours-Developper-une-application-web-en-Wicket3 の移植版である新しいサンプルです。興味がある人のために、Scala モジュールパッケージには、このアプリケーションの Scala 版が含まれています。 + +!images/zencontact! + +h2. アプリケーションサーバへのより良いデプロイ + +複数のアプリケーションサーバで、play が生成する WAR アーカイブのデプロイをテストしました。現在の "互換性マトリクス":deployment を確認することができます。 + +|| 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 || +| ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | + + +h2. テンプレートにおける新しいリバース機能 + +アクションの **絶対** URL を生成する @@ 構文をタグのパラメータとして利用することができます。例えば、以下のようにします: + +bc. #{form @@save()} +… +#{/} + +これは、URL を絶対形式で表現する必要のある email のようなものを生成するためにテンプレートエンジンを使用する場合に便利です。 + +複雑なオブジェクトのバインディングもサポートしました。このため、例えば次のようなアクションがあったとして: + +bc. public static void search(SearchParams params) { + … +} + +SearchParams が以下のような場合: + +bc. public class SearchParams { + public String keywords; + public String mode; +} + +テンプレートに次のように書くことができます: + +bc. @{search(params)} + +これは、次のような、クエリ文字列に複数の値を含む URL を生成します: + +bc. /search?params.keywords=xxxx¶ms.mode=AND + +h2. アプリケーション JavaDoc を生成する新しいコマンド + +以下を使って、プロジェクトの **JavaDoc** を容易に生成することができます: + +bc. play javadoc + +このコマンドはプロジェクトと、プロジェクトが使うモジュールの javadoc API ドキュメントを生成します。 + +h2. 今回のリリースでも Eclipse プラグインはさらに良くなります + +Eclipse プラグインには、いくつかの新機能があります: + +* コンテンツアシスタント、不明なアクションの検出とハイパーリンクを備えた高度な routes ファイルエディタ +* コンテンツアシスタントとハイパーリンクを備えた高度なテンプレートファイルエディタ +* 新規コントローラ、モデルおよびテンプレートを作成するためのウィザード +* (組込みの web ブラウザを使った) テストランナーの統合 + +!images/route-completion! + +!images/eclipse-tests! + +このプラグインをインストールするには、 @$PLAY_HOME/support/eclipse@ から @$ECLIPSE_HOME/dropins@ に JAR ファイルをコピーしてください。 + +p(note). 次のリリース: %(next)"Play 1.0.3 リリースノート":releasenotes-1.0.3% diff --git a/documentation/manual_ja/releasenotes-1.0.3.textile b/documentation/manual_ja/releasenotes-1.0.3.textile new file mode 100644 index 0000000000..7e9697c2cc --- /dev/null +++ b/documentation/manual_ja/releasenotes-1.0.3.textile @@ -0,0 +1,27 @@ +h1. Play 1.0.3 — リリースノート + +**Play 1.0.3** は Play 1.0 ブランチのメンテナンスリリースです。現在、開発はバージョン 1.1 に注力しているため、主要な新機能はありません。ドキュメントはこれまで見当たらなかったリファレンスと共に更新され、yabe チュートリアルには "国際化と地域化":guide12 に関する新しい章が含められ、そして新しいサンプルアプリケーションが追加されました。 + +p(note). **Play 1.0.3** はメンテナンスリリースであり、バージョン 1.0 シリーズとの完全な互換性があります。何か問題にぶつかったら "Google Group":http://groups.google.com/group/play-framework にて質問してください。 + +"1.0.3 ロードマップページ":http://www.playframework.org/roadmap/1.0.3 にて、修正されたバグについて読むことができます。もっとも重要な変更は、このページにてハイライトされています。 + +h2. 新しいサンプルアプリケーション: validation + +新しいサンプルアプリケーションが追加されました。このサンプルは、Play のバリデーションをサポートするフォームの様々な作り方を実演します。 + +!images/validation! + +h2. yabe チュートリアルの新しい章 + +すでにある yabe チュートリアルはアプリケーションの "国際化と地域化":guide12 によって完成します。初めからアプリケーションを国際化することもできますが、アプリケーションの最初のバージョンは単一の言語で作成し、後から複数の言語を追加するほうが、より現実的です。 + +"新しい章を読んでください":guide12 + +h2. playapps.net サポート + +リリース 1.0.3 は、クラウドプラットフォーム上の新しい "http://www.playapps.net":http://www.playapps.net でサポートされる最初のバージョンです。 + +!images/dashboard_preview! + +p(note). 次のリリース: %(next)"Play 1.1 リリースノート":releasenotes-1.1% diff --git a/documentation/manual_ja/releasenotes-1.1.textile b/documentation/manual_ja/releasenotes-1.1.textile new file mode 100644 index 0000000000..36084a110f --- /dev/null +++ b/documentation/manual_ja/releasenotes-1.1.textile @@ -0,0 +1,318 @@ +h1. Play 1.1 — リリースノート + +Play 1.1 におけるバグフィックスについては "ロードマップページ":http://www.playframework.org/roadmap/1.1 で読むことができます。このページでは重要な変更に注目します。 + +h2. Play 1.0.x からの以降 + +Play 1.0.x からの以降は実に簡単です。アプリケーションのレイアウトはまったく変わらないので、同じアプリケーションが Play1.0.3.2 と Play1.1 の両方で動作するでしょう。しかし、アプリケーションにおいて外部のモジュールを使用している場合、Play1.1 と互換性のある、より新しいバージョンを使用しなければならないかもしれません。対応するモジュールのページをチェックしてください。 + +* 長い間、非推奨とした後に Play 1.1 においていくつかの API を削除しましたが、ほとんどの public な API は変わりありません。どのようにして解決したらよいか分からないコンパイルエラーがあったら Google グループに尋ねてください。 +* プラグインの順序を変更しました。特定のプラグインをデフォルトのプラグインの前または後に実行する必要がある場合、プラグインのインデックスを変更する必要があるでしょう。 +* 多くのメソッドがチェック例外をスローしていた play.libs.IO は、実行時例外をスローするようになります。これらの例外をキャッチしていた場合は、キャッチする例外を変更するか、例外をキャッチしないよう変更する必要があるかもしれません。 +* 本番モードでは、モデルを変更しても自動的にデータベースを更新することはしません。自動的に更新したい場合は、 application.conf に次の一行を追加してください: + +bc. jpa.ddl = update + +h2. 新しいヘッドレステストランナー + +ご存知のとおり、Play のテストランナーでアプリケーションテストを実行するためにはブラウザが必要です。これは、アプリケーションを実際のブラウザでテストできるようにする "Selenium":http://seleniumhq.org/ テストをサポートするためです。 + +しかし、継続的統合において、CI サーバ上で自動的に実際のブラウザを実行することはしばしば困難を伴います。そこで、今回のリリース以降、Play は "HtmlUnit":http://htmlunit.sourceforge.net/ に基づく画面無しのブラウザを組み込むことにしました。 + +@play auto-test@ コマンドにてテストを実行すると、このブラウザが使用されます。 + +!images/auto-test! + +この画面無しのブラウザが Internet Explorer 8 のように振る舞おうとすることに注意してください。ほとんどの開発者は Firefox や Webki といった本物のブラウザを使用し、手元でテストするので、この画面無しブラウザが IE8 のように振る舞おうとすることは、統合されたプラットフォーム上における Javascript の互換性を明示的に確認することになるので、良いことです。 + +h2. JBoss Netty ベースの新しい HTTP サーバ + +Play 1.1 は HTTP サーバとして Apache Mina の代わりに "JBoss Netty":http://www.jboss.org/netty を使用します。このことは、あなたのアプリケーションを何も変更しませんし、HTTP 層におけるパフォーマンスも以前と同じですが、Play 1.0.x に影響していたいくつかの小さな HTTP 関連のバグが解消されます。 + +間もなく、この新しい HTTP サーバによって WebSockets のような、より高度な HTTP の機能をサポートできるようになるでしょう。 + +h2. コアライブラリの更新と命名の改善 + +Play framework はフルスタックフレームワークなので、必要な Java ライブラリを直接組み込んでいます。JPA 2 をサポートする Hibernate 3.5.x を含むこれらすべてのライブラリを更新しました。 + +組込みライブラリのために、より良い命名規則を採用しました。 @framework/lib@ ディレクトリの中を確認すれば、それぞれのライブラリの正確なバージョンが分かります。 + +h2. データベースに捉われない play.db.Model API + +Play 1.1 は、新しい **play.db.Model** API を導入します。この API はアプリケーションから直接使用されることを意図していませんが、(NoSQL ベースのものも含めて) どのような種類のデータベースに対しても統合を提供できるよう、モジュール作者が実装できる包括的なデータストアインタフェースを提供します。 + +これは、Play 1.1 では JPA サポートはコアフレームワークから分離されるということを意味します。JPA サポートは依然としてデフォルトのプラグインであり、 *@Entity* クラスをひとつでも見つけると自動的に有効化されますが、 *CRUD* や *Fixtures* などは直接 JPA に依存しません。これらのコンポーネントは、例えば "MongoDB 用の play-morphia モジュール":/modules/morphia のような、 *play.db.Model* の実装を提供する、どのようなモジュールとでも動作することができます。 + +h2. Scala 言語のサポート + +コアフレームワークの内部は、Scala 言語でも動作するようにリファクタリングされました。"Scala モジュール":http://www.playframework.org/modules/scala は Scala と Play framework の完全な統合を提供します。 + +h2. ネイティブな Glassfish へのデプロイサポート + +今回の Play には "Glassfish アプリケーションサーバ":https://glassfish.dev.java.net/ 用のネイティブなコンテナが備わっています。これは、Play コンテナを追加することで、どの Glassfish サーバにおいても既存のあらゆるアプリケーションが実行できることを意味します。 + +!images/glassfish! + +Glassfish コンテナは、 http://github.com/playframework/play-glassfish で管理されており、間もなく Glassfish の @contrib@ リポジトリから直接利用できるようになるはずです。 + +Glassfish は複数のアプリケーションを同時に実行できるので、複数のアプリケーションを単一の JVM 上で実行することができます。 + +!images/glassfish2! + +アプリケーションを WAR ファイルにパッケージするわけではないことに注意してください。Glassfish 用の Play コンテナはアプリケーションをネイティブ実行します: Glassfish 用の Play コンテナは Servlet コンテナを使用しませんし、アプリケーションを何か特別な方法でパッケージする必要もありません。 + +h2. routes におけるバーチャルホストのサポート + +@routes@ ファイルでホストマッチングをサポートしました。これは、ホストのパラメータからアクションのパラメータを抽出しなければならない場合に便利です。例えば、ある SaaS アプリケーションにおいて、以下のように使用することができます: + +bc. GET {client}.mysoftware.com/ Application.index + +こうすることで、他のあらゆるリクエストパラメータとも同じように @client@ の値が自動的に検索されます: + +bc. public static void index(String client) { + … +} + +テンプレートにおいて (リバースルーティングを行う) @@{…} 記法を使用すると、ルートに対応付けられたホストが使用されます。これは、いくつかの状況で役に立つ場合があります。 + +例えば、運用環境において静的なコンテンツを配布するためにコンテンツ配信ネットワークを使用したい場合、 @routes@ ファイルを以下のように書くことができます: + +bc. #{if play.Play.mode.isDev()} + GET /public/ staticDir:public +#{/} +#{else} + GET assets.myapp.com/ staticDir:public +#{/} + +そして、テンプレートに以下のように記述します: + +bc. + +これは、DEV モードでは @http://locahost:9000/public/images/logo.png@ にリバースされ、PROD モードでは @http://assets.myapp.com/images/logo.png@ にリバースされます。 + + +h2. カスタムバインディングのサポート + +バインディングシステムはより多くのカスタマイズをサポートするようになりました。 + +h3. @play.data.binding.As + +最初に紹介するのは、文脈的にバインディングを構成できるようにする新しい @play.data.binding.As アノテーションです。これは例えば、 @DateBinder@ によって使用される日付のフォーマットを指定するために使います: + +bc. public static void update(@As("dd/MM/yyyy") Date updatedAt) { + … +} + +この @As アノテーションは、ロケールごとに特定のアノテーションを提供できることを意味する国際化もサポートします: + +bc. public static void update( + @As( + lang={"fr,de","en","*"}, + value={"dd/MM/yyyy","dd-MM-yyyy","MM-dd-yy"} + ) + Date updatedAt + ) { + … +} + +この @As アノテーションは、これをサポートするすべてのバインダと共に動作します。以下は、 @ListBinder@ を使用する例です: + +bc. public static void update(@As(",") List items) { + … +} + +このバインダは、単純にカンマで分けられた @String@ を @List@ にバインドします。 + +h3. @play.data.binding.NoBinding + +新たに追加された @play.data.binding.NoBinding は、バインド非対象フィールドをマークし、潜在的なセキュリティ問題を解決します。以下に例を示します: + +bc. public class User extends Model { + @NoBinding("profile") public boolean isAdmin; + @As("dd, MM yyyy") Date birthDate; + public String name; +} + +public static void editProfile(@As("profile") User user) { + … +} + +このようにすると、例え悪意あるユーザが偽のフォームから @user.isAdmin=true@ というフィールドを含めてポストしたとしても、 @isAdmin@ フィールドは決して @editProfile@ アクションからはバインドされません。 + +h3. play.data.binding.TypeBinder + +@As アノテーションを使って完全に独自のバインダを定義することができます。独自のバインダは、プロジェクト内にて @TypeBinder@ のサブクラスとして定義されます。以下に例を示します: + +bc. public class MyCustomStringBinder implements TypeBinder { + + public Object bind(String name, Annotation[] anns, String value, Class clazz) { + return "!!" + value + "!!"; + } + +} + +以下のようにして、いずれのアクションにおいてもこのバインダを使用することができます: + +bc. public static void anyAction(@As(binder=MyCustomStringBinder.class) String name) { + … +} + +h3. @play.data.binding.Global + +対応する型にだけ適用されるグローバルなカスタムバインダを定義することもできます。例えば、以下のようにして @java.awt.Point@ クラスにバインドできるバインダを定義することができます: + +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]) + ); + } + +} + +見てのとおり、グローバルバインダは @play.data.binding.Global でアノテーションされた古典的なバインダです。外部モジュールは再利用可能な拡張バインダを定義することで、プロジェクトにバインダを提供することができます。 + + +h2. 新しく強力な非同期 WS ライブラリ + +@play.libs.WS@ ライブラリによって、プロジェクトを web クライアントのように振る舞わせることができます。今回のリリースで "AsyncHttpClient":http://github.com/AsyncHttpClient/async-http-client をベースにした新しい非同期処理の実装を取り入れました。この新しい実装は、リモートの資源を非同期に取得する新しい @xxxAsync@ メソッドを提供します。 + +@waitFor(…)@ メソッドと共に使用することで、既存のアプリケーションとマッシュアップする、ブロックされない高パフォーマンスのアプリケーションを作ることができます: + +bc. public static void mirrorFeed() throws Exception { + if (request.isNew) { + Future feed = WS.url( + "http://planet.playframework.org/feed" + ).getAsync(); + request.args.put("futureFeed", feed); + waitFor(feed); + } else { + HttpResponse res = ( + (Future)request.args.get("futureFeed") + ).get() + renderXml(res.getXml()); + } +} + +h2. OAuth サポート + +今回のリリースに "OAuth":http://oauth.net/ プロトコルをサポートする @play.libs.OAuth@ ライブラリがあります。OAuth は、web アプリケーションからシンプルで標準的な方法で、安全に認証を行う API を提供するオープンなプロトコルです。 + +新たに用意した @twitter-oauth@ サンプルアプリケーションは、セキュアに twitter API に接続することで、この API の使い方を実演します。 + +!images/sample-twitter! + +h2. HTTPS サポート + +今回のリリースで組み込まれたサーバは HTTPS プロトコルをサポートします。もちろん、必要に応じて運用環境で使用することができます。このサーバは、古典的な Java の **keystore** もしくはシンプルな @cert@ と @key@ ファイルのどちらでも証明書を管理することができます。 @application.conf@ ファイルにて @https.port@ の設定プロパティを宣言するだけで、アプリケーションは HTTPS 接続を開始します: + +bc. http.port=9000 +https.port=9443 + +@conf@ ディレクトリに証明書を配置する必要があります。Play は X509 証明書とキーストア証明書をサポートします。X509 証明書は次のように命名されなければなりません: +証明書には *host.cert* と命名し、キーには *host.key* と命名します。キーストアを使用する場合、デフォルトでは *certificate.jks* という名前が付けられます。 + +X509 証明書を使用する場合、 @application.conf@ ファイルに以下のパラメータを設定することができます: + +bc. # X509 certificates +certificate.key.file=conf/host.key +certificate.file=conf/host.cert +# In case your key file is password protected +certificate.password=secret +trustmanager.algorithm=JKS + +キーストアを使用する場合は: + +bc. keystore.algorithm=JKS +keystore.password=secret +keystore.file=conf/certificate.jks + +上記の値はデフォルト値であることに注意してください。 + +*openssl* を使って自己署名証明書を生成することができます: + +bc. openssl genrsa 1024 > host.key +openssl req -new -x509 -nodes -sha1 -days 365 -key host.key > host.cert + +java のキーストア機能を使用する場合、 @application.conf@ ファイルに以下のパラメータを設定することができます: + +bc. # Keystore +ssl.KeyManagerFactory.algorithm=SunX509 +trustmanager.algorithm=JKS +keystore.password=secret +keystore.file=certificate.jks + +上記の値はデフォルト値です。 + +h2. 新しいキャッシュ機能 + +アクションとテンプレートにより簡単にキャッシュを統合することができる 2 つの新機能を用意しました。最初に、 @play.cache.CacheFor アノテーションを追加することで、アクションの結果を容易にキャッシュすることができるようになりました。これは見せかけの静的ページを作るのにとても便利です。 + +bc. @CacheFor("1h") +public static void index() { + render(); +} + +2 番目に、 @#{cache}@ タグによってテンプレートの断片を簡単にキャッシュすることができるようになりました: + +bc.

    Very complex home page to build

    + +#{cache 'home-' + connectedUser.email, for:'15min'} + … +#{/cache} + +これらの新機能は標準の Play キャッシュと同じキャッシュ実装を使用しています。 + +h2. WAR アーカイブの完全プリコンパイル + +今回のリリースから @play precompile@ コマンドは、アプリケーションを静的な Java のバイトコードに完全にコンパイルします。これは、完全にコンパイルされた Play アプリケーションを配布することができること、そしてテンプレートを含め、すべての @app/@ ソースファイルを削除できることを意味します。 + +@play war@ コマンドで生成されたすべての **WAR** ファイルは、自動的にプリコンパイルされます。 + +標準的な方法でアプリケーションを実行する場合、デフォルトでは、Play は変更を検出するために常にアプリケーションのソースコードをチェックします。このステップが不要であり、プリコンパイルされたクラスからアプリケーションを開始したい場合、システムプロパティに @precompiled=true@ を指定します: + +bc. play start myApp -Dprecompiled=true + +h2. グローバル route 引数 + +新しく追加された @play.mvc.Controller.routeArgs@ スコープには、リクエストの間において、あらゆるリバースルーティングからグローバルに使用される変数を定義することができます。例えば、多数のルートに共通のパラメータがあるとします: + +bc. GET /{lang}/ Application.index +GET /{lang}/users Application.users +GET /{lang}/items Application.items + +この @lang@ パラメータをすべてのアクションから省略し、単一の @Before フィルタで管理することができます: + +bc. @Before +static setLang(String lang) { + Lang.set(lang); + routeArgs.put("lang", lang); +} + +@routeArgs@ スコープに @lang@ 引数を追加することで、特に指定しなくても自動的にリバースルーティングに使用されます: + +bc. Users list + +例えばこれは、次のようにリバースされます: + +bc. Users list + +h2. カスタムコマンドを書くための更なる柔軟性 + +モジュール作成者は、独自の Python コマンドをより柔軟に書けるようになります。モジュールが提供する @command.py@ ファイルは、既存のどの組み込みコマンドにもフックすることができます。また、モジュールが提供するコマンドは @play help@ コマンドで一覧化されます。 + +h2. その他の小さな機能 + +"230 の修正されたバグ":http://www.playframework.org/roadmap/1.1 のほかにも、以下を含むいくつかの小さな新機能があります: + +* @play.libs.XPath@ ライブラリにおける名前空間のサポート +* ジョブスケジューリングにおける @never@ 値のサポート +* サーバ上の静的なリソースに対する Mime タイプを @application.conf@ に定義することができます +* @play.mvc.Http.Response@ におけるクロスドメイン XHR ヘルパ +* @HTTPOnly@ クッキーのサポート +* 新しい Play framework のリリースを確認する新しい @play check@ コマンド + diff --git a/documentation/manual_ja/releasenotes-1.2.4.textile b/documentation/manual_ja/releasenotes-1.2.4.textile new file mode 100644 index 0000000000..12e481256c --- /dev/null +++ b/documentation/manual_ja/releasenotes-1.2.4.textile @@ -0,0 +1,44 @@ +h1. Play 1.2.4 -- リリースノート + +Play! 1.2.4 で直されたバグは "ロードマップ・ページ":http://www.playframework.org/roadmap/1.2.4 で読むことができます。ここでは重要な変更をハイライトします。 + +h2. Java7 サポート + +Java7 は Play! で難しい設定などは一切なしでサポートされました。次のコードは問題なく書くことができます。 + +bc. Map> map = new HashMap<>(); +String version = "1.2.4"; +switch(version) { + case "1.2.4": + //code + break; + case "1.2.3": + //code + break; + case "1.2.2": + //code + default: + //code + break; +} + + +h2. 新しいバインダーの実装 + +新しいバインダーの実装はより柔軟になり、jQuery から Play へのマッピングを簡単にします。さらに、この新しい実装を使って、より多くの複合オブジェクトをマッピングすることができます。 + +h2. 最新の Websocket のサポート + +最新ドラフト (hybi-00 から hybi-10) をサポートしています。 詳しくは "websocket specification":http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10 を参照して下さい。 + +h2. 複合 ID のサポート + +複合 ID は Fixtures でサポートされました。モデルと Fixture で次のアノテーションもサポートされました。 @IdClass, @EmbeddedId . より詳しくは "hibernate documentation":http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html/entity.html#d0e2177 を参照してください。 + +h2. IntelliJ サポートの改善 + +play idealize コマンドで完全な IntelliJ プロジェクトが作成できるようになりました。IntelliJ から Play! のプロジェクトを直接開くことができます。 + +h2. その他の小さな変更 + +"86件のバグ修正":https://play.lighthouseapp.com/projects/57987-play-framework/milestones/121681-124 といくつかの小さな改良を行いました。 \ No newline at end of file diff --git a/documentation/manual_ja/releasenotes-1.2.5.textile b/documentation/manual_ja/releasenotes-1.2.5.textile new file mode 100644 index 0000000000..8456b6021b --- /dev/null +++ b/documentation/manual_ja/releasenotes-1.2.5.textile @@ -0,0 +1,10 @@ +h1. Play 1.2.5 -- リリースノート + +このリリースにおける変更は Lighthouse の "Play 1.2.5 マイルストーン":https://play.lighthouseapp.com/projects/57987-play-framework/milestones/131471-125 に、86 件の解決済みチケットを含めて一覧されています。最も重要な変更は下記のとおりです: + +* 複数の continuations/await に関するバグの修正 +* ハッシュ衝突攻撃に対するセキュリティ修正 +* Glassfish 3 における JNDI データソース +* チャンク転送/ストリーミングサポートの改善 +* hibernate 4.1.1 と netty 3.4.2 を含むすべてのライブラリのアップグレード +* すべてのブラウザにおける web socket のサポート diff --git a/documentation/manual_ja/releasenotes-1.2.textile b/documentation/manual_ja/releasenotes-1.2.textile new file mode 100644 index 0000000000..f9b1ebae78 --- /dev/null +++ b/documentation/manual_ja/releasenotes-1.2.textile @@ -0,0 +1,196 @@ +h1. Play 1.2 — リリースノート + +Play 1.2 におけるバグフィックスについては "ロードマップページ":http://www.playframework.org/roadmap/1.2 で読むことができます。このページでは重要な変更に注目します。 + +h2. 1.1.x からの移行 + +Play 1.1.x からの以降は実に簡単です。アプリケーションのレイアウトはまったく変わらないので、あなたのアプリケーションは Play 1.2 で動作するでしょう。しかし、アプリケーションにおいて外部のモジュールを使用している場合、Play 1.2 と互換性のある、より新しいバージョンを使用しなければならないかもしれません。対応するモジュールのページをチェックしてください。 + +長い間、非推奨とした後に Play 1.2 においていくつかの API を削除しましたが、ほとんどの public な API は変わりありません。どのようにして解決したらよいか分からないコンパイルエラーがあったら "Google グループ":https://groups.google.com/forum/#!forum/play-framework で質問してください。 + +@FunctionalTest@ からコントローラを呼び出すと、このアクションは独自のトランザクション配下で実行されるようになりました。テスト自身も独自のトランザクションで実行されるため、デッドロックが発生する可能性があります。このため、テストの実行中はデータベースを *READ_UNCOMMITED* に設定した方がよいです。インメモリの H2 データベースでテストをくり返すのであれば、以下のデータベース設定を使用してください: + +bc. %test.db.url=jdbc:h2:mem:play;MODE=MYSQL;LOCK_MODE=0@ + +データベースに *READ_UNCOMMITED* を設定できない場合は、(@JPA.em().getTransaction()@ を使って) 手動で JPA トランザクションを管理するか、 *ジョブ* を使って起動することで、すべての JPA アクセスをそれぞれ独自のトランザクションで実行する必要があります。 + + +h2. 依存性管理 + +"Play の依存性管理システム":dependency によって、アプリケーションの外部依存性を単一の @dependencies.yml@ ファイルに記述することができます。 + +Play アプリケーションには三種類の依存性があります: + +* Play フレームワーク自身。Play アプリケーションは常に Play フレームワークに依存します。 +* アプリケーションの @lib/@ ディレクトリにインストールされた **JAR** ファイルとして提供されるあらゆる Java ライブラリ +* アプリケーションの @modules/@ ディレクトリにインストールされた (実際はアプリケーションの断片である) Play モジュール + +これらの依存性をアプリケーションの @conf/dependencies.yml@ ファイルに記述すると、Play はすべての必要な依存性を解決し、ダウンロードしてインストールします。 + +例えば、このファイルは以下のように使います: + +bc. # Application dependencies + +require: + - play + - com.google.guava -> guava r07 + - play -> pdf 0.2 + +@`play dependencies`@ を実行することができます: + +!images/dependencies! + +この依存性管理は、内部では "Apache Ivy":http://ant.apache.org/ivy/ を利用しており、Maven 互換のリポジトリをサポートします。 + +h2. より良い非同期機能 + +Play 1.1 から既に Java Future と、 @waitFor(…)@ と @suspend(…)@ のコントローラメソッドを使って非同期処理を達成していました。しかし、これら初期のものは決して使い易くありませんでした。そこで Play 1.2 では新しく一貫性のある完全な機能セットを実装しました。 + +h3. Promises + +Play 1.2 には Play のカスタム Future 型である Promise が導入されます。実際のところ、 @Promise@ は @Future@ でもあるので、標準的な Future に対しても使用することができます。しかし、Promise には興味深い追加機能: 期待する値が利用可能になると、できるだけ早く呼び出される @onRedeem(…)@ を使ってコールバックを登録する機能が備わっています。フレームワークが、自身を登録し、利用可能になったらすぐにリクエストを実行するようリスケジュールすることができます。 + +h3. play.libs.F + +Promise 型は、いくつかの便利な関数プログラミング構造を提供する新しいライブラリ (play.libs.F) の一部です。私たちは Java におけるパターンマッチングも必要だと感じました。残念ながら Java は組み込みのパターンマッチングを持ち合わせておらず、関数構造の欠如により、パターンマッチングをライブラリとして追加することも困難でした。とにかく、私たちはそれほど悪くない解決策に取り組みました。 + +h3. await(…) + +アプリケーションコードが @Promise@ を使って、まだ利用可能でない値を返すとき、リクエストを再開する前に、この期待する結果が利用可能になるのを待つよう Play に指示できたらよいと思うでしょう。これは、コードに明記することができます。 + +bq. "あとで結果が利用可能になるまで待ちます" + +そこでフレームワークは以下のように扱います + +bq. "了解、コードを中止し、他のリクエストを扱うためにスレッドを再利用します。そして期待する値が利用可能になったら、速やかにコードを再開します" + +h3. 継続 + +フレームワークは、別のリクエストを扱うために使用していたスレッドを回収する必要があるので、コードの実行を中断しなければなりません。以前のバージョンの Play の @waitFor(…) と等価である @await(…) は、アクションを中断し、その後、始めから再度呼び出します。 + +Play 1.2 において非同期処理をより簡単に扱うために、継続を紹介します。継続は等価的にコードを中断し、再開します。 + +h3. Response ストリーム + +リクエストをブロックせずにループすることができるので、結果の一部の準備が整い次第、ブラウザにデータを送りたくなるかもしれません。これは HTTP レスポンスタイプ @Content-Type:Chunked@ のポイントです。このレスポンスタイプでは、複数のチャンクを使って数回の HTTP レスンポンすを送ることができます。ブラウザはチャンクが発行され次第、これを受け取ります。 + +h3. WebSockets + +WebSockets は、ブラウザとアプリケーション間の双方向コミュニケーションチャネルを開く方法です。WebSockets は Play アプリケーションでサポートされるようになりました。 + +h3. 新しいチャットサンプルアプリケーション + +!images/chat-websocket! + +これらすべての新機能は、標準的なチャットアプリケーションを三つの異なる方法で達成している *Chat* サンプルアプリケーションで使用されています: + +* 能動的な更新 +* Ajax による長時間ポーリング +* WebSockets + +h2. routes ファイルの改善 + +@routes@ ファイルは一連の新機能をサポートします。ルートパスの正規表現に *{* と *}* の文字を安全に使うこともできます。 + +h3. staticFile: マッピング + +古い @staticDir@ マッピングのように、描画する静的なフィルへの URL パスを直接マッピングすることができます。 + +bc. # Serve index.html static file for home requests +GET /home staticFile:/public/html/index.html + +h3. 404 アクション + +アプリケーションにおいて無視されなければならない URL パスを印付けるために、 @404@ をアクションルートとして直接使用することができます。例えば: + +bc. # Ignore favicon requests +GET /favicon.ico 404 + +h3. WS メソッド + +新しい @WS@ メソッドは、WebSocket にマッピングされるルートを定義することができます。 + +bc. # A WebSocket +WS /chat/messages Chat.messages + +以下のような URL をリバースして生成するために、 @@{Chat.messages()} のような注記を使うことができます: + +bc. ws://localhost:9000/chat/messages + + +h2. データベースエボリューション + +リレーショナルデータベースを使用する場合、データベーススキーマの変更を追跡し、管理する方法が必要になります。Play は自動的にこれらの変更を追跡し、スキーマを更新します。 + +!images/evolutions! + +これは、同じアプリケーションで複数の開発者が作業するときに発生する競合も解決します。 + +h2. Invocation コンテキストアノテーション + +Play は (例えば HTTP リクエスト、WebSocket メッセージ、または非同期ジョブの実行などの) 起動を、それぞれ **Invocation** としてマッピングします。あらゆる起動は、ある特定の起動を扱い方を変更するためにプラグインから使用されるアノテーションによって、注釈することができます。 + +例えば、 **JPA Plugin** はデータベースが設定されている場合、それぞれの呼び出しについて自動的にデータベーストランザクションを開始します。特定の呼び出しにはデータベース接続が必要ない場合、 @NoTransaction で註釈することができます: + +bc. @NoTransaction +public static void index() { + render(); +} + +別のアノテーションでは、特定の呼び出しに読み出し専用のトランザクションを指定することができます; + +bc. @Transactional(readOnly=true) +public static show(Long id) { + Post post = Post.findById(id); + render(post); +} + +この起動コンテキストの概念は、あらゆるプラグインに適用することができます。 + +h2. デフォルトのインメモリ H2 データベース + +Play のインメモリデータベースとして "H2 データベース":http://www.h2database.com/ を使用します。H2 データベースは MySQL のような本番用データベースとの互換性が高いので、デプロイにおける問題は少なくなるでしょう。 + +良い副作用として、H2 は *db=mem* が設定されているどのような Play アプリケーション でも */@db* という URL で起動できる Web コンソールを提供します。 + +!images/h2console! + +h2. テストランナーの更新 + +テストランナーに関するいくつかの更新があります。 + +h3. あらゆる JUnit テストランナーにおける JUnit テストの実行 + +Eclipse が提供するような、あらゆる既存の JUnit テストランナーにおいて Java テストケースを直接実行することができます。 + +!images/eclipse-test-runner! + +h3. Surefire レポート + +どのような Java テストクラスでも、テストを実行すると CI ソフトウェアと容易に統合できる Surefire レポートが生成されます。 + +h3. YAML フィクスチャ + +複数の YAML ファイルから一度にフィクスチャをロードできますし、ある種の動的なデータを追加するために YAML 定義にテンプレートエンジンマークアップ言語を使用することもできます。 + +h3. 複数のテスト ID + +@test-*@ にマッチするフレームワーク ID を指定することで、ひとつ以上のテスト環境を作成することができます。 + +h2. チートシートの提供 + +Play のドキュメントに、一般的な Play の関数を素早く参照するためのいくつかのチートシートを含めました。このチートシートは簡単に印刷できます。 + +!images/cheatsheet! + +h2. その他の小さな機能 + +"130 のバグフィックス":http://play.lighthouseapp.com/projects/57987-play-framework/milestones/current に加えて、以下の小さな新機能が含まれます。 + +* JPA モデル用の新しい @create()@ と @validateAndCreate()@ メソッド +* @play@ コマンドへの @--pid_file=@, @--http.port=@, @--https.port=@ コマンドラインオプションの追加 +* 言語-国ペア (例えば en-GB) のサポート +* 複数値や入れ子、その他の @Map@ へのバインディング +* 独自の CRUD @ObjectType@ を作成する機能 +* play.lib.WS クライアントのタイムアウトのサポート +* セッションが空の場合にセッション Cookie を送信しない diff --git a/documentation/manual_ja/releasenotes.textile b/documentation/manual_ja/releasenotes.textile new file mode 100644 index 0000000000..efb6548b91 --- /dev/null +++ b/documentation/manual_ja/releasenotes.textile @@ -0,0 +1,227 @@ +h1. Play 1.0.1 — リリースノート + +**play 1.0.1** は play 1.0 ブランチのメンテナンスリリースです。このリリースは大量の小さなバグを修正し、JPA オブジェクトのより良いバインディング、XSS セキュリティ問題を回避する HTML および Javascript コードの自動エスケープを提供します。 + +p(note). **play 1.0.1** はメンテナンスリリースであり、バージョン 1.0 との完全な互換性があります。何か問題にぶつかったら Google Group にて質問してください。 + +"1.0.1 ロードマップページ":http://www.playframework.org/roadmap/1.0.1 にて、修正されたバグについて読むことができます。もっとも重要な変更は、このページにてハイライトされています: + +h2. テンプレートにおける HTML コードの自動エスケープ + +全ての動的な式は、アプリケーションにおける XSS セキュリティ問題を回避するために、テンプレートエンジンによってエスケープされます。このため、 **==<h1>Title</h1>==** を含む title 変数は、以下のようにエスケープされます: + +bc. ${title} --> <h1>Title</h1> + +これらを本当にエスケープせずに表示したい場合は、 **raw()** メソッドを明示的に呼び出す必要があります: + +bc. ${title.raw()} -->

    Title

    + +素の HTML の大部分を表示したい場合は、 **#{verbatim /}** タグを使うこともできます: + +bc. #{verbatim} + ${title} -->

    Title

    +#{/verbatim} + +もちろん、この機能は既存のアプリケーションを壊してしまうかもしれないので、デフォルトでは有効ではありません。これは以下の行を **application.conf** ファイルに追加することで有効にすることができます: + +bc. future.escapeInTemplates=true + +この行は、リリース 1.0.1 で作られた新規アプリケーションには追加されます。 + +h2. @javax.inject.Inject サポート + +"Spring module":spring モジュールで提供されるような DI コンテナを使っている場合、 **@Inject** アノテーションを使うことができます。この **@Inject** アノテーションは、定義された Java Beans をコントローラ、ジョブ、またはメーラに自動的にインジェクションします。このアノテーションは static フィールドに作用します。 + +例えば、Spring に定義された **PriceWatcher** サービスをコントローラにインジェクションするには、以下のようにするだけです: + +bc. public class Application extends Controller { + + @Inject + static PriceWatcher prices; + + public static void index() { + prices.getAll(); // prices is defined here + } + +} + +自動リロードは期待通り動作するはずです。 + +h2. JPA オブジェクトのより良いバインディング + +以前は、HTTP を使って JPA オブジェクトを自動的に紐付ける方法はありませんでした。例えば: + +bc. public static void save(User user) { + user.save(); // fail in 1.0 +} + +**User** は JPA エンティティクラスであり、user は Binder によって作成された一時的なオブジェクトで、まだ Hibernate によって管理されていないので、このコードは失敗します。 + +古いやり方は、以下のように **edit()** メソッドを使うものでした: + +bc. public static void save(Long id) { + User user = User.findById(id); + user.edit(params); + user.save(); // ok +} + +今では、HTTP パラメータに **user.id** フィールドを渡すことができます。Play は **id** フィールドを見つけると、HTTP パラメータを編集する前に、データベースからマッチするインスタンスをロードします。その後、HTTP リクエストの他のパラメータが適用されます。こうして、HTTP パラメータを直接保存することができます。 + +bc. public static void save(User user) { + user.save(); // ok with 1.0.1 +} + +もちろん、この機能は既存のアプリケーションを壊してしまうかもしれないので、デフォルトでは有効ではありません。これは以下の行を **application.conf** ファイルに追加することで有効にすることができます: + +bc. future.bindJPAObjects=true + +この行は、リリース 1.0.1 で作られた新規アプリケーションには追加されます。 + +h2. コマンドラインからのフレームワーク id の設定 + +フレームワーク id によって、同じ play! アプリケーションを異なる設定 (例えば dev, test, staging, production, などなど...) で実行することができます。 + +コマンドラインを使うことで、アプリケーションが使用する "フレームワーク id":ids を指定することができます。例えば、アプリケーションを production モードで実行するには、以下のコマンドを: + +bc. play run --%production + +以下の行を **application.conf** ファイルに定義して実行します: + +bc. application.mode=dev +%production.application.mode=prod + +これは、フレームワーク id 情報を使用する全ての既存のコマンドと互換性があります。今でも、デフォルト id は **play id** コマンドを使って定義します。 + +ちなみに **play test** は以下と等価です: + +bc. play run --%test + +h2. カスタムバリデーション + +必要なバリデータを **play.data.validation** パッケージから探すことができませんか?自分で書きましょう。一般的な **@CheckWith** アノテーションを使って、独自の **Check** 実装を紐付けることができます。 + +例: + +bc. public class User { + + @Required + @CheckWith(MyPasswordCheck.class) + public String password; + + static class MyPasswordCheck extends Check { + + public abstract boolean isSatisfied(Object user, Object password) { + return notMatchPreviousPasswords(password); + } + + } +} + +h2. テストランナーの更新 + +selenium をバージョン 1.0.1 final に更新し、UI を改良しました。Selenium テストは全画面で実行されます。さらに "Run all tests" のような新機能がいくつか追加されています。 + +!images/selenium-fullscreen! + +アプリケーションを test モードで実行した場合、ローカルのドキュメントを利用することもできます。 + +h2. HTML5 をデフォルトの doctype へ、そして JQuery を新規アプリケーションへ + +新規アプリケーションにはデフォルトの HTML スケルトンが含まれます。以前、このスケルトンは **XHTML 1.0 Transitional** でフォーマットされた HTML でした。今では、デフォルトで **HTML5** doctype となります: + +bc. + + + + #{get 'title' /} + + + #{get 'moreStyles' /} + + + #{get 'moreScripts' /} + + + #{doLayout /} + + + +これは単なるデフォルト設定であり、当然、お望みであればどのような doctype にも変更可能です。しかしながら、"HTML5":http://html5.org は間違いなく次世代的であり、簡単です。 + +デフォルトテンプレートには **JQuery** Javascript ライブラリが含まれます。これは、モダンな web アプリケーションには優れた Javascript フレームワークが必要であり、かつ我々が "JQuery":http://www.jquery.com を愛しているからです。こちらも単なるデフォルト設定であり、お望みのどんな Javascript ライブラリで置き換えることもできます。 + +ああ、そして、デフォルトテンプレートには追加のスクリプトと/またはスタイルを挿し込む 2 つのデフォルトのプレースホルダが含まれます。例えば、以下のようにしてビューに追加します: + +bc. #{set 'moreScripts'} + +#{/set} + +これで、このビューにて gear Javascript ライブラリにアクセスすることができるようになります。 + +h2. #{list} タグの改善 + +items パラメータはオプションとなり、デフォルトの arg 引数で置き換えることが可能です。 + +このため: + +bc. #{list items:users, as:'user'} +
  • ${user}
  • +#{/list} + +を、以下のように書き換えることができます: + +bc. #{list users, as:'user'} +
  • ${user}
  • +#{/list} + +as パラメータもまたオプションです。デフォルト変数名として _ を使用します: + +bc. #{list users} +
  • ${_}
  • +#{/list} + +h2. 新しい #{jsAction /} タグ + +この **#{jsAction /}** タグは、サーバ側で定義されたルートを Javascript 関数として取り込めるようにします。これは、自由な引数を使う AJAX によって URL を呼び出す必要がある場合に、とても便利です。 + +例を見てみましょう: + +bc. GET /users/{id} Users.show + +このルートをクライアント側に取り込むことができます: + +bc. + +見ての通り、自由な引数を定義するために **:ame** 構文を使用します。可変引数と固定引数を一緒に使用することも可能です。この **#{jsAction /}** タグは Javascript 関数を出力します。この関数は全ての可変引数を定義する Javascript オブジェクトを引数として受け取ります。 + +h2. 新しいサンプルアプリケーション: 'booking' + +この予約アプリケーションは、有名な "JBoss seam フレームワークの予約アプリケーション":http://docs.jboss.com/seam/latest/reference/en-US/html/tutorial.html#booking を移植したものです。 + +この予約アプリケーションは、ステートレスなフレームワークを使って、クライアント側で RestFul な方法で複雑な状態をどのように管理するかを示します。 + +!images/booking! + +h2. Eclipse プラグインは順調です + +eclipse 専用プラグインを提供するためにがんばって作業しています。現状はアルファ版ですが、すでに利用可能です。このプラグインは play インストールパスの **support/eclipse** ディレクトリにあります。 + +!images/eclipse-plugin! + +h2. IntelliJ IDEA のサポート + +今やPlay! フレームワークは、そのままで "IntelliJ IDEA":http://www.jetbrains.com/idea/ をサポートします! + +Play! アプリケーションを IntelliJ IDEA プロジェクトに変換するには、 **idealize** コマンドを使います: + +bc. # play idealize myApp + +!images/intellij! + +コンテキストメニューから実行やデバッグを行うことができます。 diff --git a/documentation/manual_ja/routes.textile b/documentation/manual_ja/routes.textile new file mode 100644 index 0000000000..0eb6d95459 --- /dev/null +++ b/documentation/manual_ja/routes.textile @@ -0,0 +1,342 @@ +h1. HTTP ルーティング + +Router は、送り込まれた HTTP リクエストをアクション (コントローラの static で public なメソッド) の呼び出しに対応付けるコンポーネントです。 + +HTTP リクエストは MVC フレームワークにおいてイベントと見なされます。このイベントは 2 つの主要な情報を含んでいます: + +* クエリ文字列を含んだ (@/clients/1542@ や @/photos/list@ のような) リクエストパス +* HTTP メソッド (GET, POST, PUT, DELETE) + +h2. REST について + +REST (Representational state transfer) は、World Wide Web などの分散ハイパーメディアシステムのためのソフトウェアアーキテクチャスタイルです。 + +REST が述べるいくつかの主要な設計原理は以下の通りです: + +* アプリケーションの機能はリソースに分割される +* あらゆるリソースは URI を使用することで一意にアドレス付けられる +* すべてのリソースはクライアントとリソースの間で状態を転送するために統一インタフェースを共有する + +HTTP を使用する場合は、インタフェースは利用可能な HTTP メソッドのセットとして定義されます。 リソース状態にアクセスするために使用されるプロトコルは、以下の通りです: + +* クライアント-サーバ +* ステートレス +* キャッシュ可能 +* 階層化 + +アプリケーションが主な REST 設計原理に従うなら、そのアプリケーションは RESTful です。Play フレームワークを使うと、容易に RESTful アプリケーションを構築することができます: + +* Play の router は Java の呼び出しにリクエストを転送するために URI と HTTP メソッドの両方を解釈します。正規表現ベースの URI パターンは高い柔軟性を提供します。 +* プロトコルはステートレスです。これは、2 つの連続したリクエスト間において、どのような状態もサーバに保存できないことを意味します。 +* Play は HTTP を重要な特徴と見なすので、HTTP に含まれる情報への完全なアクセスを提供します。 + +h2. routes ファイルの構文 + +@conf/routes@ ファイルは、Router が使用する設定ファイルです。このファイルはアプリケーションに必要なすべてのルートを記載します。各ルートは Java 呼び出しに関連付けられた HTTP メソッド + URI パターンで構成されます。 + +ルート定義がどのようなものか見てみましょう: + +bc. GET /clients/{id} Clients.show + +各ルートは HTTP メソッドから始まり、URI パターンが続きます。ルーティングの最後の要素は Java 呼び出しの定義です。 + +@#@ 文字でルートファイルにコメントを追加できます。 + +bc. # Display a client +GET /clients/{id} Clients.show + +h3. HTTP メソッド + +HTTPによってサポートされてる有効なメソッドのうち、いずれかひとつをルートの HTTP メソッドに指定することができます: + +* **GET** +* **POST** +* **PUT** +* **DELETE** +* **HEAD** + +Moreover it supports @WS@ as action method to indicate a "WebSocket":asynchronous#UsingWebSockets request. + +HTTP メソッドに * を指定した場合、このルートはどのようなメソッドの HTTP リクエストにもマッチします。 + +bc. * /clients/{id} Clients.show + +このルートは以下のいずれにも適合します: + +bc. GET /clients/1541 +PUT /clients/1212 + + +h3. URI パターン + +URI パターンはルートのリクエストパスを定義します。ルートの一部を動的に定義することが可能です。動的部分はすべて 中括弧 {…} の中に指定しなければいけません。 + +bc. /clients/all + +完全にマッチします: + +bc. /clients/all + +しかし … + +bc. /clients/{id} + +これらについてもそれぞれマッチします: + +bc. /clients/12121 +/clients/toto + +URI パターンにはより多くの動的な部分があるかもしれません: + +bc. /clients/{id}/accounts/{accountId} + +動的部分のデフォルトのマッチング方式は、正規表現 @/[^/]+/@ として定義されています。動的部分の正規表現を独自に定義することができます。 + +この正規表現は id に数値のみを許可します: + +bc. /clients/{<[0-9]+>id} + +これは id が 4~10 文字の小文字だけを含む単語であることを保証します: + +bc. /clients/{<[a-z]{4,10}>id} + +ここではどのような正規表現も使用することができます。 + +p(note). **注意** + +動的部分には名前が付けられます。コントローラは後から HTTP パラメータの Map から動的部分を検索することができます。 + +デフォルトで、Play は URL のあとに続くスラッシュを重要視します。例えば次のようなルートの場合: + +bc. GET /clients Clients.index + +*/clients* という URL にはマッチしますが、 */clients/* にはマッチしません。スラッシュの後にクエスチョンマークを追加することで、Play に両方の URL にマッチするよう伝えることができます。例えば、次のようにします: + +bc. GET /clients/? Clients.index + +p(note). この URI パターンは、最後のスラッシュ以外にはどのような追加部分も持つことはできません。 + +h3. Java の呼び出し定義 + +ルート定義の最後の部分は Java 呼び出しです。この部分はアクションメソッドの完全修飾名で定義されます。アクションメソッドは Controller クラスの @public static void@ なメソッドでなければいけません。Controller クラスは @controllers@ パッケージに定義しなければならず、また、 @play.mvc.Controller@ のサブクラスでなければいけません。 + +コントローラが controllers パッケージの直下に定義されていない場合は、クラス名の前に Java パッケージ名を追記することができます。 @controllers@ パッケージ自体は暗黙的なので、特に指定する必要はありません。 + +bc. GET /admin admin.Dashboard.index + +h3. 404 アクション + +アプリケーションにおいて無視されなければならない URL パスを印付けるために、 @404@ をアクションルートとして直接使用することができます。例えば: + +bc. # Ignore favicon requests +GET /favicon.ico 404 + +h3. 静的引数の割り当て + +ある場合においては、アクションは再利用しても、いくつかの引数の値に基づいてより特別なルートを定義したいことがあります。 + +これがどのようなものか、以下の例を見てみましょう: + +bc. public static void page(String id) { + Page page = Page.findById(id); + render(page); +} + +対応するルートは次のとおりです: + +bc. GET /pages/{id} Application.page + +ここで、このページに 'home' という ID で別名を定義したいと思います。静的引数を使って別のルートを定義することができます: + +bc. GET /home Application.page(id:'home') +GET /pages/{id} Application.page + +最初のルートは、ページの id が 'home' である場合、2 番目のルートと等価です。しかし、最初のルートはより高い優先度を持つので、id が 'home' である Application.page 呼び出しのデフォルト定義として使用されます。 + +h3. 変数とスクリプト + +テンプレートと同様、 @routes@ ファイルにおいても変数に @${ … }@ 構文を、式に @%{ … }@ 構文を使うことができます。例えば: + +bc. %{ context = play.configuration.getProperty('context', '') }% + +# Home page +GET ${context} Secure.login +GET ${context}/ Secure.login + +別の例として、すべての型についてコントローラルート定義を生成するために @crud.types@ タグを使ってモデルの型全体をループする CRUD モジュールの @routes@ ファイルがあります。 + + +h2. ルーティングの優先順位 + +複数のルートが同じリクエストにマッチすることができます。競合がある場合は、(宣言した順番に従い) 最初のルートが使用されます。 + +例えば: + +bc. GET /clients/all Clients.listAll +GET /clients/{id} Clients.show + +これらの定義において、URI が次のとおりである場合: + +bc. /clients/all + +(2 番目のルートもリクエストにマッチするとしても) 最初のルートが適用されて、Clients.listAll をコールします。 + + +h2. 静的リソースの配信 + +h3. staticDir: マッピング + +静的なリソースのコンテナとして公開したい場合、各フォルダを特別なアクション @staticDir@ を使って示してください。 + +例えば: + +bc. GET /public/ staticDir:public + +@/public/*@ パスに対するリクエストがあった場合、Play はアプリケーションの /public フォルダからファイルを配信します。 + +優先度は通常のルートと同じように適用されます。 + +h3. staticFile: マッピング + +URL パスをレンダリングする静的なファイルに直接マッピングすることもできます。 + +bc. # Serve index.html static file for home requests +GET /home staticFile:/public/html/index.html + +h2. URL エンコーディング + +URL をデコードして再度エンコードすることは不可能 (例えば、スラッシュがスラッシュなのか %2F なのかは分かりません) なので、URL はエンコードされて表現されるべきです。UTF-8 がデフォルトのエンコーディングですが、defaultWebEncoding 設定パラメータを使って ISO-8859-1, UTF-16BE, UTF-16LE, UTF-16 のうちひとつを使うことができます。詳細は http://download.oracle.com/javase/1.4.2/docs/api/java/nio/charset/Charset.html を参照してください。 + +例えば: + +# map /stéphane +bc. GET /st%C3%A9phane Application.stephane + +h2. リバースルーティング : URL の生成 + +Java 呼び出し中に URL を生成するのに Router が使用できます。このため、すべての URI をただひとつの設定ファイルに集約することが可能であり、より自信をもってアプリケーションをリファクタリングすることができます。 + +例えば、次のようなルート定義において: + +bc. GET /clients/{id} Clients.show + +Clients.show を起動することができる URL を、コードから生成することができます: + +bc. map.put("id", 1541); +String url = Router.reverse("Clients.show", map).url;// GET /clients/1541 + +p(note). この URL 生成機能はフレームワークの様々なコンポーネントに統合されています。決して Router.reverse を直接使用するべきではありません。 + +URI パターンに含まれていないパラメタを加えると、これらのパラメタはクエリストリングに追加されます: + +bc. map.put("id", 1541); +map.put("display", "full"); +// GET /clients/1541?display=full +String url = Router.reverse("Clients.show", map).url; + +URL を生成するもっとも特別なルートを見つけるため、優先順位が再度使用されます。 + + +h2. content type の設定 + +Play は @request.format@ に従って HTTP レスポンスの "メディアタイプ":http://en.wikipedia.org/wiki/Internet_media_type を選択します。この値は、どの view テンプレートを使用するかをファイル拡張子によって決定し、また Play の @mime-types.properties@ ファイルがこのフォーマットにマッピングするメディアタイプを、レスポンスの @Content-type@ に設定します。 + +Play に対するリクエストのデフォルトフォーマットは @html@ です。そのため、コントローラの @index()@ メソッド (そして @html@ フォーマット) に対するデフォルトのテンプレートは @index.html@ です。もし違うフォーマットを指定する場合、いくつかある方法のひとつとして、代替テンプレートを選択することができます。 + +@render@ メソッドを呼び出す前に、このフォーマットをプログラム上から設定することができます。例えば、 @text/css@ メディアタイプと共に CSS を提供する場合、次のように設定します: + +bc. request.format = "css"; + +一方、 @routes@ ファイルにおいて URL を使ってフォーマットを指定する、よりきれいなアプローチもあります。コントローラのメソッドにフォーマットを指定することで、特定のルートにフォーマットを追加することができます。例えば、以下のルートは @/index.xml@ に対するリクエストをハンドリングし、フォーマットを @xml@ に設定し、 @index.xml@ テンプレートをレンダリングします。 + +bc. GET /index.xml Application.index(format:'xml') + +同様に: + +bc. GET /stylesheets/dynamic_css css.SiteCSS(format:'css') + +Play は、以下のルートのように、URL から直接フォーマットを抽出することもできます。 + +bc. GET /index.{format} Application.index + +このルートでは、 @index.xml@ に対するリクエストには @xml@ がフォーマットに設定されて XML テンプレートがレンダリングされますが、 @index.txt@ に対するリクエストにはプレーンテキストのテンプレートがレンダリングされます。 + +Play は HTTP コンテントネゴシエーションを使って自動的にフォーマットを設定することもできます。 + + +h2. HTTP コンテントネゴシエーション + +Play が他の RESTful アーキテクチャと共通していることのうちのひとつは、HTTP を隠そうとしたり、HTTP の上に抽象層を置こうとしたりせず、HTTP の機能を直接使用するということです。"コンテントネゴシエーション":http://en.wikipedia.org/wiki/Content_negotiation は、HTTP サーバが、HTTP クライアントによりリクエストされたメディアタイプに従って、同じ URL に対して異なる "メディアタイプ":http://en.wikipedia.org/wiki/Internet_media_type を提供できるようにする HTTP の機能です。クライアントは、例えば XML レスポンスを要求する場合、以下のように @Accept@ ヘッダを使って受信できるコンテントタイプを指定します: + +bc. Accept: application/xml + +クライアントはひとつ以上のメディアタイプを指定するかもしれませんし、ワイルドカード (@*/*@) を使用して、どのようなメディアタイプも受信できると指定するかもしれません: + +bc. Accept: application/xml, image/png, */* + +古臭い web ブラウザは、どのようなメディアタイプでも受信しようと @Accept@ ヘッダにいつでもワイルドカードを含めます: そして Play は HTML を - デフォルトの 'フォーマット' として提供します。コンテントネゴシエーションは JSON レスポンスを要求する Ajax リクエストや、PDF や EPUB バージョンのドキュメントを要求する e-book リーダのような、カスタマイズされたクライアントにおいて、より頻繁に利用されるでしょう。 + +h3. HTTP ヘッダによるコンテントタイプの設定 + +@Accept@ ヘッダに @text/html@ か @application/xhtml@ が含まれる場合、または ワイルドカード @*/*@ が指定されている場合、Play はデフォルトのリクエストフォーマットである @html@ を選択します。ワイルドカード値が存在しない場合はデフォルトフォーマットは選択されません。 + +Play はいくつかのフォーマット: @html@, @txt@, @json@ と @xml@ を組み込みでサポートします。例えば、いくつかのデータをレンダリングするコントローラメソッドを定義します: + +bc. public static void index() { + final String name = "Peter Hilton"; + final String organisation = "Lunatech Research"; + final String url = "http://www.lunatech-research.com/"; + render(name, organisation, url); +} + +このメソッドにマッピングされた URL (新しい Play アプリケーションの場合は @http://localhost:9000/@) を web ブラウザからリクエストすると、web ブラウザは値に @text/html@ を含む @Accept@ ヘッダを送信するので、Play は @index.html@ テンプレートをレンダリングします。 + +Play は、リクエストフォーマットに @xml@ を設定し、例えば以下のような @index.xml@ テンプレートをレンダリングすることで、 @Accept: text/xml@ ヘッダを含むリクエストに応答します: + +bc. + +${name} +${organisation} +${url} + + +@index()@ コントローラメソッドに対する組み込みの @Accept@ ヘッダとフォーマットのマッピングは下記のように動作します: accept ヘッダは、Play がやがてはテンプレートファイルに紐付けられることになるフォーマットにマッピングしたメディアタイプを含みます。 + +|_. Accept header |_. Format |_. Template file name |_. Mapping | +| null | null | index.html | null フォーマットに対するデフォルトのテンプレート拡張 | +| image/png | null | index.html | フォーマットにマッピングされないメディアタイプ | +| ==*/*==, image/png | html | index.html | html フォーマットにマッピングされるデフォルトのメディアタイプ | +| text/html | html | index.html | 組み込みフォーマット | +| application/xhtml | html | index.html | 組み込みフォーマット | +| text/xml | xml | index.xml | 組み込みフォーマット | +| application/xml | xml | index.xml | 組み込みフォーマット | +| text/plain | txt | index.txt | 組み込みフォーマット | +| text/javascript | json | index.json | 組み込みフォーマット | +| application/json, ==*/*== | json | index.json | デフォルトのメディアタイプを無視する組み込みフォーマット | + + +h3. カスタムフォーマット + +HTTP リクエストが対応するメディアタイプを選択する場合にのみ、リクエストヘッダを調査し、それに従ってフォーマットを設定し、独自のメディアタイプについてコンテントネゴシエーションを追加することができます。例えば、"vCard":http://en.wikipedia.org/wiki/Vcard を @text/x-vcard@ メディアタイプと共に提供するには、コントローラにて全てのリクエストの前に独自のフォーマッとチェックを行います: + +bc. @Before +static void setFormat() { + if (request.headers.get("accept").value().equals("text/x-vcard")) { + request.format = "vcf"; + } +} + +これで、 @Accept: text/x-vcard@ を含むリクエストは、以下のような @index.vcf@ テンプレートをレンダリングするようになります: + +bc. BEGIN:VCARD +VERSION:3.0 +N:${name} +FN:${name} +ORG:${organisation} +URL:${url} +END:VCARD + + +p(note). **考察を続けます** + +Router が受け取った HTTP リクエストによって起動する Java 呼び出しを決定したら、Play フレームワークはその Java 呼び出しを起動します。 %(next)"コントローラ":controllers% がどのように動作するのかを見てみましょう。 diff --git a/documentation/manual_ja/samples.textile b/documentation/manual_ja/samples.textile new file mode 100644 index 0000000000..3e3881b0e9 --- /dev/null +++ b/documentation/manual_ja/samples.textile @@ -0,0 +1,45 @@ +h1. サンプルアプリケーション + +Play は、フレームワークをインストールしたパスの @samples-and-test/@ ディレクトリ内にある以下のサンプルアプリケーション一式と共に提供されています。私たちは自動化されたテストスイートを動かすためにそれらを使用しますし、あなたは Play の典型的な使い方を見つけるためにそれらを覗いてみることができます。 + +h2. Yet Another Blog Engine + +"Play ガイド":guide1 チュートリアルで作成するアプリケーションの完成形です。 + +!images/yabe! + +h2. Zencontact + +簡単な連絡先管理アプリケーションです。 + +!images/zencontact! + +h2. Booking + +よく知られている JBoss seam フレームワークの 'booking' アプリケーションのポーティングです。 + +!images/booking! + +h2. Forum + +JPA を使用する簡単なフォーラムアプリケーションです。 + +!images/forum! + +h2. Jobboard + +CRUD モジュールを使用して管理画面を自動生成するフル機能のジョブボードアプリケーションです。 + +!images/jobboard! + +h2. Chat + +HTTP ベースのチャットアプリケーションです。リソースを浪費せずに多くの同時接続ユーザを扱うことができるよう、非同期リクエストモデルを使用します。 + +!images/chat! + +h2. Validation + +バリデーションがサポートされたフォームを作成する様々な方法を実演します。 + +!images/validation! \ No newline at end of file diff --git a/documentation/manual_ja/secure.textile b/documentation/manual_ja/secure.textile new file mode 100644 index 0000000000..86f2a56e74 --- /dev/null +++ b/documentation/manual_ja/secure.textile @@ -0,0 +1,148 @@ +h1. Secure モジュール + +このシンプルな **Secure** モジュールは、アプリケーションの基本的な認証および認可管理をセットアップする手助けとなります。このモジュールは、 @With アノテーションを使うことで容易にコントローラに追加することのできるインターセプタ一式を定義する、シンプルな @controllers.Secure@ コントローラを提供します。 + + +h2. モジュールのセットアップ + +h3. アプリケーションで Secure モジュールを利用可能にする + +@/conf/dependencies.yml@ ファイルの @require:@ の後に次の行を追加することで、Secure モジュールを有効にします: + +bc. require: + - play -> secure + +その後、 @play dependencies@ コマンドを実行してモジュールをアプリケーションに追加します。 + + +h3. デフォルトの Secure ルートの取り込み + +@conf/routes@ ファイルに次の行を追加することで、モジュールのデフォルトのルートを取り込みます: + +bc. # Import Secure routes +* / module:secure + +p(note). デフォルトのルートファイルの使用は必須ではないことに **注意** してください。独自のルートを定義することも、これら 2 つを混ぜ合わせることも可能です。 + +h2. コントローラの保護 + +コントローラを保護するために必要なのは、 @With で注釈することだけです。例えば、以下のようにします: + +bc. @With(Secure.class) +public class Application extends Controller { + + public static void index() { + render(); + } +} + +このコントローラは自動的にデフォルトの認証ページで保護されます。 + +h2. 認証メカニズムのカスタマイズ + +デフォルトでは、このログインページはどのような login/password も受け付けます。これをカスタマイズするには、アプリケーションが **Security** プロパイダを提供する必要があります。まずは @controllers.Secure.Security@ クラスを継承するクラスを、 @controllers@ パッケージに作成してください。その後、 @authenticate(String username, String password)@ メソッドをオーバーライドします。 + +bc. package controllers; + +public class Security extends Secure.Security { + + static boolean authenticate(String username, String password) { + User user = User.find("byEmail", username).first(); + return user != null && user.password.equals(password); + } +} + +p(note). アプリケーションが認証イベント (onAuthenticated, onDisconnected) にどのように反応すべきかカスタマイズするために、他のメソッドも同様にオーバーライドできることを **確認** してください。 + +h2. 接続したユーザの検索 + +たった今、作成した Security ヘルパを、接続したユーザを検索するためにアプリケーションコードから再利用することができます。 + +bc. @With(Secure.class) +public class Application extends Controller { + + public static void index() { + String user = Security.connected(); + render(user); + } +} + +h2. 認証チェックの追加 + +h3. コントローラアノテーション + +Secure モジュールに、接続したユーザがこのアクションをコールするために必要な権限を持っているかチェックすることを伝えるために、 @Check アノテーションをコントローラクラスまたはアクションメソッドのどちらにでも使用することができます。 + +例えば、あるアクションメソッド権限のあるユーザに制限するためには、次のようにします: + +bc. @With(Secure.class) +public class Application extends Controller { + … + + @Check("isAdmin") + public static void delete(Long id) { + … + } +} + +ひとつのコントローラにあるすべてのアクションメソッドを制限するためには、コントローラクラスに @Check アノテーションを使用します: + +bc. @With(Secure.class) +@Check("administrator") +public class Application extends Controller { + … + + public static void delete(Long id) { + … + } + + public static void edit(Long id) { + … + } +} + +デフォルトでは、secure モジュールは常に全てのチェックを承認します。作成した @Security@ クラスでひとつ以上のメソッドをオーバーライドして、カスタマイズしなければなりません。 + +bc. package controllers; + +public class Security extends Secure.Security { + … + + static boolean check(String profile) { + User user = User.find("byEmail", connected()).first(); + if ("administrator".equals(profile)) { + return user.admin; + } + else { + return false; + } + } +} + +h3. テンプレートタグ + +テンプレート中で権限をチェックするために @secure.check@ を使うこともできます。これは、コントローラのアノテーションによって保護された操作に対するユーザインタフェースコントロールを、条件付きで表示する場合に便利です。例えば、次のようにします: + +bc. #{secure.check "administrator"} + Delete +#{/secure.check} + +このタグは認証されたユーザにのみ、そのボディをレンダリングするので、‘Delete’ リンクはユーザが @delete@ コントローラアクションの実行を許可された場合にのみ表示されます。 + +h2. コマンド + +Secure モジュールはログインページをオーバーライドするために使用する @play secure:override@ を提供しているので、アプリケーションをカスタマイズすることができます。これは、モジュールからアプリケーションにコピーされた関連するファイルが替わりに使用されることで動作します。 + +@play secure:override@ は @play secure:ov@ と省略することができます。 + +h3. CSS のオーバーライド + +ログインページのルックアンドフィールを編集してカスタマイズできるように、CSS ファイルをアプリケーションにコピーするには、 @play secure:override --css@ コマンドを使用します。 + +h3. ログインテンプレートのオーバーライド + +デフォルトのログインページテンプレートをオーバーライドするように、ログインページのビューテンプレートをアプリケーションにコピーするには、 @play secure:override --login@ コマンドを使用します。 + +h3. メインレイアウトのオーバーライド + +ログインページが使用するレイアウトをオーバーライドするように、メインレイアウトをコピーするには、 @play secure:override --layout@ コマンドを使用します。 \ No newline at end of file diff --git a/documentation/manual_ja/security.textile b/documentation/manual_ja/security.textile new file mode 100644 index 0000000000..1df3aece81 --- /dev/null +++ b/documentation/manual_ja/security.textile @@ -0,0 +1,96 @@ +h1. セキュリティガイド + +Play フレームワークは、セキュリティを念頭に置いて設計されています - しかし、開発者が Play フレームワークを誤って使い、セキュリティホールを開けてしまうことを防ぐのは不可能です。このガイドはウェブアプリケーションにおけるセキュリティ上の一般的な問題と、Play アプリケーション でどのようにしてそれらを避けるかについて説明します。 + +h2. セッション + +ユーザに関連する情報、特にログイン状態を保つ必要があることが、よくあります。セッションがなければ、ユーザは、リクエスト毎に認証照明を送信しなければなりません。 + +セッションは、これら: ユーザのブラウザに保存される、ユーザをウェブサイトで特定するクッキーのセット、ウェブアプリケーションが保存先にデータ層よりもむしろセッションを選ぶかもしれない、例えば言語情報のために存在します。 + +h3. 秘密鍵は… 秘密のまま + +セッションは、署名はされていますが暗号化はされていないキー/値のハッシュです。これは、秘密鍵が安全である限り、第三者がセッションを捏造できないことを意味します。 + +秘密鍵は @conf/application.conf@ に保存されます。これを自分だけのものとして保持することは非常に重要です: 公開されたリポジトリにこれをコミットしてはいけませんし、他の誰かによって書かれたアプリケーションをインストールするときには、秘密鍵を自分自身の物に変更しなければなりません。これは、"play secret" コマンドで行うことができます。 + +h3. 重要なデータは保存しない + +とは言え、セッションは暗号化されていないので、セッションに重要なデータを保存するべきではありません。LAN や Wi-Fi 上の接続を盗聴することで、これらのデータをユーザクッキー上に見ることができます。 + +セッションはクッキーの中に保存され、クッキーは 4KB に制限されます。この制限に加え、保存できるのは文字列のみです。 + +h2. クロスサイトスクリプティング + +クロスサイトスクリプティングはウェブアプリケーションでもっとも一般的な脆弱性の 1 つです。XSS は、アプリケーションが提供するフォームを使用することで悪意ある JavaScript をウェブページに挿し込むことで成立します。 + +誰でもコメントできるブログシステム書いているとしましょう。この HTML ページに訪問者が書き込む内容を盲目的に受け入れる場合、このサイトは攻撃者に対して開かれています。攻撃は以下のように行われます: +* 訪問者にポップアップを表示します +* 攻撃者によって制御されたサイトに訪問者をリダイレクトします +* 現在の訪問者にのみが閲覧できるべきであった情報が盗まれ、攻撃者のサイトに送信されます + +したがって、これらの攻撃からアプリケーションを保護するのは非常に重要です。 + +Play のテンプレートエンジンは自動的に文字列をエスケープします。エスケープしていないHTMLを入れたければ、文字列でのJava拡張 "raw()":javaextensions#raw を使ってください。しかしユーザーの入力した文字列には、最初にサニタイズされているかの確認が必要になります。 + +ユーザ入力をサニタイズするときは、常に (安全でないタグを禁止し、それ以外を許可する) ブラックリスト方式よりも、(安全なタグのみを許可する) ホワイトリスト方式を優先してください。 + +"クロスサイトスクリプティングについてもっと詳しく":http://en.wikipedia.org/wiki/Cross-site_scripting + +h2. SQL インジェクション + +SQL インジェクションは、開発者が意図しない SQL クエリを実行するユーザ入力を使用することで成立する脆弱性です。データを破壊したり、または現在のユーザにとって見えるべきでないデータにアクセスするために使用されます。 + +!http://imgs.xkcd.com/comics/exploits_of_a_mom.png! + +高レベルの "find" メソッドを使用する場合、SQL インジェクションに対応するべきです。手動でクエリを作成する場合、@+@ で文字列を連結するのではなく、 @?@ プレースホルダーを使用するように注意するべきです。 + +これは良いです: + +bc. createQuery("SELECT * from Stuff WHERE type= ?1").setParameter(1, theType); + +これは良くありません: + +bc. createQuery("SELECT * from Stuff WHERE type=" + theType); + +h2. クロスサイトリクエストフォージェリ + +CSRF は、ウェブアプリケーションにおいて真の問題になる場合があります: + +bq. この攻撃手法は、ユーザが認証されていると信じている web アプリケーションにアクセスするページに悪意あるコードかリンクを含めることで実行されます。この web アプリケーションのセッションがタイムアウトされていない場合、攻撃者は認証されていない命令を実行できる場合があります。 + +この攻撃を防ぐために最初にすることは、GET と POST メソッドを適切に使用することです。これは、アプリケーションの状態を変更に使用されるのは POST メソッドだけであるべきということを意味します。 + +POST リクエストをセキュアで重要なアクションとして適切に保証する唯一の方法は、認証トークンを発行することです。現在の Play にはこれを扱う組み込みのヘルパがあります: + +* 新しい @checkAuthenticity()@ メソッドがコントローラで利用可能であり、これはリクエストパラメータ中に有効な認証トークンが存在するかどうかチェックし、何かが正しくない場合は、認証不可のレスポンスを送信します。 +* @session.getAuthenticityToken()@ は、現在のセッションにおいてのみ有効な認証トークンを生成します。 +* @#{authenticityToken/}@ は、どのようなフォームにも加えることができる hidden 入力フィールドを作成します。 + +例えば、以下のようにすると: + +bc. public static destroyMyAccount() { + checkAuthenticity(); + ... +} + +適切な認証トークンを含むフォームから呼ばれた場合にのみ動作します: + +bc.
    + #{authenticityToken /} + +#{/form} + +POST リクエストで, Play の "form タグ":tags#form で信頼性トークンが自動的に生成されます。: + +bc. #{form @destroyMyAccount()} + +#{/form} + +もちろん、コントローラ階層におけるすべてのアクションを保護したい場合は、これを "事前フィルタ":controllers#before をcallする @checkAuthenticity()@ メソッドとして追加することができます。 + +"クロスサイトリクエストフォージェリについてもっと詳しく":http://en.wikipedia.org/wiki/Cross-site_request_forgery + +p(note). **考察を続けます** + +次: %(next)"Play モジュール":modules% \ No newline at end of file diff --git a/documentation/manual_ja/tags.textile b/documentation/manual_ja/tags.textile new file mode 100644 index 0000000000..b12d29ce4b --- /dev/null +++ b/documentation/manual_ja/tags.textile @@ -0,0 +1,557 @@ +h1. 組込みテンプレートタグ + +以下は、中心となる "テンプレートエンジン構文":templates#syntax に従うことで利用できる、組み込みのタグです。 + +p(note). こことは別に %(next)"Java エクステンション":javaextensions% マニュアルページがあります。 + + +h2. a + +@a@ タグはコントローラのアクションへの HTML リンクを挿入します。 + +bc. #{a @Application.logout()}Disconnect#{/a} + +以下のようにレンダリングされます: + +bc. Disconnect + +もし呼び出そうとしているアクションが GET メソッドを使用して起動するどんなルートも持たない場合、Play は自動的に、リンクをクリックしたときに JavaScript を使ってサブミットする隠しフォームを生成します。 + + +h2. authenticityToken + +"クロスサイトリクエストフォージェリ":security#csrf を防ぐため、どのフォームでも使用できる、生成されたトークンを含む hidden input フィールドをレンダリングします。 + +bc. #{authenticityToken /} + +以下のようにレンダリングされます: + +bc. + + +h2. cache + +タグパラメータにキャッシュのキーを指定し、 @play.cache.Cache@ API を使ってタグの内容をキャッシュします。 + +bc. #{cache 'startTime'} + ${new java.util.Date()} +#{/cache} + +デフォルトでは、タグの内容は永久にキャッシュされますが、 @for@ パラメータで有効期間を指定することができます。 + +bc. #{cache 'currentTime', for:'3s'} + ${new java.util.Date()} +#{/cache} + + +h2. doLayout + +このタグは、テンプレート継承と共に使用し、評価された子テンプレートの内容を挿入します。 + +bc. +
    + #{doLayout /} +
    + + + +h2. else + +当然、 @if@ タグと共に使用されます。 + +bc. #{if user} + Connected user is ${user} +#{/if} +#{else} + Please log in +#{/else} + +一方で、 @list@ タグと共に使用することも可能であり、list が空の場合に実行されます。 + +bc. #{list items:task, as:'task'} +
  • ${task}
  • +#{/li} + +#{else} + Nothing to do... +#{/else} + + +h2. elseif + +bc. #{if tasks.size() > 1} + Busy tasklist +#{/if} + +#{elseif tasks} + One task on the list +#{/elseif} + +#{else} + Nothing to do +#{/else} + +@else@ と同様に、 @list@ タグと共に使用することも可能です。 + + +h2. error + +入力エラーが存在する場合に、タグパラメータでフィールドを指定して、エラーメッセージを出力します。 + +bc. #{error 'user.name'/} + +オプションの @field@ パラメータを使って、違うフィールドのエラーメッセージを使用することができます。これは、複数のフィールドで共通のエラーメッセージを共有する場合に便利です。 + +bc. #{error 'contact.street', field:'contact.address'/} +#{error 'contact.city', field:'contact.address'/} +#{error 'contact.country', field:'contact.address'/} + + +h2. errorClass + +タグパラメータで指定したフィールドに入力エラーがある場合に @hasError@ という文字列を出力します。これは、エラーがある入力フィールドに対する CSS クラスを設定する場合に便利です。 + +bc. + +これは、以下と等価です: + +bc. + + +h2. errors + +現在のバリデーションエラーをくり返します。 + +bc.
      +#{errors} +
    • ${error}
    • +#{/errors} +
    + +このタグはボディに暗黙的な変数を定義します。 + +* @error@, エラーです +* @error_index@, エラーの index で、1 から始まります +* @error_isLast@, 最後の要素の場合に true になります +* @error_isFirst@, 最初の要素の場合に true になります +* @error_parity@, @odd@ と @even@ が交互に値になります + +bc. + +#{errors} + +#{/errors} +
    #Error
    ${error_index}${error}
    + +あるフィールドに関連するエラーのみをフィルタリングするためにオプションのフィールドパラメータを使用することも、あるいはデフォルトのパラメータを使用することもできます。 + +bc.
      +#{errors 'myField'} + There where errors with the field myField
      +
    • ${error}
    • +#{/errors} +
    + + +h2. extends + +そのテンプレートに、他のテンプレートを継承させます。 + +bc. #{extends 'main.html' /} + + +h2. field + +field タグは Don't Repeat Yourself の精神に基づいたヘルパです。このタグは次のようにして動作します: + +以下のように書く代わりに: + +bc.

    + + + ${errors.forKey('user.name')} +

    + +以下のように書くことができます: + +bc. #{field 'user.name'} +

    + + + ${field.error} +

    +#{/field} + +何度も何度も @user.name@ をくり返してはいけません。 + + +h2. form + +@form@ タグを挿入します。Play はデフォルトを POST として route ファイルから HTTP メソッドを推測します。URL に対して GET と POST の両方のルートが設定されている場合、このタグは、デフォルトでは定義されたルートのうち最初のルートを使用します。 + +* オプションの @method@ は POST または GET です。 +* オプションの @id@ 属性は form 要素に ID を設定します。 +* オプションの @enctype@ 属性は form のデータエンコーディングを設定します。デフォルトは‘application/x-www-form-urlencoded’です。 + +文字コードは常に **utf-8** です。 + +bc. #{form @Client.details(), method:'GET', id:'detailsForm'} + ... +#{/form} + +は以下のようにレンダリングされます: + +bc. + ... +
    + +アクションメソッドの一部として対象のエンティティを指定することもできます: + +bc. #{form @Client.details(client.id)} + ... +#{/form} + +HTTP パラメータの名前はアクションメソッドに定義した名前から検出されます。 + +bc. public static void details(String clientId){ + // ... +} + +Play は clientId を含むアクション URL を生成します: + +bc.
    + ... +
    + +@form@ タグは GET 以外のメソッドでは "真性性トークン":#authenticityToken を自動的に含める。 + +bc. #{form @Client.create(), method:'POST', id:'creationForm', + enctype:'multipart/form-data' } + ... +#{/form} + +以下のようにレンダリングされます: + +bc.
    + + ... +
    + +もしフォームがサーバサイドのリソースを更新するのであれば、 **POST** メソッドを使用する _べき_ です。もしフォームがデータをフィルタするために使用され、ドメインを更新しないのであれば GET を使用することができます。 "等べき":http://en.wikipedia.org/wiki/Idempotence を参照してください。GET, PUT そして DELETE が等べきである一方、POST は等べきではありません。 + + +h2. get + +"set":#set タグで定義された値を検索します。get/set メカニズムを使うことで、テンプレート間、すなわちレイアウトと子テンプレート間で値をやり取りすることができます。 + +bc. + #{get 'title' /} + + +以下のように、title が指定されていない場合には "Homepage" を表示するような方法で、このタグを使用することもできます。 + +bc. + #{get 'title'}Homepage #{/get} + + +@if@ タグを使って値が設定されているかテストすることができます: + +bc. #{if get('title')} +

    #{get 'title' /}

    +#{/if} + +h2. i18n + +ローカライズされた Javascript メッセージを外部化します。ローカライズされたメッセージは @i18n()@ 関数を使うことで JavaScript から利用することができます。 + +@conf/messages@ ファイルに訳語を定義してください。 + +bc. hello_world=Hello, World ! +hello_someone=Hello %s ! + +テンプレート (またはレイアウト) ページにメッセージを取り込んでください: + +bc. #{i18n /} + +そして JavaScript からキーを使って検索してください: + +bc. alert(i18n('hello_world')); +alert(i18n('hello_someone', 'John')); + +オプションとして、このタグをいくつかのメッセージのみに制限することができます。ワイルドカードは末尾に指定することができます: + +bc. #{i18n keys:['title', 'menu.*'] /} + +h2. if + +指定された条件を評価し、true の場合はタグボディを評価します。 + +bc. #{if user.countryCode == 'en' } + Connected user is ${user} +#{/if} + +条件を組み合わせて使用する場合は、以下のようにします: + +bc. #{if ( request.actionMethod == 'administer' && user.isAdmin() ) } + You are admin, allowed to administer. +#{/if} + + +h2. ifError + +対応する入力項目がエラーである場合、タグの内容がレンダリングされます。 + +bc. #{ifError 'user.name'} +

    + User name is invalid: + #{error 'user.name' /} +

    +#{/ifError} + +h2. ifErrors + +バリデーションエラーが発生している場合、タグの内容がレンダリングされます。 + +bc. #{ifErrors} +

    Error(s) found!

    +#{/ifErrors} + +h2. ifnot + +きれいに #{if !condition} を書き換えます。 + +bc. #{ifnot tasks} + No tasks today +#{/ifnot} + + +h2. include + +別のテンプレートを取り込みます。取り込み側のテンプレート上の全ての変数は、取り込んだ側のテンプレートにて直接利用することができます。 + +bc.
    + #{include 'tree.html' /} +
    + + +h2. jsAction + +この @#{jsAction /}@ タグは、サーバアクションに基づいた URL と自由な変数から成る JavaScript 関数を返却します。この関数では AJAX リクエストを行わないので、返却された URL を使って手作業で Ajax リクエストを実行する必要があります。 + +例を見てみましょう: + +bc. GET /users/{id} Users.show + +クライアント側でこのルートをインポートすることができます: + +bc. + + +h2. jsRoute + +@#{jsRoute /}@ タグは @#{jsAction /}@ タグに似ていて,サーバーアクションおよび対応するHTTPメソッド(GET, POST など)に基づくURLを構築する両方の関数を含むオブジェクトを返します。 + +Example: + +bc. PUT /users/{id} Users.update + +Then, in a template: + +bc. + + +h2. list + +オブジェクトのコレクションをくり返します。 + +bc.
      +#{list items:products, as:'product'} +
    • ${product}
    • +#{/list} +
    + +このタグは、ボディに暗黙的な変数を定義します。変数名には、接頭辞としてループ変数名が付きます。 + +* @name_index@, アイテムの index で、1 から始まります +* @name_isLast@, 最後の要素の場合に true になります +* @name_isFirst@, 最初の要素の場合に true になります +* @name_parity@, @odd@ と @even@ が交互に値になります + +bc.
      +#{list items:products, as:'product'} + ${product_index}. ${product} + ${product_isLast ? '' : '-'} +#{/list} +
    + +@items@ パラメータはオプションであり、デフォルトの @arg@ 引数で置き換えることが可能です。 + +このため: + +bc. #{list items:users, as:'user'} +
  • ${user}
  • +#{/list} + +を、以下のように書き換えることができます: + +bc. #{list users, as:'user'} +
  • ${user}
  • +#{/list} + +Groovy の **range** オブジェクトを使うことで、容易に **for** ループを作ることができます: + +bc. #{list items:0..10, as:'i'} + ${i} +#{/list} + +bc. #{list items:'a'..'z', as:'letter'} + ${letter} ${letter_isLast ? '' : '|' } +#{/list} + +@as@ パラメータもまたオプションです。デフォルト変数名として @_@ を使用します: + +bc. #{list users} +
  • ${_}
  • +#{/list} + + +h2. option + +テンプレートに @option@ タグを挿入します。 + +* @value@ - option の値 + +bc. #{option user.id} ${user.name} #{/option} + +これは、以下のように出力されるでしょう: + +bc. + +h2. script + +テンプレートに @script@ タグを挿入します。利便性のため、このタグは @/public/javascripts@ 内のスクリプトを参照します。 + +* @src@ (必須) - @/public/javascripts@ パスを含まないスクリプトファイル名 +* @id@ (オプション) - 生成された @script@ タグの @id@ 属性の値 +* @charset@ (オプション) - ソースのエンコーディングを設定します - デフォルトは UTF-8 です + +@src@ パラメータはデフォルトの @arg@ 引数に置き換えることもできます。 + +bc. #{script 'jquery-1.4.2.min.js' /} +#{script id:'datepicker' , src:'ui/ui.datepicker.js', charset:'utf-8' /} + + +h2. render + +タグパラメータでパスを指定してテンプレートをレンダリングします。パスは絶対パスか、または @/app/views@ に対する相対パスです。 + +bc. #{render 'Application/other.html'/} + + +h2. select + +テンプレートに @select@ タグを挿入します。 + +* @name@ (必須) - select 要素に name 属性を設定する + +不明な属性はすべて HTML 要素として取り扱われ、"そのまま" レンダリングされます。 + +bc. #{select 'booking.beds', value:2, id:'select1'} + #{option 1}One king-size bed#{/option} + #{option 2}Two double beds#{/option} + #{option 3}Three beds#{/option} +#{/select} + +これは、以下のように出力されるでしょう: + +bc. + + +このタグは @items@ 属性を使って options を生成することもできます。 + +* @items@ (オプション) - @options@ を作成するために使われるオブジェクトのリスト +* @value@ (オプション) - @items@ の中の選択された要素(注意: 複数選択はサポートしません) +* @labelProperty@ (オプション) - それぞれのアイテムについて option のラベルとして使用される属性 +* @valueProperty@ (オプション) - それぞれのアイテムについて option の値として使用される属性。デフォルトでは @id@ が使用される + +p(note). @labelProperty@ と @valueProperty@ はプリミティブ値であってはなりません。そのため、例えば @int@ や @long@ を使わずに、 @Integer@ や @Long@ 変数を使ってください。 + +例えば、それぞれが name 属性を持つ User のリストを与えられた場合: + +bc. #{select 'users', items:users, valueProperty:'id', labelProperty:'name', value:5, class:'test', id:'select2' /} + +これは、以下のように出力されるでしょう: + +bc. + +h2. set + +同じテンプレートかレイアウトにおいて @get@ タグを使って検索できる値を定義します。 + +bc. #{set title:'Admin' /} +#{set style:'2columns' /} + +変数を使用することも可能です: + +bc. #{set title:'Profile of ' + user.login /} + +変数の値をボディ内で定義することも可能です: + +bc. #{set 'title'} + Profile of ${user.login} +#{/set} + + +h2. stylesheet + +テンプレートに @link@ タグを挿入します。利便性のため、このタグは @/public/stylesheets@ 内の CSS ファイルを参照します。 + +* @src@ (必須) - @/public/stylesheets@ パスを含まないファイル名 +* @id@ (オプション) - 生成された @link@ タグの @id@ 属性の値 +* @media@ (オプション) - @media@ 属性の値: screen, print, aural, projection… +* @title@ (オプション) - @title@ 属性の値 (または説明) + +@src@ パラメータはデフォルトの @arg@ 引数に置き換えることもできます。 + +bc. #{stylesheet 'default.css' /} +#{stylesheet id:'main', media:'print', src:'print.css', title:'Print stylesheet' /} + + +h2. verbatim + +Java 拡張の "raw()":javaextensions#arawa のように、しかしタグボティ全体のテンプレート出力における HTML エスケープを無効にします。 + +bc. ${'&'} +#{verbatim}${'&'}#{/verbatim} + +この例の場合、最初の行は @&@ を出力しますが、二番目の行はアンパサンドを出力します。 + diff --git a/documentation/manual_ja/templates.textile b/documentation/manual_ja/templates.textile new file mode 100644 index 0000000000..fd3b2b0ebc --- /dev/null +++ b/documentation/manual_ja/templates.textile @@ -0,0 +1,374 @@ +h1. テンプレートエンジン + +Play には、HTML、XML、JSON、その他あらゆるテキストベースのドキュメントを動的に生成する効率的なテプレートシステムがあります。テンプレートエンジンは式言語として "Groovy":http://groovy.codehaus.org/ を使用します。タグシステムによって、再利用可能な機能を作成することができます。 + +テンプレートは @app/views@ ディレクトリに保存されます。 + +h2. テンプレートの構文 + +テンプレートファイルは、一部に動的に内容を生成するためのプレースホルダを持つテキストファイルです。テンプレートの動的な要素は、 "Groovy":http://groovy.codehaus.org/ 言語を使用して書かれます。Groovy の構文は Java のそれにとてもよく似ています。 + +動的な要素はテンプレートの実行中に解決されます。レンダリング結果は、HTTP レスポンスの一部として送信されます。 + +h3. Expressions: ${…} + +動的な要素を作る最もシンプルな方法は、式を宣言することです。ここで使用する構文は @${…}@ です。この式の評価結果は、この式部分に挿入されます。 + +例えば、以下のようにします: + +bc.

    Client ${client.name}

    + +client が null でないと確信できない場合には、Groovy のショートカットがあります: + +bc.

    Client ${client?.name}

    + +これは client が null でない場合にのみ、client の name を表示します。 + +h3. テンプレートデコレータ : #{extends /} と #{doLayout /} + +デコレータは複数テンプレートに渡ってページのレイアウト (またはデザイン) を共有するきれいなソリューションを提供します。 + +p(note). テンプレートとデコレータの間で変数を共有するには、 "#{get}":tags#ageta および "#{set}":tags#aseta タグを使用してください。 + +デコレータにページを埋め込むことは一行で行えます。 + +bc. #{extends 'simpledesign.html' /} + +#{set title:'A decorated page' /} +This content will be decorated. + +デコレータ : @simpledesign.html@ + +bc. + + #{get 'title' /} + + + +

    #{get 'title' /}

    + #{doLayout /} + + + + +h3. タグ: #{tagName /} + +タグは、パラメータと共に呼ぶことができるテンプレートの断片です。タグに 1 つのパラメータしかない場合、規約によりそのパラメータは "arg" と呼ばれ、省略することができます。 + +例えば、このタグは JavaScript ファイルをロードするための SCRIPT タグを挿入します: + +bc. #{script 'jquery.js' /} + +タグは直接閉じるか、終了タグによって閉じられなければなりません: + +bc. #{script 'jquery.js' /} + +または、以下のようにします。 + +bc. #{script 'jquery.js'}#{/script} + +例えば、 @list@ タグはどんなコレクションでもくり返すことができます。list タグは 2 つの必須パラメータを受け取ります: + +bc.

    Client ${client.name}

    +
      + #{list items:client.accounts, as:'account' } +
    • ${account}
    • + #{/list} +
    + +すべての動的な式は、アプリケーションの XSS セキュリティ問題を回避するために、テンプレートエンジンによってエスケープされます。そのため ==<h1>Title</h1>== を含む @titile@ 変数はエスケープされてしまいます: + +bc. ${title} --> <h1>Title</h1> + +エスケープせずに表示したい場合は、明示的に @raw()@ メソッドをコールする必要があります: + +bc. ${title.raw()} -->

    Title

    + +素の HTML のかなりの部分を表示したい場合は、 @#{verbatim /}@ タグを使用することができます: + +bc. #{verbatim} + ${title} -->

    Title

    +#{/verbatim} + +h3. アクション: @{…} または @@{…} + +Router を使用することで、指定されたルートに対応する URL を (リバースして) 生成することができます。特別な @{…} 構文を使用して、テンプレートからこれを行うことができます。 + +例えば、以下のようにします: + +bc.

    Client ${client.name}

    +

    + All accounts +

    +
    +Back + +@@{…} 構文は、(とりわけ email, … に便利な) 絶対 URL を生成する点以外は同じです。 + +h3. メッセージ: &{…} + +アプリケーションを国際化する必要がある場合は、 @&{…}@ 構文を使って国際化されたメッセージを表示することができます: + +例えば @conf/messages@ ファイルに次のように指定した場合: + +bc. clientName=The client name is %s + +このメッセージをテンプレートに表示する使い方はシンプルです: + +bc.

    &{'clientName', client.name}

    + +h3. コメント: ==*{…}*== + +コメントはテンプレートエンジンによって評価されません。コメントはただのコメントです… + +bc. *{**** Display the user name ****}* +
    + ${user.name} +
    + +h3. スクリプト: ==%{…}%== + +スクリプトは、より複雑な式のセットです。スクリプトは、いくつかの変数を宣言して、いくつかの命令を定義できます。 @%{…}%@ 構文を使用してスクリプトを挿入してください。 + +bc. %{ + fullName = client.name.toUpperCase()+' '+client.forname; +}% + +

    Client ${fullName}

    + +スクリプトは、 @out@ オブジェクトを使用して、動的な内容を直接出力することができます: + +bc. %{ + fullName = client.name.toUpperCase()+' '+client.forname; + out.print('

    '+fullName+'

    '); +}% + +テンプレート内においてくり返しなどの構造を作成するためにスクリプトを使用することができます: + +bc.

    Client ${client.name}

    +
      +%{ + for(account in client.accounts) { +}% +
    • ${account}
    • +%{ + } +}% +
    + +テンプレートが複雑なことをする場所でないことを覚えておいてください。タグを使えるところではタグを使い、そうでない場合は計算処理をコントローラかモデルオブジェクトに移動してください。 + +h2. テンプレートの継承 + +テンプレートは別のテンプレートを引き継ぐこと、すなわち、他のテンプレートの一部として取り込まれることができます。 + +別のテンプレートを継承するには @extends@ タグを使用します: + +bc. #{extends 'main.html' /} + +

    Some code

    + +@main.html@ テンプレートは標準のテンプレートですが、内容を取り込むために @doLayout@ タグを使用します: + +bc.

    Main template

    + +
    + #{doLayout /} +
    + + +h2. カスタムテンプレートタグ + +アプリケーションのための特別なタグを容易に作成することができます。タグは、 @app/views/tags@ ディレクトリに格納されるシンプルなテンプレートファイルです。テンプレートのファイル名はタグ名として使用されます。このテンプレートファイル名はタグの名前として使用されます。 + +@hello@ タグを作成するためには、単に @app/views/tags/hello.html@ ファイルを作成してください。 + +bc. Hello from tag! + +設定は一切必要ありません。このタグを直接使用することができます。 + +bc. #{hello /} + +h3. タグパラメータの取得 + +タグパラメータは、テンプレートの変数として表現されます。変数名は ‘_’ 文字をパラメータ名の前に付けた状態で構成されます。 + +例えば、以下のようにします: + +bc. Hello ${_name} ! + +そして、この name パラメータをタグに渡すことができます: + +bc. #{hello name:'Bob' /} + +タグにパラメータが 1 つしかない場合、暗黙的な名前である @arg@ というデフォルトパラメータ名を使うことができます。 + +例えば: + +bc. Hello ${_arg}! + +次のようにして、これを容易に使用することができます: + +bc. #{hello 'Bob' /} + +h3. タグボディの実行 + +タグが @body@ をサポートする場合、 @doBody@ タグを使用することでタグコードの任意の位置にボディの内容を取り込むことができます。 + +例えば、以下のようにします: + +bc. Hello #{doBody /}! + +そして、タグボディをこの名前として渡すことができます: + +bc. #{hello} + Bob +#{/hello} + +h3. フォーマット特有のタグ + +異なる "content types":routes#content-types に違うバージョンのタグを指定することができ、Play は適切なタグを選択します。例えば、Play は @request.format@ が @html@ の場合は @app/views/tags/hello.html@ を、フォーマットが @xml@ の場合は @app/views/tags/hello.xml@ を選択します。 + +content type が何であれ、フォーマット特有のタグが利用できない場合、Play は例えば @app/views/tags/hello.tag@ のような 拡張子 @.tag@ にフォールバックします。 + + +h2. カスタム Java タグ + +Java コードでカスタムタグを定義することも可能です。Java エクステンションが @play.templates.JavaExtensions@ クラスを継承して動作するのと同じように、FastTag を作るためには @play.templates.FastTags@ を継承するクラスにメソッドを作成する必要があります。タグとして実行するいずれのメソッドも、以下のメソッドシグネチャに従わなければなりません。 + +bc. public static void _tagName(Map args, Closure body, PrintWriter out, + ExecutableTemplate template, int fromLine) + +p(note). タグ名の前のアンダースコアに注意してください。 + +実際のタグの作り方を理解するために、二つの組み込みタグを見てみましょう。 + +例えば、 @verbatim@ タグは単純にタグボディを引き渡し、 Java エクステンション上で toString を呼び出す一行のメソッドとして実装されています。 + +bc. public static void _verbatim(Map args, Closure body, PrintWriter out, + ExecutableTemplate template, int fromLine) { + + out.println(JavaExtensions.toString(body)); +} + +タグボディは開きタグと閉じタグの間にあるであろう何かなので + +bc. My verbatim + +ボディの値は以下のようになります。 + +bc. My verbatim + +二つ目の例は、その機能が親タグに依存するため、やや複雑な @option@ タグです。 + +bc. public static void _option(Map args, Closure body, PrintWriter out, + ExecutableTemplate template, int fromLine) { + + Object value = args.get("arg"); + Object selection = TagContext.parent("select").data.get("selected"); + boolean selected = selection != null && value != null + && selection.equals(value); + + out.print(""); +} + +このコードは HTML の @option@ タグを出力し、親タグでどの値が選択されたかを確認して selected 値を設定します。最初の三行は出力に使う変数を設定します。その後、最後の三行でこのタグの結果を出力します。 + +組み込みタグのソースコードに、様々な度合いの複雑さを持った多くのサンプルがあります。"github の FastTags.java":https://github.com/playframework/play/blob/master/framework/src/play/templates/FastTags.java を参照してください。 + +h3. タグの名前空間 + +タグが、プロジェクト間、または Play の中心的なタグと衝突しないことを保証するために、クラスレベルのアノテーション @FastTags.Namespace を使って名前空間を設定することができます。 + +以下のようにして @hello@ タグの名前空間を @my.tags@ にします。 + +bc. @FastTags.Namespace("my.tags") +public class MyFastTag extends FastTags { + public static void _hello (Map args, Closure body, PrintWriter out, + ExecutableTemplate template, int fromLine) { + ... + } +} + +そして、テンプレートから以下のようにして hello タグを参照します。 + +bc. #{my.tags.hello/} + + + +h2. テンプレートにおける Java オブジェクトの拡張 + +テンプレートエンジン内において Java オブジェクトを使用すると、新しいメソッドが追加されます。これらのメソッドは、元の Java クラスには存在しておらず、テンプレートエンジンによって動的に追加されます。 + +例えば、テンプレートにおいて数値を簡単にフォーマットするために、 @java.lang.Number@ に @format@ メソッドが追加されます。 + +数値をフォーマットするのは非常に簡単です: + +bc.
      +#{list items:products, as:'product'} +
    • ${product.name}. Price: ${product.price.format('## ###,00')} €
    • +#{/list} +
    + +@java.util.Date@ にも同様に適用することができます。 + +型ごとに利用できるメソッドの一覧は "API ドキュメント":http://www.playframework.org/@api/play/templates/JavaExtensions.html で見つけることができます。 + + +h3. 独自拡張の作成 + +プロジェクトが、特別なフォーマットを必要とする場合、独自の拡張を提供することができます。 + +必要なのは @play.templates.JavaExtensions@ を拡張する Java クラスを作成することだけです。 + +例えば、数値にカスタム通貨フォーマットを提供するためには、以下のようにします: + +bc. package ext; + +import play.templates.JavaExtensions; + +public class CurrencyExtensions extends JavaExtensions { + + public static String ccyAmount(Number number, String currencySymbol) { + String format = "'"+currencySymbol + "'#####.##"; + return new DecimalFormat(format).format(number); + } + +} + +拡張メソッドは static なメソッドであり、ページに書き戻すために @java.lang.String@ を返すべきです。最初のパラメータは拡張されるオブジェクトを保持します。 + +このフォーマットは以下のようにして使います: + +bc. Price: ${123456.324234.ccyAmount("€")} + +テンプレート拡張クラスは Play によって起動時に自動的に検出されます。それらを利用可能にするために必要なのは、アプリケーションを再起動することだけです。 + +h2. テンプレートで利用可能な暗黙オブジェクト + +@renderArgs@ スコープに追加されたすべてのオブジェクトは、テンプレート変数として直接注入されます。 + +例えば、コントローラからテンプレートに ‘user’ Bean を注入するためには、以下のようにします: + +bc. renderArgs.put("user", user ); + +アクションからテンプレートをレンダリングするとき、フレームワークは以下の暗黙オブジェクトも追加します: + +|| 変数 || 説明 || API ドキュメント || 参考 || +| @errors@ | バリデーションエラー | "play.data.validation.Validation.errors()":/@api/play/data/validation/Validation.html#errors%28%29 | "HTTP フォームデータのバリデーション":validation | +| @flash@ | フラッシュスコープ | "play.mvc.Scope.Flash":/@api/play/mvc/Scope.Flash.html | "コントローラ - セッションとフラッシュのスコープ":controllers#session | +| @lang@ | 現在の言語 | "play.i18n.Lang":/@api/play/i18n/Lang.html | "I18N の設定 - 言語の定義":i18n#languages | +| @messages@ | メッセージのマップ | "play.i18n.Messages":/@api/play/i18n/Messages.html | "I18N の設定 - メッセージの外部化":i18n#messages | +| @out@ | 出力ストリーム | java.io.PrintWriter | | +| @params@ | 現在のパラメータ | "play.mvc.Scope.Params":/@api/play/mvc/Scope.Params.html | "コントローラ - HTTP パラメータ":controllers#params | +| @play@ | フレームワークの中心クラス | "play.Play":/@api/play/Play.html | | +| @request@ | 現在の HTTP リクエスト | "play.mvc.Http.Request":/@api/play/mvc/Http.Request.html | | +| @session@ | セッションスコープ | "play.mvc.Scope.Session":/@api/play/mvc/Scope.Session.html | "コントローラ - セッションとフラッシュのスコープ":controllers#session | + +上記のリストに加えて @owner@, @delegate@ そして @it@ は Groovy で予約されており、テンプレート内の変数名として使うべきではありません。 + +p(note). 次: %(next)"フォームデータのバリデーション":validation% \ No newline at end of file diff --git a/documentation/manual_ja/test.textile b/documentation/manual_ja/test.textile new file mode 100644 index 0000000000..d2243a23a7 --- /dev/null +++ b/documentation/manual_ja/test.textile @@ -0,0 +1,231 @@ +h1. アプリケーションのテスト + +自動実行できるテストスイートを作成するのは、アプリケーションを堅牢にする良い方法です。自動テストスイートを作成することで、アジャイルなやり方で作業することができます。 + +Play のテストは、何をテストするかによって "Junit 4":http://www.junit.org/ または "Selenium":http://seleniumhq.org/ を使って作成されます。 + +h2. テストの記述 + +テストは @test/@ ディレクトリに作成されなければなりません。アプリケーションが @test@ モードで実行するときにだけ、このフォルダはソースパスに加えられます。テストは、異なる 3 種類の方法で書くことができます。 + +h3. 単体テスト + +単体テストは JUnit を使用して書かれます。このテストでは、(いくつかのユーティリティを含む) アプリケーションのモデルをテストすることができます。 + +単体テストの例を以下に示します: + +bc. import play.test.*; +import org.junit.*; + +public class MyTest extends UnitTest { + + @Test + public void aTest() { + assertEquals(2, 1 + 1); // A really important thing to test + } + + @Test + public void testUsers() { + assertEquals(3, Users.count()); + } +} + +h3. 機能テスト + +機能テストは JUnit を使用して書かれます。このテストでは、直接コントローラオブジェクトにアクセスすることでアプリケーションをテストします。 + +機能テストの例を以下に示します: + +bc. import play.test.*; +import play.mvc.*; +import play.mvc.Http.*; +import org.junit.*; + +public class ApplicationTest extends FunctionalTest { + + @Test + public void testTheHomePage() { + Response response = GET("/"); + assertStatus(200, response); + } + +} + +レスポンスそのものに関するアサーションを作成する代わりに、 @renderArgs()@ メソッドを使うことでビューに渡された引数に直接アクセスすることができます。例えば: + +bc. @Test +public void testUserIsFoundAndPassedToView() { + Response response = POST("/user/find?name=mark&dob=18011977") + assertThat(renderArgs("user"), is(notNullValue()); + User user = (User) renderArgs("user"); + assertThat(user.name, is("mark")); +} + +h3. Selenium テスト + +受入テストは Selenium を使用して書かれます。このテストでは、自動化されたブラウザでこれを実行することでアプリケーションをテストします。 + +Selenium テストは HTML テーブルを使用して書かれます。従来のこの構文を使用するか、または @#{selenium /}@ タグを使用することができます。 + +Selenium テストの例を以下に示します: + +bc. #{selenium 'Test security'} + + // Try to log in the administration area + clearSession() + open('/admin') + assertTextPresent('Login') + type('login', 'admin') + type('password', 'secret') + clickAndWait('signin') + + // Verify that the user in correctly logged in + assertText('success', 'Welcom admin!') + +#{/selenium} + +Slenium テストはブラウザを通して実行されるので、モックで送信された e メールや、Play Cache に格納された文字列にアクセスするためには、Selenium エクステンションを使用しなければなりません。 + +あるアカウントに送信された最近の e メールにアクセスする例を以下に示します: + +bc. #{selenium 'Test email sending'} + + // Open email form and send an email to boron@localhost + open('/sendEmail') + assertTextPresent('Email form') + type('To', 'boron@localhost') + type('Subject', 'Berillium Subject') + clickAndWait('send') + + // Extract the last email sent to boron@localhost into a JavaScript + // variable called email + storeLastReceivedEmailBy('boron@localhost', 'email') + // Extract the subject line from the email variable into a variable + // called subject + store('javascript{/Subject:\s+(.*)/.exec(storedVars["email"])[1]}', 'subject') + // Test the contents of the subject variable + assertEquals('Berillium Subject', '$[subject]') + +#{/selenium} + +Play Cache に格納された文字列 (例えば、キャプチャに対する正しい答え) にアクセスする例を以下に示します: + +bc. #{selenium 'Get string from cache'} + + open('/register') + assertTextPresent('Registration form') + type('Email', 'my@email.com') + type('Password', 'secretpass') + type('Password2', 'secretpass') + // .. Fill in the registration form .. + + // Get the value of the magicKey variable from the cache + // (set to the CAPTCHA answer in the application) + storeCacheEntry('magicKey', 'captchaAnswer') + // Type it into the form + type('Answer', '$[captchaAnswer]') + + clickAndWait('register') + +#{/selenium} + +h2. フィクスチャ + +テストを実行するとき、アプリケーションのための安定したデータを必要とします。最も簡単な方法は、各テストの前にデータベースをリセットすることです。 + +@play.test.Fixtures@ クラスは、データベースを操作して、テストデータを注入する手助けをします。通常、これは JUnit テストの @Before メソッドで使用します。 + +bc. @Before +public void setUp() { + Fixtures.deleteDatabase(); +} + +データをインポートするには、Fixtures が自動的にインポートすることができる YAML ファイルに定義するのが簡単です。 + +bc. # Test data + +Company(google): + name: Google + +Company(zen): + name: Zenexity + +User(guillaume): + name: guillaume + company: zen + +その後、以下のようにします: + +bc. @Before +public void setUp() { + Fixtures.deleteDatabase(); + Fixtures.loadModels("data.yml"); +} + +p(note). "YAML マニュアルページ":yaml で Play と YAML について詳しく読むことができます。 + +Selenium テストのために @#{fixture /}@ タグを使用することができます: + +bc. #{fixture delete:'all', load:'data.yml' /} + +#{selenium} + + // Write your test here + +#{/selenium} + +データをいくつかの YAML ファイルに分割すると便利な場合があります。複数のファイルかた一度にフィクスチャをロードすることができます: + +bc. Fixtures.loadModels("users.yml", "roles.yml", "permissions.yml"); + +Selenium テストは以下のようになります: + +bc. #{fixture delete:'all', load:['users.yml', 'roles.yml', 'permissions.yml'] /} + +h2. テストの実行 + +テストを実行するには、 @play test@ コマンドを使ってアプリケーションを @test@ モードで実行しなければなりません。 + +bc. # play test myApp + +このモードでは、Play は自動的に @test-runner@ モジュールをロードします。このモジュールは "http://localhost:9000/@tests ":http://localhost:9000/@tests という URL で利用可能な Web ベースのテストランナーを提供します。 + +!images/test-runner! + +テストを実行すると、その結果はアプリケーションの @/test-result@ ディレクトリに保存されます。 + +テストランナーページでは、各テストはリンクになっています。‘右クリック’ して ‘新しいタブで開き’ 、テストランナーの外で直接実行することができます。 + +この方法でテストを実行する場合、Play は特別なフレームワーク ID @test@ で始動します。このため、 @application.conf@ ファイルで特別な設定を定義することができます。 + +いくつかの異なるテスト設定が必要な場合、パターン @test-?.*@ にマッチするフレームワーク ID (例: 'test-special') を使用することができます。 + +デフォルトの @test@ とは違うフレームワーク ID を使用する場合は、@application.conf@ にあるすべてのテスト用設定が、そのフレームワーク ID で利用可能であることをしっかりと確認しなければなりません。特別なテストフレームワーク ID でテストを起動するときは、このようにします: 'play test --%test-your-special-id' + +例えば + +bc. %test.db=mem +%test.jpa.ddl=create-drop + +h2. 継続的統合と自動的なテストの実行 + +@auto-test@ コマンドは @test@ コマンドと同じことをしますが、こちらは自動的にブラウザを起動し、すべてのテストを実行し、そして停止します。 + +継続的統合システムを構築する場合、これは便利なコマンドです; + +実行後、すべての結果は @/test-result@ ディレクトリに保存されます。さらに、このディレクトリにはテストスイートの最終的な結果を示すマーカーファイル ( @result.failed@ または @result.passed@ のどちらか) を含んでいます。最終的に、このディレクトリは @application.log@ ファイルにすべてのログを含みます。 + +このためアプリケーションをテストするための継続的統合システムシステムの構築手順は、以下のようになるかもしれません: + +* 最新版のアプリケーションをチェックアウトする +* @play auto-test@ を実行する +* プロセスの完了を待つ +* @/test-result@ ディレクトリ内のマーカファイル @result.passed@ または @result.failed@ を確認する + +これらの手順が CRON タブで実行されれば完了です! + +"headlessBrowser":configuration#headlessBrowser を設定することで、ヘッドレスブラウザによって利用される web ブラウザ互換性を変更することができます。 + +p(note). **考察を続けます** + +Next: %(next)"セキュリティガイド":security%. \ No newline at end of file diff --git a/documentation/manual_ja/usability.textile b/documentation/manual_ja/usability.textile new file mode 100644 index 0000000000..c81a6f4ae5 --- /dev/null +++ b/documentation/manual_ja/usability.textile @@ -0,0 +1,52 @@ +h1. 利便性 - 細部は機能と同様に重要 + +おそらく、Play フレームワークに関するもっとも衝撃的な事実は、他の Java web アプリケーション開発フレームワークに対する最大の利点がきちんとした機能リストに当てはまらないということであり、Play フレームワークを使って何かを作って初めてその最大の利点が明らかになるということです。その利点とは、利便性です。 + +利便性は機能と切り離されることに注意してください。以下において、他のフレームワークではこれを達成できないと示唆するわけではありません: ただ、Play においては、より簡単に、快適に達成できると主張するだけです。ギークたちは、難解な事柄を解決することを楽しみ、ただ動くだけのものの価値を正当に評価せず、しばしば利便性について完全に盲目的になってしまうので、このことを強調する必要があります。 + +h2. web デベロッパによる web デベロッパのためのフレームワーク + +Play フレームワークが 'web デベロッパによる web デベロッパのためのフレームワーク' であること、web の原則を優先して Java のそれを二の次にするという型破りな立ち位置にあることを初めて耳にしたとき、それはここから始まる何か変わったことの最初のヒントになります。特に、Play フレームワークは Java エンタープライズエディション (Java EE) の規約と共にあることよりも、W3C の "World Wide Web アーキテクチャ":http://www.w3.org/TR/webarch/ に沿うことを優先します。 + +h2. 完全主義者のための URL + +例えば他のモダンな web フレームワークのように、Play フレームワークは Servlet API には常に欠けている任意の '清潔な' URL のためのファーストクラスサポートを提供します。これを書いている時点 (2010 年 3 月) で "完全主義者のための Struts URL":http://www.lunatech-research.com/archives/2005/07/29/struts-urls という Servlet API ベースである Struts 1.x web フレームワークのための回避策一式が、前世代 Java web テクノロジーについての 2005 年の記事であるにも関わらず、 "www.lunatech-research.com":http://www.lunatech-research.com にある 160 の記事から人気記事トップ 3 に残り続けているのは偶然ではありません。 + +Servlet ベースのフレームワークにおいて、Servlet API は有用な URL ルーティングサポートを提供しません; Servlet ベースのフレームワークは、全てのリクエストをひとつのコントローラ Servlet へ転送するよう @web.xml@ を構成し、さらなる追加設定と共に、フレームワーク内に URL ルーティングをじっそうします。この段階では、Servlet API が充分に強力でなかったために URL ルーティング問題を解決しようとして失敗してきたことや、web アプリケーションを直接作らなくて済むような低レベル API を目指してきたことは重要ではありません。いずれにせよ、結果は同じです: web フレームワークはそれ自身が HTTP の上のレイヤであり、Servlet API の上にレイヤを追加します。 + +Play は web フレームワーク、HTTP API そして HTTP サーバを結合し、同じことをより少ないレイヤと単一の URL ルーティング構成行えるようにします。Grails や Cake PHP のようなこの構成は、HTTP リクエスト - HTTP メソッド、URL パスを反映し、マッピングを行います: + +bc. # Play 'routes' configuration file… + +# Method URL path Controller + +GET / Application.index +GET /about Application.about +POST /item Item.addItem +GET /item/{id} Item.getItem +GET /item/{id}.pdf Item.getItemPdf + +この例には、ひとつ以上のコントローラがあります。最後のふたつの URL において @id@ という URL パラメータを使っていることも確認できます。 + +h2. より良いユーザビリティは普通の人々だけのためのものではありません + +Play が web デベロッパによる web デベロッパのためのフレームワークであるという考えのもうひとつの見方は、web デベロッパのソフトウェアデザインに対するアプローチが Java EE デベロッパとどのように異なり得るのかを考えることです。ソフトウェアを書くとき、なにを主要なインタフェースとするでしょうか? web デベロッパの場合、主要なインタフェースは、HTML, CSS そして (どんどん増えている) JavaScript によって構築された web ベースのユーザインタフェースです。一方、Java EE デベロッパは、システム内の他のレイヤから使用される Java API や、もしかしたら web サービス API を主要なインタフェースとして考えるかもしれません。 + +Java インタフェースは他のプログラマによって使われることを意図している一方、web のユーザインタフェースはプログラマでない人々によって使われることを意図しているため、この違いは大事です。どちらの場合も、良いデザインは利便性を含んでいますが、普通の人々のための利便性はプログラマのための利便性とは同じではありません。ソフトウェアのこととなるとプログラマは貧弱な利便性にもよく対処してしまうので、プログラマ向けの利便性よりも一般の人向けの利便性の方が、ある意味では高い水準となります。これは "Good Grips":http://www.designcouncil.org.uk/Case-Studies/All-Case-Studies/OXO-Good-Grips/ という台所用品に少し似ています: これは、元は関節炎を患う高齢者向けのよい利便性を持つように設計されていましたが、より持ち易いように道具を作ることは全てのユーザにとって良いことであることが分かりました。 + +フレームワークそれ自身の中に web アプリケーションにおいて達成したい利便性が含まれているため、Play フレームワークは違います。例えば、ブラウザに表示されるこのフレームワークのドキュメントやエラーメッセージのような web インタフェースは、ただただ有用です。同様に、エラーが発生した際は、ページいっぱいの無関係な情報やスタックトレースが出力されることを避け、web デベロッパのための、より注目すべき、より有用な情報を残します。 + +!images/usability-trace! + + +この短いスタックトレースを提供する JSF web アプリケーションを想像してみてください。実際のところ、Play はさらに先へ行きます: スタックトレースを表示する替わりに、web アプリケーションはスタックトレースの中に見られるアプリケーションコードの最終行を示します。結局のところ、本当に知りたいのは自身のコードにおいて最初におかしくなったのはどこかということです。 + +!images/usability-exception! + +この類いの利便性は、それ自身では起こりません; Play フレームワークは、重複し、無関係な情報を取り除き、何が重要かということに注目するよう、かなりの努力をします。 + +h2. 品質は細部に宿る + +Play フレームワークにおいて、多くの品質は細部に宿ることが分かります: それらひとつひとつは大きく重要な機能と比べると小さなものかもしれませんが、結果として開発体験をより快適かつ生産的なものにします。Play を使って何かを作るときに感じる温かみは、通常、フレームワークとの格闘の結果として生じるフラストレーションの不在なのです。 + +p(note). オリジナルは "Peter Hilton":http://hilton.org.uk/about_ph.phtml による "Lunatech Research":http://www.lunatech-research.com/archives/2010/03/15/play-framework-usability ブログで公開されました。 \ No newline at end of file diff --git a/documentation/manual_ja/validation-builtin.textile b/documentation/manual_ja/validation-builtin.textile new file mode 100644 index 0000000000..4cc3808ba9 --- /dev/null +++ b/documentation/manual_ja/validation-builtin.textile @@ -0,0 +1,230 @@ +h1. 組み込みバリデーション + +Play にはいくつか組み込みバリデーションが用意されており、使用方法は "バリデーション":validation の章で説明されています。 + +バリデーションにはそれぞれエラーメッセージが関連づけられています。エラーメッセージは @PLAY_HOME/resources/messages@ に定義され、キーは @validation.@ の後にバリデーションの名前が付いたものです。この定義は同一のキーでアプリケーションの @conf/messages@ ファイルにメッセージを定義することで上書きすることが出来ます。また他言語用のメッセージファイルを使うことでローカライズすることも出来ます。 + +

    email

    + +メールアドレス形式かどうかをチェックします。 + +bc. validation.email(address); + +アノテーション構文: + +bc. @Email String address + +メッセージキー: @validation.email@ + +

    equals

    + +他のパラメータの値と等しいかどうかをチェックします。チェックには検証値の @equals@ メソッドが使用されます。例えばパスワードの確認フィールドのチェックに使用します。 + +bc. validation.equals(password, passwordConfirmation); + +アノテーション構文: + +bc. @Equals("passwordConfirmation") String password + +メッセージキー: @validation.equals@ + +

    future

    + +未来の日付かどうかをチェックします。2番目の日付が基準日として指定された場合、基準日に対して未来、言い換えると基準日よりも後でなければいけません。 + +bc. validation.future(dueDate); +validation.future(dueDate, shipmentDate); + +アノテーション構文: + +bc. @InFuture String dueDate +@InFuture("1979-12-31") String birthDate + +メッセージキー: @validation.future@ + +

    ipv4Address

    + +バージョン 4 のプロトコルに沿った IP アドレスかどうかをチェックします。空文字列は有効な値 (valid) だと見なされます。 + +bc. validation.ipv4Address(value); + +アノテーション構文: + +bc. @IPv4Address String ip + +メッセージキー: @validation.ipv4@ + +

    ipv6Address

    + +バージョン 6 のプロトコルに沿った IP アドレスかどうかをチェックします。空文字列は有効な値 (valid) だと見なされます。 + +bc. validation.ipv6Address(value); + +アノテーション構文: + +bc. @IPv6Address String ip + +メッセージキー: @validation.ipv6@ + +

    isTrue

    + +@true@ と評価される @String@ または @Boolean@ かどうかをチェックします。例えばチェック必須の '規約に同意します' のチェックボックスがチェックされているかどうかや、非ゼロの @Number@ かどうかをチェックするときに使用します。ヌル値は false または不正な値だとみなされます。 + +bc. validation.isTrue(agree); + +アノテーション構文: + +bc. @IsTrue String agree + +メッセージキー: @validation.isTrue@ + +

    match

    + +指定された正規表現にマッチするかどうかをチェックします。空文字列は有効な値 (valid) だと見なされます。 + +bc. validation.match(abbreviation, "[A-Z]{3}"); // TLA + +アノテーション構文: + +bc. @Match("[A-Z]{3}") String abbreviation + +メッセージキー: @validation.match@ + +

    max

    + +指定された数値以下の @String@ または @Number@ であるかどうかをチェックします。ヌル値は有効な値 (valid) だと見なされます。 + +bc. validation.max(wordCount, 7500); // Short story + +アノテーション構文: + +bc. @Max(7500) String wordCount + +メッセージキー: @validation.max@ + +

    maxSize

    + +指定された長さ以下の @String@ かどうかをチェックします。空文字列は有効な値 (valid) だと見なされます。 + +bc. validation.maxSize(url, 2083); // IE 4.0 - 8 + +アノテーション構文: + +bc. @MaxSize(2083) String value + +メッセージキー: @validation.maxSize@ + +

    min

    + +指定された数値以上の @String@ または @Number@ かどうかをチェックします。ヌル値は有効な値 (valid) だと見なされます。 + +bc. validation.min(age, 18); // Adult + +アノテーション構文: + +bc. @Min(18) Long age + +メッセージキー: @validation.min@ + +

    minSize

    + +指定された長さ以上の @String@ かどうかをチェックします。空文字列は有効な値 (valid) だと見なされます。 + +bc. validation.minSize(value, 42); + +アノテーション構文: + +bc. @MinSize(42) String value + +メッセージキー: @validation.minSize@ + +

    past

    + +過去の日付かどうかをチェックします。2番目の日付が基準日として指定された場合、基準日に対して過去、言い換えると基準日よりも前でなければいけません。 + +bc. validation.past(actualDepartureDate); +validation.past(expectedDepartureDate, expectedArrivalDate); + +アノテーション構文: + +bc. @InPast String actualDepartureDate +@InPast("1980-01-01") String birthDate + +メッセージキー: @validation.past@ + +

    phone

    + +正当な電話番号かどうかをチェックします。空文字列は有効な値 (valid) だと見なされます。バリデーションは厳密ではなく、基本的な電話番号のパターンに沿っているかどうかをチェックするだけです。国固有のバリデーションは独自の @Match を実装してください。 + +bc. validation.phone(value); + +アノテーション構文: + +bc. @Phone String phone + +メッセージキー: @validation.phone@ + +フォーマット: @+CCC (SSSSSS)9999999999xEEEE@ + +* @+@ 任意の国コードマーク。 +* @CCC@ 任意の国コード。3 桁以下で、直後に区切り文字が必要です。 +* @(SSSSSS)@ 任意のサブゾーン。6 桁以下。 +* @9999999999@ 必須の番号。20 桁以下 (現在と未来の既知のケースが全て網羅されているべきです)。 +* @x@ 任意の拡張。"ext" や "extension" と書くことも出来ます。 +* @EEEE@ 任意の拡張番号。最大 4 桁。 +* 区切り文字には _半角スペース_、@-@, @.@ や @/@ を使用可能で、番号のどこにでも使うことが出来ます。 + +例: + +* @アメリカ:(305) 613 09 58 ext 101@ +* @フランス:+33 1 47 37 62 24 x3@ +* @ドイツ:+49-4312 / 777 777@ +* @中国:+86 (10)69445464@ +* @イギリス:(020) 1234 1234@ + +

    range

    + +指定された 2 つの数値の範囲に含まれる数値かどうかをチェックします。 + +bc. validation.range(wordCount, 17500, 40000); // 短編小説 + +アノテーション構文: + +bc. @Range(min = 17500, max = 40000) String wordCount + +メッセージキー: @validation.range@ + +

    required

    + +@String@, @Collection@, @File@ または配列が空でないかどうかをチェックします。 + +bc. validation.required(value); + +アノテーション構文: + +bc. @Required String value + +メッセージキー: @validation.required@ + +

    unique

    + +データベースに問い合わせることで、このアノテーションを持つカラムの値がユニークであることをチェックします。(JPA によってのみ動作します) 追加のカラムをカンマ区切りのリストとしてアノテーションの値に定義することができます。このため、 @c@ と呼ばれるプロパティを @Unique("a, b") で注釈すると、プロパティ @a@, @b@ そして @c@ の値の組み合わせがユニークであることをチェックします。 + +アノテーション構文: + +bc. @Unique(additionalColumns) String productCode +@Unique("postCode") String houseNumber + +メッセージキー: @validation.unique@ + +

    url

    + +正当な URL かどうかをチェックします。空文字列は有効な値 (valid) だと見なされます。(RFC 1738 に定義されている) 正当な URL が全て受け入れられるわけではないことに注意してください。@http@, @https@ と @ftp@ スキーマの URL だけが有効な値 (valid) だと見なされます。 + +bc. validation.url(value); + +アノテーション構文: + +bc. @URL String address + +メッセージキー: @validation.url@ diff --git a/documentation/manual_ja/validation.textile b/documentation/manual_ja/validation.textile new file mode 100644 index 0000000000..354768fef4 --- /dev/null +++ b/documentation/manual_ja/validation.textile @@ -0,0 +1,458 @@ +h1. Play による HTTP データのバリデーション + +バリデーションは、そのデータが妥当な値を持っている、または指定された要件を満たしていることを保証します。バリデーションを使ってデータベースに保存する前にデータが正しいことを確認したり、あるいは HTTP パラメータに直接バリデーションを使用することで簡易なフォームの妥当性を検証することができます。 + +h2. Play のバリデーションはどのように動作するのでしょうか? + +各リクエストは、エラーを集める @Validation@ オブジェクトを持ちます。 バリデーションを定義する方法は三つあります。 + +# コントローラのメソッドの中から、コントローラの @validation@ フィールドにあるメソッドを直接呼び出します。 @play.data.validation.Validation@ のクラスの static なメソッドを使用することで、この API のサブセットにアクセスすることも可能です。 +# コントローラのメソッド引数の宣言部分にバリデーション用のアノテーションを追加します。 +# アクションメソッドの POJO 引数に @Valid アノテーションを追加し、この POJO のプロパティにバリデーション用のアノテーションを追加します。 + +validation オブジェクトは、 @play.data.validation.Error@ オブジェクトのコレクションを保持します。各エラーには、2 つのプロパティがあります: + +* @key@ 。key は、どのデータ要素がエラーを引き起こしたか判断する手助けになります。キーの値は任意に設定できますが、Play がエラーを生成するときは、Java の変数名に従ったデフォルトの規約を使用します。 + +* @message@ 。message は、エラーのテキスト記述を含みます。message は、平文のメッセージか、または (通常は国際化対応のために) メッセージバンドルの key を参照します。 + +最初の方法を使って、単純な HTTP パラメータの妥当性を検証するやり方を見てみましょう: + +bc. public static void hello(String name) { + validation.required(name); + … +} + +このコードは、name 変数が正しく設定されていることをチェックします。正しく設定されていない場合、対応するエラーが現在のエラーコレクションに追加されます。 + +妥当性の検証が必要なだけ、この操作をくり返すことができます: + +bc. public static void hello(String name, Integer age) { + validation.required(name); + validation.required(age); + validation.min(age, 0); + … +} + +h2. バリデーションエラーメッセージ + +バリデーションの終了時に、何かエラーが作成されているか、そしてそれらを表示するかどうかをチェックすることができます: + +bc. public static void hello(String name, Integer age) { + validation.required(name); + validation.required(age); + validation.min(age, 0); + + if(validation.hasErrors()) { + for(Error error : validation.errors()) { + System.out.println(error.message()); + } + } +} + +name と age が @null@ の場合、以下のように表示されます: + +bc. Required +Required + +これは、 @$PLAY_HOME/resources/messages@ に定義されたデフォルトのメッセージが以下の通りであるためです: + +bc. validation.required=Required + +このバリデーションメッセージをカスタマイズする方法が三つあります。 + +# アプリケーションの @messages@ ファイルでメッセージを再定義することにより、デフォルトメッセージを上書きする。 +# 追加のバリデーション引数として、カスタマイズしたメッセージを提供する。 +# 追加のバリデーション引数として、国際化したメッセージへのキーを提供する。 + +h3. バリデーションメッセージの国際化 + +これらのメッセージを上書きするもっとも単純な方法は、アプリケーションの @conf/messages@ ファイルにおいて同じメッセージキーを使用することです。例えば、以下のようにします: + +bc. validation.required = Please enter a value + +"国際化":i18n で述べられているように、他の言語による地域化されたメッセージを提供することもできます。 + +h3. バリデーションメッセージの引数 + +メッセージの中で、エラーのキー文字列のためのプレースホルダを使うことができます。 + +bc. validation.required=%s is required + +この出力結果は以下のように変わります: + +bc. name is required +age is required + +p(note). *制限事項*: この @validation.required(age)@ 構文を使った必須フィールドのバリデーションがひとつ以上失敗した場合、Play は正しいパラメータ名を判断することができません。この場合、フィールド名を直接、すなわち @validation.required("age", age)@ のように指定しなければなりません。 + +このエラーキーはデフォルトではパラメータ名であり、パラメータそのものをメッセージ検索に使用します。例えば、 @hello@ アクションメソッド 上の @name@ 引数は以下のようにして地域化することができます: + +bc. name = Customer name + +この結果、出力は以下のようになります: + +bc. Customer name is required +age is required + +@error.message(String key)@ メソッドを使ってエラーキーを上書きすることもできます。例えば: + +bc. Error error = validation.required(name).error; +if(error != null) { + System.out.println(error.message("Customer name")); +} + +いくつかの組み込みのバリデータにはバリデーション引数に対応した追加のメッセージ引数が定義されています。例えば、'match' バリデーションは正規表現を指定するために、前述の @%s@ プレースホルダとはパラメータインデックスに‘2’を指定する点で異なる @String@ 型の第二引数を定義します: + +bc. validation.match=Must match %2$s + +同様に、'range' バリデーションは 2 および 3 でインデックス付けされた二つの数値型の追加パラメータを定義します: + +bc. validation.range=Not in the range %2$d through %3$d + +その他のバリデーションがどのようなパラメータを持つか確認するには @$PLAY_HOME/resources/messages@ を見てみてください。 + +h3. 地域化されたバリデーションメッセージのカスタマイズ + +@$PLAY_HOME/resources/messages@ のバリデーションメッセージは、Play の "組み込みバリデーション":validation-builtin それぞれに用意されたデフォルトのメッセージキーを使用します。例えば、以下のようにして異なるメッセージキーを指定することができます: + +bc. validation.required.em = You must enter the %s! + + +アクションメソッド中の手動バリデーションで、このメッセージキーを使用します: + +bc. validation.required(manualKey).message("validation.required.em"); + + +あるいは、このキーをアノテーションの @message@ パラメータで使用します: + +bc. public static void hello(@Required(message="validation.required.em") String name) { + … +} + + +このバリデーション用アノテーションと同じテクニックを JavaBean プロパティに使用することができます: + +bc. public static void hello(@Valid Person person) { + … +} + +public class Person extends Model { + @Required(message = "validation.required.emphasis") + public String name; + … +} + +h3. (地域化されない) 文字列バリデーションメッセージのカスタマイズ + +Play のメッセージ検索は、キーに対応するメッセージが定義されていない場合、単にそのメッセージキーを返すので、必要があればメッセージキーの替わりに文字列のメッセージそのものを使用することもできます。手動バリデーションにおいて、上の例と同様にこれを使用します: + +bc. validation.required(manualKey).message("Give us a name!"); + +以下のようにして、アクションメソッドパラメータに使用します: + +bc. public static void save(@Required(message = "Give us a name!") String name) { + … +} + +以下のようにして、JavaBean プロパティのアノテーションに使用します: + +bc. public static void save(@Valid Person person) { + … +} + +public class Person extends Model { + @Required(message = "Give us a name!") + public String name; + … +} + + + + +h2. テンプレートへのエラーの表示 + +ほとんどの場合、ビューテンプレートにエラーメッセージを表示したくなると思います。 @errors@ オブジェクトを使用することで、テンプレート中でエラーメッセージにアクセスすることができます。いくつかのタグは、エラーを表示する手助けをします: + +サンプルを見てみましょう: + +bc. public static void hello(String name, Integer age) { + validation.required(name); + validation.required(age); + validation.min(age, 0); + render(name, age); +} + +そして、現在のテンプレートは以下のようになります: + +bc. #{ifErrors} + +

    Oops…

    + + #{errors} +
  • ${error}
  • + #{/errors} + +#{/ifErrors} +#{else} + + Hello ${name}, you are ${age}. + +#{/else} + +しかし、実際のアプリケーションでは、もとのフォームを再表示したくなると思います。これを行うためには、2 つのアクションが存在します: 1 つのアクションではフォームを表示し、もう 1 つのアクションでは POST を扱います。 + +もちろん、バリデーションは 2 番目のアクションで実行され、ここで何らかのエラーが発生した場合には 1 番目のアクションにリダイレクトしなければなりません。この場合、リダイレクトの間にエラーを保持する特別なトリックが必要になります。 @validation.keep()@ メソッドを使用してください。このメソッドは、次のアクションのためにエラーコレクションを保持します。 + +実際のサンプルを見てみましょう: + +bc. public class Application extends Controller { + + public static void index() { + render(); + } + + public static void hello(String name, Integer age) { + validation.required(name); + validation.required(age); + validation.min(age, 0); + if(validation.hasErrors()) { + params.flash(); // add http parameters to the flash scope + validation.keep(); // keep the errors for the next request + index(); + } + render(name, age); + } + +} + +そして @view/Application/index.html@ テンプレートは以下のようになります: + +bc. #{ifErrors} +

    Oops…

    + + #{errors} +
  • ${error}
  • + #{/errors} +#{/ifErrors} + +#{form @Application.hello()} +
    + Name: +
    +
    + Age: +
    +
    + +
    +#{/form} + +エラーを生成したフィールドの隣にそれぞれのエラーメッセージを表示することによって、より良いユーザー体験を作成することができます: + +bc. #{ifErrors} +

    Oops…

    +#{/ifErrors} + +#{form @Application.hello()} +
    + Name: + #{error 'name' /} +
    +
    + Age: + #{error 'age' /} +
    +
    + +
    +#{/form} + + +h2. アノテーションによるバリデーション + +@play.data.validation@ パッケージ内のアノテーションは、 @Validation@ オブジェクトのそれぞれのメソッドに対応したアノテーションによって入力チェック制約を指定する、もっと簡単な代替手段を提供します。この入力チェック用アノテーションは、コントローラのメソッド引数を注釈するだけで使用することができます: + +bc. public static void hello(@Required String name, @Required @Min(0) Integer age) { + if(validation.hasErrors()) { + params.flash(); // add http parameters to the flash scope + validation.keep(); // keep the errors for the next request + index(); + } + render(name, age); +} + +h2. 複雑なオブジェクトのバリデーション + +アノテーションを使用して、モデルオブジェクトに容易に制約を追加することができ、コントローラではすべてのプロパティが妥当でなければならないことを指定します。User クラスを使用して前述の例を書き直してみましょう。 + +はじめは、プロパティにバリデーション用のアノテーションを設定した @User@ クラスです: + +bc. package models; + +public class User { + + @Required + public String name; + + @Required + @Min(0) + public Integer age; +} + +次は、 @User@ オブジェクトのプロパティが妥当でなければならないことを指定する @Valid アノテーションを設定した @hello@ アクションを以下のように変更します: + +bc. public static void hello(@Valid User user) { + if(validation.hasErrors()) { + params.flash(); // add http parameters to the flash scope + validation.keep(); // keep the errors for the next request + index(); + } + render(name, age); +} + +最後に、フォームを以下のように変更します: + +bc. #{ifErrors} +

    Oops…

    +#{/ifErrors} + +#{form @Application.hello()} +
    + Name: + #{error 'user.name' /} +
    +
    + Age: + #{error 'user.age' /} +
    +
    + +
    +#{/form} + +h2. 組み込みのバリデーション + +@play.data.validation@ パッケージには、 @Validation@ オブジェクトにおいて、またはアノテーションとして使用できる、いくつかの "組み込みのバリデーション":validation-builtin が含まれています。 + +h2. @CheckWith を使ったカスタムバリデーション + +@play.data.validation@ パッケージに必要なバリデータを見つけることができませんか? 自分で書いてしまいましょう。総称的な @CheckWith アノテーションを使って、独自の @Check@ 実装を紐付けることができます。 + +例えば、以下のようにします: + +bc. public class User { + + @Required + @CheckWith(MyPasswordCheck.class) + public String password; + + static class MyPasswordCheck extends Check { + + public boolean isSatisfied(Object user, Object password) { + return notMatchPreviousPasswords(password); + } + } +} + +デフォルトのバリデーションエラーメッセージキーは @validation.invalid@ です。デフォルトのキーを使用するためには、メッセージのキーとパラメータを使って @Check.setMessage@ をコールします。 + +bc. static class MyPasswordCheck extends Check { + + public boolean isSatisfied(Object user, Object password) { + final Date lastUsed = dateLastUsed(password); + setMessage("validation.used", JavaExtensions.format(lastUsed)); + return lastUsed == null; + } +} + +メッセージ検索は、常に第一引数としてフィールド名を持ち、後続のパラメータとしてメッセージパラメータを持ちます。このため、上記の例では以下のようにメッセージを定義することができます: + +bc. validation.used = &{%1$s} already used on date %2$s +user.password = Password + +@&{%1$s}@ では、1 というインデックス (フィールド名) でメッセージ引数を別のメッセージ検索のためのメッセージキーとして使用しており、また @%2$s@ はメッセージの第二引数 (フォーマットされた日付) です。 + +p(note). このメッセージ構文 - @%s@, @%s2$s@ と @&{…}@ - は "ローカライズされたメッセージの検索":i18n#retrieve 方法の章に説明があります。 + + +h2. カスタムアノテーション + +より複雑ですが、モデルのコードをクリーンにし、バリデータのパラメータを案内することのできる、独自のアノテーションによるバリデーションを書くこともできます。 + +例えば、 "@URL":validation-builtin#url バリデーションの限定的なバージョンが必要だとすると、 @file://@ URL などのようなスキーマの URL でも認めますし、どのスキーマを認めるかパラメータにて正確に指定します。 + +まず始めに、デフォルトのメッセージをオーバーライドするためのパラメータと共に、独自のバリデーションアノテーションを書きます: + +bc. import net.sf.oval.configuration.annotation.Constraint; +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Constraint(checkWith = URICheck.class) +public @interface URI { + String message() default URICheck.message; +} + +このアノテーションは @net.sf.oval.configuration.annotation.AbstractAnnotationCheck@ の実装を参照します。 + +bc. public class URICheck extends AbstractAnnotationCheck { + + /** Error message key. */ + public final static String message = "validation.uri"; + + /** URI schemes allowed by validation. */ + private List schemes; + + @Override + public void configure(URI uri) { + setMessage(uri.message()); + this.schemes = Arrays.asList(uri.schemes()); + } + + /** + * Add the URI schemes to the message variables so they can be included + * in the error message. + */ + @Override + public Map createMessageVariables() { + final Map variables = new TreeMap(); + variables.put("2", JavaExtensions.join(schemes, ", ")); + return variables; + } + + @Override + public boolean isSatisfied(Object validatedObject, Object value, + OValContext context, Validator validator) throws OValException { + + requireMessageVariablesRecreation(); + try { + final java.net.URI uri = new java.net.URI(value.toString()); + final boolean schemeValid = schemes.contains(uri.getScheme()); + return schemes.size() == 0 || schemeValid; + } catch (URISyntaxException e) { + return false; + } + } +} + +この @isSatisfied@ メソッドは、メッセージをレンダリングする前に @createMessageVariables()@ をコールするよう Oval に指示するために、 @requireMessageVariablesRecreation()@ をコールします。これは、メッセージフォーマッタに引き渡された変数の順序付けられたマップを返します。このマップのキーは使用されません; この例にある @"2"@ はメッセージパラメータのインデックスを示しています。先ほどと同様、最初の引数はフィールド名です。 + +これを使うためには、モデルのプロパティにこのアノテーションを使用します。 + +bc. public class User { + + @URI(message = "validation.uri.schemes", schemes = {"http", "https"}) + public String profile; +} + +以下のようにメッセージを定義することができます: + +bc. validation.uri = Not a valid URI +validation.uri.schemes = &{%1$s} is not a valid URI - allowed schemes are %2$s + + + +p(note). **考察を続けます** + +Play アプリケーションの最後の層は %(next)"ドメインオブジェクトモデル":model% です: diff --git a/documentation/manual_ja/yaml.textile b/documentation/manual_ja/yaml.textile new file mode 100644 index 0000000000..01b3d7117e --- /dev/null +++ b/documentation/manual_ja/yaml.textile @@ -0,0 +1,135 @@ +h1. YAML + +h2. YAML syntax + +Your application tests will often use data structures that consist of related model objects, which can be tedious to create programmatically in Java. Play includes a "YAML":http://en.wikipedia.org/wiki/YAML parser which makes the task much simpler. + +Create a YAML file like the following, in the @conf@ directory. + +bc.. City(capitalfederal): + name: Capital Federal + state: Buenos Aires + +User(john): + name: "John Smith" + email: john@john.com + lang: es + created: 2007-01-01 + +User(frank): + name: "Frank Franken" + email: frank@frank.com + lang: en + created: 2007-08-05 + +Neighborhood(villalugano): + name: Villa Lugano + city: capitalfederal + tags: "shopping, cafes" + users: [john, frank] + +Neighborhood(coghlan): + name: Coghlan + city: capitalfederal + tags: "cafes, bars" + users: [john] + +p. Play’s YAML syntax varies slightly from the standard. Each stanza must start with the name of the model class (eg @Neighborhood@) and be followed by an identifier in brackets (eg @villalugano@), which you can use to reference the object elsewhere in the file. This is different to standard YAML syntax, which uses the ampersand character to indicate a reference. + +Play uses "SnakeYAML":http://code.google.com/p/snakeyaml/, which has an excellent "documentation page":http://code.google.com/p/snakeyaml/wiki/Documentation#YAML_syntax with many examples. + +h2. Model classes + +The YAML file can contain one-to-many, many-to-one and many-to-many relationships. These relationships must be explicity annotated in the model classes, and you must indicate which is the "owning side" of the relationship. + +p. @City.java@ + +bc.. package models; + +import java.util.List; +import javax.persistence.*; +import play.db.jpa.Model; + +@Entity +public class City extends Model { + public String name; + public String state; + + // bi-directional one-to-many, inverse side + @OneToMany(mappedBy = "city", cascade = CascadeType.ALL) + public List neighborhoods; +} + +p. @Neighborhood.java@ + +bc.. package models; + +import java.util.List; +import javax.persistence.*; +import play.db.jpa.Model; + +@Entity +public class Neighborhood extends Model { + public String name; + + // bi-directional one-to-many, owning side + @ManyToOne + public City city; + + public String tags; + + // owning side + @ManyToMany + public List users; +} + +p. @User.java@ + +bc.. package models; + +import java.util.*; +import javax.persistence.*; +import play.db.jpa.Model; + +@Entity +//@Table(name = "`user`") +public class User extends Model { + public String name; + public String email; + public String lang; + public Date created; + + // inverse side of the relation + @ManyToMany(mappedBy = "users") + public List neighborhoods; +} + + +p(note). Note that if you are using PostgreSQL you may need to explicitly set the name of the @user@ table with escape characters because @user@ is a reserved word. + +h2. Loading the data structure in your application + +Typically you will load the data structure defined in the YAML file as part of a "unit test":test#aFixturesa. But sometimes you may wish to populate the database with specific data in other circumstances, for example "when your application starts":jobs#aBootstrapjoba for the first time. + +bc.. import play.*; +import play.jobs.*; +import play.test.*; +import models.*; + +@OnApplicationStart +public class Bootstrap extends Job { + public void doJob() { + // Check if the database is empty + if(User.count() == 0) { + Fixtures.loadModels("initial-data.yml"); + } + } +} + +p. The objects will be created in the database, and you can then access them as you would any other model object. + +bc. City city = City.all().first(); +List neighborhoods = city.neighborhoods; + + + \ No newline at end of file diff --git a/framework/templates/tags/welcome.html b/framework/templates/tags/welcome.html index 2299e32efc..6f11716f59 100644 --- a/framework/templates/tags/welcome.html +++ b/framework/templates/tags/welcome.html @@ -7,122 +7,18 @@ #{/set} -
    - -
    - - - -

    Browse

    - - -

    Contents

    -
    - -#{if modules} -

    Installed modules

    -
      - #{list modules, as:'module'} -
    • - ${module} - #{if apis.contains(module)} - — Browse API - #{/if} -
    • - #{/list} -
    +%{ + header = request.headers.get("accept-language"); + String docLang = header!=null? header.value().split(",")[0] : ""; + docLang = docLang.length()>2? docLang.substring(0,2) : docLang; +}% + +#{if "ja".equalsIgnoreCase(docLang)} + #{welcome_ja /} #{/if} - -

    Search

    -

    Get help with google

    - - -
    - -
    - -
    -

    Your new application is ready!

    -

    - Congratulation, you've just created a new play application. This page will help you in the few next steps. -

    -

    Why do you see this page?

    -

    - The conf/routes file defines a route that tell play to invoke the Application.index action - when a browser requests the / URI using the GET method: -

    -
    # Application home page
    -GET     /         Application.index
    -

    - So play has invoked the controllers.Application.index() method: -

    -
    public static void index() {
    -    render();
    -}
    -

    - Using the render() call, this action asks play to display a template. By convention play has - displayed the app/views/Application/index.html template: -

    -
    #{extends 'main.html' /}
    -#{set title:'Home' /}
    -
    -#{welcome /}
    -

    - This template extends the app/views/main.html, and uses the #{welcome /} tag to display this - welcome page. -

    -

    Need to set up a Java IDE?

    -

    - You can start right now to hack your application using any text editor. Any changes will be automatically realoaded at the - next page refresh, including modifications made to Java sources files. -

    -

    - If you want to set up your application in Eclipse, Netbeans or any other Java IDE, check - the Setting up your preferred IDE page. -

    -

    Need to connect to a database?

    -

    - You can quickly set up a developement database (either in memory or written to the filesystem), by adding one of these - lines to the conf/application.conf file: -

    -
    # For a transient in memory database (H2 in memory)
    -db=mem
    -
    -# for a simple file written database (H2 file stored)
    -db=fs
    -

    - If you want to connect to an existing MySQL5 server, use: -

    -
    db=mysql:user:pwd@database_name
    -

    - If you need to connect to another JDBC compliant database, first add the corresponding driver library to the - lib/ directory of your application, and add these lines to the conf/application.conf file: -

    -
    db.url=jdbc:postgresql:database_name
    -db.driver=org.postgresql.Driver
    -db.user=root
    -db.pass=secret
    -

    Need more help?

    -

    - When your application run in DEV mode, you can access directly the current documentation at the - /@documentation URL or go to http://www.playframework.org. -

    -

    - The Play Google Group is where Play users come to seek help, announce projects, and discuss. - If you don't have any google account, you can still join the mailing list sending an email to -
    play-framework+subscribe@googlegroups.com. -

    -
    -
    - -
    - +#{else} + #{welcome_en /} +#{/else} diff --git a/framework/templates/tags/welcome_en.html b/framework/templates/tags/welcome_en.html new file mode 100644 index 0000000000..93e43ed893 --- /dev/null +++ b/framework/templates/tags/welcome_en.html @@ -0,0 +1,116 @@ +
    + +
    + + + +

    Browse

    + + +

    Contents

    +
    + +#{if modules} +

    Installed modules

    +
      + #{list modules, as:'module'} +
    • + ${module} + #{if apis.contains(module)} + — Browse API + #{/if} +
    • + #{/list} +
    +#{/if} + +

    Search

    +

    Get help with google

    + + +
    + +
    + +
    +

    Your new application is ready!

    +

    + Congratulation, you've just created a new play application. This page will help you in the few next steps. +

    +

    Why do you see this page?

    +

    + The conf/routes file defines a route that tell play to invoke the Application.index action + when a browser requests the / URI using the GET method: +

    +
    # Application home page
    +GET     /         Application.index
    +

    + So play has invoked the controllers.Application.index() method: +

    +
    public static void index() {
    +    render();
    +}
    +

    + Using the render() call, this action asks play to display a template. By convention play has + displayed the app/views/Application/index.html template: +

    +
    #{extends 'main.html' /}
    +#{set title:'Home' /}
    +
    +#{welcome /}
    +

    + This template extends the app/views/main.html, and uses the #{welcome /} tag to display this + welcome page. +

    +

    Need to set up a Java IDE?

    +

    + You can start right now to hack your application using any text editor. Any changes will be automatically realoaded at the + next page refresh, including modifications made to Java sources files. +

    +

    + If you want to set up your application in Eclipse, Netbeans or any other Java IDE, check + the Setting up your preferred IDE page. +

    +

    Need to connect to a database?

    +

    + You can quickly set up a developement database (either in memory or written to the filesystem), by adding one of these + lines to the conf/application.conf file: +

    +
    # For a transient in memory database (H2 in memory)
    +db=mem
    +
    +# for a simple file written database (H2 file stored)
    +db=fs
    +

    + If you want to connect to an existing MySQL5 server, use: +

    +
    db=mysql:user:pwd@database_name
    +

    + If you need to connect to another JDBC compliant database, first add the corresponding driver library to the + lib/ directory of your application, and add these lines to the conf/application.conf file: +

    +
    db.url=jdbc:postgresql:database_name
    +db.driver=org.postgresql.Driver
    +db.user=root
    +db.pass=secret
    +

    Need more help?

    +

    + When your application run in DEV mode, you can access directly the current documentation at the + /@documentation URL or go to http://www.playframework.org. +

    +

    + The Play Google Group is where Play users come to seek help, announce projects, and discuss. + If you don't have any google account, you can still join the mailing list sending an email to +
    play-framework+subscribe@googlegroups.com. +

    +
    +
    + +
    + diff --git a/framework/templates/tags/welcome_ja.html b/framework/templates/tags/welcome_ja.html new file mode 100644 index 0000000000..7b88df54b2 --- /dev/null +++ b/framework/templates/tags/welcome_ja.html @@ -0,0 +1,111 @@ + +
    + +
    + + + +

    閲覧

    + + +

    コンテンツ

    +
    + + #{if modules} +

    Installed modules

    +
      + #{list modules, as:'module'} +
    • + ${module} + #{if apis.contains(module)} + — Browse API + #{/if} +
    • + #{/list} +
    + #{/if} + +

    検索

    +

    google でヘルプを検索する

    + + +
    + +
    + +
    +

    新しいアプリケーションの準備が整いました!

    +

    + おめでとうございます。新しい play アプリケーションが作成されました。このページは次のいくつかの手順であなたのお手伝いをします。 +

    +

    なぜこのページが表示されるのですか?

    +

    + conf/routes ファイルには、ブラウザが GET メソッドを使って / URI をリクエストしたとき、play に Application.index を起動するよう指示するルートが定義されています。 +

    +
    # Application home page
    +GET     /         Application.index
    +

    + そのため、play は controllers.Application.index() メソッドを起動しました: +

    +
    public static void index() {
    +    render();
    +}
    +

    + このアクションは render() の呼び出しを使って play にテンプレートを表示するよう依頼します。play は規約によって app/views/Application/index.html テンプレートを表示しました: +

    +
    #{extends 'main.html' /}
    +#{set title:'Home' /}
    +
    +#{welcome /}
    +

    + このテンプレートは app/views/main.html を拡張し、そして #{welcome /} タグを使ってこの welcome ページを表示します。 +

    +

    Java IDE をセットアップする必要がありますか?

    +

    + あなたは任意のテキストエディタを使って、今すぐにアプリケーションのハックを始めることができます。Java ソースファイルに加えられた更新を含むすべての変更は、次回のページ更新時に自動的に再度読み込まれます。 +

    +

    + Eclipse, Netbeans またはその他の Java IDE にてアプリケーションをセットアップしたい場合は、お好みの IDE を設定しよう ページを確認してください。 +

    +

    データベースに接続する必要がありますか?

    +

    + conf/application.conf ファイルにこれらのいずれかの行を追加することで、すぐに (インメモリまたはファイルシステムに書き込まれる) 開発データベースをセットアップすることができます: +

    +
    # 一時的なインメモリデータベース用 (H2 in memory)
    +db=mem
    +
    +# シンプルなファイル書き込みデータベース用 (H2 file stored)
    +db=fs
    +

    + 既存の MySQL5 サーバ に接続したい場合は、以下を使用します: +

    +
    db=mysql:user:pwd@database_name
    +

    + その他の JDBC 準拠のデータベースに接続する必要がある場合は、まず対応するドライバのライブラリを lib/ ディレクトリに追加し、そして conf/application.conf ファイルに以下の行を追加してください: +

    +
    db.url=jdbc:postgresql:database_name
    +db.driver=org.postgresql.Driver
    +db.user=root
    +db.pass=secret
    +

    もっとヘルプが必要ですか?

    +

    + アプリケーションを DEV モードで実行している場合は /@documentation という URL で最新のドキュメントに直接アクセスすることができます。あるいは http://www.playframework.org を訪問してください。 +

    +

    + Play ユーザは Play Google Group でヘルプを検索し、プロジェクトを発表し、議論します。もし google アカウントを持っていない場合でも、play-framework+subscribe@googlegroups.com にメールを送ってメーリングリストに参加することができます。 +

    +
    +
    + +
    + + + + + diff --git a/modules/docviewer/app/DocViewerPlugin.java b/modules/docviewer/app/DocViewerPlugin.java index ba483380f9..4d56d5a7ca 100644 --- a/modules/docviewer/app/DocViewerPlugin.java +++ b/modules/docviewer/app/DocViewerPlugin.java @@ -1,10 +1,13 @@ -import play.*; -import play.mvc.Http.*; -import play.mvc.*; -import play.libs.*; -import play.vfs.*; +import play.Play; +import play.PlayPlugin; +import play.libs.IO; +import play.libs.MimeTypes; +import play.mvc.Http.Request; +import play.mvc.Http.Response; +import play.mvc.Router; +import play.vfs.VirtualFile; -import java.io.*; +import java.io.File; public class DocViewerPlugin extends PlayPlugin { @@ -16,17 +19,17 @@ public boolean rawInvocation(Request request, Response response) throws Exceptio return true; } if (request.path.startsWith("/@api/")) { - if(request.path.matches("/@api/-[a-z]+/.*")) { - String module = request.path.substring(request.path.indexOf("-")+1); + if (request.path.matches("/@api/-[a-z]+/.*")) { + String module = request.path.substring(request.path.indexOf("-") + 1); module = module.substring(0, module.indexOf("/")); - VirtualFile f = Play.modules.get(module).child("documentation/api/"+request.path.substring(8+module.length())); - if(f.exists()) { + VirtualFile f = Play.modules.get(module).child("documentation/api/" + request.path.substring(8 + module.length())); + if (f.exists()) { response.contentType = MimeTypes.getMimeType(f.getName()); response.out.write(f.content()); } return true; } - File f = new File(Play.frameworkPath, "documentation/api/"+request.path.substring(6)); + File f = new File(Play.frameworkPath, "documentation/api/" + request.path.substring(6)); if (f.exists()) { response.contentType = MimeTypes.getMimeType(f.getName()); response.out.write(IO.readContent(f)); @@ -40,12 +43,17 @@ public boolean rawInvocation(Request request, Response response) throws Exceptio public void onRoutesLoaded() { Router.prependRoute("GET", "/@documentation/?", "PlayDocumentation.index"); Router.prependRoute("GET", "/@documentation/{id}", "PlayDocumentation.page"); + Router.prependRoute("GET", "/@documentation/home", "PlayDocumentation.index"); + Router.prependRoute("GET", "/@documentation/{docLang}/{id}", "PlayDocumentation.page"); Router.prependRoute("GET", "/@documentation/images/{name}", "PlayDocumentation.image"); Router.prependRoute("GET", "/@documentation/files/{name}", "PlayDocumentation.file"); + Router.prependRoute("GET", "/@documentation/{docLang}/images/{name}", "PlayDocumentation.image"); + Router.prependRoute("GET", "/@documentation/{docLang}/files/{name}", "PlayDocumentation.file"); Router.prependRoute("GET", "/@documentation/modules/{module}/{id}", "PlayDocumentation.page"); Router.prependRoute("GET", "/@documentation/modules/{module}/images/{name}", "PlayDocumentation.image"); Router.prependRoute("GET", "/@documentation/modules/{module}/files/{name}", "PlayDocumentation.file"); Router.prependRoute("GET", "/@documentation/cheatsheet/{category}", "PlayDocumentation.cheatSheet"); + Router.prependRoute("GET", "/@documentation/{docLang}/cheatsheet/{category}", "PlayDocumentation.cheatSheet"); } } diff --git a/modules/docviewer/app/controllers/PlayDocumentation.java b/modules/docviewer/app/controllers/PlayDocumentation.java index 9f45e300ec..3a86b15cdb 100644 --- a/modules/docviewer/app/controllers/PlayDocumentation.java +++ b/modules/docviewer/app/controllers/PlayDocumentation.java @@ -1,28 +1,44 @@ package controllers; -import play.*; -import play.mvc.*; -import play.libs.*; -import play.vfs.*; - import helpers.CheatSheetHelper; -import java.io.*; -import java.util.*; +import helpers.LangMenuHelper; +import helpers.LangMenuHelper.*; +import play.Logger; +import play.Play; +import play.libs.IO; +import play.mvc.Controller; +import play.mvc.Http; +import play.vfs.VirtualFile; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; public class PlayDocumentation extends Controller { public static void index() throws Exception { - page("home", null); + Http.Header header = request.headers.get("accept-language"); + String docLang = header!=null? header.value().split(",")[0] : ""; + docLang = docLang.length()>2? docLang.substring(0,2) : docLang; + page("home", null, docLang); } - public static void page(String id, String module) throws Exception { - File page = new File(Play.frameworkPath, "documentation/manual/"+id+".textile"); - if(module != null) { - page = new File(Play.modules.get(module).getRealFile(), "documentation/manual/"+id+".textile"); + public static void page(String id, String module, String docLang) throws Exception { + String docLangDir = (docLang != null && (!"en".equalsIgnoreCase(docLang) && !docLang.matches("en-.*") ) ) ? "_" + docLang + "/" : "/"; + + File page = new File(Play.frameworkPath, "documentation/manual" + docLangDir + id + ".textile"); + if(!page.exists()){ + page = new File(Play.frameworkPath, "documentation/manual/" + id + ".textile"); } - if(!page.exists()) { - notFound("Manual page for "+id+" not found"); + + if (module != null) { + page = new File(Play.modules.get(module).getRealFile(), "documentation/manual/" + id + ".textile"); + } + + if (!page.exists()) { + notFound("Manual page for " + id + " not found"); } String textile = IO.readContentAsString(page); String html = toHTML(textile); @@ -30,25 +46,29 @@ public static void page(String id, String module) throws Exception { List modules = new ArrayList(); List apis = new ArrayList(); - if(id.equals("home") && module == null) { - for(String key : Play.modules.keySet()) { + if (id.equals("home") && module == null) { + for (String key : Play.modules.keySet()) { VirtualFile mr = Play.modules.get(key); - VirtualFile home = mr.child("documentation/manual/home.textile"); - if(home.exists()) { + VirtualFile home = mr.child("documentation/manual" + docLang + "home.textile"); + if (home.exists()) { modules.add(key); } - if(mr.child("documentation/api/index.html").exists()) { + if (mr.child("documentation/api/index.html").exists()) { apis.add(key); } } } - - render(id, html, title, modules, apis, module); + List langMenuList = LangMenuHelper.getMenuList(); + render(id, html, title, modules, apis, module, docLang, langMenuList); } - public static void cheatSheet(String category) { - File[] sheetFiles = CheatSheetHelper.getSheets(category); - if(sheetFiles != null) { + + + + + public static void cheatSheet(String category, String docLang) { + File[] sheetFiles = CheatSheetHelper.getSheets(category, docLang); + if (sheetFiles != null) { List sheets = new ArrayList(); for (File file : sheetFiles) { @@ -56,30 +76,30 @@ public static void cheatSheet(String category) { } String title = CheatSheetHelper.getCategoryTitle(category); - Map otherCategories = CheatSheetHelper.listCategoriesAndTitles(); + Map otherCategories = CheatSheetHelper.listCategoriesAndTitles(docLang); - render(title, otherCategories, sheets); + render(title, otherCategories, sheets, docLang); } notFound("Cheat sheet directory not found"); } - public static void image(String name, String module) { - File image = new File(Play.frameworkPath, "documentation/images/"+name+".png"); - if(module != null) { - image = new File(Play.modules.get(module).getRealFile(),"documentation/images/"+name+".png"); + public static void image(String name, String module, String lang) { + File image = new File(Play.frameworkPath, "documentation/images/" + name + ".png"); + if (module != null) { + image = new File(Play.modules.get(module).getRealFile(), "documentation/images/" + name + ".png"); } - if(!image.exists()) { + if (!image.exists()) { notFound(); } renderBinary(image); } - public static void file(String name, String module) { - File file = new File(Play.frameworkPath, "documentation/files/"+name); - if(module != null) { - file = new File(Play.modules.get(module).getRealFile(),"documentation/files/"+name); + public static void file(String name, String module, String lang) { + File file = new File(Play.frameworkPath, "documentation/files/" + name); + if (module != null) { + file = new File(Play.modules.get(module).getRealFile(), "documentation/files/" + name); } - if(!file.exists()) { + if (!file.exists()) { notFound(); } renderBinary(file); @@ -92,7 +112,7 @@ static String toHTML(String textile) { } static String getTitle(String textile) { - if(textile.length() == 0) { + if (textile.length() == 0) { return ""; } return textile.split("\n")[0].substring(3).trim(); diff --git a/modules/docviewer/app/helpers/CheatSheetHelper.java b/modules/docviewer/app/helpers/CheatSheetHelper.java index 3c514f6317..82df469eb5 100644 --- a/modules/docviewer/app/helpers/CheatSheetHelper.java +++ b/modules/docviewer/app/helpers/CheatSheetHelper.java @@ -1,5 +1,7 @@ package helpers; +import play.Play; + import java.io.File; import java.io.FileFilter; import java.util.Arrays; @@ -7,15 +9,18 @@ import java.util.LinkedHashMap; import java.util.Map; -import play.Play; - public class CheatSheetHelper { private static final File cheatSheetBaseDir = new File(Play.frameworkPath, "documentation/cheatsheets"); - public static File[] getSheets(String category) { - File cheatSheetDir = new File(cheatSheetBaseDir, category); + public static File[] getSheets(String category, String docLang) { + String docLangDir = (docLang != null && (!"en".equalsIgnoreCase(docLang) && !docLang.matches("en-.*"))) ? "_" + docLang : ""; + File cheatSheetDir = new File(cheatSheetBaseDir + docLangDir, category); - if(cheatSheetDir.exists() && cheatSheetDir.isDirectory()) { + if (!cheatSheetDir.exists()){ + cheatSheetDir = new File(cheatSheetBaseDir, category); + } + + if (cheatSheetDir.exists() && cheatSheetDir.isDirectory()) { File[] sheetFiles = cheatSheetDir.listFiles(new FileFilter() { public boolean accept(File pathname) { @@ -27,13 +32,13 @@ public boolean accept(File pathname) { Arrays.sort(sheetFiles, new Comparator() { public int compare(File f1, File f2) { - + String o1 = f1.getName(); String o2 = f2.getName(); - + if (o1.contains("-") && o2.contains("-")) { return o1.substring(0, o1.indexOf("-")) - .compareTo(o2.substring(0, o1.indexOf("-"))); + .compareTo(o2.substring(0, o1.indexOf("-"))); } else { return o1.compareTo(o2); } @@ -48,7 +53,7 @@ public int compare(File f1, File f2) { public static String getCategoryTitle(String category) { // split camelCaseWord into separate words - String[] parts = category.trim().split("(? listCategoriesAndTitles() { - File[] categories = cheatSheetBaseDir.listFiles(new FileFilter() { + public static Map listCategoriesAndTitles(String docLang) { + String docLangDir = (docLang != null && (!"en".equalsIgnoreCase(docLang) && !docLang.matches("en-.*"))) ? "_" + docLang : ""; + File[] categories = new File(cheatSheetBaseDir + docLangDir).listFiles(new FileFilter() { public boolean accept(File pathname) { return pathname.isDirectory(); } }); + if(categories==null || categories.length<=0){ + categories = cheatSheetBaseDir.listFiles(new FileFilter() { + public boolean accept(File pathname) { + return pathname.isDirectory(); + } + }); + } + Arrays.sort(categories); - + Map categoriesAndTitles = new LinkedHashMap(); for (File category : categories) { diff --git a/modules/docviewer/app/helpers/LangMenuHelper.java b/modules/docviewer/app/helpers/LangMenuHelper.java new file mode 100644 index 0000000000..20871145bc --- /dev/null +++ b/modules/docviewer/app/helpers/LangMenuHelper.java @@ -0,0 +1,49 @@ +package helpers; + +import play.Play; + +import java.io.File; +import java.io.FileFilter; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class LangMenuHelper { + private static final File baseDir = new File(Play.frameworkPath, "documentation"); + private static final Pattern ptn = Pattern.compile("manual_(.*)"); + + public static List getMenuList() { + List langMenuList = new ArrayList(); + LangMenu defaultLangMenu = new LangMenu(); + defaultLangMenu.key = "en"; + defaultLangMenu.value = "English"; + langMenuList.add(defaultLangMenu); + File[] dirs = baseDir.listFiles(new FileFilter() { + @Override + public boolean accept(File file) { + return file.isDirectory() && ptn.matcher(file.getName()).find(); + } + }); + for (final File dir : dirs) { + Matcher m = ptn.matcher(dir.getName()); + String langCd = ""; + if (m.find()) { + langCd = m.group(1); + } + if (langCd.length() <= 0) continue; + LangMenu langMenu = new LangMenu(); + langMenu.key = langCd; + langMenu.value = new Locale(langCd).getDisplayName(); + langMenuList.add(langMenu); + } + + return langMenuList; + } + + public static class LangMenu { + String key; + String value; + } +} diff --git a/modules/docviewer/app/views/PlayDocumentation/cheatSheet.html b/modules/docviewer/app/views/PlayDocumentation/cheatSheet.html index 9b5a72099d..225ee59574 100644 --- a/modules/docviewer/app/views/PlayDocumentation/cheatSheet.html +++ b/modules/docviewer/app/views/PlayDocumentation/cheatSheet.html @@ -23,7 +23,7 @@

    ${title}