Windows10+WSL (Windows Subsystem for Linux) の環境整備

開発業務用にWin10マシンが支給されました。
私はいままでWin7マシンを用い、作業用のコンソールとして、Cygwinを使っていましたが、これを機に以前より楽しみにしていたWSL+Ubuntu環境へ移行することにしました。

方針

WSLの使い方として Java, WebLogic Server, Oracle Database などは Windows にインストールし、Linuxにはインストールしない方針にしました。
これは、業務関係者のPC環境としてWindowsが主流であり、いつでもその構成で動かせる環境にしておくことで、問い合わせを受けた際のトラブルの再現性を高めたいためです。

コンソールの文字化け

この方針で試しにLinuxで sqlplus を起動してみると文字化けしました。

$ sqlplus.exe

SQL*Plus: Release 11.2.0.2.0 Production on �� 11�� 27 18:58:01 2018

Copyright (c) 1982, 2014, Oracle.  All rights reserved.

���[�U�[�����͂��Ă�������:

Linux側のコンソールはUTF-8を期待していますが, Windows側で実行される sqlplus.exe は SHIFT-JIS の入出力で動作するため文字化けしています。
Linux の .bashrc に以下を追加して解決しました。

# WSLENVで指定した変数は、Windows側に伝搬します
export WSLENV=NLS_LANG
export NLS_LANG=Japanese_Japan.AL32UTF8
$ sqlplus.exe

SQL*Plus: Release 11.2.0.2.0 Production on 火 1127 19:05:05 2018

Copyright (c) 1982, 2014, Oracle.  All rights reserved.

ユーザー名を入力してください: sys
パスワードを入力してください: abcdefg

ただし、パスワード入力時に文字がマスクがされず、むき出しになります。完璧とはいかないようです。

.exe をつけずにコマンド実行

次は、コマンド名に .exe をつけなくても実行できるようにします。
Linuxから Windows にインストールされた sqlplus を実行する場合、 sqlplus.exe のように.exeをつけないとコマンドが見つからずにエラーになります。
一方で、通常 Linux 向けに提供されるスクリプトは、この .exe を含まないない形でコマンドを呼び出す記述がなされています。
スクリプトの手直しは避けたいので、パスの通ったディレクトリに、次のようなシンボリックリンクを作成して回避することにしました。
(ちなみに当初は .bashrc で alias を指定して解決しようと試みましたが、スクリプトに記述されたコマンドに対してaliasは無効であり、意味がありませんでした)

sqlplus -> /mnt/c/app/db/app/oracle/product/11.2.0/server/bin/sqlplus.exe
subl -> /mnt/c/Program Files/Sublime Text 3/subl.exe
docker -> /mnt/c/Program Files/Docker/Docker/resources/bin/docker.exe

以下は $JAVA_HOME/bin 配下のコマンドと、コマンドリストファイル exe-list からシンボリックリンクを作成する自作スクリプトです。
java, javac は文字化けを回避するオプションを指定するためため、シンボリックリンクではなく、bashスクリプトで呼び出す形にしています。

.exeへのシンボリックリンク+呼び出しスクリプトの作成処理
#!/bin/bash

SCRIPT_DIR=$(cd $(dirname $0); pwd)

COMMAND_LIST_FILE=$SCRIPT_DIR/exe-list
BIN_DIR=$SCRIPT_DIR

create_link(){
  while read CMD ARGS || [ -n "${CMD}" ]; do
    # remove current file
    [ -e $BIN_DIR/$CMD ] && rm $BIN_DIR/$CMD
    if [ -z "$ARGS" ]; then
      # create symbolic link
      which $CMD.exe && ln -s "`which $CMD.exe`" $BIN_DIR/$CMD || echo "[ERROR] $CMD"
    else
      # create bash script
      which ${CMD}.exe && echo -e "#!/bin/bash\n`which ${CMD}.exe` $ARGS \$*" > $BIN_DIR/${CMD} && chmod u+x $BIN_DIR/${CMD}
    fi
  done
}

ls $JAVA_HOME/bin | grep ".exe" |  sed -e "s/\.exe//" | create_link
cat $COMMAND_LIST_FILE | create_link
上記スクリプトの入力ファイル: exe-list
sqlplus
docker
docker-compose
docker-credential-wincred
docker-machine
kubectl
notary
subl
explorer
java -Dfile.encoding=UTF-8
javac -J-Dfile.encoding=UTF-8
一旦これでOK

これで、WSL 環境がある程度整いました。
今後も文字化けやスクリプトエラーが起きる可能性があり、問題を見つけては潰すといったモグラ叩き感は否めませんが、よく使うコマンドに関しては許容できるレベルの使い勝手になりました。

vaadin-notification で通知パネルの背景色を動的に変更する

Vaadin Flow(1.0.0 Beta 3)で、通知パネルの色を動的に変更する方法を調べました。
通知コンポーネント vaadin-notification では、コンテンツ表示用のパネルに相当する部分は vaadin-notification-card コンポーネントで実装されています。
そのため、vaadin-notification-card コンポーネントに対して ThemableMixin でスタイルをあてるとパネルの背景色を変えられます。

<!-- src/theme/ex-notification-style.html -->
<dom-module id="ex-notification-style" theme-for="vaadin-notification-card">
  <template>
    <style>
      :host {
        --lumo-base-color: blue; /** 有効 */
      }
      [part="overlay"] {
        color: white;
        background-color: red; /** 効かない。セレクタを div[part="overlay"]にすると有効になる */
      }
    </style>
  </template>
</dom-module>
<!-- 通知を利用するコンポーネントでは、通知コンポーネントのMixinスタイルをインポート -->
<link rel="import" href="/frontend/src/theme/ex-notification-style.html">
<dom-module>
  ...
</dom-module>
// Javaで通知を表示する
Notification.show("Hello!", 5000, Position.TOP_STRETCH);

f:id:yoshiob:20180411170834p:plain

しかし、この方法では、Javaから通知パネルの背景色を動的に変更することができません。
vaadin-notification では、その内部で、通知パネルに相当する vaadin-notification-card コンポーネントのDOMを、body 配下に作成した vaadin-notification-container に移動させています。
そのため、vaadin-notification のスタイルクラスを動的に変えても、 vaadin-notification-card と親子関係がないので vaadin-notification-card に適用されるスタイルを変えることができません。
また、このDOM操作のためか vaadin-notification-card は、Javaから扱うことができませんでした。 (正確には何らかの手段があるのかもしれませんが...)

vaadin-notification-card に適用するスタイルを動的に変える手立てが見つけられないので、独自コンポーネントを作成して、Java側から背景色を指定できるように拡張します。

Javaでは、Notificationを継承し、プロパティ notificationType を指定できるようにします。

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.PropertyDescriptor;
import com.vaadin.flow.component.PropertyDescriptors;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.dependency.HtmlImport;
import com.vaadin.flow.component.notification.Notification;

@Tag("ex-notification")
@HtmlImport("src/component/ex-notification.html")
public class ExNotification extends Notification {

    private static PropertyDescriptor<String, String> NOTIFICATION_TYPE = PropertyDescriptors.propertyWithDefault("notificationType", "");

    public ExNotification() {}

    public ExNotification(String text) {
        super(text);
    }

    public ExNotification(String text, int duration) {
        super(text, duration);
    }

    public ExNotification(String text, int duration, Position position) {
        super(text, duration, position);
    }

    public ExNotification(Component... components) {
        super(components);
    }

    public String getNotificationType() {
        return get(NOTIFICATION_TYPE);
    }

    public void setNotificationType(String notificationType) {
        set(NOTIFICATION_TYPE, notificationType);
    }

    public static ExNotification showAsInfo(String text, int duration,
            Position position) {
        ExNotification notification = new ExNotification(text, duration, position);
        notification.setNotificationType("info");
        notification.open();
        return notification;
    }

    public static ExNotification showAsError(String text, int duration,
            Position position) {
        ExNotification notification = new ExNotification(text, duration, position);
        notification.setNotificationType("error");
        notification.open();
        return notification;
    }

}

HTMLでは、プロパティ notificationType を、vaadin-notification-card の属性に伝搬させます。
また、CSSで、notification-type属性に応じて背景色が切り替わるようにします。

<!-- src/component/ex-notification.html -->
<link rel="import" href="/frontend/bower_components/vaadin-notification/vaadin-notification.html"/>
<dom-module id="ex-notification-style" theme-for="vaadin-notification-card">
  <template>
    <style>
      :host([notification-type="info"]) [part="overlay"] {
        color: white;
        background-color: blue;
      }
      :host([notification-type="error"]) [part="overlay"] {
        color: white;
        background-color: red;
      }
    </style>
  </template>
</dom-module>
<dom-module id="ex-notification">
  <script>
    class ExNotification extends Vaadin.NotificationElement {
      static get is() {
        return 'ex-notification'
      }
      static get properties() {
        return {notificationType: {type: String, observer: '_changeNotificationType'}};
      }
      _changeNotificationType(notificationType) {
        const card = this._card || this.$['vaadin-notification-card'];
        if(notificationType){
          card.setAttribute('notification-type', notificationType);
        } else {
          card.removeAttribute('notification-type');
        }
      }
    }
    customElements.define(ExNotification.is, ExNotification);
  </script>
</dom-module>

このコンポーネントを利用すると以下のようなコードで、背景色を変えてメッセージを表示することができるようになりました。

ExNotification.showAsInfo("Info message!", 5000, Position.TOP_STRETCH);
ExNotification.showAsError("Error message!", 5000, Position.TOP_STRETCH);

f:id:yoshiob:20180411170839p:plain

Vaddin Flow は、現時点ではまだβ版です。
ニーズが高く、不足している機能はいずれ提供されそうな気がしますが、今の所は、独自コンポーネントによる拡張で補うのが良さそうです。

なお、私自身は、Vaadin初学者で、まだドキュメントを読みながら試行錯誤している段階です。
もっと良いやり方があれば、提示頂けると大変助かります。

vaadin-date-picker の拡張 (独自コンポーネント作成)

Vaadin Flow (beta3) を勉強しています。その試行錯誤の記録です。

 vaadin-date-picker では日付書式を変更するのに、JavaScripti18n.formatDate関数や i18n.parseDate 関数を書く必要があるようです。面倒なので、属性で指定できるように独自のカスタムコンポーネントを作ってみました。

Java側では、DatePickerを継承するだけ。

@Tag("ex-date-picker")
@HtmlImport("src/component/ex-date-picker.html")
public class ExDatePicker extends DatePicker {
}

HTML(src/component/ex-date-picker.html)では、コンポーネントにプロパティlocale, format, pickerTitle を新設し、それを元に i18n オブジェクトを構築するようにしました。
とりあえず日本語しか対応してません。

<link rel="import" href="/frontend/bower_components/vaadin-date-picker/vaadin-date-picker.html"/>
<script src="/frontend/bower_components/momentjs/2.21.0/moment.js"></script>
<script src="/frontend/bower_components/momentjs/2.21.0/locale/ja.js"></script>
<script src="/frontend/bower_components/moment-jdateformatparser/1.0.2/moment-jdateformatparser.js"></script>
<dom-module id="ex-date-picker">
  <script>
    class ExDatePicker extends Vaadin.DatePickerElement {
      static get is() {
        return 'ex-date-picker'
      }
      static get properties() {
        return {locale: String, format: String, pickerTitle: String};
      }
      static get observers() {
        return ['_setupI18n(locale, format, pickerTitle)'];
      }
      ready() {
        super.ready();
      }
      _setupI18n(locale, format, pickerTitle) {
        // TODO: 日本語しか対応してない。ロケール毎に i18n オブジェクトやデフォルトフォーマットを切り替える必要がある
        const pickerLocale = locale || 'ja';
        const javaDateFormat = format || 'yyyy/MM/dd';
        const pickerTitleFormat = pickerTitle || '{fullYear}年 {monthName}';
        const dateFormat = moment().toMomentFormatString(javaDateFormat);
        this.i18n = {
          week: '週',
          calendar: 'カレンダー',
          clear: 'クリア',
          today: '本日を設定',
          cancel: 'キャンセル',
          firstDayOfWeek: 0,
          monthNames: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
          weekdays: ['日曜日', '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日'],
          weekdaysShort: ['日', '月', '火', '水', '木', '金', '土'],
          formatDate: function(dateInputObject) {
            return moment(dateInputObject).locale(pickerLocale).format(dateFormat);
          },
          formatTitle: function(monthName, fullYear) {
            return pickerTitleFormat.replace('{fullYear}', fullYear).replace('{monthName}', monthName);
          },
          parseDate: function(dateString) {
            const date = moment(dateString, dateFormat).locale(pickerLocale).toDate();
            return {day: date.getDate(), month: date.getMonth(), year: date.getFullYear()};
          }
        };
      }
    }
    customElements.define(ExDatePicker.is, ExDatePicker);
  </script>
</dom-module>

ここで使用している moment.js などは、mavenで次の依存を追加して導入しています。

        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>momentjs</artifactId>
            <version>2.21.0</version>
        </dependency>
        <dependency>
            <groupId>org.webjars.bower</groupId>
            <artifactId>moment-jdateformatparser</artifactId>
            <version>1.0.2</version>
        </dependency>

これで部品ができました。
次のように使います。

<ex-date-picker format="yyyy年M月d日" locale="ja"></ex-date-picker>

これで日付を日本語で表示できました。

f:id:yoshiob:20180406152651p:plain
vaadin-date-picker拡張コンポーネント

こんな感じでカスタムコンポーネントを作れることがわかりました。
それにしても vaadin-date-picker のUXはとても優れていますね。