読者です 読者をやめる 読者になる 読者になる

JavaFXで印刷

/IT系 /Java

年賀状印刷をMS Word+MS Excelの組み合わせでやっていたが、毎年印刷方法に泣かされるので簡単な自前アプリで出来ないものかと、まずは試してみようと思う。
手近な印刷用SDKとしては.NETかJavaがあったが、迷わずJavaで試す。
Javaには古くからあるAWTやJava Print Serviceなどがあるが、それらではなくJavaFX 8から追加になったJavaFX printing APIを使う。

サンプルアプリ

簡単なJavaFXのUIを使っていくつかのAPIを試して行く。

以下のようなサンプルアプリを作った。
f:id:nave_kazu:20170106185642p:plain

上のツールバーAPIを呼び出す各種ボタンと、そのツールバーの右側に部数を入力するエリア。
下の「Log area」にAPIを呼び出した結果をテキストで出力するエリア。
真ん中にアンカーペインを置いて、その中にラベルで「Hello JavaFX Print.」を表示している。
今回はこのアンカーペインを印刷する。

Scene Builderで見たHierarchyは下記の通り。
f:id:nave_kazu:20170106185651p:plain

画面の表示方法やイベント処理方法は今回の範囲外なので適当に。
まずはここまでのソースとFXMLの内容は下記の通り。

まずはFXML。
パスは「/fxml/Main.fxml」

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>

<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="500.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="tools.javafx_printsample.MainController">
   <top>
      <ToolBar prefHeight="40.0" prefWidth="200.0" BorderPane.alignment="CENTER">
        <items>
          <Button fx:id="defaultPrinterButton" mnemonicParsing="false" onAction="#handleDefaultPrinterButtonAction" text="DefaultPrinter" />
            <Button fx:id="allPrintersButton" mnemonicParsing="false" onAction="#handleAllPrintersButtonAction" text="AllPrinters" />
            <Button fx:id="printButton" mnemonicParsing="false" onAction="#handlePrintButtonAction" text="Print" />
            <Separator orientation="VERTICAL" />
            <Label text="Copies" />
            <TextField fx:id="copiesField" alignment="CENTER_RIGHT" prefWidth="50.0" text="1" />
        </items>
      </ToolBar>
   </top>
   <bottom>
      <TextArea fx:id="logArea" prefHeight="100.0" promptText="Log area" BorderPane.alignment="CENTER" />
   </bottom>
   <center>
      <AnchorPane fx:id="printCanvas" BorderPane.alignment="CENTER">
         <children>
            <Label text="Hello JavaFX Print." />
         </children>
      </AnchorPane>
   </center>
</BorderPane>


続いてmain関数があるApp.java

package tools.javafx_printsample;

import java.io.IOException;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class App extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws IOException {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Main.fxml"));
        loader.load();
        Parent root = loader.getRoot();
        Scene scene = new Scene(root);
        primaryStage.setTitle("Print sample");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

}


最後に画面のコントローラ。

package tools.javafx_printsample;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;

public class MainController {
    @FXML Button defaultPrinterButton;
    @FXML Button allPrintersButton;
    @FXML Button printButton;
    @FXML TextField copiesField;
    @FXML AnchorPane printCanvas;
    @FXML TextArea logArea;

    @FXML
    public void handleDefaultPrinterButtonAction(ActionEvent event) {
    }

    @FXML
    public void handleAllPrintersButtonAction(ActionEvent event) {
    }

    @FXML
    public void handlePrintButtonAction(ActionEvent event) {
    }

    private void addLog(String text) {
        logArea.appendText(text+"\n");
    }
}

一番下の addLog メソッドはログエリアに引数の文字列と改行を追記する "手抜き" ユーティリティメソッド。

ここまでは下準備で、以降ではこのアプリに印刷用の処理を入れて行く。

プリンタの選択

印刷をするには印刷を実行させたいプリンタを選択しないとならない。
まずはプリンタ情報の取得を試してみる。

JavaFXの印刷APIは、パッケージで言うと「javafx.print」に入っている。
このパッケージのクラスを見ると、そのものずばりな「Printer」クラスがあり、プリンタの情報はこのクラスを通じて取得できる。

Printerクラスから各種情報を取得してログエリアに出力するメソッドをMainControllerに用意。

    private void outputPrinterInformation(Printer printer) {
        addLog("*** Name:"+printer.getName());

        // ページ・レイアウトの情報を取得して出力
        PageLayout pageLayout = printer.getDefaultPageLayout();
        PageOrientation pageOrientation = pageLayout.getPageOrientation();
        addLog("    PageLayout:");
        addLog("        TopMargin:"+pageLayout.getTopMargin());
        addLog("        LeftMargin:"+pageLayout.getLeftMargin());
        addLog("        RightMargin:"+pageLayout.getRightMargin());
        addLog("        BottomMargin:"+pageLayout.getBottomMargin());
        addLog("        PrintableHeight:"+pageLayout.getPrintableHeight());
        addLog("        PrintableWidth:"+pageLayout.getPrintableWidth());
        addLog("        PageOrientation:"+pageOrientation.name());

        // 用紙の情報を取得して出力
        Paper paper = pageLayout.getPaper();
        addLog("        Paper:");
        addLog("            Name:"+paper.getName());
        addLog("            Height:"+paper.getHeight());
        addLog("            Width:"+paper.getWidth());
    }

デフォルトプリンタの取得

Windowsで言う「通常使うプリンター」を取得するには、Printerクラスのスタティックメソッド getDefaultPrinter() を使う。
このアプリを試したWindows PCのプリンタ設定は下記のようになっている。
f:id:nave_kazu:20170106185657p:plain

3台登録されていて、「通常使うプリンター」にはPDF出力用のCubePDF、あとはWindows7標準のFAXとXPS Writerという構成。

