astamuse Lab

astamuse Labとは、アスタミューゼのエンジニアとデザイナーのブログです。アスタミューゼの事業・サービスを支えている知識と舞台裏の今を発信しています。

JDK12で変わったswitchに触れてみる。

お久しぶりです。開発部のyanagitaです。
4月に入り新社会人になられた方や新しい職場に移られた方、多くの職場で新たな出会いがあったのではないでしょうか?
アスタミューゼでは事業拡大に伴い、多くのエンジニア&デザイナーとの出会いを求めてます!

話を戻して、
今回は2019/03/19に正式リリースされたJDK12のswitchの新機能に触ってみたい思います。

switch文の新機能

switch文の新機能は大きく3つあります。

  • case句の複数条件の表記改善
  • break句の省略
  • switch文の式として利用

その前に

今回JDK12で公開されたswitch文の新機能はすべてプレビュー機能に分類されます。 switchの新機能を含むソースコードのコンパイル、実行、jshell上でプレビュー機能を使用するには--enable-previewオプションをつけてあげる必要があります。

コンパイル時

$ javac --enable-preview -source 12 Sample.java

コンパイル時は "--enable-preview" に合わせて "-source 12" が必要になります。(12はjdkバージョン)

実行時

$ java --enable-preview Sample

jshell起動時

$ jshell --enable-preview

※ 本記事ではjavaコマンドでjavaファイルを直接実行します。

case句の複数条件の表記改善

これまでswitch文で複数条件をマッチさせる場合、case句を条件分宣言する必要がありました。
慣れてしまうとそういうものだと思ってしまいますが、条件が増えてくるとcase句を多量に書かなくてはならず、スマートな記述が出来ていませんでした。
■ 従来のswitch文

public class SwitchSample {
    public static void main(String args[]) {
       switch (args[0]) {
           case "red" :  // マッチ条件だけcase句を記述する
           case "green" :
           case "blue" : System.out.printf("%sは色です。\n", args[0]); break;
           default : System.out.printf("条件に一致しません。[%s]\n", args[0]); break;
       }
    }
}

実行結果

$ java SwitchSample.java red
$ redは色です。
$ java SwitchSample.java blue
$ buleは色です。
$ java SwitchSample.java cat
$ 条件に一致しません。[cat]

新機能では、複数条件を1つのcase句にカンマ区切りで定義することが出来ます。

■ 新機能で書いたswitch文

public class SwitchSample {
    public static void main(String args[]) {
       switch (args[0]) {
           // マッチ条件をカンマ区切りで記述する
           case "red", "green", "blue" : System.out.printf("%sは色です。\n", args[0]); break;
           default : System.out.printf("条件に一致しません。[%s]\n", args[0]); break;
       }
    }
}

実行結果

$ java --enable-preview --source 12 SwitchSample.java red
$ redは色です。
$ java --enable-preview --source 12 SwitchSample.java blue
$ buleは色です。
$ java --enable-preview --source 12 SwitchSample.java cat
$ 条件に一致しません。[cat]

行数も減り、だいぶスッキリしました。

break句の省略

これまで条件にマッチしたcase句のみを実行する場合、case句の終わりにbleak句を記述してあげる必要がありました。新機能ではこのbreak句を省略することが可能になります。
省略する際は、これまでのcase 条件 :と記述していたところをcase 条件 ->とcase句の締めを「:」から「->」変更します。
■ 新機能で書いたswitch文

public class SwitchSample {
    public static void main(String args[]) {
       switch (args[0]) {
           case "red", "green", "blue" -> System.out.printf("%sは色です。\n", args[0]); // break; ← 省略
           default -> System.out.printf("条件に一致しません。[%s]\n", args[0]);  // break; ← 省略
       }
    }
}

ここで2点注意があります。

  1. break句の省略形式(case 条件 ->)と従来のcase句の形式を混在させたswithc文はコンパイルエラーとなるため、break句の省略を採用した場合はすべてのcase句でbreak省略形式で記述する必要があります。
  2. break句の省略形式で記述した場合、switch文内でreturnを使用することはできずコンパイルエラーとなります。

