LoginSignup
101
107

More than 5 years have passed since last update.

Play Framework (java)で簡単な検索アプリケーションを開発する

Last updated at Posted at 2015-08-31

概要

Play Framework (java)の開発環境の構築を行い、続いて簡単な検索アプリケーションを開発します。
(Spring Bootで簡単な検索アプリケーションを開発するを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

参考

下記のサイトを参考にさせて頂きました。

完成図

actor_list.png

github

ソースコードはgithub rubytomato/actor-search-play-exampleにあります。

事前準備

インストール

Play Frameworkよりインストール用のアーカイブファイルをダウンロードします。
アーカイブファイルにはオンライン用(約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秒に設定しました。

activatorconfig.txt
# 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

プロジェクトの雛形を生成

プロジェクト名: 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プラグインが有効になります。

plugins.sbt
// 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にアクセスしてページが表示されることを確認します。

playframework_startup.png

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
  • ユーザー名: (空)
  • パスワード: (空)

h2_login.png

h2_ui.png

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コマンドを実行して変更を反映します。

update
[actor-search-play-example] $ update

MySQLを使用する場合、libraryDependenciesコマンドを実行してライブラリーにmysql-connector-javaが追加されているか確認します。

libraryDependencies
[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)を付加します。

messages
# 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

ビューヘルパーメソッドとしても使用します。

DateParser.java
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メソッドを実装することでバリデーションが行われるようになります。

ActorForm
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テーブルに対応するモデルクラスです。

Actor.java
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テーブルに対応するモデルクラスです。

Prefecture.java
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>&nbsp;</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
Form template helpers

Controller

Application.java

雛形として作成された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メソッドが見つからないというエラーが起きていると思いますが、テンプレートの変更が反映されていないためにおこります。

最初は下記の定義だったものを

index.scala.html
@(message: String)

このように引数を追加しました。

index.scala.html
@(message: String, result: List[models.Actor])

上記のテンプレートの変更がeclipse側に反映されていないため、renderメソッドの部分でエラーが起きます。

Application.java
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!"というエラーページが表示されますが

evolution.png

そのまま、"Apply this script now!"ボタンをクリックします。
これでデータベース上にテーブルが作成されます。

俳優一覧ページが表示されますが、データが未登録なので一覧は空の状態です。
メニューのinitリンクテキストをクリックすると初期データを登録します。

actor_empty.png

初期データの登録が終わると一覧ページへリダイレクトし初期データが表示されます。

actor_list.png

開発時

~ 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)をカスタマイズすることができます。

default_error.png

参考
Handling errors

テンプレートを作成

views/errorsパッケージを作成し、そこへエラーページ用のテンプレートを作成します。

badrequest.scala.html

badrequest
@(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

notfound
@(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は単純に文字列を返します。

CustomErrorHandler.java
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に上記のエラーハンドラーを指定します。

application.conf
# Handling errors
play.http.errorHandler = "controllers.CustomErrorHandler"

検証用のアクションを追加

故意にInternal Server Errorを発生させるアクションをApplicationコントローラーに追加します。

Application.java
public Result error500(Integer f) {
  if (f == 1) {
    throw new RuntimeException("Internal Server Error");
  }
  return ok();
}

routes

マッピングを設定します。

routes
GET     /error500                   controllers.Application.error500(f: Integer ?= 0)

確認

Bad Request

bad_request.png

Not Found

notfound.png

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) のコマンドライン文字列の制限
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を使用するとパッケージングの方法をカスタマイズすることができます。
下記の例は上記の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

%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

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
101
107
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
101
107