「DefaultPrinter」ボタンをクリックした時のイベントハンドラに対して、getDefaultPrinter() の結果を outputPrinterInformation() に渡す処理を記述する。

    @FXML
    public void handleDefaultPrinterButtonAction(ActionEvent event) {
        Printer defaultPrinter = Printer.getDefaultPrinter();
        outputPrinterInformation(defaultPrinter);
    }


アプリを起動して実行し、「DefaultPrinter」ボタンをクリックした時の実行結果は下記の通り。

*** Name:CubePDF
    PageLayout:
        TopMargin:54.0
        LeftMargin:54.0
        RightMargin:54.0
        BottomMargin:54.0
        PrintableHeight:734.0
        PrintableWidth:487.0
        PageOrientation:PORTRAIT
        Paper:
            Name:A4
            Height:842.0
            Width:595.0

プリンタの名前が「CubePDF」で、四方のマージンの情報や、用紙の情報が出力された。
現在「通常使うプリンター」に設定しているのは「CubePDF」なので、意図した結果が得られた。

ちなみに数値の単位は「ポイント」で、これはDTPの用語。
1ポイントは「1/72 インチ」なので、上のA4用紙の高さ「842.0」は
842/72で約「11.69 インチ」。1インチは25.4mmなので、mmにすると「297mm」。
WikipediaによるとA4用紙のサイズは「210mm×297mm」だそうなので、842ポイントは297mmで正しいというのが分かる。

プリンタの検索

現在インストールされているプリンタ全ての情報を取得するには、Printerクラスのスタティックメソッド getAllPrinters() を使う。

「AllPrinters」ボタンをクリックした時のイベントハンドラに対して、getAllPrinters() の結果を outputPrinterInformation() に渡す処理を記述する。

    @FXML
    public void handleAllPrintersButtonAction(ActionEvent event) {
        Collection<Printer> collection = Printer.getAllPrinters();
        collection.stream().forEach(this::outputPrinterInformation);
    }

アプリを起動して実行し、「AllPrinters」ボタンをクリックした時の実行結果は下記の通り。

*** Name:CubePDF
    PageLayout:
        TopMargin:54.0
        LeftMargin:54.0
        RightMargin:54.0
        BottomMargin:54.0
        PrintableHeight:734.0
        PrintableWidth:487.0
        PageOrientation:PORTRAIT
        Paper:
            Name:A4
            Height:842.0
            Width:595.0
*** Name:Fax
    PageLayout:
        TopMargin:54.0
        LeftMargin:54.0
        RightMargin:54.0
        BottomMargin:54.0
        PrintableHeight:684.0
        PrintableWidth:504.0
        PageOrientation:PORTRAIT
        Paper:
            Name:Letter
            Height:792.0
            Width:612.0
*** Name:Microsoft XPS Document Writer
    PageLayout:
        TopMargin:54.0
        LeftMargin:54.0
        RightMargin:54.0
        BottomMargin:54.0
        PrintableHeight:734.0
        PrintableWidth:487.0
        PageOrientation:PORTRAIT
        Paper:
            Name:A4
            Height:842.0
            Width:595.0

CubePDF、Fax、XPS Writerの3つ全ての情報が得られた。

プリンタの情報が得られたのは分かったので、実際に印刷を行なってみる。

出力方法の設定

印刷の単位は「印刷ジョブ」と呼ばれる。
javafx.print」パッケージを見ると、これまたドンピシャな「PrinterJob」クラスがある。
APIドキュメントにはご丁寧にサンプルまである。
印刷の手順は、

  • 印刷ジョブを作る
  • 印刷対象のノードを渡す
  • 印刷を終了する

の3ステップ。

ここで言うノードは、JavaFXのシーングラフのことで、印刷対象となるGUIのツリー階層のルートノードのこと。
ルートノードを渡せば、それに登録されているサブノードも一式印刷される。

今回は印刷対象にラベル「Hello JavaFX Print.」を子に持つアンカーペイン「printCanvas」を渡す。
「Print」ボタンをクリックした時のイベントハンドラに対して、APIドキュメントのサンプルを参考にprintCanvasを印刷する処理を記述する。

    @FXML
    public void handlePrintButtonAction(ActionEvent event) {
        PrinterJob job = PrinterJob.createPrinterJob();
        job.printPage(printCanvas);
        job.endJob();
    }

※エラー処理を省いているので、コピペ禁止

PrinterJob.createPrinterJob() は「通常使うプリンター」に対してジョブを投入する。
もし対象のプリンタを指定したければプリンタを指定するPrinterJob.createPrinterJob(Printer printer) があるのでそちらを実行するか、setPrinter(Printer printer) メソッドで指定をする。

この状態で実行して「Print」ボタンをクリックすると、CubePDFが起動してPDFの保存先設定が表示され、「変換」をクリックするとPDFが保存される。
f:id:nave_kazu:20170106185703p:plain

保存したPDFを表示すると下記の通り、printCanvas の内容が印刷されていることがわかる。
f:id:nave_kazu:20170106185708p:plain

(仮に)部数の設定

もうちょっといじってみよう。
PrinterJob クラスから取得出来る JobSettings クラスを使用すると、印刷ジョブの設定が出来る。
JobSettings を使って、ジョブの名前やページ範囲や色(モノクロ・カラーの選択)が変更できる。
部数を入力する欄をツールバーに設けているので、その内容を渡して部数設定をしてみる。

    @FXML
    public void handlePrintButtonAction(ActionEvent event) {
        PrinterJob job = PrinterJob.createPrinterJob();

        JobSettings jobSettings = job.getJobSettings();
        jobSettings.setCopies(Integer.parseInt(copiesField.getText()));

        job.printPage(printCanvas);
        job.endJob();
    }

※エラー処理を省いているので、コピペ禁止

部数を入力するエリアに「2」と入れて実行・・・したが、CubePDFの問題なのか分からないが2部印刷が出来ない。
実際のプリンタに出力してみたら2部出た。
何だろ?
f:id:nave_kazu:20170106185712j:plain