2はあまりないケースだと思いますが、修正される場合はお気をつけください。

switch文の式として利用

Scalaなどでifやmatchなど使っていると値を戻すことが当たり前になっているので、久しぶりにjavaに触れるとswitch文の手前で変数宣言をしてあげる必要があり、改めて面倒に感じることが多かったのですが、新機能ではswitch文を処理結果を返す式として扱うことができるようになります。

■ 従来のswitch文

public class SwitchSample {
    public static void main(String args[]) {
        String result = null; // switch文より前で値を受け取る変数を宣言する必要がありました。
        switch (args[0]) {
            case "red" :
            case "green" :
            case  "blue" : result = String.format("%sは色です。\n", args[0]); break;
            default : result = String.format("条件に一致しません。[%s]\n", args[0]); break;
        }
        System.out.print(result);
    }
}

■ 新機能で書いたswitch文

public class SwitchSample {
    public static void main(String args[]) {
        // switch文内の処理結果を受け取れるようになります。
        String result = switch (args[0]) {
            case "red" :
            case "green" : 
            case "blue" : break String.format("%sは色です。\n", args[0]); 
            default : break String.format("条件に一致しません。[%s]\n", args[0]);
        }; // ← switch文が式扱いになるため、「;」で終わること
        System.out.print(result);
    }
}

従来の記述に比べ、switch文が式化したことで変数への代入が一箇所にまとまり代入漏れが減るように感じます。
ここでは注意が3点あり、

  1. 値を返すのは「return 値」ではなく、「break 値」になります。returnを使用するとコンパイルエラーになります。
  2. 戻す型は、switch文から代入する変数の型のみとなります。それ以外の型になっているとコンパイルエラーとなります。
  3. 例のコメントアウトにもありますが、switch文を式として扱うため、switch文の終わりに「;」が必要となります。

全機能使用すると

■ switchの新機能を全部盛り込むと

public class SwitchSample {
    public static void main(String args[]) {
        // 従来の複数マッチ 
        String result = switch (args[0]) {
            case "red", "green", "blue" -> String.format("%sは色です。\n", args[0]);
            default -> String.format("条件に一致しません。[%s]\n", args[0]);
        };
        System.out.print(result);
    }
}

switch文がすごいスッキリしたと思います。個人的には式対応したことはすごく嬉しいです。

おまけ

従来のswitch文と新機能のswitch文で処理速度を比較してみました。
が!差は全くありませんでした。ネタにならず・・・

最後にもう一度

アスタミューゼではたくさんのエンジニア&デザイナをまだまだまだ募集しています。 気になる方は下からご応募下さい!新しい出会いをメンバー一同お待ちしてます!

Linuxでユーザアカウントを無効化するエトセトラ

f:id:astamuse:20190326183431p:plain

こんにちは。並河(@namikawa)です。

すっかり春に近づきつつあり、肌寒い日と陽気な日が入り乱れる近頃ですが、ヒノキ花粉のファーストパンチにやられてしまい、著しく生産性が低下しているここ2〜3日です。

さて、例によって今回もLinux運用環境での小ネタを書いておこうかと思いますが、今日はLinuxサーバでアカウントを残しつつ、アクセスは無効化したい時などの話題です。

今更感がある話題でもありますが、改めて備忘録的に書いておきたいと思います。

試した環境とか背景とか

Linuxでもディストリビューションによって、挙動に若干の差異があるかもしれないので、記載しておきますが、今回、"Ubuntu 18.04 LTS" を使って、試してみました。

会社やプロジェクトでサーバを運用していると、どうしてもプロジェクトから抜けたメンバーや退職者などで、ユーザを削除・無効化など棚卸ししたくなるケースが出てくると思います。

そんな場合に、素直にユーザ削除(userdel)できればよいのですが、事情によってはそのアカウントをしばらく残しておきたい等もあるかと思いますので、そんな場合どうしたらよいのか、が今回のお題です。

あと、普段はあまり使わないですが、わかりやすくするために、今回はあえてSSHでのパスワード認証を許可(PasswordAuthentication yes)しています。

