概要
Play Framework (java)の開発環境の構築を行い、続いて簡単な検索アプリケーションを開発します。
([Spring Bootで簡単な検索アプリケーションを開発する] (http://qiita.com/rubytomato@github/items/e4fda26faddbcfd84d16)をPlay Frameworkを使って書き直したものになります。)
環境
下記の環境で動作確認を行いました。
- Windows7 (64bit)
- Java 1.8.0_60
- Play Framework 2.4.2
- Activator 1.3.5
- sbt 0.13.8
- Eclipse 4.4
- MySQL 5.6.25
参考
下記のサイトを参考にさせて頂きました。
- [Play Framework] (https://www.playframework.com/)
- [Play 2.4.x documentation] (https://www.playframework.com/documentation/2.4.x/Home)
- [Play 2.3.x documentation (日本語)] (https://www.playframework.com/documentation/ja/2.3.x/Home)
完成図
github
ソースコードはgithub [rubytomato/actor-search-play-example] (https://github.com/rubytomato/actor-search-play-example)にあります。
事前準備
インストール
[Play Framework] (https://www.playframework.com/)よりインストール用のアーカイブファイルをダウンロードします。
アーカイブファイルにはオンライン用(約1MB)とオフライン用(約400MB)が用意されていますが、今回はオフライン用のアーカイブファイルを使用してインストールを進めていきます。
ダウンロードファイル: typesafe-activator-1.3.5.zip
インストール
インストールはアーカイブファイルを展開し、そのディレクトリを環境変数Pathへ追加するだけです。
インストールの確認
コマンドプロンプトからactivator help
とコマンドを実行すると初回時はいろいろなファイルのダウンロードが行われます。
ダウンロードが終了するとhelpコマンドの実行結果が表示されます。
> activator help
...省略...
Commands:
ui Start the Activator UI
new [name] [template-id] Create a new project with [name] using template [template-id]
list-templates Print all available template names
help Print this message
Options:
-jvm-debug [port] Turn on JVM debugging, open at the given port. Defaults to 9999 if no port given.
Environment variables (read from context):
JAVA_OPTS Environment variable, if unset uses ""
SBT_OPTS Environment variable, if unset uses ""
ACTIVATOR_OPTS Environment variable, if unset uses ""
Please note that in order for Activator to work you must have Java available on the classpath
activatorの設定
activatorconfig.txt
activatorconfig.txtというファイルを%USERPROFILE%/.activator
ディレクトリに作成します。
この設定ファイルでプロキシやタイムアウト時間を設定することができます。私の環境ではダウンロードがよくタイムアウトしたため下記のようにタイムアウトを30秒に設定しました。
# This are the proxy settings we use for activator
# Multiple proxy hosts can be used by separating them with a '|' sign
# Do not enclose the proxy host(s) in quotes
# -Dhttp.proxyHost=PUT YOUR PROXY HOST HERE
# -Dhttp.proxyPort=PUT YOUR PROXY PORT HERE
# Here we configure the hosts which should not go through the proxy. You should include your private network, if applicable.
-Dhttp.nonProxyHosts="localhost|127.0.0.1"
# These are commented out, but if you need to use authentication for your proxy, please fill these out.
#-Dhttp.proxyUser=PUT YOUR PROXY USER HERE
#-Dhttp.proxyPassword=PUT YOUR PROXY PASSWORD HERE
# Add
-Dactivator.timeout=30s
# Add
# -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000
# Add
-Dinput.encoding=Cp1252
# Add
-Dakka.loglevel=DEBUG
- activator.timeout : タイムアウトまでの時間を指定します。デフォルトでは10秒になっています。
- input.encoding=Cp1252 : コマンドヒストリー参照の文字化けを防止します。Cp1252はAsciiの別名です。
- akka.loglevel : activatorのログレベルを指定します。
参照
[TYPESAFE ACTIVATOR DOCUMENTATION] (https://www.typesafe.com/activator/docs)
プロジェクトの雛形を生成
プロジェクト名: actor-search-play-example
雛形の生成
コマンドプロンプトを開きactivator new
コマンドでプロジェクトの雛形を生成します。
newコマンドの引数にプロジェクト名とプロジェクトのテンプレート名を渡します。
> activator new actor-search-play-example play-java
Fetching the latest list of templates...
OK, application "actor-search-play-example" is being created using the "play-java" template.
To run "actor-search-play-example" from the command line, "cd actor-search-play-example" then:
D:\dev\actor-search-play-example/activator run
To run the test for "actor-search-play-example" from the command line, "cd actor-search-play-example" then:
D:\dev\actor-search-play-example/activator test
To run the Activator UI for "actor-search-play-example" from the command line, "cd actor-search-play-example" then:
D:\dev\actor-search-play-example/activator ui
- new <- コマンド
- actor-search-play-example <- プロジェクト名
- play-java <- プロジェクトテンプレート名
プロジェクトの設定
プロジェクトが生成されたら下記の設定ファイルを編集します。
project/plugins.sbt
projectディレクトリ内のplugins.sbtファイルに下記の修正を行います。
この修正でeclipseプラグインが有効になります。
// The Play plugin
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.4.2")
// Web plugins
addSbtPlugin("com.typesafe.sbt" % "sbt-coffeescript" % "1.0.0")
addSbtPlugin("com.typesafe.sbt" % "sbt-less" % "1.0.6")
addSbtPlugin("com.typesafe.sbt" % "sbt-jshint" % "1.0.3")
addSbtPlugin("com.typesafe.sbt" % "sbt-rjs" % "1.0.7")
addSbtPlugin("com.typesafe.sbt" % "sbt-digest" % "1.1.0")
addSbtPlugin("com.typesafe.sbt" % "sbt-mocha" % "1.1.0")
// Play enhancer - this automatically generates getters/setters for public fields
// and rewrites accessors of these fields to use the getters/setters. Remove this
// plugin if you prefer not to have this feature, or disable on a per project
// basis using disablePlugins(PlayEnhancer) in your build.sbt
addSbtPlugin("com.typesafe.sbt" % "sbt-play-enhancer" % "1.1.0")
// Play Ebean support, to enable, uncomment this line, and enable in your build.sbt using
// enablePlugins(SbtEbean). Note, uncommenting this line will automatically bring in
// Play enhancer, regardless of whether the line above is commented out or not.
// addSbtPlugin("com.typesafe.sbt" % "sbt-play-ebean" % "1.0.0")
// Add
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "4.0.0")
project/build.properties
このファイルは特に修正する必要はありませんでした。
#Activator-generated Properties
#Mon Aug 31 08:59:46 JST 2015
template.uuid=4908845b-9453-410b-af0f-404c1440dff1
sbt.version=0.13.8
build.sbt
プロジェクトのルートディレクトリ内のbuild.sbtファイルに下記の修正を行います。
この修正もeclipseで開発を進めるために必要になります。
name := """actor-search-play-example"""
version := "1.0-SNAPSHOT"
lazy val root = (project in file(".")).enablePlugins(PlayJava)
scalaVersion := "2.11.6"
libraryDependencies ++= Seq(
javaJdbc,
cache,
javaWs
)
// Play provides two styles of routers, one expects its actions to be injected, the
// other, legacy style, accesses its actions statically.
routesGenerator := InjectedRoutesGenerator
// Add
// Compile the project before generating Eclipse files, so that generated .scala or .class files for views and routes are present
EclipseKeys.preTasks := Seq(compile in Compile)
// Java project. Don't expect Scala IDE
EclipseKeys.projectFlavor := EclipseProjectFlavor.Java
// Use .class files instead of generated .scala files for views and routes
EclipseKeys.createSrc := EclipseCreateSrc.ValueSet(EclipseCreateSrc.ManagedClasses, EclipseCreateSrc.ManagedResources)
コンソールでコンパイル
コンソールからライブラリ/プラグインの更新、コンパイルなどを行ってみます。
コンソール起動時にいろいろなファイルのダウンロードが行われかなり時間が掛かることがあります。
なお、ダウンロードがタイムアウトしてコンソールが起動しないことがありますが、何度かリトライを繰り返すとダウンロードが完了してコンソールが起動する場合があります。
コンソールを起動するにはプロジェクトディレクトリに移動してactivator
コマンドを実行します。
> cd actor-search-play-example
> activator
update
update
Resolves and optionally retrieves dependencies, producing a report.
updateコマンドでライブラリの更新を行います。初回実行時はダウンロードが行われて時間がかかります。
[actor-search-play-example] $ update
[info] Updating {file:/D:/dev/actor-search-play-example/}root...
[info] Resolving jline#jline;2.12.1 ...
...省略...
[info] Done updating.
[success] Total time: 2 s, completed 2015/08/31 9:03:24
compile
compile
Compiles sources.
compileコマンドでアプリケーションのコンパイルを行います。この時点では雛形で生成されたサンプルコードがコンパイルされます。
ここでも初回実行時はダウンロードが行われるので時間がかかる場合があります。
[actor-search-play-example] $ compile
[info] Compiling 6 Scala sources and 2 Java sources to D:\dev\actor-search-play-example\target\scala-2.11\classes...
[success] Total time: 8 s, completed 2015/08/31 9:04:17
plugins
plugins
Lists currently available plugins.
pluginsコマンドで有効なプラグインを確認することができます。この後で使用するEclipsePlugin(com.typesafe.sbteclipse.plugin.EclipsePlugin
)が表示されているか確認します。
[actor-search-play-example] $ plugins
In file:/D:/dev/play-example/
sbt.plugins.IvyPlugin: enabled in root
sbt.plugins.JvmPlugin: enabled in root
sbt.plugins.CorePlugin: enabled in root
sbt.plugins.JUnitXmlReportPlugin: enabled in root
play.sbt.Play: enabled in root
play.sbt.PlayAkkaHttpServer
play.sbt.PlayJava: enabled in root
play.sbt.PlayLayoutPlugin: enabled in root
play.sbt.PlayNettyServer: enabled in root
play.sbt.PlayScala
play.sbt.routes.RoutesCompiler: enabled in root
play.twirl.sbt.SbtTwirl: enabled in root
...省略...
com.typesafe.sbteclipse.plugin.EclipsePlugin: enabled in root
eclipse
eclipseコマンドでeclipseのプロジェクト設定ファイルを生成します。
このコマンドはeclipseプラグインが有効でないと使用できません。またライブラリの依存関係(追加や削除、バージョン変更)を変更した場合にも都度実行する必要があります。
[actor-search-play-example] $ eclipse
[info] About to create Eclipse project files for your project(s).
[info] Successfully created Eclipse project files for project(s):
[info] actor-search-play-example
run
activator run
コマンドを実行して雛形アプリケーションを実行します。
[actor-search-play-example] $ run
--- (Running the application, auto-reloading is enabled) ---
[DEBUG] [08/31/2015 09:05:47.114] [pool-9-thread-5] [EventStream(akka://application)] logger log1-Logging$DefaultLogger started
[DEBUG] [08/31/2015 09:05:47.115] [pool-9-thread-5] [EventStream(akka://application)] Default Loggers started
[info] p.a.l.c.ActorSystemProvider - Starting application default Akka system: application
[info] p.c.s.NettyServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000
(Server started, use Ctrl+D to stop and go back to the console...)
アプリケーションが起動したら次のURLにアクセスしてページが表示されることを確認します。
exit
アプリケーションはCtrl + Dでストップします。
exitでコンソールを終了します。
データベースの設定
MySQLとH2の接続設定の例を記載します。開発ではH2を使って進めました。
MySQLの設定
MySQLを使用する場合の設定
application.conf
datasourceの設定を行います。
# Database configuration
# ~~~~~
# You can declare as many datasources as you want.
# By convention, the default datasource is named `default`
#
db.default.driver=com.mysql.jdbc.Driver
db.default.url="jdbc:mysql://localhost/sample_db"
db.default.username=test_user
db.default.password=test_user
ebean.default = ["models.*"]
build.sbt
下記の行を追加します。
libraryDependencies += "mysql" % "mysql-connector-java" % "5.1.36"
H2の設定
H2を使用する場合の設定
application.conf
datasourceの設定を行います。
# Default database configuration using H2 database engine in an in-memory mode
db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play;MODE=MYSQL;DB_CLOSE_DELAY=-1"
ebean.default = ["models.*"]
H2コンソール
H2にはブラウザベースのUIがあります。
コンソールからh2-browser
コマンドを実行するとH2コンソールが立ち上がります。
[actor-search-play-example] $ h2-browser
ログイン画面で下記の情報を入力し接続ボタンを押します。
- ドライバクラス :
org.h2.Driver
- JDBC URL :
jdbc:h2:mem:play;MODE=MYSQL;DB_CLOSE_DELAY=-1
- ユーザー名: (空)
- パスワード: (空)
Ebeanの設定
ORMにEbeanを使用するので有効化します。(デフォルトでは無効)
plugins.sbt
下記の行がコメントされているので有効にします。
// Play Ebean support, to enable, uncomment this line, and enable in your build.sbt using
// enablePlugins(SbtEbean). Note, uncommenting this line will automatically bring in
// Play enhancer, regardless of whether the line above is commented out or not.
addSbtPlugin("com.typesafe.sbt" % "sbt-play-ebean" % "1.0.0")
build.sbt
下記の行を修正します。
lazy val root = (project in file(".")).enablePlugins(PlayJava, PlayEbean)
設定の反映
コンソールからupdateコマンドを実行して変更を反映します。
[actor-search-play-example] $ update
MySQLを使用する場合、libraryDependenciesコマンドを実行してライブラリーにmysql-connector-javaが追加されているか確認します。
[actor-search-play-example] $ libraryDependencies
[info] List(org.scala-lang:scala-library:2.11.6, com.typesafe.play:play-enhancer:1.1.0, com.typesafe.play:twirl-api:1.1.1, com.typesafe.play:play-ebean:1.0.0, com.typesafe.play:play-server:2.4.2, com.
typesafe.play:play-test:2.4.2:test, com.typesafe.play:play-omnidoc:2.4.2:docs, com.typesafe.play:play-java:2.4.2, com.typesafe.play:play-netty-server:2.4.2, com.typesafe.play:play-java-jdbc:2.4.2, com
.typesafe.play:play-cache:2.4.2, com.typesafe.play:play-java-ws:2.4.2, mysql:mysql-connector-java:5.1.36)
プロジェクトをeclipseに取り込む
メニュバーの"File" -> "Import" -> "Import..." -> "General" -> "Existing Projects into workspace"を選択します。
これで開発する準備が完了しました。
アプリケーションの開発
ここからはeclipseとactivatorを使用して開発を進めていきます。
設定
application.conf
使用言語にja
を追加します。
# The application languages
# ~~~~~
play.i18n.langs = [ "ja", "en" ]
logback.xml
アプリケーションのloggerを追加します。
<logger name="controllers" level="DEBUG" />
<logger name="models" level="DEBUG" />
<logger name="views" level="DEBUG" />
メッセージリソース
conf/messages
messagesというファイル名のリソースはデフォルトとして扱われます。
各言語別に用意する場合は、messages-jaのようにファイル名の末尾に言語コード(ISO 639-2)や国コード(ISO 3166-1 alpha-2)を付加します。
# label name
actor.id=ID
actor.name=名前
actor.height=身長
actor.blood=血液型
actor.birthday=誕生日
actor.birthplace=出生地
actor.update_at=更新日
# validation message
actor.validation.error=Please correct errors above.
actor.save.success=正常に登録できました
actor.delete.success=正常に削除できました
actor.delete.error=削除に失敗しました
actor.name.required=名前は必須です
actor.height.range=身長は{0}から{1}の範囲で入力してください
actor.blood.kind=血液型はA,B,AB,Oのどれかを入力してください
actor.birthday.pattern=誕生日は'yyyy-MM-dd'の形式で入力してください
actor.birthplace.range=出身地は北海道から沖縄までの都道府県を選んでください
静的ファイル
静的ファイルはpublicディレクトリ内に配置します。
bootstrap3とjQueryを使用するのでこれらのファイルを配置するディレクトリをpublic/vendor
に作成し、それぞれのサイトからダウンロードしたファイルを配置しました。
- bootstrap3 :
public/vendor/bootstrap-3.3.5
- jquery :
public/vendor/jquery
Utils
app/utils
パッケージを作成し下記のクラスを作成します。
DateParser
ビューヘルパーメソッドとしても使用します。
package utils;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
public class DateParser {
public static Date parse(String str) {
LocalDate ld = LocalDate.parse(str, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
Instant i = ld.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant();
return Date.from(i);
}
public static String format(Date date) {
if (date == null) {
return "---";
}
Instant i = date.toInstant();
LocalDate ld = LocalDateTime.ofInstant(i, ZoneId.systemDefault()).toLocalDate();
return ld.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}
}
Form
app/views/form
パッケージを作成し下記のクラスを作成します。
ActorForm
俳優の登録フォームで使用します。
validateメソッドを実装することでバリデーションが行われるようになります。
package views.form;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import play.data.validation.ValidationError;
import play.i18n.Messages;
public class ActorForm {
public String id = "";
public String name = "";
public String height = "";
public String blood = "";
public String birthday = "";
public String birthplaceId = "";
public ActorForm() {
}
public ActorForm(String id, String name, String height, String blood,
String birthday, String birthplaceId) {
this.id = id;
this.name = name;
this.height = height;
this.blood = blood;
this.birthday = birthday;
this.birthplaceId = birthplaceId;
}
public List<ValidationError> validate() {
List<ValidationError> errors = new ArrayList<ValidationError>();
if (name == null || name.length() == 0) {
errors.add(new ValidationError("name", Messages.get("actor.name.required")));
}
if (StringUtils.isNotEmpty(height)) {
Integer tmpH = Integer.valueOf(height);
if (tmpH < 1 && tmpH > 200) {
errors.add(new ValidationError("height", Messages.get("actor.height.range", 1, 200)));
}
}
if (StringUtils.isNotEmpty(blood) && !blood.matches("A|B|AB|O")) {
errors.add(new ValidationError("blood", Messages.get("actor.blood.kind")));
}
if (StringUtils.isNotEmpty(birthday) && !birthday.matches("\\d{4}-\\d{2}-\\d{2}")) {
errors.add(new ValidationError("birthday", Messages.get("actor.birthday.pattern")));
}
if (StringUtils.isNotEmpty(birthplaceId)) {
Integer tmpB = Integer.valueOf(birthplaceId);
if (tmpB < 1 && tmpB > 47) {
errors.add(new ValidationError("birthplaceId", Messages.get("actor.birthplace.range")));
}
}
if(errors.size() > 0) {
System.out.println("ActorForm#validate errors");
return errors;
}
return null;
}
@Override
public String toString() {
return "ActorForm [id=" + id + ", name=" + name + ", height=" + height
+ ", blood=" + blood + ", birthday=" + birthday + ", birthplaceId="
+ birthplaceId + "]";
}
}
Model
app/models/
パッケージを作成し下記のクラスを作成します。
Actor
actorテーブルに対応するモデルクラスです。
package models;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Version;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import org.apache.commons.lang3.StringUtils;
import utils.DateParser;
import views.form.ActorForm;
import com.avaje.ebean.Model;
@Entity(name = "actor")
public class Actor extends Model {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
@NotNull
@Size(min = 1, max = 30)
public String name;
public Integer height;
@Pattern(regexp = "A|B|AB|O")
public String blood;
public Date birthday;
@Min(1)
@Max(47)
public Integer birthplaceId;
@Version
public Date updateAt;
public Actor() {
}
public Actor(Long id, String name, Integer height, String blood,
Date birthday, Integer birthplaceId) {
this.id = id;
this.name = name;
this.height = height;
this.blood = blood;
this.birthday = birthday;
this.birthplaceId = birthplaceId;
}
public static Find<Long, Actor> finder = new Find<Long, Actor>(){};
public static Actor convertToModel(ActorForm form) {
Actor actor = new Actor();
actor.id = StringUtils.isNotEmpty(form.id) ? Long.valueOf(form.id) : null;
actor.name = form.name;
actor.height = StringUtils.isNotEmpty(form.height) ? Integer.valueOf(form.height) : null;
actor.blood = form.blood;
actor.birthday = StringUtils.isNotEmpty(form.birthday) ? DateParser.parse(form.birthday) : null;
actor.birthplaceId = StringUtils.isNotEmpty(form.birthplaceId) ? Integer.valueOf(form.birthplaceId) : null;
return actor;
}
public static ActorForm convertToForm(Actor actor) {
ActorForm form = new ActorForm();
form.id = actor.id.toString();
form.name = actor.name;
form.height = actor.height != null ? actor.height.toString() : null;
form.blood = actor.blood;
form.birthday = actor.birthday != null ? DateParser.format(actor.birthday) : null;
form.birthplaceId = actor.birthplaceId != null ? actor.birthplaceId.toString() : null;
return form;
}
@Override
public String toString() {
return "Actor [id=" + id + ", name=" + name + ", height=" + height
+ ", blood=" + blood + ", birthday=" + birthday + ", birthplaceId="
+ birthplaceId + ", updateAt=" + updateAt + "]";
}
}
Prefecture
prefectureテーブルに対応するモデルクラスです。
package models;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import com.avaje.ebean.Model;
@Entity(name = "prefecture")
public class Prefecture extends Model {
@Id
@Min(1)
@Max(2)
public Integer id;
@NotNull
@Size(min = 1, max = 6)
public String name;
public Prefecture() {
}
public Prefecture(Integer id, String name) {
this.id = id;
this.name = name;
}
public static List<Prefecture> list() {
return Prefecture.finder.order("id").findList();
}
public static Find<Long, Prefecture> finder = new Find<Long, Prefecture>(){};
@Override
public String toString() {
return "Prefecture [id=" + id + ", name=" + name + "]";
}
}
Views
app/views/
パッケージにテンプレートを作成します。
(main.scala.htmlとindex.scala.htmlは雛形作成時に作られたものです)
main.scala.html
各ページのベースになるテンプレートです。
@content
の部分に各ページのコンテンツがはめ込まれます。
@(title: String, header: String)(content: Html)
<!DOCTYPE html>
<html lang="ja">
<head>
<title>@title</title>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" media="screen" href="@routes.Assets.versioned("vendor/bootstrap-3.3.5/css/bootstrap.min.css")" />
<link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/main.css")">
<link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")">
<script src="@routes.Assets.versioned("javascripts/hello.js")" type="text/javascript"></script>
</head>
<body>
<div class="container">
<div class="page-header">
<h1>@header</h1>
</div>
<div class="row">
<div class="col-md-12">
<ul class="nav nav-pills">
<li role="presentation"><a href="/actor">index</a></li>
<li role="presentation"><a href="/actor/create">create</a></li>
<li role="presentation"><a href="/init">init</a></li>
</ul>
</div>
</div>
@content
<div class="page-footer">
<div>footer</div>
</div>
</div>
<script src="@routes.Assets.versioned("vendor/jquery/jquery-1.11.3.js")" ></script>
<script src="@routes.Assets.versioned("vendor/bootstrap-3.3.5/js/bootstrap.js")" ></script>
</body>
</html>
index.scala.html
俳優一覧ページのテンプレートです。
@main("Actor Index", "俳優一覧") {
ここに記述されたコードがmain.scala.htmlの@content部分に反映されます。
}
@(message: String, result: List[models.Actor])
@import java.util.Date
@import play.i18n._
@import utils.DateParser
@*****************************
* Declaring reusable blocks *
*****************************@
@d(date: Date) = {
@if(date != null) {
@date.format("yyyy-MM-dd")
} else {
---
}
}
@main("Actor Index", "俳優一覧") {
@if(flash.containsKey("success") || flash.containsKey("error")) {
<div class="row">
<div class="col-md-12 well">
@if(flash.containsKey("success")) {
<div id="success-message" class="text-success">
@flash.get("success")
</div>
}
@if(flash.containsKey("error")) {
<div id="error-message" class="text-danger">
@flash.get("error")
</div>
}
</div>
</div>
}
<div class="row">
<div class="col-md-12">
<table class="table table-striped">
<thead>
<tr>
<th>@Messages.get("actor.id")</th>
<th>@Messages.get("actor.name")</th>
<th>@Messages.get("actor.height")</th>
<th>@Messages.get("actor.blood")</th>
<th>@Messages.get("actor.birthday")</th>
<th>@Messages.get("actor.birthplace")</th>
<th>@Messages.get("actor.update_at")</th>
<th> </th>
</tr>
</thead>
<tbody>
@for(actor <- result) {
<tr>
<td>
<a class="btn btn-default" href="/actor/detail/@actor.id">@actor.id</a>
</td>
<td>@actor.name</td>
<td>@if(actor.height != null) {@actor.height} else {---}</td>
<td>@if(actor.blood) {@actor.blood} else {---}</td>
<td>@DateParser.format(actor.birthday)</td>
<td>@actor.birthplaceId</td>
<td>@actor.updateAt.format("yyyy-MM-dd HH:mm:ss")</td>
<td>
<form action="/actor/delete/@actor.id" method="post">
<input class="btn btn-warning" type="submit" value="delete">
</form>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
メッセージリソースは@Messages.get()で取得できます。
Date型フィールドではformatメソッドが使用できます。
@actor.birthday.format("yyyy-MM-dd")
String型フィールドではtoLowerCaseメソッドが使用できます。
@actor.blood.toLowerCase
detail.scala.html
俳優詳細ページのテンプレートです。
@(message: String, actor: models.Actor)
@import play.i18n._
@import utils.DateParser
@main("Actor Detail", "俳優詳細") {
<div class="row">
<div class="col-md-12">
@if(actor != null) {
<table class="table">
<tr>
<th class="col-md-3 active">@Messages.get("actor.id")</th>
<td class="col-md-9">@actor.id</td>
</tr>
<tr>
<th class="col-md-3 active">@Messages.get("actor.name")</th>
<td class="col-md-9">@actor.name</td>
</tr>
<tr>
<th class="col-md-3 active">@Messages.get("actor.height")</th>
<td class="col-md-9">@if(actor.height != null) {@actor.height} else {---}</td>
</tr>
<tr>
<th class="col-md-3 active">@Messages.get("actor.blood")</th>
<td class="col-md-9">@if(actor.blood) {@actor.blood.toLowerCase} else {---}</td>
</tr>
<tr>
<th class="col-md-3 active">@Messages.get("actor.birthday")</th>
<td class="col-md-9">@DateParser.format(actor.birthday)</td>
</tr>
<tr>
<th class="col-md-3 active">@Messages.get("actor.birthplace")</th>
<td class="col-md-9">@actor.birthplaceId</td>
</tr>
<tr>
<th class="col-md-3 active">@Messages.get("actor.update_at")</th>
<td class="col-md-9">@actor.updateAt.format("yyyy-MM-dd HH:mm:ss")</td>
</tr>
</table>
} else {
<div class="alert alert-warning">
<span>データはありません。</span>
</div>
}
</div>
</div>
}
create.scala.html
俳優登録ページのテンプレートです。
@(message: String, actorForm: Form[views.form.ActorForm])
@import play.i18n._
@import models.Prefecture
@main("Actor Create", "俳優登録") {
@if(flash.containsKey("error")) {
<div class="row">
<div class="col-md-12 well">
<div id="error-message" class="text-danger">
@flash.get("error")
</div>
</div>
</div>
}
<div class="row">
<div class="col-md-12">
<form class="form-horizontal" role="form" action="/actor/save" method="post">
@defining(actorForm("id")) { idField =>
<input type="hidden" id="@idField.id" name="@idField.name" value="@idField.value">
}
<!-- name -->
<div class="form-group">
@defining(actorForm("name")) { nameField =>
<label for="nameField.id" class="col-sm-2 control-label">@Messages.get("actor.name")</label>
<div class="col-sm-10">
<input type="text" id="@nameField.id" class="form-control" name="@nameField.name" value="@nameField.value">
@if(nameField.hasErrors) {
<span class="help-block">@nameField.errors.mkString(", ")</span>
}
</div>
}
</div>
<!-- height -->
<div class="form-group">
@defining(actorForm("height")) { heightField =>
<label for="heightField.id" class="col-sm-2 control-label">@Messages.get("actor.height")</label>
<div class="col-sm-10">
<input type="text" id="@heightField.id" class="form-control" name="@heightField.name" value="@heightField.value">
@if(heightField.hasErrors) {
<span class="help-block">@heightField.errors.mkString(", ")</span>
}
</div>
}
</div>
<!-- blood -->
<div class="form-group">
@defining(actorForm("blood")) { bloodField =>
<label for="bloodField.id" class="col-sm-2 control-label">@Messages.get("actor.blood")</label>
<div class="col-sm-10">
<input type="text" id="@bloodField.id" class="form-control" name="@bloodField.name" value="@bloodField.value">
@if(bloodField.hasErrors) {
<span class="help-block">@bloodField.errors.mkString(", ")</span>
}
</div>
}
</div>
<!-- birthday -->
<div class="form-group">
@defining(actorForm("birthday")) { birthdayField =>
<label for="birthdayField.id" class="col-sm-2 control-label">@Messages.get("actor.birthday")</label>
<div class="col-sm-10">
<input type="text" id="@birthdayField.id" class="form-control" name="@birthdayField.name" value="@birthdayField.value">
@if(birthdayField.hasErrors) {
<span class="help-block">@birthdayField.errors.mkString(", ")</span>
}
</div>
}
</div>
<!-- birthplaceId -->
<div class="form-group">
@defining(actorForm("birthplaceId")) { birthplaceIdField =>
<label for="birthplaceIdField.id" class="col-sm-2 control-label">@Messages.get("actor.birthplace")</label>
<div class="col-sm-10">
<select id="@birthplaceIdField.id" class="form-control" name="@birthplaceIdField.name">
<option value="">---</option>
@for(pref <- Prefecture.list()) {
<option value="@pref.id">@pref.name</option>
}
</select>
@if(birthplaceIdField.hasErrors) {
<span class="help-block">@birthplaceIdField.errors.mkString(", ")</span>
}
</div>
}
</div>
<div class="form-group">
<input class="btn btn-default" type="submit" value="save" />
</div>
</form>
</div>
</div>
}
コードブロックの定義
テンプレート内に再利用可能なコードブロックを定義できます。
(もっとも通常は再利用するコードは、コードブロックではなくヘルパークラスに作成するようです。)
例えば、下記のような日付フィールドのフォーマット表示は
<td>@actor.birthday.format("yyyy-MM-dd")</td>
このようなコードブロックを定義することで
@d(date: Date) = {
@if(date != null) {
@date.format("yyyy-MM-dd")
} else {
---
}
}
下記のようにコードを簡潔にすることができます。
<td>@d(actor.birthday)</td>
- ポイント
下記のように@if
と(
の間に半角スペースがあるとコンパイルエラーになります。
@if (date != null) {
フォームヘルパ
フォーム部品の組み立てにヘルパメソッドが使用できます。
(下記はほんの一部です。)
@defining(_form("name")) { nameField =>
<input type="text" id="@nameField.id" class="form-control" name="@nameField.name" value="@nameField.value">
}
@helper.input(actorForm("name"), 'class -> "form-control", '_label -> null, 'size -> 30, 'placeholder -> "Your name") { (id, name, value, args) =>
<input type="text" name="@name" id="@id" value="@value" @toHtmlArgs(args)>
}
@helper.inputText(field = actorForm("name"),
args = 'id -> "id_name2", 'class -> "form-control", 'size -> 30, 'placeholder -> "Your name", '_label -> null)
[Handling form submission] (https://www.playframework.com/documentation/2.4.x/JavaForms)
[Form template helpers] (https://www.playframework.com/documentation/2.4.x/JavaFormHelpers)
Controller
Application.java
雛形として作成されたApplication.javaに修正を加えます。
package controllers;
import java.util.List;
import models.Actor;
import models.Prefecture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.SqlRow;
import com.avaje.ebean.SqlUpdate;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.TxCallable;
import play.data.Form;
import play.i18n.Messages;
import play.mvc.*;
import utils.DateParser;
import views.form.ActorForm;
import views.html.*;
public class Application extends Controller {
private static final Logger logger = LoggerFactory.getLogger(Application.class);
public Result index() {
logger.info("Application#index");
List<Actor> actor = Actor.finder.all();
return ok(index.render("Actor List", actor));
}
public Result detail(Long id) {
logger.info("Application#detail");
Actor actor = Actor.finder.byId(id);
return ok(detail.render("Actor Detail", actor));
}
public Result create() {
logger.info("Application#create");
ActorForm form = new ActorForm();
Form<ActorForm> formData = Form.form(ActorForm.class).fill(form);
return ok(create.render("Actor Create", formData));
}
public Result save() {
logger.info("Application#save");
Form<ActorForm> formData = Form.form(ActorForm.class).bindFromRequest();
if (formData.hasErrors()) {
flash("error", Messages.get("actor.validation.error"));
return badRequest(create.render("retry", formData));
} else {
Actor actor = Actor.convertToModel(formData.get());
Ebean.execute(()->{
SqlRow row = Ebean.createSqlQuery("SELECT MAX(id) AS cnt FROM actor").findUnique();
Long cnt = row.getLong("cnt");
actor.id = cnt == null ? 1L : (cnt + 1L);
actor.save();
});
flash("success", Messages.get("actor.save.success"));
}
return redirect(routes.Application.index());
}
public Result delete(Long id) {
logger.info("Application#delete");
if (id == null || id == 0L) {
flash("error", Messages.get("actor.validation.error"));
List<Actor> actor = Actor.finder.all();
return badRequest(index.render("Actor List", actor));
} else {
Boolean result = Ebean.execute(new TxCallable<Boolean>() {
@Override
public Boolean call() {
Actor actor = Actor.finder.byId(id);
actor.delete();
return Boolean.TRUE;
}
});
if (result) {
flash("success", Messages.get("actor.delete.success"));
} else {
flash("error", Messages.get("actor.delete.error"));
}
}
return redirect(routes.Application.index());
}
public Result init() {
logger.info("Application#init");
try (Transaction txn = Ebean.beginTransaction()) {
SqlUpdate actorAllDelete = Ebean.createSqlUpdate("DELETE FROM actor");
actorAllDelete.execute();
txn.setBatchMode(true);
txn.setBatchSize(10);
txn.setBatchGetGeneratedKeys(false);
new Actor( 1L, "丹波哲郎", 175, "O", DateParser.parse("1922-07-17"), 13).save();
new Actor( 2L, "森田健作", 175, "O", DateParser.parse("1949-12-16"), 13).save();
new Actor( 3L, "加藤剛", 173, null, DateParser.parse("1949-12-16"), 22).save();
new Actor( 4L, "島田陽子", 171, "O", DateParser.parse("1953-05-17"), 43).save();
new Actor( 5L, "山口果林", null, null, DateParser.parse("1947-05-10"), 13).save();
new Actor( 6L, "佐分利信", null, null, DateParser.parse("1909-02-12"), 1).save();
new Actor( 7L, "緒方拳", 173, "B", DateParser.parse("1937-07-20"), 13).save();
new Actor( 8L, "松山政路", 167, "A", DateParser.parse("1947-05-21"), 13).save();
new Actor( 9L, "加藤嘉", null, null, DateParser.parse("1913-01-12"), 13).save();
new Actor(10L, "菅井きん", 155, "B", DateParser.parse("1926-02-28"), 13).save();
new Actor(11L, "笠智衆", null, null, DateParser.parse("1904-05-13"), 43).save();
new Actor(12L, "殿山泰司", null, null, DateParser.parse("1915-10-17"), 28).save();
new Actor(13L, "渥美清", 173, "B", DateParser.parse("1928-03-10"), 13).save();
SqlUpdate prefectureAllDelete = Ebean.createSqlUpdate("DELETE FROM prefecture");
prefectureAllDelete.execute();
new Prefecture( 1, "北海道").save();
new Prefecture( 2, "青森県").save();
new Prefecture( 3, "岩手県").save();
new Prefecture( 4, "宮城県").save();
new Prefecture( 5, "秋田県").save();
new Prefecture( 6, "山形県").save();
new Prefecture( 7, "福島県").save();
new Prefecture( 8, "茨城県").save();
new Prefecture( 9, "栃木県").save();
new Prefecture(10, "群馬県").save();
new Prefecture(11, "埼玉県").save();
new Prefecture(12, "千葉県").save();
new Prefecture(13, "東京都").save();
new Prefecture(14, "神奈川県").save();
new Prefecture(15, "新潟県").save();
new Prefecture(16, "富山県").save();
new Prefecture(17, "石川県").save();
new Prefecture(18, "福井県").save();
new Prefecture(19, "山梨県").save();
new Prefecture(20, "長野県").save();
new Prefecture(21, "岐阜県").save();
new Prefecture(22, "静岡県").save();
new Prefecture(23, "愛知県").save();
new Prefecture(24, "三重県").save();
new Prefecture(25, "滋賀県").save();
new Prefecture(26, "京都府").save();
new Prefecture(27, "大阪府").save();
new Prefecture(28, "兵庫県").save();
new Prefecture(29, "奈良県").save();
new Prefecture(30, "和歌山県").save();
new Prefecture(31, "鳥取県").save();
new Prefecture(32, "島根県").save();
new Prefecture(33, "岡山県").save();
new Prefecture(34, "広島県").save();
new Prefecture(35, "山口県").save();
new Prefecture(36, "徳島県").save();
new Prefecture(37, "香川県").save();
new Prefecture(38, "愛媛県").save();
new Prefecture(39, "高知県").save();
new Prefecture(40, "福岡県").save();
new Prefecture(41, "佐賀県").save();
new Prefecture(42, "長崎県").save();
new Prefecture(43, "熊本県").save();
new Prefecture(44, "大分県").save();
new Prefecture(45, "宮崎県").save();
new Prefecture(46, "鹿児島県").save();
new Prefecture(47, "沖縄県").save();
txn.commit();
} catch (Exception e) {
System.out.println(e.getStackTrace());
}
return redirect(routes.Application.index());
}
}
この時点でeclipse上では下記のrender
メソッドが見つからないというエラーが起きていると思いますが、テンプレートの変更が反映されていないためにおこります。
最初は下記の定義だったものを
@(message: String)
このように引数を追加しました。
@(message: String, result: List[models.Actor])
上記のテンプレートの変更がeclipse側に反映されていないため、renderメソッドの部分でエラーが起きます。
return ok(index.render("Actor List", actor));
activatorコンソールで一度コンパイルを行い、その後eclipse上でプロジェクトをリフレッシュ(F5)します。
Transaction
トランザクション管理の方法はいくつかあります。
TxCallableを使用する方法
Boolean result = Ebean.execute(new TxCallable<Boolean>() {
@Override
public Boolean call() {
...処理...
if (error) {
return Boolean.FALSE;
}
return Boolean.TRUE;
}
});
TxRunnableを使用する方法
Ebean.execute(new TxRunnable() {
@Override
public void run() {
...処理...
}
});
@Transactional
を使用する方法
@Transactional
public Result save() {
...処理...
}
ルート
URLとコントローラーのアクションのマッピングルールを定義します。
routes
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~
GET / controllers.Application.index()
GET /actor controllers.Application.index()
GET /actor/detail/:id controllers.Application.detail(id: Long)
GET /actor/create controllers.Application.create()
POST /actor/save controllers.Application.save()
POST /actor/delete/:id controllers.Application.delete(id: Long)
GET /init controllers.Application.init()
# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
実行
コンソールからコンパイルを行い、エラーがなければrunコマンドで実行します。
[actor-search-play-example] $ compile
[info] Compiling 1 Java source to D:\dev\actor-search-play-example\target\scala-2.11\classes...
[success] Total time: 1 s, completed 2015/08/31 9:52:54
[actor-search-play-example] $ run
--- (Running the application, auto-reloading is enabled) ---
[DEBUG] [08/31/2015 09:52:57.275] [pool-23-thread-6] [EventStream(akka://application)] logger log1-Logging$DefaultLogger started
[DEBUG] [08/31/2015 09:52:57.276] [pool-23-thread-6] [EventStream(akka://application)] Default Loggers started
[info] p.a.l.c.ActorSystemProvider - Starting application default Akka system: application
[info] p.c.s.NettyServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000
(Server started, use Ctrl+D to stop and go back to the console...)
アプリケーションの起動が完了したら下記のURLにアクセスします。
初回は"Database 'default' needs evolution!"というエラーページが表示されますが
そのまま、"Apply this script now!"ボタンをクリックします。
これでデータベース上にテーブルが作成されます。
俳優一覧ページが表示されますが、データが未登録なので一覧は空の状態です。
メニューのinitリンクテキストをクリックすると初期データを登録します。
初期データの登録が終わると一覧ページへリダイレクトし初期データが表示されます。
開発時
~ run
コマンドでauto-reloadが有効になります。ソースコードを変更すると自動的に反映されます。
[actor-search-play-example] $ ~ run
--- (Running the application, auto-reloading is enabled) ---
[info] p.a.l.c.ActorSystemProvider - Starting application default Akka system: application
[info] p.c.s.NettyServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000
(Server started, use Ctrl+D to stop and go back to the console...)
[info] Compiling 1 Java source to D:\dev\actor-search-play-example\target\scala-2.11\classes...
[success] Compiled in 1s
停めるにはCtrl + D
(なかなか直ぐに停まらないです。)
エラーページのカスタマイズ
デフォルトのエラーページ(下図はBad Request)をカスタマイズすることができます。
参考
[Handling errors] (https://www.playframework.com/documentation/2.4.x/JavaErrorHandling)
テンプレートを作成
views/errors
パッケージを作成し、そこへエラーページ用のテンプレートを作成します。
badrequest.scala.html
@(message: String, detail: String, status: Int)
@main("Bad Request", "Bad Request") {
<div class="row">
<div class="col-md-12 well">
<h2>@status : @message</h2>
<div class="alert alert-error">
@detail
</div>
</div>
</div>
}
notfound.scala.html
@(message: String, detail: String, status: Int)
@main("Not Found", "Not Found") {
<div class="row">
<div class="col-md-12 well">
<h2>@status : @message</h2>
<div class="alert alert-error">
@detail
</div>
</div>
</div>
}
エラーハンドラーを作成
DefaultHttpErrorHandlerを継承し必要なメソッドをオーバーライドします。
この例では、onNotFoundとonBadRequestはテンプレートを使用し、onDevServerErrorと`onProdServerErrorは単純に文字列を返します。
package controllers;
import javax.inject.Inject;
import javax.inject.Provider;
import play.Configuration;
import play.Environment;
import play.api.OptionalSourceMapper;
import play.api.UsefulException;
import play.api.routing.Router;
import play.http.DefaultHttpErrorHandler;
import play.libs.F.Promise;
import play.mvc.Http.RequestHeader;
import play.mvc.Result;
import play.mvc.Results;
import views.html.errors.notfound;
import views.html.errors.badrequest;
public class CustomErrorHandler extends DefaultHttpErrorHandler {
@Inject
public CustomErrorHandler(Configuration configuration, Environment environment,
OptionalSourceMapper sourceMapper, Provider<Router> routes) {
super(configuration, environment, sourceMapper, routes);
System.out.println("CustomErrorHandler constructor");
}
@Override
protected Promise<Result> onNotFound(RequestHeader request, String exception) {
System.out.println("onNotFound");
return Promise.<Result>pure(
Results.notFound(notfound.render("ページがみつかりません", exception, 404))
);
}
@Override
protected Promise<Result> onBadRequest(RequestHeader request, String exception) {
System.out.println("onBadRequest");
return Promise.<Result>pure(
Results.badRequest(badrequest.render("不正なリクエストです", exception, 400))
);
}
@Override
protected Promise<Result> onDevServerError(RequestHeader request, UsefulException exception) {
System.out.println("onDevServerError");
return Promise.<Result>pure(
Results.internalServerError("[DEV] A server error occurred: " + exception.getMessage())
);
}
@Override
protected Promise<Result> onProdServerError(RequestHeader request, UsefulException exception) {
System.out.println("onProdServerError");
return Promise.<Result>pure(
Results.internalServerError("[PROD] A server error occurred: " + exception.getMessage())
);
}
}
application.conf
application.confに上記のエラーハンドラーを指定します。
# Handling errors
play.http.errorHandler = "controllers.CustomErrorHandler"
検証用のアクションを追加
故意にInternal Server Errorを発生させるアクションをApplicationコントローラーに追加します。
public Result error500(Integer f) {
if (f == 1) {
throw new RuntimeException("Internal Server Error");
}
return ok();
}
routes
マッピングを設定します。
GET /error500 controllers.Application.error500(f: Integer ?= 0)
確認
Bad Request
Not Found
Internal Server Error
ブラウザに下記の文字列が表示されます。
DEV: A server error occurred: Execution exception[[RuntimeException: Internal Server Error]]
デプロイ
この例ではデプロイ用のアーカイブファイルを作成してプロダクション環境へリリースする方法を想定しています。
アーカイブファイルの作成
distタスクを実行するとアーカイブファイルを作成することができます。
[actor-search-play-example] $ ; clean ; compile ; dist
- 複数のタスクを順番に実行するには
; <task>
のようにタスクの前に;を付けます。
distタスクが成功するとtarget/universalディレクトリにactor-search-play-example-1.0-SNAPSHOT.zipというファイルが生成されます。
アプリケーションの実行
アーカイブファイルをアプリケーションを実行するディレクトリへ展開(D:\play\actor-search-play-example-1.0-SNAPSHOT)し、bin\actor-search-play-example.batを実行します。
D:\play\actor-search-play-example-1.0-SNAPSHOT> bin\actor-search-play-example.bat
入力行が長すぎます。
コマンドの構文が誤っています。
しかし、この環境では上記のエラーが発生しました。
プロジェクト名や実行時のディレクトリ名が長いとsetコマンドで設定するclasspathの文字数がOSの制限(Windows7では8191文字が最大長)を超えてしまう可能性があります。
[コマンド プロンプト (cmd.exe) のコマンドライン文字列の制限] (https://support.microsoft.com/ja-jp/kb/830473)
Microsoft Windows XP 以降の Windows を実行しているコンピューターでは、コマンド プロンプトで使用できる文字列の最大長は 8191 文字です。
今回は展開するディレクトリ名を短くして対応しました。(D:\play)
また、開発時はH2データベースを使用していましたが、この例ではMySQLを使用します。
D:\play>bin\actor-search-play-example.bat -Dplay.evolutions.db.default.enabled=false
[info] - application - Creating Pool for datasource 'default'
[info] - play.api.db.DefaultDBApi - Database [default] connected at jdbc:mysql://localhost/sample_db
CustomErrorHandler constructor
[info] - play.api.libs.concurrent.ActorSystemProvider - Starting application default Akka system: application
CustomErrorHandler constructor
CustomErrorHandler constructor
[info] - play.api.Play - Application started (Prod)
[info] - play.core.server.NettyServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000
- 引数に
-Dplay.evolutions.db.default.enabled=false
を指定してevolutionを無効にしています。
SBT Native Packager plugin
[SBT Native Packager Plugin] (http://www.scala-sbt.org/sbt-native-packager/)を使用するとパッケージングの方法をカスタマイズすることができます。
下記の例は上記のdistタスクと同じアーカイブファイルを作成します。
[actor-search-play-example] $ universal:packageBin
[info] Packaging D:\dev\actor-search-play-example\target\scala-2.11\actor-search-play-example_2.11-1.0-SNAPSHOT-sources.jar ...
[info] Wrote D:\dev\actor-search-play-example\target\scala-2.11\actor-search-play-example_2.11-1.0-SNAPSHOT.pom
[info] Done packaging.
[info] Main Scala API documentation to D:\dev\actor-search-play-example\target\scala-2.11\api...
[info] Packaging D:\dev\actor-search-play-example\target\scala-2.11\actor-search-play-example_2.11-1.0-SNAPSHOT-web-assets.jar ...
[info] Packaging D:\dev\actor-search-play-example\target\scala-2.11\actor-search-play-example_2.11-1.0-SNAPSHOT.jar ...
[info] Done packaging.
[info] Done packaging.
model contains 41 documentable templates
[info] Main Scala API documentation successful.
[info] Packaging D:\dev\actor-search-play-example\target\scala-2.11\actor-search-play-example_2.11-1.0-SNAPSHOT-javadoc.jar ...
[info] Done packaging.
[info] Packaging D:\dev\actor-search-play-example\target\scala-2.11\actor-search-play-example_2.11-1.0-SNAPSHOT-sans-externalized.jar ...
[info] Done packaging.
[success] Total time: 7 s, completed 2015/10/11 14:07:29
メモ
activatorコマンド
new
新しいプロジェクトを生成します。
ui
ブラウザベースのGUIを起動します。
今回使用したバージョンのWindows版に有る不具合で使い難かったため使用しませんでした。
[Windows | Git Bash | activator run results in IllegalArgumentException: URI has an authority component #1033] (https://github.com/typesafehub/activator/issues/1037#issue-84988752)
%USERPROFILE%/.sbt
ディレクトリにrepositories
というファイルがありますが、その中の記述でfileスキームのスラッシュが2つになっていることが原因でエラーが発生します。
[repositories]
local
activator-launcher-local: file://${activator.local.repository-${activator.home-${user.home}/.activator}/repository}, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]
activator-local: file://${activator.local.repository-//D:/dev/activator-dist-1.3.5/repository}, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]
maven-central
typesafe-releases: http://repo.typesafe.com/typesafe/releases
typesafe-ivy-releasez: http://repo.typesafe.com/typesafe/ivy-releases, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]
コンソール
コンソールの起動はプロジェクトディレクトリに移動してactivator
コマンドを実行します。
> activator
[info] Loading project definition from D:\dev\eclipse-jee-luna-SR2-workspace\play-example\project
[info] Set current project to play-example (in build file:/D:/dev/eclipse-jee-luna-SR2-workspace/play-example/)
[play-example] $
[actor-search-play-example] $ help
help Displays this help message or prints detailed help on requested commands (run 'help <command>').
completions Displays a list of completions for the given argument string (run 'completions <string>').
about Displays basic information about sbt and the build.
tasks Lists the tasks defined for the current project.
settings Lists the settings defined for the current project.
reload (Re)loads the current project or changes to plugins project or returns from it.
projects Lists the names of available projects or temporarily adds/removes extra builds to the session.
project Displays the current project or changes to the provided `project`.
set [every] <setting> Evaluates a Setting and applies it to the current project.
session Manipulates session settings. For details, run 'help session'.
inspect [uses|tree|definitions] <key> Prints the value for 'key', the defining scope, delegates, related definitions, and dependencies.
<log-level> Sets the logging level to 'log-level'. Valid levels: debug, info, warn, error
plugins Lists currently available plugins.
; <command> (; <command>)* Runs the provided semicolon-separated commands.
~ <command> Executes the specified command whenever source files change.
last Displays output from a previous command or the output from a specific task.
last-grep Shows lines from the last output for 'key' that match 'pattern'.
export <tasks>+ Executes tasks and displays the equivalent command lines.
exit Terminates the build.
--<command> Schedules a command to run before other commands on startup.
show <key> Displays the result of evaluating the setting or task associated with 'key'.
all <task>+ Executes all of the specified tasks concurrently.
More command help available using 'help <command>' for:
!, +, ++, <, alias, append, apply, eval, iflast, onFailure, reboot, shell
tasks
[actor-search-play-example] $ tasks
This is a list of tasks defined for the current project.
It does not list the scopes the tasks are defined in; use the 'inspect' command for that.
Tasks produce values. Use the 'show' command to run the task and print the resulting value.
clean Deletes files produced by the build, such as generated sources, compiled classes, and task caches.
compile Compiles sources.
console Starts the Scala interpreter with the project classes on the classpath.
consoleProject Starts the Scala interpreter with the sbt and the build definition on the classpath and useful imports.
consoleQuick Starts the Scala interpreter with the project dependencies on the classpath.
copyResources Copies resources to the output directory.
doc Generates API documentation.
mochaOnly Execute the mocha tests provided as arguments or all tests if no arguments are provided.
package Produces the main artifact, such as a binary jar. This is typically an alias for the task that actually does the packaging.
packageBin Produces a main artifact, such as a binary jar.
packageDoc Produces a documentation artifact, such as a jar containing API documentation.
packageSrc Produces a source artifact, such as a jar containing sources and resources.
publish Publishes artifacts to a repository.
publishLocal Publishes artifacts to the local Ivy repository.
publishM2 Publishes artifacts to the local Maven repository.
run Runs a main class, passing along arguments provided on the command line.
runMain Runs the main class selected by the first argument, passing the remaining arguments to the main method.
test Executes all tests.
testOnly Executes the tests provided as arguments or all tests if no arguments are provided.
testQuick Executes the tests that either failed before, were not run or whose transitive dependencies changed, among those provided as arguments.
update Resolves and optionally retrieves dependencies, producing a report.
More tasks may be viewed by increasing verbosity. See 'help tasks'.
playGenerateSecret
[actor-search-play-example] $ playGenerateSecret
[info] Generated new secret: qJ_8EqO/WX[Mte?=tlFHX/eDTkij?w_Clvvx6z?;<0tn?/@axJNPCzd[0CrIgJ<
OutOfMemoryError
このissueと関係があるかは不明ですが、開発時にたびたびOutOfMemoryErrorが発生しました。
[Add documentation for debugging thread leaks #4469] (https://github.com/playframework/playframework/issues/4469)
java.lang.OutOfMemoryError: Metaspace
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at sbt.ConsoleLogger.trace(ConsoleLogger.scala:178)
at sbt.AbstractLogger.log(Logger.scala:29)
at sbt.MultiLogger$$anonfun$dispatch$1.apply(MultiLogger.scala:35)
at sbt.MultiLogger$$anonfun$dispatch$1.apply(MultiLogger.scala:33)
at scala.collection.immutable.List.foreach(List.scala:318)
at sbt.MultiLogger.dispatch(MultiLogger.scala:33)
at sbt.MultiLogger.trace(MultiLogger.scala:26)
at sbt.State$.logFullException(State.scala:250)
at sbt.State$.handleException(State.scala:245)
at sbt.State$$anon$1.handleError(State.scala:205)
at sbt.MainLoop$.handleException(MainLoop.scala:105)
at sbt.MainLoop$.next(MainLoop.scala:101)
at sbt.MainLoop$.run(MainLoop.scala:91)
at sbt.MainLoop$$anonfun$runWithNewLog$1.apply(MainLoop.scala:70)
at sbt.MainLoop$$anonfun$runWithNewLog$1.apply(MainLoop.scala:65)
at sbt.Using.apply(Using.scala:24)
at sbt.MainLoop$.runWithNewLog(MainLoop.scala:65)
at sbt.MainLoop$.runAndClearLast(MainLoop.scala:48)
at sbt.MainLoop$.runLoggedLoop(MainLoop.scala:32)
at sbt.MainLoop$.runLogged(MainLoop.scala:24)
at sbt.StandardMain$.runManaged(Main.scala:53)
Error during sbt execution: java.lang.OutOfMemoryError: Metaspace