ページ設定ダイアログ

印刷時のページ設定を JobSettings などを通して設定も出来るが、印刷時にユーザに設定させることも出来る。
それが「ページ設定ダイアログ」で、下記のように PrinterJob に対して showPageSetupDialog() メソッドで表示させる。

    @FXML
    public void handlePrintButtonAction(ActionEvent event) {
        PrinterJob job = PrinterJob.createPrinterJob();

        JobSettings jobSettings = job.getJobSettings();
        jobSettings.setCopies(Integer.parseInt(copiesField.getText()));

        job.showPageSetupDialog(null);

        job.printPage(printCanvas);
        job.endJob();
    }

※エラー処理を省いているので、コピペ禁止

親のWindowを渡してモーダルにすることも出来るが、面倒だったのでnullを渡してモーダレスで試す。
実行すると下記のようにページ設定ダイアログが表示される。
f:id:nave_kazu:20170106185719p:plain

印刷ダイアログ

印刷対象のプリンタを選択する印刷ダイアログを選択させることも出来る。
それが「印刷ダイアログ」で、下記のように PrinterJob に対して showPrintDialog() メソッドで表示させる。

    @FXML
    public void handlePrintButtonAction(ActionEvent event) {
        PrinterJob job = PrinterJob.createPrinterJob();

        JobSettings jobSettings = job.getJobSettings();
        jobSettings.setCopies(Integer.parseInt(copiesField.getText()));

        job.showPageSetupDialog(null);
        job.showPrintDialog(null);

        job.printPage(printCanvas);
        job.endJob();
    }

※エラー処理を省いているので、コピペ禁止

実行すると下記のように印刷ダイアログが表示される。
f:id:nave_kazu:20170106185723p:plain

まとめ

ページ送りはどうするとかまだ課題はあるが、まずは入り口としてはこのような感じで。

Selenideを試した

/IT系 /Java

先日参加した JJUG CCC 2016 FALL で、うらがみさんがSelenideの発表をしていた。

これを拝聴して便利に感じたので試してみた。

スライドはこちら。
Try Selenide

Selenideとは?

SelenideはSelenum WebDriverをラップしたブラウザテストの自動化フレームワークのこと。
Selenum WebDriverは各種ブラウザのWebDriver APIを呼び出す、これも自動化フレームワーク
WebDriverW3C規格なのでプロプラではなくオープンなAPIと言える。

テスト対象のアプリからSelenum WebDriverをラップしたSelenideを呼び出し、Selenum WebDriverは各種ブラウザごとのWebDriverを呼び出すことで、ブラウザからテスト対象のアプリを実行する、というのが全体の流れ。
f:id:nave_kazu:20161207152321p:plain

各種ブラウザのWebDriverを準備する

Selenide/Selenum WebDriverを使うには、まずテスト対象の各種ブラウザのWebDriverを仕込まないとならない。

Selenum WebDriverのサイトに各種ブラウザのWebDriverダウンロード先が記載してあるので、そこから目的のブラウザのWebDriverをダウンロードする。
Selenum WebDriver - Third Party Browser Drivers

ChromeならGoogle Chrome Driver
FirefoxならMozilla GeckoDriver
Internet ExplorerはSelenumが提供しているらしく、Selenum WebDriver - Downloadsの「The Internet Explorer Driver Server」にある。
EdgeからはMicrosoftが提供しているらしく、MicrosoftのサイトWebDriver - Microsoft Edge Development にある。

ダウンロードしたWebDriverは展開して適当なフォルダに保存する。
今回試したのは32bit Windows環境なので、上のサイトから32bit Windows用のChromeFirefoxInternet ExplorerのWebDriverをダウンロードして「C:\SeleniumDriver」に保存した。

ビルドスクリプトの準備と依存関係の追加

ビルドスクリプトを用意し、Selenideを含む関連するライブラリを依存関係に記述する。
関連するライブラリは、Selenide内で使用しているSLF4Jの実装と、単体テストスイートとしてJUnitになる。

Mavenのpom.xmlは下記の通り。

<dependencies>
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.21</version>
  </dependency>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>com.codeborne</groupId>
    <artifactId>selenide</artifactId>
    <version>4.0</version>
    <scope>test</scope>
  </dependency>
</dependencies>

Gradleのbuild.gradleは下記の通り。

dependencies {
  compile "org.slf4j:slf4j-simple:1.7.21"
  testCompile "junit:junit:4.12"
  testCompile "com.codeborne:selenide:4.0"
}

テストクラスの用意とSelenide/Selenum WebDriverの設定

まずJUnitから呼ばれるテストクラスを用意し、テストクラスの初期化メソッドを定義する。

public class AppTest {
    @BeforeClass
    public static void beforeClass() throws Exception {
    }
}

続いて初期化メソッドにSelenideとSelenum WebDriverの設定をする。

    @BeforeClass
    public static void beforeClass() throws Exception {
        // WebDriverのパスを設定
        System.setProperty("webdriver.ie.driver", "C:\\SeleniumDriver\\IEDriverServer.exe");
        System.setProperty("webdriver.gecko.driver", "C:\\SeleniumDriver\\geckodriver.exe");
        System.setProperty("webdriver.chrome.driver", "C:\\SeleniumDriver\\chromedriver.exe");

        // テスト結果の出力先
        Configuration.reportsFolder = "test-result/reports";

        // 使用するブラウザの指定
//        Configuration.browser = WebDriverRunner.INTERNET_EXPLORER;
//        Configuration.browser = WebDriverRunner.FIREFOX;
        Configuration.browser = WebDriverRunner.CHROME;
    }

System.setProperty()で、ダウンロードしたInternet ExplorerFirefoxChromeのWebDriverのパスを設定する。