アカウントをロックする

一番簡単なのは、アカウントをロックしちゃうことでしょうか。

メンバーがプロジェクトに戻ってきた場合などでも、簡単に戻せます。

実行は以下のように passwd コマンドを使うと簡単です。

( testuser は対象のユーザ名です )

# passwd -l testuser

確認は、以下の通りで、2列目が "L" になっていれば Lock されたことを意味します。

# passwd -S testuser
testuser L 03/22/2019 0 99999 7 -1

他、/etc/shafow のパスワードフィールドの先頭にアスタリスク(!)が2つ付いていると Lock を意味します。

ログインシェルを無効にする

あまりメンバーのアカウントをシステムアカウント的に使うことはないかと思うのですが、やむを得ない事情でプロセスの起動アカウントとして使いたいが、ログインはさせたくない、といったこともあるでしょう。(無いようにはしたいですが...)

そういった場合は、ログインシェルを無効なものに変更することで、ログインできなくすることもできます。

この話をし始めると、よく /bin/false/sbin/nologin のどちらを使うのかという議論になります。

で、細かい違いはあれど、 /etc/shells で定義されたものがログインシェルとして正当なプログラムと扱われるわけで、今回テストしている環境である Ubuntu 18.04 LTS では、 /etc/shells に上記2つはどちらも含まれていませんでした。

ちなみに最近の Ubuntu では /sbin/nologin ではなく /usr/sbin/nologin にプログラムが存在します。

/usr/sbin/nologin

# usermod -s /usr/sbin/nologin testuser

設定はこんな感じで実施。

$ sudo su - testuser
This account is currently not available.

su しようとすると not available と警告が出て、suできない感じです。

$ ssh testuser@servername
testuser@servername's password:
Welcome to Ubuntu 18.04.2 LTS (GNU/Linux 4.15.0-1028-gcp x86_64)

・・・・・

Last login: Fri Mar 22 12:50:33 2019 from 122.219.65.217
This account is currently not available.
Connection to servername closed.

リモートから ssh で接続しようとしても同様の表示。ただし motd のメッセージが出力されます。 (こちらについての対応は後述。)

/bin/false

# usermod -s /bin/false testuser

先ほど同様、こんな感じで設定できます。

$ su - testuser
Password:

パスワード入力するもログインはできません。

$ ssh testuser@servername
testuser@servername's password:
Welcome to Ubuntu 18.04.2 LTS (GNU/Linux 4.15.0-1028-gcp x86_64)

・・・・・

Last login: Fri Mar 22 12:49:16 2019 from 122.219.65.217
Connection to servername closed.

リモートから ssh で接続しようとしても motd のメッセージが表示された後に切断されます。

motd のメッセージが表示されないようにする

これについては man 1 login を読むと以下の記載があります。

   $HOME/.hushlogin
      Suppress printing of system messages.

ホームディレクトリ配下に .hushlogin という空ファイルを作っておくと、システムメッセージの抑制ができます。

# sudo -u testuser touch /home/testuser/.hushlogin

というわけで、該当ユーザ配下に touch でファイルを置いて、、、

$ ssh testuser@servername
testuser@servername's password:
Connection to servername closed.

試してみると、表示されなくなりました。ここまではやっておいた方が良さそうです。

さいごに

最後に、毎度で恐縮なPRタイムですが、弊社ではエンジニア・デザイナーを絶賛大募集しておりますので、少しでも気になれば、カジュアルにランチでもしながらお話ししましょうー!

弊社は、最近オフィスの引っ越しを行ったのですが、オフィスビルの隣には有名なカレー屋(ボンディ)さんがありますので、美味しいカレーでも食べながら是非!

少しでもご興味があればお手数ですが (@namikawa) まで気軽にDM等いただければと思います。