Configuration.reportsFolderで、テスト結果(スクリーンショットなど)の保存先を指定する。
今回は相対パスで指定しているので、テスト実行時のカレントフォルダにConfiguration.reportsFolderで指定したフォルダが作成される。

Configuration.browserで、テストするブラウザを指定する。WebDriverRunner.CHROMEを指定しているので、テストを実行すると、chromedriver.exeを経由してChromeブラウザが立ち上がってテストが行われる。
Internet Explorerでテストする場合はWebDriverRunner.INTERNET_EXPLORERを指定して、Firefoxでテストする場合はWebDriverRunner.FIREFOXを指定する。
(うらがみさんは使用するブラウザを実行時引数で指定していたので、その方法はうらがみさんのスライドを参照)

テストメソッドの用意

今回は以下の手順のテストを行う。

  • http://www.yahoo.co.jp/にアクセスする
  • 検索ワードに「selenide」を入力する
  • 検索を実行する
  • 「selenide」の検索結果が出力されているか検証する

最後にスクリーンショットを保存する。

まずはテストクラスにテストメソッドを用意する。

    @Test
    public void selenideTest() throws Exception {
    }

テスト対象のメソッドに@Testを付ける。

http://www.yahoo.co.jp/にアクセスする処理を記述する。

    @Test
    public void selenideTest() throws Exception {
        open("http://www.yahoo.co.jp/");
    }

staticインポートしたopen()メソッドにアクセスするURLを指定する。

次に、検索ワードに「selenide」を入力する処理を記述する。
Yahoo!の検索ワードは下記のHTMLで表現されている。

<input name="p" id="srchtxt" type="text" value="">

なので、セレクタでID「srchtxt」を検索し、そこに値「selenide」を入れる処理を記述することになる。

    @Test
    public void selenideTest() throws Exception {
        open("http://www.yahoo.co.jp/");
        $("#srchtxt").val("selenide");
    }

jQuery風の$メソッドでDOM要素を選択して、val()メソッドで値を入れる。

さらに、検索を実行する処理を記述する。
Yahoo!の検索ボタンは下記のHTMLで表現されている。

</label><input type="submit" id="srchbtn" class="srchbtn" value="検索">

先ほどと同様にセレクタでID「srchbtn」を検索し、クリックを実行するclick()メソッドを呼び出す。

    @Test
    public void selenideTest() throws Exception {
        open("http://www.yahoo.co.jp/");
        $("#srchtxt").val("selenide");
        $("#srchbtn").click();
    }

その後、検索結果のHTMLのbody部に「selenide」の検索結果が出力されているか確認する。

    @Test
    public void selenideTest() throws Exception {
        open("http://www.yahoo.co.jp/");
        $("#srchtxt").val("selenide");
        $("#srchbtn").click();

        $("body").shouldHave(text("Selenide: concise UI tests in Java - このページを和訳"));
    }

最後にスクリーンショットを保存する。

    @Test
    public void selenideTest() throws Exception {
        open("http://www.yahoo.co.jp/");
        $("#srchtxt").val("selenide");
        $("#srchbtn").click();

        $("body").shouldHave(text("Selenide: concise UI tests in Java - このページを和訳"));
        screenshot("selenideTest");
    }

今回は相対パスで指定しているので、テスト実行時のカレントフォルダにConfiguration.reportsFolderで指定したフォルダが作成される。

テストの実行

Mavenで実行する場合、下記を実行する。

mvn test

Gradleで実行する場合、下記を実行する。

mvn test

どちらの場合でも、ブラウザが立ち上がって(今回の場合はChrome)、検索ワード「selenide」を入れて、検索して、検索結果が表示されて、ブラウザが終了してテストが正常終了すると思う。

テスト結果を保存しているはずなので、カレントフォルダの「test-result/reports」を見てみると、「selenideTest.png」と「selenideTest.html」が出来ている。
「selenideTest.png」はスクリーンショットの画像イメージで、「selenideTest.html」はその時のHTML。両方撮ってくれる。

「selenideTest.png」はこんな感じ。
f:id:nave_kazu:20161212113408p:plain

最後に

駆け足に試したが、Selenideいいね。
今後も使うと思う。

うらがみさんのスライドの中では他に、ページオブジェクトパターンやSeleniumが提供しているDockerイメージを使ったテストなども紹介している。
これらも試してみようと思う。

JJUG CCC 2016 FALLに参加してきた #jjug_ccc

/IT系 /Java

半年ごとの恒例イベントJJUG CCCの、2016年秋の会「JJUG CCC 2016 FALL」に参加してきた。
以下、自分用のメモとして参加したセッションの感想をまとめる。
f:id:nave_kazu:20161205174624p:plain

Be a great engineer!~ フォローすべきトレンド、スルーすべきトレンドをどう見抜くのか

speakerdeck.com
谷本 心(日本Javaユーザーグループ)さんの発表。
Twitterハッシュタグ#ccc_a1

浮き沈みや当たり外れの多いこのIT業界での効率的な成長の仕方が話の本質にあると思った。
技術を表面的なテクニックで捉えるのではなく、メタで捉えて「要するに何なのか」を理解することが重要とのこと。

「破壊的イノベーションと持続的イノベーション」の話で、「前に失敗したよね、これ」はたまに自分も使うから注意が必要で、再注目される理由を知ったうえで判断するのは重要だと思った。
そうだ。昔Palmイノベーションを感じて追っかけたけど廃れて、数年後にiPhoneが出たのを思い出した。Palmは失敗したけどiPhoneは大成功しているしね。つまりそういうことか。

技術カンファレンスでイノベーションの情報を手に入れるのは同意で、自分もそれが得たくて技術カンファレンスに出席している。
情報の入り口を広げるためにも英語を習得するのは個人的に必要なことだなと思う。

あとガートナー社が注目のテクノロジトレンドを発表しているらしい。
これのことかな?
ガートナーの2017年テクノロジトレンドにAIやブロックチェーン - ZDNet Japan
じっくり読んでみよう。

日本でも出来る!最先端のDevOpsを導入する方法

牛尾 剛(Microsoft Corporation)さんの発表。
Twitterハッシュタグ#ccc_c3 #ccc_cd3

「筋肉は世の中の99%の問題を解決する」とか言ったり、オープニングの「DevOps!」の合唱とか、のっけからパワフルな牛尾さん。

日本と西洋の生産性の違いについて話をしていて、カンファレンスの参加レポートをアメリカは何枚かの写真とURL貼ってメール送るだけで約2時間で完了するが、日本はプレゼン資料作ってプレゼンして・・・と約3日かかると。
なぜこのような話をしたかというと、DevOpsは西洋文化の上で成り立っているので日本文化の上でやるにはいろいろと無理が出てくるとのこと。
日本でDevOpsやるなら日本文化とDevOpsの間に西洋文化のフィルタを挟むのが良いとか。
あとDevOpsはアジャイルであることが前提。

無駄をあぶりだすのに使用するのがvalue stream mapで、ここから削れる箇所を見つけるそうな。
value stream mapはこういうもの。
情報システム用語辞典:バリューストリーム・マップ(ばりゅーすとりーむ・まっぷ) - ITmedia エンタープライズ

話の中で「エッセンシャル思考」という引用があって、それはこの本のことかな。
エッセンシャル思考 最少の時間で成果を最大にする

DevOpsスタートキットを公開しているそうなので、見てみようと思う。
DevOps スタータキットの公開 - メソッド屋のブログ

牛尾さんは「オブジェクト脳のつくり方」の著者なのか。
この本は熟読した覚えがある。

発表の最後にブログを公開していて、それがこちら。
「知らないこと」を恐れないマインドセットが技術導入を加速する - メソッド屋のブログ

GitBucketを支えるJava技術とグローバルで使われるOSSの作り方

www.slideshare.net
竹添 直樹さんの発表。
Twitterハッシュタグ#ccc_c4 #ccc_cd4

GitBucketは自分の仕事場でも使っている。
竹添さんのGitBucketの開発を通したOSS活動とはどういうことなのか?ということを知りたく聞きに行った。
まずはGitBucketの紹介。さすが竹添さん、Scala推し。

個人的にGitの技術的なところに興味があったので、GitBucketの技術スタックが見れて良かった。
GitRepositoryにアクセスするのにJGitを使っているのか、なるほど。

さまざまな拡張ポイントを作って、それをプラグインが利用するというやり方は、rebuild.fmでJenkinsのKawguchiさんが言ってたやつだ。
Rebuild: 161: Angry Jenkins Angers Me (kohsuke)
みんながやりたいことをすべて取り込めないし、他人が書いた(イケてない)コードを取り込むこともしたくないから、そういうのはプラグインで好きにやってくれという。

OSS活動で重要なのは

  • ユーザ
  • 開発者
  • コミュニティ
  • エコシステム

の4つとスライドでは書いてあったが、最も重要なのは英語。
GitBucketのUI、ドキュメント、ブログ、GitHub上でのやりとりすべてを英語でするようにしていて、それがユーザを広げるし開発者・コミュニティ・エコシステムの発展に寄与している。

やっぱ英語だよね・・・。

JavaFX アプリケーションを素敵に着飾ってみる

speakerdeck.com
トースト小僧さんの発表。
Twitterハッシュタグ#ccc_l5

JavaFXアプリの見た目を変える方法について話してくれた。
見た目はCSSで変えられて、標準のCSSテーマがあってそれを拡張・更新して見た目を変えて行く。

標準のCSSテーマは前までCaspinaと呼ばれるテーマだったが、JavaFX 8からはModenaと呼ばれるものに変わった。
ModenaのCSSは3,000行を超えるので読むのはしんどい・・・。ということで、CSSを作るツールをトースト小僧は作ったそうな。

他、テーマを変更するためのライブラリが世の中にはあるそうで、

  • JFoenix
  • RichTextFX

などが紹介された。

RichTextFXはすてきだな。
TextPaneに行番号表示やシンタックスハイライトを与えてくれるライブラリとのこと。

JavaFXはあまり注目されない技術だけど、クライアントアプリを作るのに世話になっているから、こうして盛り上げるのは良いことだと思う。
このスライドツール自体JavaFXで作られていて、なるほどドッグフードしている感があふれて好感が持てる。

受験勉強経験も留学経験もない日本人がJavaOneで英語で講演できるようになるまで

山本 裕介さんの発表。
Twitterハッシュタグ#ccc_l6

実は山本さんは帰国子女なので「そういうオチかい!」となりそうになったが、幼い時に海外生活をしていたということでノーカンだそうな。
「世界をまたにかけて仕事をする父親に憧れて」、日本の企業から外資系の会社に転職し、英語から逃れられない環境に自分を追い込んだそうで、その結果が今の姿なのだなと実感した。
「英語学習に王道はない」とのことなので、自分も愚直に出来る範囲で勉強しようと思う。

Selenideを試行錯誤しながら実践するブラウザ自動テスト

Selenideを試行錯誤しながら実践するブラウザ自動テスト
うらがみさんの発表。
Twitterハッシュタグ#ccc_c6 #ccc_cd6

SelenideはSelenium WebDriverを使いやすくラップしたフレームワークで、ブラウザテストが自動で出来るものとのこと。

序盤はSelenium WebDriverとSelenideの比較をしてくれたが、自分はSelenium WebDriverすら知らなかったので比較もさることながらテストをしている様子からして衝撃的。
比較はSelenideのGitHub Wikiにも載っているそうなので、そちらも参照。
Selenide vs Selenium ・ codeborne/selenide Wiki