それでは!=͟͟͞͞(๑•̀=͟͟͞͞(๑•̀д•́=͟͟͞͞(๑•̀д•́๑)=͟͟͞͞(๑•̀д•́

@namikawa が書いた過去記事)

働きたくないのでコードを自動生成してみたよ

こんにちは、開発部のNishikawaです。

今日はScala と Play frameworkを使ってコードの自動生成を行なった時の話をまとめたいと思います。

どうしてこの記事を書こうと思ったか

Webアプリケーションの開発をしていると、よく本質じゃないところに時間がかかることがままあります。

例えば、データアクセス部分とか、コントローラの実装部分です。プログラムとしての責務がちゃんと別れれば別れるほど刺身たんぽぽ感が出てくるので、心を無にしてひたすらコピペとクラス名やメソッド名などの修正を行う人が多いと思います。

しかし、心を無にして釈迦のような志のもと作業を行なっていても所詮は人間なのでミスは起こります。そうすると、釈迦のような志から一変、鬼の形相でひたすらバグを潰しさらに時間がなくなるという無駄が無駄を産む状況になりがちです。

そういうのは一人で仕事を進める上ではとても無駄だし、本当に避けたい状況です。そこで目をつけたのはコードの自動生成です。

実際どういう作戦で何をしたのか

一括りにコードの自動生成と言っても、使うにはやはり一定の条件があります。今回Webアプリケーションを作成するにあたり以下のような構成のプログラムの実装を行いました。

  • ビュー
実装はhtmlとjavascriptで行い、バックエンド側のAPIから情報を取得しレンダリングする。
  • コントローラ
REST APIの口を設けてView層からのリクエストを受け付ける
  • モデル
データへのアクセスはSlickを使用し、取得した値をプレゼンテーション層へ渡すためのモデルに移し替える

今回はコードを自動生成するにあたり以下のsbtプラグインとライブラリを使用しました。

  • sbt-swagger-codegen

https://github.com/unicredit/sbt-swagger-codegen

  • slick-codegen

http://slick.lightbend.com/doc/3.2.0/code-generation.html

  • flyway-play

https://github.com/flyway/flyway-play

sbt-swagger-codegen

これは、sbtのプラグインでswagger specからPlay framework のコントローラとroutesファイル、json返却用のモデルを生成してくれます。

slick-codegen

これはデータベースにある実際のテーブル構造からSlickのコードを自動生成してくれます。

flyway-play

これはflywayのPlay framework用ライブラリです。これを使ってSQLを管理しました。

コード生成

前提

実際にコード生成を行う前に前提として、今回コード生成に使用したプロジェクトのディレクトリ構成を記載します。

example-web      =============================================== > プロジェクトディレクトリ
├── app
│   ├── ErrorHandler.scala
│   ├── Module.scala
│   ├── com
│   │   └── astamuse
│   │       └── ・・・
│   └── infrastructure
│       └── models
│           └── postgresql
│               └── Tables.scala  ============================== > 今回自動生成したSlickコード。
├── build.sbt
├── conf
│   ├── application.conf
│   ├── db
│   │   └── migration
│   │       └── default
│   │           ├── V1.0.0__example_database_web_app.sql    == > flayway用のDDL その1。
│   │           └── V1.0.1__example_database_dummy_api.sql  == > flayway用のDDL その2。
│   ├── logback.xml
│   └── routes  ================================================ > 今回sbt-swagger-codegenで自動生成したroutesファイル。
├── docs
│   └── swagger
│       └── ExampleWebAPIController.yaml
├── project
│   ├── build.properties
│   ├── plugins.sbt
│   ├── project
│   │   └── target
│   └── target
├── target
│   ├── scala-2.12
│   │   ├── classes
│   │   ├── resolution-cache
│   │   ├── routes
│   │   ├── src_managed
│   │   │   └── main  ========================================== > 自動生成されたコードが本来格納されるディレクトリ。今回sbt-swagger-codegenで自動生成したコントローラやモデルなどのコードが格納される。
│   │   │       └── swagger
│   │   │           └── codegen
│   │   │               ├── Model.scala
│   │   │               ├── controller
│   │   │               │   └── ExampleWebAPIController.scala
│   │   │               └── json
│   │   │                   └── package.scala
│   │   └── test-classes
│   ├── streams
│   └── test-reports
└── test  ====================================================== > テストディレクトリ
    └── generator
        └── slick
            └── SlickCodeGenerator.scala  ====================== > 今回はテストコードでslickのコードを生成するためのプログラムを書きました。

Slickコードの自動生成

ここからは、実際にSlickコードを自動生成する方法について述べます。

Slickのコード生成をするためにsbtに以下を書き込みライブラリを追加します。

libraryDependencies ++= Seq(
  "com.typesafe.slick" %% "slick-codegen" % "3.3.0"
  ,"org.flywaydb" %% "flyway-play" % "5.2.0"
)

追加したらプロジェクトディレクトリ配下の「conf/db/migration/default」にflywayのファイル命名規則に則りDDLを作成します。

作成したら以下のコードをテストディレクトリに作成します。

注意)今回は諸々省くためにテストコードに実装を行なっております。

package generator.slick

import org.flywaydb.core.Flyway
import org.scalatest.WordSpec
import play.api.Logger
import slick.jdbc.H2Profile.api._
import slick.codegen.SourceCodeGenerator

class SlickCodeGenerator extends WordSpec {

  private val logger: Logger = Logger("verification")

  "Generated code due to access to postgresql for slick." in {
    logger.debug("Start slick verification.")

    // Basic information
    val slickProfile = "slick.jdbc.PostgresProfile"
    val jdbcDriver = "org.postgresql.Driver"
    val url = "jdbc:postgresql://localhost:5432/example_db"
    val user = "admin"
    val password = ""
    val outputDirectory = "app"
    val pkg = "infrastructure.models.postgresql"

    // Execute migration for flyway
    val flyway: Flyway = Flyway.configure().dataSource(url , user, password).load()
    flyway.migrate()

    // Execute code generate for slick
    val args: Array[String] = Array( slickProfile, jdbcDriver, url, outputDirectory, pkg, user, password )
    SourceCodeGenerator.main(args)

    logger.debug("End slick verification.")

    true === true
  }
}

以上を記載したら、ローカル環境でPostgreSQLを起動し、コードに記載した内容でログインできることを確認したのちに実行します。

$ sbt "testOnly generator.slick.SlickCodeGenerator"

実行が完了したら、プロジェクトディレクトリの「app/infrastructure/models/postgresql」にコードが生成されていると思います。

Swagger Specからのroutesファイルおよびコントローラとモデルの自動生成

次はSwagger Specからroutesファイルとコントローラ、モデルを生成していきます。

sbtに以下のプラグインを追加します。

  • project/plugin.sbt
addSbtPlugin("eu.unicredit" % "sbt-swagger-codegen" % "0.0.11")

プラグインを追加したら、プロジェクトにプラグインを有効にし、 Play frameworkのディレクトリレイアウトのプラグインは無効にします。

  • build.sbt
lazy val `example-web` = (project in file("."))
  .enablePlugins(PlayScala)
  .disablePlugins(PlayLayoutPlugin)
  .enablePlugins(SwaggerCodegenPlugin)

・・・
(中略)
・・・

scalaSource in Compile := baseDirectory.value / "app"
resourceDirectory in Compile := baseDirectory.value / "conf"
scalaSource in Test := baseDirectory.value / "test"
resourceDirectory in Test := baseDirectory.value / "conf"

// Custom Settings
// For swagger
swaggerCodeProvidedPackage := "com.astamuse.example"
swaggerSourcesDir := file("docs/swagger")
swaggerGenerateServer := true

swaggerModelCodeTargetDir := file("target/scala-2.12/src_managed/main")
swaggerServerCodeTargetDir := file("target/scala-2.12/src_managed/main")

以下を記載したら、プロジェクトディレクトリ配下に「docs/swagger」ディレクトリを作成し、そこにSwagger Specを配置します。

諸々の配置が完了したら以下のコマンドを実行します。

$ sbt swaggerRoutesCodeGen
$ sbt compile

コマンドの実行が完了すると、「target/scala-2.12/src_managed/main」配下にコードが作成されそれを含めた状態でアプリケーションをコンパイルしてくれます。

まとめると・・・

いかがslick-codegenとsbt-swagger-codegenを利用するために使った設定ファイルの内容です。

  • project/plugin.sbt
logLevel := Level.Warn

resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/"

addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.7.0")
addSbtPlugin("eu.unicredit" % "sbt-swagger-codegen" % "0.0.11")
  • build.sbt
//SwaggerCodegenPlyginのインポート
import eu.unicredit.swagger.SwaggerCodegenPlugin

name := "example-web"

version := "1.0.0"

lazy val `example-web` = (project in file("."))
  .enablePlugins(PlayScala)
  .disablePlugins(PlayLayoutPlugin)
  .enablePlugins(SwaggerCodegenPlugin)

resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases"

resolvers += "Akka Snapshot Repository" at "http://repo.akka.io/snapshots/"

scalaVersion := "2.12.2"

libraryDependencies ++= Seq(
  ehcache
  ,ws
  ,guice
  ,"com.typesafe.play" %% "play-slick" % "4.0.0"
  ,"com.typesafe.slick" %% "slick-codegen" % "3.3.0"
  ,"org.flywaydb" %% "flyway-play" % "5.2.0"
  ,"org.postgresql" % "postgresql" % "42.2.5"
  ,"org.scalatestplus.play" %% "scalatestplus-play" % "4.0.0" % Test
  ,"com.h2database" % "h2" % "1.4.197" % Test
)

unmanagedResourceDirectories in Test +=  baseDirectory ( _ /"target/web/public/test" ).value

testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "exclude", "SLICK_CODE_GENERATE")