Selenideを使えば各種ブラウザで所定のURLにアクセスし、フォーム送信し、結果のスクリーンショットを撮る。などのテストが容易に行える。
これは非常に便利。

うらがみさんがデモを見せてくれたが、デモも気になるがMacの壁紙の女子力の高さと「Vimを使ってくれてありがとう」も気になって仕方がなかった。

早速まずは試してみようと思う。
このセッションはテクニカルな面での今日イチの収穫だった。
あとうらがみさんがvim派だというのも収穫。

Javaエンジニアのキャリアを考えるパネルディスカッション

Jun Ohtaniさん、山本 裕介さん、Yoshiori SHOJIさん、hishidamaさんの発表。
Twitterハッシュタグ#ccc_c7 #ccc_cd7

結局は自分の道は自分で切り開くしかないのだけれど、四者四様の経歴と考え方が聞けて良かった。
よしおりさんの「周りで一番ヘタクソになるために転職」したというのは、なかなか真似できない。

「アウトプット大事」「英語大事」すごく響いた。

なのでこれからSelenideを試してアウトプットする・・・。

PlantUMLを入れてみた

/IT系

PlantUMLを使ってUMLを書いてみようと思い、まずはセットアップしたのでメモを残す。

PlantUMLとは?

PlantUMLはUMLダイアグラムを記述するためのオープンソースのツールで、他のツールとの違いはテキストファイルから図を生成するという点。
astah* UMLとか他のツールはマウスを使ってGUIで図を書くのと比べて、PlantUMLはテキストファイルに記述した定義に則って図を生成するというのが違い。
テキストファイルなのでバージョン管理もしやすいし、他のメンバーとのコラボレーションもしやすい。
反面、書き方を覚えないとならないので、イニシャルコストはかかる。

生成する図はPNG(ラスタ画像)やSVG(ベクタ画像)で生成できるので、用途に応じて使い分ける。

生成にはPlantUMLの他にGraphvizというものを使う。
PlantUMLはJavaで書かれているのでクロスプラットフォームだが、Graphvizはネイティブなので各プラットフォームに応じたバイナリを使用する。

PlantUMLのインストール

PlantUMLのダウンロードページからplantuml.jarをダウンロードして適当なフォルダに保存。

Graphvizのインストール

Graphvizのダウンロードページから適したバイナリをダウンロード。
今回はWindows版ZIPファイルをダウンロードして、releaseフォルダの中身をC:\Program Files\graphviz-2.38に展開した。
(C:\Program Files\graphviz-2.38\binに実行ファイルがあるようにした)

インストール後の動作確認

plantuml.jarが置いてあるフォルダでコマンドプロンプトを立ち上げて、以下を実行する。

java -jar plantuml.jar -testdot

PlantUMLとGraphvizがうまく動けば以下のようなメッセージが出るはず。

The environment variable GRAPHVIZ_DOT has not been set
Dot executable is c:\Program Files\graphviz-2.38\bin\dot.exe
Dot version: dot - graphviz version 2.38.0 (20140413.2041)
Installation seems OK. File generation OK

Graphvizの場所は探してくれるのね。
指定したい場合は環境変数GRAPHVIZ_DOTに設定すれば良いみたい。

PlantUMLの動作確認

plantuml.jarと同じところにsample.txtを作成。
内容は以下の通り。

@startuml
Class1 -> Class2 : 処理A
Class1 <-- Class2
@enduml

Class1から「処理A」というメッセージを送って、Class2から返ってくるという、簡単なシーケンス図。

plantuml.jarが置いてあるフォルダでコマンドプロンプトを立ち上げて、以下を実行する。

java -jar plantuml.jar sample.txt

すると下記のPNGファイル(sample.png)が生成される。
f:id:nave_kazu:20161125155400p:plain

既定値ではPNGが作成されるので、明示的にPNGを生成したい場合は下記で実行する。

java -jar plantuml.jar -tpng sample.txt

SVGで出力したい場合、-tsvgを指定する。

java -jar plantuml.jar -tsvg sample.txt

するとSVGファイル(sample.svg)が生成される。

PlantUMLの書き方

日本語リファレンスがあるので、これを参考に書いてみよう。

Spring Day 2016に参加してきた

/IT系 /Java

JJUG(日本Javaユーザグループ)のイベントは何度か参加したことがあったが、今回初めてJSUG(日本Springユーザ会)のイベントに参加した。
以下、自分用のメモとして参加したセッションの感想をまとめる。

Spring Framework 5.0 (ROOM1-2)

speakerdeck.com

来年3月にリリース予定のSpring Framework 5.0のお話。
今日の主なテーマは下記の4つ。
・パフォーマンス改善
・JDK9とJigsaw
・HTTP/2とServlet 4.0
・リアクティブアーキテクチャ

その中で一番時間を割いて説明していたのはリアクティブについて。
リアクティブとかノンブロッキングとかの話についていけてないので、このあたりを勉強しないといけない。

災害対策(DR)対応の超ミッション・クリティカル Cloud Foundry は Azure !! (ROOM1-3)

docs.com

Azureは全世界に30のリージョンがあるから、超ミッション・クリティカルなシステムでも運用できますよというお話。
30のリージョンというのはアマゾン ウェブ サービスとGoogle Cloud Platformを合わせた数よりも多いとか。
AWSはいま14リージョン、Google Cloud Platformはいま6リージョン?自分の調べたソースが正しいのか分からないが、確かに合わせた数よりも多い。
日本だけでも東日本と西日本の2箇所があるので、災害対策はバッチリでしょということ。

事例紹介としてFordPass ( https://www.fordpass.com/ ) が紹介されていた。
FordPass、いいな。
駐車場をアプリから予約出来たり、車生活での不安を解消するサービスになると思う。

モノリシックなシステムからマイクロサービスベースのシステムに移行することを薦めていた。
システム開発は建築からいろいろとノウハウを吸収したが、建築は30年の耐用年数で考えて設計するのにシステムはそれよりはるかに短い期間で作り直すのだから、そもそもの思想が違うのだからシステム構築に合った考え方を導入しましょう。その解がマイクロサービスだし、基盤としてCloud Foundry + Azureの組み合わせが良いよという話。

Spring Security で作る Web API アクセス制御の最適解 (ROOM3-5)

www.slideshare.net

個人的にも最近「Web APIのアクセス制御って何が正解なんだろ?」と思うところがあった矢先のこのセッションだった。
「最適解」というタイトルだが結論は「case by case」とのこと。

プライベートネットワーク内であればSSLいらない、外とつながるならSSL必須。
SSL使うならBasic認証もありだけど、SSLも万全ではないしリクエスト毎に静的・無期限のクレデンシャル(ユーザーの認証に用いられる情報)を流すのには抵抗がある。

リクエスト毎に異なるクレデンシャルを流す方法としてHMACを用いたAPIキー署名がある。

OAuth 2.0が例に挙がったが、そもそもOAuthは認証の仕組みでも認可の仕組みでもない。
Aが持っている認可をBに部分委譲するための仕組みがOAuth。
この委譲の仕組みを使ってアクセス制御をする。
詳しくはスライドの神様を参照。

Web APIの認証とは関係ないが登壇者的に面白いと思う技術がOpenID Connect。
詳細はスライドの38枚目を参照。
(OpenID Connectがしてくれること)

Spring5に備えるリアクティブプログラミング入門 (ROOM3-6)

www.slideshare.net

Spring Framework 5.0のお話のときも思ったが、まだまだこのあたりの話についていけてないので勉強しないといけない。

Microservices Architectureを超大規模プロジェクトでやってみた。 (ROOM2-7)

speakerdeck.com

実践的なお話を聞いた。
ビルド時間14時間、.warを動かすのに32Gbyteのメモリが必要なほどの巨大なモノリシックシステムを数年かけて今もマイクロサービス化しているという話。
聞くも涙、語るも涙。

どの単位で切り出すかを手探りでやって、まずは「業務機能での何となくの塊」に分けてマイクロサービス化する。
巨大なシステムだからコストかかる → コストを下げるためにスリム化する → 32Gbyteメモリが1-4Gbyteまでスリム化。

Grailsドメイン駆動設計を実践する時の勘所 (ROOM2-8)

www.slideshare.net

ドメイン駆動設計に惹かれて参加。

GrailsフルスタックWebアプリケーションフレームワークで、「動くものがすぐに作れる」「記述が簡潔」「GORMが強力」「プラグイン機構」という特徴を持っている。
ドメイン駆動設計は「ドメインそのものとドメインのロジックに焦点を置いてシステム設計をする」手法。
自分もエリックさんの本を持っているが、難解で重くて読むのつらい。登壇者は「500ページを超える鈍器」と表現。確かに。

GrailsとDDDの相性は非常に良いらしい。

が、Grailsの知識はなく、DDDの知識も中途半端なので、両方を勉強しなおしてからスライドを再度見させていただこうと思う。

Data Microservices with Spring Cloud Stream, Task, and Data Flow (ROOM2-9)

www.slideshare.net

マイクロサービスをLinux/UNIXのパイプのようにつないで一連の処理を実現しようという話。
パイプ好きにはたまらない仕組み。

Spring Cloud Streamが長命のタスク向き、Spring Cloud Taskが短命のタスク向き。
クラウド上で動くので、スケールアップ・スケールアウトは容易だし、データのルーティングも出来るので細かな制御が可能。

Spring Cloud StreamとSpring Cloud Taskのデモをやってくれた。
PCF Devで試してみよう。

再来週は

次はJJUG CCC 2016 Fallに参加予定。
JJUG CCC 2016 Fall

FolderBrowserDialogのRootFolderをDesktopDirectoryにすると、Windows10では死ぬ

/IT系

.NETでフォルダ参照ダイアログにFolderBrowserDialogを使ってるのだが、プロパティのRootFolderにDesktopDirectoryを指定すると、Windows10ではフォルダ参照が機能しなくなる。

サンプルのフォルダ参照ダイアログ表示処理

RootFolderを指定しないFolderBrowserDialogの例として、以下のようなプログラムを用意した。

FolderBrowserDialog dialog = new FolderBrowserDialog();
dialog.Description = "素のFolderBrowserDialog";
DialogResult result = dialog.ShowDialog();

RootFolderを指定するFolderBrowserDialogの例として、以下のようなプログラムを用意した。

FolderBrowserDialog dialog = new FolderBrowserDialog();
dialog.RootFolder = Environment.SpecialFolder.DesktopDirectory;
dialog.Description = "RootFolderをDesktopDirectoryにしたFolderBrowserDialog";
DialogResult result = dialog.ShowDialog();

Windows7での動作

上のサンプルをWindows7で動かしてみる。
まずはRootFolderを指定しないFolderBrowserDialog。
f:id:nave_kazu:20160914175801p:plain

続いてRootFolderを指定するFolderBrowserDialog。
f:id:nave_kazu:20160914175806p:plain

見た目はまったく同じ。

Windows10での動作

これがWindows10だと、こうなる。
まずはRootFolderを指定しないFolderBrowserDialog。
f:id:nave_kazu:20160914175811p:plain

続いてRootFolderを指定するFolderBrowserDialog。
f:id:nave_kazu:20160914175814p:plain

すっからかんになった。
デスクトップの下はデスクトップにあるフォルダしか表示されなくなる。

回避策

そもそも、RootFolderの指定にDesktopDirectoryを指定するからこうなるのであって、デスクトップを初期表示したければ規定値のDesktop(DesktopDirectoryではなくDesktop)を使えということ。
MSDNによるとDesktopDirectoryは「デスクトップ上のファイル オブジェクトを物理的に格納するために使用されるディレクトリ。」となっている。
Desktopは「物理的なファイル システム上の場所ではない論理的なデスクトップ。」となっている。

いままでは物理的には実在しない「コンピュータ」とか「コントロールパネル」を表示していたが、Windows10からは厳密に実在するフォルダしか表示しないという動きに変わったということ。
Windows8.1は、Windows7と同じ動きをしていた。

Windows10対応のひとつだね。

Linux (CentOS 7) で GitBucket を動かす

/IT系 @Git

開発サーバーにGitBucketを入れたので、その手順をメモする。
入れたものは下記のとおり。

製品 バージョン 参照先
JDK 8u101-linux-x64 Java SE Development Kit 8 - Downloads
Tomcat 8.5.5 Apache Tomcat® - Apache Tomcat 8 Software Downloads
GitBucket 4.4 Release 4.4 · gitbucket/gitbucket · GitHub

それぞれの手順を順に記す。

JDK

TomcatとGitBucketを動かすためにJDKを入れる。
Oracleのダウンロードページから、現在最新版の8u101をダウンロードする。
手元のLinuxは64bitなので、linux-x64を。
RPMの方が楽なのでjdk-8u101-linux-x64.rpmをダウンロード。

JDKのインストール

RPMファイルがあるフォルダで下記コマンドを実行

yum localinstall --nogpgcheck jdk-8u101-linux-x64.rpm

インストール出来たか確認する。

# java -version
java version "1.8.0_101"
Java(TM) SE Runtime Environment (build 1.8.0_101-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.101-b13, mixed mode)

環境変数を設定

/etc/profileに環境変数 JAVA_HOME を設定する。
/etc/profile をエディタ開いて、下記を追加。

export JAVA_HOME=/usr/java/default

Tomcat

GitBucketは組み込みのウェブサーバーが入っているのでそちらで動かしても良いが、今回はTomcatの上で動かす。

ユーザー作成

まずはTomcatを動かすためのユーザー tomcat を作る。

useradd -s /sbin/nologin tomcat

Tomcatのインストール

ダウンロードサイトからTomcatのtar.gzファイルをダウンロードし、適当なフォルダに展開する。

tar xvzf apache-tomcat-8.5.5.tar.gz

展開したファイルを /usr/local に移動する。

mv apache-tomcat-8.5.5 /usr/local

所有者を先ほど作成したtomcatに変更する。

cd /usr/local
chown -R tomcat:tomcat apache-tomcat-8.5.5

Tomcatシンボリックリンクを作成する。

ln -s apache-tomcat-8.5.5 tomcat

環境変数を設定

/etc/profileに環境変数 CATALINA_HOME を設定する。
/etc/profile をエディタ開いて、下記を追加。

export CATALINA_HOME=/usr/local/tomcat

Tomcatをデーモンとして動かす

サーバーなのでサーバー起動時に自動で立ち上がって欲しいからデーモンとして動かす。
Tomcatには jsvc というデーモン化ツールがあるので、それをセットアップして使う。

まずはTomcatのインストールフォルダのbinに移動。

cd /usr/local/tomcat/bin

jsvcのアーカイブを展開。

tar xvzf commons-daemon-native.tar.gz

展開後のフォルダに移動。

cd  commons-daemon-1.0.15-native-src/unix

Makeファイルを生成。

./configure

makeを実行。

make

出来たjsvcをコピー。

cp jsvc ../..

ここまででデーモン起動用のシェルが作成されるので、試しに起動してみる。

cd /usr/local/tomcat/bin
./daemon.sh start

ブラウザで http://localhost:8080/ にアクセスする。
リモートの環境ならlocalhostではなく対象のIPアドレスなりを入力すること。
もし実行環境のファイアーウォールが原因でアクセスできない場合は8080ポートを空ける等、適宜対応を。

firewall-cmd --zone=public --add-port=8080/tcp --permanent
firewall-cmd --reload

起動用のスクリプトを /usr/lib/systemd/system/tomcat.service に作成する。
/usr/lib/systemd/system/tomcat.service をエディタで新規作成する。
内容は下記のとおり。

[Unit]
Description=Apache Tomcat Web Application Container
After=network.target

[Service]
Type=forking
PIDFile=/var/run/tomcat.pid
Environment=CATALINA_PID=/var/run/tomcat.pid
Environment=JAVA_HOME=/usr/java/default
Environment=CATALINA_HOME=/usr/local/tomcat
Environment=CATALINA_BASE=/usr/local/tomcat
Environment=CATALINA_OPTS=

ExecStart=/usr/local/tomcat/bin/jsvc \
            -Dcatalina.home=${CATALINA_HOME} \
            -Dcatalina.base=${CATALINA_BASE} \
            -cp ${CATALINA_HOME}/bin/commons-daemon.jar:${CATALINA_HOME}/bin/bootstrap.jar:${CATALINA_HOME}/bin/tomcat-juli.jar \
            -user tomcat \
            -java-home ${JAVA_HOME} \
            -pidfile /var/run/tomcat.pid \
            -errfile SYSLOG \
            -outfile SYSLOG \
            $CATALINA_OPTS \
            org.apache.catalina.startup.Bootstrap

ExecStop=/usr/local/tomcat/jsvc \
            -pidfile /var/run/tomcat.pid \
            -stop \
            org.apache.catalina.startup.Bootstrap

[Install]
WantedBy=multi-user.target
	||<

サービス登録をする。
>|sh|
systemctl enable tomcat.service

GitBucket

最後にGitBucketを入れる。
GitBucketのWARファイルをダウンロードし、Tomcat の webapps フォルダにコピーする。

cp gitbucket.war /usr/local/tomcat/webapps

ホットデプロイされていないようなら Tomcat を再起動する。

systemctl restart tomcat

ブラウザで http://localhost:8080/gitbucket/ にアクセスする。
GitBucketのトップ画面が出てきたらOK。