scalaSource in Compile := baseDirectory.value / "app"
resourceDirectory in Compile := baseDirectory.value / "conf"
scalaSource in Test := baseDirectory.value / "test"
resourceDirectory in Test := baseDirectory.value / "conf"

// Custom Settings
// For swagger
swaggerCodeProvidedPackage := "com.astamuse.example"
swaggerSourcesDir := file("docs/swagger")
swaggerGenerateServer := true

swaggerModelCodeTargetDir := file("target/scala-2.12/src_managed/main")
swaggerServerCodeTargetDir := file("target/scala-2.12/src_managed/main")

以下に躓きやすいところを記載しておきます。

なぜPlay frameworkのディレクトリレイアウトのプラグインを無効にするのか。

Play frameworkのディレクトリレイアウトを利用するとコントローラのパッケージが「app/controllers」配下に固定されるので、コード生成したコントーラを読み込んでくれないためです。(もしかしたら他にやり方があるかもしれない)

なぜPostgreSQLを使ってコード生成するのか

h2を使って生成を試みたが、なぜかうまくいかなかったので泣く泣くPostgreSQLを利用しました。今後はh2で生成できるようにするつもりです。

今後に向けての課題

今回はSwaggerでの生成部分については「target/scala-2.12/src_managed/main」にコードを吐き出しましたが、Slickについてはそれができませんでした。 理由はh2を利用してのコード生成ができなかったためです。

なぜ、h2が使えないと「target/scala-2.12/src_managed/main」にコードが吐けないかというと、このディレクトリは毎回コンパイルのたびにコードを生成するためのものを配置するようにできております。そのため、何度コンパイルしてもコード生成を成功させてコードを配置しなければならないのですが、flywayを使っている以上、重複してDDLを実行するとエラーになります。そのため今回のようにコードの生成を「app」配下にし固定することでこの問題を回避しました。

今後はここの技術課題を克服してDAO部分も完全にコード管理しないようにしていきたいと思います。

まとめ

今回はコード生成に向けて色々と作り込みを行いました。この方法はあくまで少人数・・・というか個人で何かものを短期間で作らないといけない場合に有効だと思っております。

ピーキーな実装が求められるところについてはあまりお勧めできませんが、人数に関係なくコード実装量を減らしてくれる上、コードの管理量も減るため、試しに採用してみてはいかがでしょうか。

以上

Copyright © astamuse company, ltd. all rights reserved.