Javaの起動オプションにGCログ設定とヒープダンプを設定する

Java

Javaを用いたプログラムで、運用開始後に原因不明の障害が発生する場合は多々あります。


原因不明のメモリリーク、CPUの一時的な極端な上昇といった現象は、アプリケーションのログからはなかなか原因が突き止められない場合があります。
こういった事象に対応するために、GCログの出力と、OutOfMemory発生時のヒープダンプ出力をあらかじめ仕込んでおく必要があります。


今回は、Javaアプリケーションの起動設定について、GCログとヒープダンプの出力設定をおこなう方法について紹介します。


環境情報


  • Java 1.8.0_281

GCログの出力設定


GCログの出力設定は、Javaの起動オプションとして設定します。
設定オプションは以下になります。


 

-verbose:gc

GCログを出力する。

-Xloggc

GCログの出力先を設定する。

例)-Xloggc:./gc.log-%t

 

ファイル名に設定できる変数要素としては以下の2つがあります。

%t=ログファイルを作成した日時

%p=JavaプロセスのID

-XX:+UseGCLogFileRotation

GCログのローテーションをおこないます。

-XX:+PrintGCDateStamps

GCログに時間のタイムスタンプを表示します。

-XX:+PrintGCDetails

GCログの詳細を出力します。

-XX:NumberOfGCLogFiles

GCログのローテーションをおこなう際、ローテーションするファイルの数を定義します。

-XX:GCLogFileSize

1つのログファイルのファイルサイズを定義します。

定義したファイルサイズに到達したタイミングで、ローテーションがおこなわれます。

定義したファイルサイズに到達したタイミングで、ローテーションがおこなわれます。

 

M=メガ

k=キロ(最小は7K

 


設定例としては以下になります。
実行するプログラムは「GcSample」となり、10キロのGCログファイルを最大5ファイルでローテーションをおこなっています。
GCログファイルにはログファイルを作成した日時を設定するために「%t」オプションを定義していますが、以下はWindowsのバッチファイル例なので、”%%”と記載してエスケープしています。


java -Xms256m -Xmx256m -verbose:gc -Xloggc:./gc.log-%%t -XX:+UseGCLogFileRotation -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=10k GcSample 

この定義でログファイルを出力した場合は、以下のような定義となります。


gc.log-2021-02-11_15-52-26.0
gc.log-2021-02-11_15-52-26.1
gc.log-2021-02-11_15-52-26.2
gc.log-2021-02-11_15-52-26.3
gc.log-2021-02-11_15-52-26.4.current ← 出力中ファイル

5ファイルでローテーションされています。


出力されたファイルはGCVierなどのビューアで参照するのが一般的になります。


ヒープダンプの出力設定


GCログを設定しておくことも大事なのですが、GCログだけではOutOfMemoryが発生した場合の、リーク箇所などについては詳細を判断することはできません。

ヒープダンプを設定しておくと、OutOfMemory発生時にヒープダンプが出力されるので、詳細の原因調査が可能となります。


-XX:+HeapDumpOnOutOfMemoryError

OutOfMemoryが発生した場合にヒープダンプを出力します。

-XX:HeapDumpPath

ヒープダンプの出力先フォルダ、および、ファイル名を指定します。

ファイル名を指定しない場合は、「java_pidxxx.hprof」といったファイル名になります。

xxxはプロセスIDです。

-XX:OnOutOfMemoryError

OutOfMemoryが発生した場合に実行するコマンドを定義することができます。

実行するコマンドは、シングルクォーテーションで囲んで定義します。

 

例)-XX:OnOutOfMemoryError=”cmd string.”


OutOfMemoryが発生してヒープダンプを出力した際、指定したフォルダにダンプファイルを出力するのですが、同名ファイルが存在する場合は上書きしてくれません。
つまり、同じプロセスIDのJavaプロセスで複数回のOutOfMemoryが発生した場合、一番最初のヒープダンプだけが残ります。


一番最初のヒープダンプを残すのではなく、一番最後のヒープダンプを残したい場合は、OnOutOfMemoryErrorオプションを使います。
以下の例では、ヒープダンプが発生したタイミングで出力したダンプファイルをバックアップフォルダに移動をおこないます。
この定義をおこなうことで、ヒープダンプ出力先フォルダにはダンプファイルは残らず、バックアップフォルダに最後のヒープダンプが残ることになります。


java -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./ -XX:OnOutOfMemoryError="D:/gc/gc-move.bat" GcSample 

OnOutOfMemoryErrorオプションに定義したgc-move.batの中身は以下になります。
backというフォルダに、出力したヒープダンプファイルを強制上書きコピーしています。

move /Y java*.hprof ./back/

OnOutOfMemoryErrorオプションに定義したコマンドは、ヒープダンプが出力された後に実行されるようです。


①:OutOfMemoryが発生

②:ヒープダンプを出力

③:OnOutOfMemoryErrorオプションに定義したコマンドを実行


このような動きであることを考慮して、OnOutOfMemoryErrorオプションに定義したコマンドを検討する必要があります。


仮に、最初のヒープダンプファイル・最後のヒープダンプファイル、という事ではなく、ヒープダンプの履歴を取りたい(全てのヒープダンプファイルを残しておきたい)場合は、上記例の「gc-move.bat」を修正すれば、様々な定義をおこなうことができます。
しかし、ヒープダンプファイルはプロセスのヒープ情報をそのままダンプしているので、かなりサイズが大きいファイルとなります。
そのため、ダンプファイルの履歴をとることはおすすめはしません。



JavaのSupplierを使って汎用的なリトライ処理を実装する

Java

アプリケーションの機能を実装していく中で、様々なリトライ処理が必要になってきます。
DBアクセスに失敗した場合のリトライ、HTTP通信(APIなど)に失敗した場合のリトライ、など様々かと思います。


Javaでこういったリトライ処理を実装しようとすると、普通にやるとfor文やwhile文を使ってしまいがちです。
それでもいいですが、Javaのユーティリティクラス群であるSupplierを使えば、もうちょっと汎用的に実装することができます。


今回は、Javaのユーティリティクラスである「java.util.function.Supplier」を使って、汎用的なリトライ処理を実装する方法を紹介していきます。


環境情報


  • OS:Windows10
  • Java:Java1.8

ユーティリティクラス群で使用されている関数型インターフェースは、Java8から導入された機能となります。
Java1.7以前は使用できないので注意してください。


java.util.functionは関数型インターフェース


java.util.function配下の機能は全て、関数型インターフェースで実装されています。
イメージとしては、実行するメソッドをパラメータとして渡す形です。


java.util.functionのAPI仕様をみると複雑なインターフェースが沢山あるようにみえるのですが、大きくわけて4つに分類されます。
今回のリトライ処理では「Supplier」を使うのですが、他3つのインターフェースは以下になります。


種類 実装するメソッド 概要
Function<T,R> R apply(T t) 実装するメソッドは、引数としてTを受け取り、結果としてRを返すものになる
Consumer<T> void accept(T t) 実装するメソッドは、引数としてTを受け取り、結果を返さず終了するものになる
Predicate<T> boolean test(T t) 実装するメソッドは、引数としてTを受け取り、boolean値を結果として返すものになる
Supplier<T> T get() 実装するメソッドは、何も引数として受け取らず、結果としてTを返すものになる

関数型インターフェースの簡単なサンプルは以下になります。
先ほど説明した通り、関数をパラメータとして渡して渡された側で関数を実行しています。


return new RetryUtil()
    .retryFunc(() -> {
        ※実行する機能
    );
public class RetryUtil {
    private <T> T retryFunc(Supplier<T> func) {
        return func.get();
    }
}

リトライ処理


リトライ処理は、java.util.function配下の「Supplier」を使用します。
「Supplier」を使用し、実施する処理自体は呼び出し元で定義し、リトライの仕組み自体は共通クラス(ユーティリティクラス)に実装しています。


まずはリトライ処理を実施するメイン処理です。

import java.util.function.Supplier;

/**
    リトライ処理サンプル
*/
public class RetrySample {

    /**
        リトライ処理メイン
    */
    public static void main(String[] args) 
        throws InterruptedException {
        System.out.print("start: RetrySample\r\n");

        // 処理結果を失敗で初期化
        boolean isDone = false;

        // リトライ処理
        try {
            isDone = retryExec();
        } catch (Exception e) {
            System.out.print("RetrySample: main is failer\r\n");
        } finally {
            System.out.print(
                "RetrySample: result is " + isDone + "\r\n");
        }

        System.out.print("end: RetrySample\r\n");
    }

    /**
        リトライ処理の実施
    */
    private static boolean retryExec () 
        throws InterruptedException {

        // 1.リトライ回数とスリープ時間を設定
        int retryNum = 3;
        long sleepMilSec = 1000;

        // 2.ユーティリティを使って〇〇〇処理を実施する
        return new RetryUtil(retryNum, sleepMilSec)
            .retryFunc(() -> {
                try {
                    System.out.print("retryExec\r\n");
                    boolean isSucess = 〇〇〇処理を実施
                    if (isSucess) {
                        return true;
                    } else {
                        throw new RetryException();
                    }
                } catch (Exception e) {
                    throw new RetryException();
                }
            }, "RetrySample");
    }
}

「1.リトライ回数とスリープ時間を設定」で、リトライユーティリティのオブジェクトに設定する初期値を定義しています。
今回はリトライ処理ということで、ありがちなリトライ回数とスリープ時間はユーティリティクラスのコンストラクタで設定できようにしています。
その他、初期値として設定したい値があるのであれば、同様にコンストラクタで定義するのがよいかと思います。


「2.ユーティリティを使って〇〇〇処理を実施する」の機能が、実際に関数型インフェースで定義しているリトライ処理する中身です。
サンプルなので「〇〇〇処理を実施」としていますが、実際にはここにリトライ制御したい機能を実装していくのがよいでしょう。
リトライする契機としては、独自例外クラスである「RetryException」が発生した場合になります。
失敗したら(isDoneがfalse)「RetryException」をスローすることで、リトライ処理がはしるようにユーティリティクラスを準備しています。


次にリトライユーティリティクラスです。

import java.text.MessageFormat;
import java.util.function.Function;
import java.util.function.Supplier;

/**
    リトライユーティリティ
*/
public class RetryUtil {

    // リトライ回数
    private final int retryNum;

    // リトライ時のスリープ時間
    private final long retryWait;

    /**
     * コンストラクタ
     */
    public RetryUtil(int retryNum, long retryWait) {

        // 1.リトライ回数とスリープ時間を設定
        this.retryNum = retryNum;
        this.retryWait = retryWait;
    }

    /**
     * リトライ処理
     */
    public <T> T retryFunc(Supplier<T> func, String funcName)
            throws RetryException, InterruptedException {
        return retryFunc(func, funcName, 1, null);
    }

    /**
     * リトライユーティリティ
     */
    private <T> T retryFunc(Supplier<T> func, 
        String funcName, int count, Exception befEx)
        throws RetryException, InterruptedException {

        // 2.Supplierで提供されたfuncを実行
        try {
            return func.get();

        // 3.例外が発生した場合はリトライ回数に
        //     達するまで自身を再度呼び出す
        } catch (RetryException e) {
            System.out.print("RetryException\r\n");
            if (count > this.retryNum) {
                System.out.print("retry end\r\n");
                throw new RetryException(e);
            }
            count++;
            Thread.sleep(this.retryWait);
            System.out.print("next retry " + 
                Integer.toString(count) + "回目の処理開始\r\n");
            return retryFunc(func, funcName, count, null);
        }
    }
}

「1.リトライ回数とスリープ時間を設定」で、リトライ回数とスリープ時間をクラス変数として格納しています。
実際のリトライ処理時に、この値を参照しています。


「2.Supplierで提供されたfuncを実行」で、実際に関数型インターフェースで渡されたメソッドを実行しています。
実行方法は、パラメータ名.get() で実行されます。


「3.例外が発生した場合はリトライ回数に達するまで自身を再度呼び出す」がリトライ処理のメインとなる部分ですね。
個別例外である「RetryException」が発生するとリトライ処理を実施します。
リトライ回数が上限に達すると上位に例外をスローしてリトライ処理を中断します。
上限に達していない場合は再度自身を呼び出して同じ処理を実行しています。


最後に個別の例外クラスです。
リトライ処理を制御しやすくするために、個別例外を準備しています。


/**
    リトライユーティリティ固有例外
*/
public class RetryException extends RuntimeException {

    /**
     * コンストラクタ
     */
    public RetryException() {
        super();
    }

    /**
     * コンストラクタ(例外指定)
     * @param cause 例外
     */
    public RetryException(Throwable cause) {
        super(cause);
    }
}


Javaのシャーロンコピーとディープコピーの違いとは?サンプルで解説

Java

Javaで配列をコピーする際、気を付けなければいけないポイントがあります。
シャーロンコピーとディープコピーの違いです。


この2つの挙動の違いを把握しておかないと、思わぬバグを埋め込んでしまうことなります。


今回は、シャーロンコピーとディープコピーの違いを、サンプルプラグラムをもとに説明していきます。


環境情報


  • OS:Windows10
  • Java:1.8.0_60

シャーロンコピーとは?ディープコピーとは?


シャーロンコピーとディープコピーの違いは何でしょうか?

まずは、この2つのコピーの違いについて説明します。


シャーロンコピー


C言語を学んだ人の場合は、アドレスコピーときくとピンとくるかと思います。
要素を指定するアドレスのみをコピーしており、要素がもつ値はコピーしていないです。


ディープコピー


値コピー、と言い換えることができます。
シャーロンコピーとは異なり、アドレスではなく、要素そのものをコピーします。


シャーロンコピーの概念が理解し辛い人が多い気がします。
C言語を使った人はわかるかと思いますが、「ポインタ」のイメージですね。


シャーロンコピーとディープコピーのサンプルプログラム


それでは、さっそくですがサンプルプログラムです。

例として、以下のサンプルプログラムになります。

  • コピー対象は、独自エントリクラス(Entry)の配列を「ArrayList」で保持する。
  • シャーロンコピー→ディープコピーの順番でコピーを実施。
  • コピー元配列の内容を表示。

サンプルプログラム


作成するクラスは2つになります。
「CopyTest.java」がコピーするメインクラス、「Entry.java」が配列要素となるエントリクラスです。


CopyTest.java

import java.util.ArrayList;

public class CopyTest {

    // メイン処理
    public static void main(String[] args){
        System.out.print("start: CopyTest\r\n");

        /**
        ①:エントリ内容の作成と確認
        */
        ArrayList<Entry> srcList = new ArrayList<Entry>();
        Entry newEnt1 = new Entry();
        newEnt1.setSei("Yamada");
        newEnt1.setNa("Taro");
        srcList.add(newEnt1);
        Entry newEnt2 = new Entry();
        newEnt2.setSei("Suzuki");
        newEnt2.setNa("jiro");
        srcList.add(newEnt2);
        System.out.print("エントリ内容の作成確認\r\n");
        dispList(srcList);

        /**
        ②:シャーロンコピー
        */
        ArrayList<Entry> shtList = srcList;
        Entry entry = shtList.get(0);
        entry.setSei("Satou");
        entry.setNa("Saburo");
        System.out.print("リストをコピー確認\r\n");
        dispList(srcList);

        /**
        ③:ディープコピー
        */
        ArrayList<Entry> deList = 
            new ArrayList<Entry>(srcList.size());
        for (Entry ent : srcList) {
            deList.add(ent.clone());
        }
        entry = deList.get(0);
        entry.setSei("Tanaka");
        entry.setNa("Shiro");
        System.out.print("リストをコピー確認\r\n");
        dispList(srcList);

        System.out.print("end: CopyTest\r\n");
    }

    private static void dispList(ArrayList<Entry> dispList){
        for (Entry dispEnt : dispList) {
            System.out.print("sei = " + dispEnt.getSei() + "\r\n");
            System.out.print("na = " + dispEnt.getNa() + "\r\n");
        }
    }
}

Entry.java

public class Entry implements Cloneable {
    private String sei;
    private String na;

    // 姓の設定
    public void setSei(String sei) {
        this.sei = sei;
    }

    // 名の設定
    public void setNa(String na) {
        this.na = na;
    }

    // 姓の取得
    public String getSei() {
        return this.sei;
    }

    // 名の取得
    public String getNa() {
        return this.na;
    }

    // クーロン
    public Entry clone() {
        try {
            return (Entry) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

シャーロンコピーの実行結果


サンプルプログラムの②の部分がシャーロンコピーのサンプルプログラムです。
シャーロンコピーを実行した結果は以下になります。

sei = Satou
na = Saburo
sei = Suzuki
na = jiro

実際にシャーロンコピーしている部分は以下です。

ArrayList<Entry> shtList = srcList;

シャーロンコピーしたコピー先リスト(shtList)の内容を変更しています。
エントリの1つ目について、以下のように変更しています。

・Sei=Satou 
・Na=Saburo

シャーロンコピーしたコピー先リスト(shtList)を変更したにも関わらず、コピー元のリスト(srcList)が変更されています。
これはなぜかというと、コピーしたのはあくまでアドレスであるシャーロンコピーをおこなっているからになります。
変更先リストを変更しているにも関わらず変更元リストが変更されている、という事です。


ディープコピーの実行結果


サンプルプログラムの③の部分がディープコピーのサンプルプログラムです。
ディープコピーを実行した結果は以下になります。

sei = Satou
na = Saburo
sei = Suzuki
na = jiro
end: CopyTest

実際のディープコピーは、「Entry」クラスのcloneメソッドになります。

ArrayList<Entry> deList = new ArrayList<Entry>(srcList.size());
for (Entry ent : srcList) {
    deList.add(ent.clone());
}

要素を完全にコピーしています。
完全にコピーした要素に対して変更しているので、変更元の配列要素は変更されていないです。


これがディープコピーになります。
通常は、上記のようにcloneメソッドを準備して、要素を完全コピーする実装を準備しておきます。


よく、様々なウェブサイトで以下のように実装すればディープコピーされると記載されているのですが、それは間違いです。
気を付けてください。

ArrayList<Entry> deList = new ArrayList<Entry>(srcList);

まとめ


ディープコピーとシャーロンコピーの違いを正しく理解してプログラミングしましょう!


よくあるのが、ディープコピーしていると思っていたがシャーロンコピーで、コピー元の要素が変更されてしまった!というパターンです。
思わぬバグを作りこんでしまう事になります。



FTPサーバとの通信をJavaでおこなうサンプルプログラム

Java

システムで外部システムと連携する方法はいろいろありますが、FTPサーバを使ってファイルのやり取りで連携するパターンも多々あります。


FTPサーバを使ってファイルのやり取りをおこなう場合、プログラムからFTPサーバにログインして、ファイルのアップロードやダウンロードをおこなう必要が出てきます。


今回は、Javaを使ってFTPサーバとのファイル送受信をおこなうサンプルプログラムを紹介します。


環境情報


  • OS:Windows10
  • Java:Java1.8.0_60
  • FTPサーバ:IIS(インターネットインフォメーションサービス)

JavaでFTPを使うために、「commons-net-3.6.jar」を使用しています。


FTPサーバについては、以前の記事で作成したIIS(インターネットインフォメーションサービス)を使います。



FTPサンプルプログラム


サンプルプログラムを紹介します。
クラスは2つです。


FTPに対する操作をおこなうクラスとして「FtpMng.java」を準備し、「FtpMng.java」のインスタンスを使って処理をおこなうクラスが「FtpSample.java」になります。


以下のクラスがFTPに対する操作をおこなうクラスである「FtpMng.java」です。
FTPに対する操作一つ一つに対してメソッドを準備しています。


FTPサーバに対しておこなっているのは、ファイルのアップロード(FTPサイトにファイルを置く)と、ファイルのダウンロード(FTPサイトからファイルをもらう)の2つになります。

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.SocketException;

public class FtpMng {

    private FTPClient   cli;

    // コンストラクタ
    public FtpMng() {
    }

    // 接続
    public boolean connect() {
        int rep;

        System.out.print("start: connect\r\n");
        try {
            cli = new FTPClient();
            cli.setDataTimeout(60000);
            cli.connect("localhost");
            rep = cli.getReplyCode();
            if (!FTPReply.isPositiveCompletion(rep)) {
                return false;
            } 
            System.out.print("end: connect\r\n");
            return true;
        } catch (SocketException e) {
            return false;
        } catch (IOException ie) {
            return false;
        }
    }

    // ログイン
    public boolean login() {
        System.out.print("start: login\r\n");
        try {
            if (!cli.login("ftp", "password")) {
                return false;
            }
            System.out.print("end: login\r\n");
            return true;
        } catch (IOException ie) {
            return false;
        }
    }

    // ダウンロード
    public void downLoad() {
        FileOutputStream    outputstream;
        boolean             isRetrieve;

        System.out.print("start: downLoad\r\n");
        try {
            outputstream =
                new FileOutputStream("download.txt");
            isRetrieve =
               cli.retrieveFile("download.txt", outputstream);
            outputstream.close();
            if (!isRetrieve) {
                System.out.print("error: downLoad\r\n");
            }
            System.out.print("end: downLoad\r\n");
            return;
        } catch (IOException ie) { 
            return;
        }
    }

    // アップロード
    public void upLoad() {
        FileInputStream     inputstream;
        boolean             isStore;

        System.out.print("start: upLoad\r\n");
        try {
            inputstream =
                new FileInputStream("upload.txt");
            isStore =
                cli.storeFile("upload.txt",
                inputstream);
            inputstream.close();
            if (!isStore) {
                System.out.print("error: upLoad\r\n");
            } 
            System.out.print("end: upLoad\r\n");
            return;
        } catch (IOException ie) {
            return;
        }
    }

    // 切断
    public boolean disConnect() {

        System.out.print("start: disConnect\r\n");
        try {
            if (cli != null && cli.isConnected()) {
                cli.disconnect(); 
            }
            System.out.print("end: disConnect\r\n");
            return true;
        } catch (IOException ie) {
            return false;
        }
    }
}

以下のクラスが実際にFTP処理をおこなう「FtpSample.java」です。
「FtpMng.java」に準備しているメソッドを順番に呼び出しているだけです。

public class FtpSample {

    public static void main(String[] args){
        System.out.print("start: FtpSample\r\n");
        FtpMng ftpClient = new FtpMng();

        // 1:接続
        ftpClient.connect();

        // 2:ログイン
        ftpClient.login();

        // 3:ダウンロード
        ftpClient.downLoad();

        // 4:アップロード
        ftpClient.upLoad();

        // 5:切断
        ftpClient.disConnect();

        System.out.print("end: FtpSample\r\n");
    }
}

FTPサンプルプログラムの解説


それでは以下に解説していきます。
そんなに難しいプログラムではないですが。


接続・ログイン・切断


FTPサーバに対してファイルをアップロード・ダウンロードする前に、FTPサーバにログインしなければいけません。
手順としては、接続→ログイン、の順番になります。


まずは接続です。

cli = new FTPClient();
cli.setDataTimeout(60000);
cli.connect("localhost");
rep = cli.getReplyCode();
if (!FTPReply.isPositiveCompletion(rep)) {
    return false;
} 

正常に接続できたかどうか?については、getReplyCode()で返り値を取得し、FTPReplyクラスで返り値を検証することで判断することができます。


次にログインです。

if (!cli.login("ftp", "password")) {
    return false;
}

IDとパスワードを指定してログインを実行です。
これでFTPサーバにログインできます。


ID ftp
パスワード password

一連の処理をおこなった後に切断して処理終了です。

if (cli != null && cli.isConnected()) {
    cli.disconnect(); 
}

サンプルプログラムでは、一応、FTPClientインスタンスが存在しており、かつ、接続されている場合に切断しています。


ダウンロード


FTPサーバからファイルを取得します。

outputstream =
    new FileOutputStream("download.txt");
isRetrieve =
    cli.retrieveFile("download.txt", outputstream);
outputstream.close();

Javaのアウトプットストリームクラスを使用してFTPサーバからファイルをダウンロードしています。
ストリームのクローズは忘れずに、ですね。


アップロード


FTPサーバにファイルを格納します。


inputstream =
    new FileInputStream("upload.txt");
isStore =
    cli.storeFile("upload.txt",
        inputstream);
inputstream.close();

こちらはダウンロードとは逆の方式です。
Javaのインプットストリームクラスを使用してFTPサーバにファイルをアップロードしています。
こちらも、ストリームのクローズは忘れずに、です。


まとめ


FTPサーバに対するファイル操作は、Javaで実装することが可能です。
サンプルプログラムでは必要最低限のソースコードしか紹介していませんが、FTPコマンドで可能な操作はJavaが全て可能です。


まとめ
  • FTPサーバに対する操作は、Javaで全て実装可能。
  • サンプル以外のFTP操作についても可能



JavaMailを使って添付ファイル付きメールの送受信をおこなう

Java

JavaMailを使ってメールの送信や受信をおこなうことができますが、添付ファイル付きメールの送信や受信もおこなうことができます。


今回は、添付ファイル付きメールの送受信について、サンプルプログラムを紹介します。
添付ファイルを付けてメールの送信をおこなうサンプルプログラムと、メールを受信して添付ファイルを保存するサンプルプログラムになります。


添付ファイルを意識していないサンプルプログラムについては、以前に作成した記事を参照ください。


環境情報


今回は、Yahooメールを使ってメールの送受信をおこなってみます。


  • Java1.8.60
  • POP3:Yahooメール
  • SMTP:Yahooメール

添付ファイル付きメールを受信する


メール受信プログラムは以下になります。
1~4が、添付ファイル保存で重要な部分になります。


import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileOutputStream;

import java.util.Properties;

import javax.mail.BodyPart;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.internet.MimeMultipart;

public class MailReceiveAtach {

    public static void main(String[] args){
        System.out.print("start: main\r\n");

        try {
            // メール受信のプロパティ設定
            Properties props = new Properties();
            props.put("mail.pop3.host", "pop.mail.yahoo.co.jp");
            props.put("mail.pop3.port", "110");

            // メール受信フォルダをオープン
            Session session = Session.getDefaultInstance(props);
            Store store = session.getStore("pop3");
            store.connect("address", "password");
            Folder folderInbox = store.getFolder("INBOX");
            folderInbox.open(Folder.READ_ONLY);

            // メッセージ一覧を取得
            Message[] arrayMessages = folderInbox.getMessages();
            for (int lc = 0; lc < arrayMessages.length; lc++) {

                // メッセージの取得
                Message message = arrayMessages[lc];

                // 件名の取得と表示
                String subject = message.getSubject();
                System.out.print("件名:" + subject + "\r\n");

                // 受信日時を表示
                String sentDate = message.getSentDate().toString();
                System.out.print("受信日時:" + sentDate + "\r\n");

                // 本文の取得と表示
                MimeMultipart mltp = 
                     (MimeMultipart)message.getContent();
                BodyPart body1 = mltp.getBodyPart(0);
                System.out.print("本文:" + 
                    body1.getContent().toString() + "\r\n");

                // 1:添付ファイルボディを取得
                BodyPart body2 = mltp.getBodyPart(1);
                String fileName = body2.getFileName();
                System.out.print(
                    "添付ファイル名:" + fileName + "\r\n");

                // 2:添付ファイルのストリームを取得
                InputStream ins = body2.getInputStream();

                // 3:出力ストリームに出力しながらファイルを保存する
                OutputStream outs = 
                    new FileOutputStream("save.txt"); 
                int out;
                while ((out = ins.read()) != -1) {
                    outs.write(out);
                }

                // 4:ストリームをクローズ
                ins.close();
                outs.close();

                // 取得の最大件数は10件
                if (lc &amp;amp;gt;= 10) {
                    break;
                }
                System.out.print("\r\n");
            }
        } catch (Exception e) {
            System.out.print("例外が発生!")
            e.printStackTrace();
        } finally {
        }
        System.out.print("end: main\r\n");
    }
}

「1」で、添付ファイルが保存されているボディパートを取得します。
本サンプルプログラムは決め打ちで「1」と記載しています。

本来であれば、ボディパートの数だけループして、添付ファイルが格納されているボディパートについてのみ、「2」以降の添付ファイル保存処理をおこなうべきです。
添付ファイルが複数存在する場合はこのボディパートが複数存在することになるので、ループで回す必要があるということですね。


「2」で、添付ファイルのインプットストリームを取得して、ファイル保存の準備をおこないます。


「3」で、アウトプットストリームを使用して添付ファイルの保存をおこないます。
添付ファイルのファイル名は「save.txt」です。


「4」でストリームをクローズして終了、です。


添付ファイル付きメールを送信する


メール送信プログラムは以下になります。
添付ファイルを2つ添付しているサンプルプログラムになります。


import java.util.Properties;

import javax.mail.Address;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Transport;
import javax.mail.Multipart;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import javax.activation.FileDataSource;
import javax.activation.DataHandler;

public class MailSendMlt {

    public static void main(String[] args){
        System.out.print("start: main\r\n");

        try {
            // メール送信のプロパティ設定
            Properties props = new Properties();
            props.put("mail.smtp.host", "smtp.mail.yahoo.co.jp");
            props.put("mail.smtp.port", "587");
            props.put("mail.smtp.auth", "true");
            props.put("mail.transport.protocol", "smtp");
            props.put("mail.smtp.ssl.trust", "*");
            props.put("mail.smtp.starttls.enable", "true");
            props.put("mail.smtp.connectiontimeout", "10000");
            props.put("mail.smtp.timeout", "10000");

            // セッションを作成する
            Session session = Session.getInstance(props,
                new javax.mail.Authenticator() {
                    protected PasswordAuthentication 
                        getPasswordAuthentication() {
                        return new PasswordAuthentication(
                            "fromAddress", "password");
                    }
                });

            // メールの送信先はYahooメール。送信元もYahooメール
            Message message = new MimeMessage(session);
            message.setFrom(new InternetAddress(
                "fromAddress", "fromAddress"));
            message.setReplyTo(
                new Address[]{new InternetAddress("toAddress")});
            message.setRecipients(
                Message.RecipientType.TO, 
                InternetAddress.parse("toAddress"));
            message.setSubject("テスト");

            // メッセージ本文
            MimeBodyPart messageBodyPart = new MimeBodyPart();
            messageBodyPart.setText("テストメール。");

            // 1:添付ファイル1を添付するボディパートを取得
            MimeBodyPart attachedFilePart1 = new MimeBodyPart();

            // 2:添付ファイル1のデータソースを取得
            FileDataSource fs1 = new FileDataSource("attach1.txt");

            // 3:ボディパート1に添付ファイル1を添付
            attachedFilePart1.setDataHandler(new DataHandler(fs1));
            attachedFilePart1.setFileName(
                MimeUtility.encodeWord(fs1.getName()));

            // 4:添付ファイル2を添付するボディパートを取得
            MimeBodyPart attachedFilePart2 = new MimeBodyPart();

            // 5:添付ファイル2のデータソースを取得
            FileDataSource fs2 = new FileDataSource("attach2.txt");

            // 6:ボディパート2に添付ファイル2を添付
            attachedFilePart2.setDataHandler(new DataHandler(fs2));
            attachedFilePart2.setFileName(
                MimeUtility.encodeWord(fs2.getName()));

            // 7:メールに、本文・添付1・添付2の3つを添付
            Multipart multipart = new MimeMultipart();
            multipart.addBodyPart(messageBodyPart);
            multipart.addBodyPart(attachedFilePart1);
            multipart.addBodyPart(attachedFilePart2);
            message.setHeader(
                "Content-Transfer-Encoding", "base64");

            // 8:メールを送信する
            message.setContent(multipart);
            Transport.send(message);

        } catch (Exception e) {
            System.out.print("例外が発生!\r\n");
            e.printStackTrace();
        } finally {
        }
        System.out.print("end: main\r\n");
    }
}

「1」~「3」が添付ファイル1の作成処理になります。


「1」で、添付ファイルのボディパートを作成します。


「2」で、添付するファイルのデータソースを作成します。


「3」で、添付ファイルを添付します。


「4」~「6」も「1」~「3」と同じで、添付ファイル2を添付します。


「7」で、本文・添付1・添付2をメッセージ本文に添付してメール自体は完成します。


最後に「8」でメールを送信します。


まとめ


紹介したサンプルプログラムで、添付ファイルのメール送受信が可能です。
参考にしてください。




POIで罫線を書く。Javaサンプルプログラムと、全ての罫線出力

ApachePOI

JavaでExcelの加工や出力をおこなう時は、Apache POIが便利です。
Apache POIは、セルへの文字列出力や塗り潰し色の設定が可能ですが、罫線の設定も可能です。


今回は、Apache POIで可能な罫線出力を、サンプルプログラムと同時に出力結果をふまえて紹介していきます。


セルの塗り潰し色設定方法については、以前の記事を参考にしてください。



環境情報


  • OS:Windows10
  • Java:Java1.8.0_60
  • Apache POI:poi 4.1.0

罫線を設定するサンプルプログラム


サンプルプログラムは以下になります。
ApachePOIで設定可能な全ての罫線を設定して出力しています。


import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.CellValue;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

public class PoiLine {

    public static void main(String[] args){
        System.out.print("start: main\r\n");

        Workbook tempbook = null;
        try {
            //--- テンプレートファイルをひらいて、シートを指定 --//
            Path tempPath = Paths.get("./template.xlsx");
            InputStream inSt = Files.newInputStream(tempPath);
            tempbook = new XSSFWorkbook(inSt);
            Sheet sheet = tempbook.getSheet("Sheet1");

            //--- B2セル 罫線なし ---//
            Row row = sheet.createRow(1);
            Cell cell = row.createCell(1);
            cell.setCellValue("罫線なし");
            CellStyle style = tempbook.createCellStyle();
            style = setBorder(style, BorderStyle.NONE);
            cell.setCellStyle(style);

            //--- B4セル 長点線 ---//
            row = sheet.createRow(3);
            cell = row.createCell(1);
            cell.setCellValue("長点線");
            style = tempbook.createCellStyle();
            style = setBorder(style, BorderStyle.DASH_DOT);
            cell.setCellStyle(style);

            //--- B6セル 長点点線 ---//
            row = sheet.createRow(5);
            cell = row.createCell(1);
            cell.setCellValue("長点点線");
            style = tempbook.createCellStyle();
            style = setBorder(style, BorderStyle.DASH_DOT_DOT);
            cell.setCellStyle(style);

            //--- B8セル 破線 ---//
            row = sheet.createRow(7);
            cell = row.createCell(1);
            cell.setCellValue("破線");
            style = tempbook.createCellStyle();
            style = setBorder(style, BorderStyle.DASHED);
            cell.setCellStyle(style);

            //--- B10セル 点線 ---//
            row = sheet.createRow(9);
            cell = row.createCell(1);
            cell.setCellValue("点線");
            style = tempbook.createCellStyle();
            style = setBorder(style, BorderStyle.DOTTED);
            cell.setCellStyle(style);

            //--- B12セル 二重線 ---//
            row = sheet.createRow(11);
            cell = row.createCell(1);
            cell.setCellValue("二重線");
            style = tempbook.createCellStyle();
            style = setBorder(style, BorderStyle.DOUBLE);
            cell.setCellStyle(style);

            //--- B14セル 中太線 ---//
            row = sheet.createRow(13);
            cell = row.createCell(1);
            cell.setCellValue("中太線");
            style = tempbook.createCellStyle();
            style = setBorder(style, BorderStyle.MEDIUM);
            cell.setCellStyle(style);

            //--- B16セル 長点線(中太線) ---//
            row = sheet.createRow(15);
            cell = row.createCell(1);
            cell.setCellValue("長点線(中太線)");
            style = tempbook.createCellStyle();
            style = setBorder(style, BorderStyle.MEDIUM_DASH_DOT);
            cell.setCellStyle(style);

            //--- B18セル 長点点線(中太線) ---//
            row = sheet.createRow(17);
            cell = row.createCell(1);
            cell.setCellValue("長点点線(中太線)");
            style = tempbook.createCellStyle();
            style = setBorder(
                style, BorderStyle.MEDIUM_DASH_DOT_DOT);
            cell.setCellStyle(style);

            //--- B20セル 破線(中太線) ---//
            row = sheet.createRow(19);
            cell = row.createCell(1);
            cell.setCellValue("破線(中太線)");
            style = tempbook.createCellStyle();
            style = setBorder(style, BorderStyle.MEDIUM_DASHED);
            cell.setCellStyle(style);

            //--- B22セル 斜長点線 ---//
            row = sheet.createRow(21);
            cell = row.createCell(1);
            cell.setCellValue("斜長点線");
            style = tempbook.createCellStyle();
            style = setBorder(style, BorderStyle.SLANTED_DASH_DOT);
            cell.setCellStyle(style);

            //--- B24セル 太線 ---//
            row = sheet.createRow(23);
            cell = row.createCell(1);
            cell.setCellValue("太線");
            style = tempbook.createCellStyle();
            style = setBorder(style, BorderStyle.THICK);
            cell.setCellStyle(style);

            //--- B26セル 細線 ---//
            row = sheet.createRow(25);
            cell = row.createCell(1);
            cell.setCellValue("細線");
            style = tempbook.createCellStyle();
            style = setBorder(style, BorderStyle.THIN);
            cell.setCellStyle(style);

            //--- B28セル 細点線 ---//
            row = sheet.createRow(27);
            cell = row.createCell(1);
            cell.setCellValue("細点線");
            style = tempbook.createCellStyle();
            style = setBorder(style, BorderStyle.HAIR);
            cell.setCellStyle(style);

            //--- out.xlsxとしてファイル出力 --//
            Path outPath = Paths.get("./out.xlsx");
            OutputStream outSt = Files.newOutputStream(outPath);
            tempbook.write(outSt);
        } catch (IOException e) {
            System.out.print("入出力例外が発生!");
        } finally {
            //--- テンプレートをクローズ --//
            try {
                if (tempbook != null) {
                    tempbook.close();
                }
            } catch (IOException e) {
                System.out.print("終了処理で入出力例外が発生!");
            }
        }
        System.out.print("end: main\r\n");
    }

    // 
    // 罫線を設定する
    // 
    public static CellStyle setBorder(
        CellStyle cellStyle, BorderStyle kind) {
        cellStyle.setBorderLeft(kind);
        cellStyle.setBorderRight(kind);
        cellStyle.setBorderTop(kind); 
        cellStyle.setBorderBottom(kind); 
        return cellStyle;
    }
}

実際に罫線を設定している処理は、「setBorder」メソッドになります。
罫線は上下左右を別々にしか設定できないため、上下左右全ての罫線を指定された罫線種別に変更するメソッドを自作しています。


setBorderLeft() セル左の罫線を設定
setBorderRight() セル右の罫線を設定
setBorderTop() セル上の罫線を設定
setBorderBottom() セル下の罫線を設定

罫線の種類


ApachePOIでは、エクセルで設定可能な罫線を全て指定可能になります。
罫線の種類はBorderStyleクラスに定義されており、サンプルプログラムで全ての罫線設定を試しています。


NONE 罫線なし
DASH_DOT 長点線
DASH_DOT_DOT 長点点線
DASHED 破線
DOTTED 点線
DOUBLE 二重線
MEDIUM 中太線
MEDIUM_DASH_DOT 長点線(中太線)
MEDIUM_DASH_DOT_DOT 長点点線(中太線)
MEDIUM_DASHED 破線(中太線)
SLANTED_DASH_DOT 斜長点線
THICK 太線
THIN 細線
HAIR 細点線

実際にサンプルプログラムを出力した結果は以下になります。
全ての罫線が出力できています。



まとめ


ApachePOIを使用すれば、簡単にエクセル操作ができます。
今回紹介した罫線の設定も可能ですが、色の設定も可能となります。


まとめ
  • Javaでエクセルを操作するためには、Apache POIをつかうべき!
  • Apache POIを使えば、エクセルへの文字列出力だけではなく、罫線設定も簡単におこなうことができる


JavaMailを使ってメール送受信をおこなう方法

Java

今回は、JavaMailを使ってメールの受信、および送信おこなう方法を紹介します。


JavaMailとは、Javaに標準実装されているメール送信APIです。
簡単な実装で、メールの受信と送信をおこなうことができます。

今回は、Yahooメールを使用した実装サンプルを紹介します。


環境情報


環境の情報は以下になります。

今回は、Yahooメールを使ってメールの送受信をおこなってみます。

メールはYahooのフリーアカウントを使用します。

  • Java1.8.60
  • POP3:Yahooメール
  • SMTP:Yahooメール

Yahooメールを受信する


メール受信プログラムは以下になります。
YahooメールのPOP3サーバに接続し、受信ボックスに存在するメールを取得して標準出力をおこなっています。
22行目のIDとパスワードは、自分のYahooメールのIDをパスワードを使用してください。


import java.util.Properties;

import javax.mail.Session;
import javax.mail.Store;
import javax.mail.Folder;
import javax.mail.Message;

public class MailReceive {

    public static void main(String[] args){
        System.out.print("start: main\r\n");

        try {
            // メール受信のプロパティ設定
            Properties props = new Properties();
            props.put("mail.pop3.host", "pop.mail.yahoo.co.jp");
            props.put("mail.pop3.port", "110");

            // メール受信フォルダをオープン
            Session session = Session.getDefaultInstance(props);
            Store store = session.getStore("pop3");
            store.connect("ID", "PW");
            Folder folderInbox = store.getFolder("INBOX");
            folderInbox.open(Folder.READ_ONLY);

            // メッセージ一覧を取得
            Message[] arrayMessages = folderInbox.getMessages();
            for (int lc = 0; lc < arrayMessages.length; lc++) {

                // メッセージの取得
                Message message = arrayMessages[lc];

                // 件名の取得と表示
                String subject = message.getSubject();
                System.out.print("件名:" + subject + "\r\n");

                // 本文の取得と表示
                String content = message.getContent().toString();
                System.out.print("本文:" + content + "\r\n");

                // 取得の最大件数は10件
                if (lc >= 10) {
                    break;
                }
                System.out.print("\r\n");
            }
        } catch (Exception e) {
            System.out.print("例外が発生!");
            e.printStackTrace();
        } finally {
        }
        System.out.print("end: main\r\n");
    }
}

Javaメールのメール受信をおこなえるようにするためには、Yahooメールでの設定変更が必要です。
Yahooメールの設定画面で、POPサーバについて、Yahoo公式サービス以外からのアクセスを許可します。
送信の場合はSMTPを使うので、POPとSMTPについては許可設定をしておく必要があります。


Yahooメール設定1
Yahooメール設定2

Yahooメールを送信する


次にメール送信です。
メールの送信元がYahooメールで、メールの送信先もYahooメールにしています。
自分自身にメールを送っています。


IDとパスワード、メールアドレスについては、実際に送信する情報にあわせて修正が必要になります。


import java.util.Properties;

import javax.mail.Address;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Transport;
import javax.mail.Multipart;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;

public class MailSend {

    public static void main(String[] args){
        System.out.print("start: main\r\n");

        try {
            // メール送信のプロパティ設定
            Properties props = new Properties();
            props.put("mail.smtp.host", "smtp.mail.yahoo.co.jp");
            props.put("mail.smtp.port", "587");
            props.put("mail.smtp.auth", "true");
            props.put("mail.transport.protocol", "smtp");
            props.put("mail.smtp.ssl.trust", "*");
            props.put("mail.smtp.starttls.enable", "true");
            props.put("mail.smtp.connectiontimeout", "10000");
            props.put("mail.smtp.timeout", "10000");

            // セッションを作成する
            Session session = Session.getInstance(props,
                new javax.mail.Authenticator() {
                    protected 
                        PasswordAuthentication 
                        getPasswordAuthentication() {
                        return new 
                            PasswordAuthentication("ID", "PW");
                    }
                });

            // メールの送信先はYahooメール。送信元もYahooメール
            Message message = new MimeMessage(session);
            message.setFrom(new InternetAddress(
                "fromAddress", "fromName"));
            message.setReplyTo(new Address[]{
                new InternetAddress("toAddress")});
            message.setRecipients(Message.RecipientType.TO, 
                InternetAddress.parse("toAddress"));
            message.setSubject("テスト");
            MimeBodyPart messageBodyPart = new MimeBodyPart();
            messageBodyPart.setText("テストメール。");

            // メールのメタ情報を作成
            Multipart multipart = new MimeMultipart();
            multipart.addBodyPart(messageBodyPart);
            message.setHeader(
                "Content-Transfer-Encoding", "base64");

            // メールを送信する
            message.setContent(multipart);
            Transport.send(message);

        } catch (Exception e) {
            System.out.print("例外が発生!\r\n");
            e.printStackTrace();
        } finally {
        }
        System.out.print("end: main\r\n");
    }
}

筆者がちょっとはまったのが、以下の例外が発生した場合です。


javax.mail.NoSuchProviderException: No provider for smtp
        at javax.mail.Session.getProvider(Session.java:464)
        at javax.mail.Session.getTransport(Session.java:659)
        at javax.mail.Session.getTransport(Session.java:640)
        at javax.mail.Session.getTransport(Session.java:697)
        at javax.mail.Transport.send0(Transport.java:192)
        at javax.mail.Transport.send(Transport.java:124)
        at MailSend.main(MailSend.java:55)

“smtpのプロバイダーが存在しないよ”、と言われてしまっています。


この例外が発生する場合は、実行時のクラスパスに「mail.jar」が含まれていないときです。
「mail.jar」がない状態でビルドしてもビルドエラーにならないので、気が付きにくいかもしれません。


最後に


いかがでしたでしょうか?
Javaメールを使った、メール受信とメール送信の方法について紹介しました。


今回はYahooメールを使用したメール送受信方法を紹介しましたが、他メールサービスでも試してみたいと思います。(いつか。。。)
それではまた!




XSLファイルを使って、SpringMVCでCSV出力をおこなう方法

SpringFrameWork

WEBアプリケーションにおいて、サーバでCSVを作成して、クライアント(ブラウザ)にダウンロードする、といった要件は頻繁に発生します。


SpringMVCでXSLファイルを上手に使えば、簡単にCSVファイルの作成とダウンロードの機能を作成することができます。


今回は、SpringMVCでCSV出力をおこなう方法を紹介します。
Javaで自力でゴリゴリ作成する方法ではなく、XSLファイルを使ってCSV出力おこなう方法となります。


XSLファイルとは、XMLファイルのスタイルを定義しているファイルになります。
出力するCSVファイルのレイアウトをXSLファイルに定義してあらかじめサーバ上に格納しておき、CSVファイルを出力する際に使用します。


XSLファイルとは何か?


XSL(eXtensible Style Language)ファイルは、XML 文書を他の文書タイプに変換したり、出力をフォーマットする際に使用できるスタイルシートです。
Styleという言葉が使われていることからもわかるように、XMLのスタイルシートがXSLになります。


このXSLファイルで便利なのが、テンプレート関数というちょっとしたロジックをXSLファイル内で使用することができる点です。
例えば、以下のような関数です。


  • テンプレート関数の定義の仕方(xsl:template、xsl:param)
  • 引数の値の参照の仕方($引数名)
  • 条件分けの仕方(xsl:if、xsl:choose)
  • XSLT関数(not、contains、substring-before、substring-after)
  • XSLT関数の使い方(<xsl:value-of select=”XSLT関数 (引数…)” />)
  • 定義されたテンプレート関数の呼び出し方(xsl:call-template、xsl:with-param:再帰呼び出しの箇所)

上記のような関数もあらかじめXSLファイル内に定義することができます。


以下に、よく使う関数を紹介していきます。


文字列の連結


concat(str1,str2,str3,・・・)
str1、str2、str3、を連結


文字列の調査


contains(str, substr)
strの中のsubstrを検索。
存在する場合はtrueを通知


数字のフォーマッティング


format-number(number, format)
format-number(number, format, formatType)
numberの数字を、formatで指定されたフォーマットに変換して出力します。


空白の除去


normalize-space(str)
strから空白を除外した文字列を返却します


先頭文字のチェック


starts-with(str, substr)
strがsubstrで始まっているかをチェックします。
始まっている場合はtrueを通知。


文字列への変換


string(val)
valを文字列に変換します。


文字列の長さを取得


string-length(str)
strの長友を取得します。


文字列の抜き出し


substring(str, start)
substring(str, start, length)
strに対して、start位置からlengthの長さの文字列を取得します。


文字列の抜き出し(前文字列の取得)


substring-before(str, substr)
strからsubstrの文字列を検索し、見つかったらその前の文字列を取得します。


substring-after(後文字列の取得)


substring-after(str, substr)
strからsubstrの文字列を検索し、見つかったらその後の文字列を取得します。


文字列の置換


translate(str, src, dest)
strに含まれるsrcという文字列をdestに変換します。


切り上げ


ceiling(val)
valの切り上げをおこないます。


ノード数の取得


count(node)
XML内に含まれるnodeで指定されたノードの数を取得します。

<a>
  <b></b>
  <b></b>
</a>

この例で、’b’のノード数は2です。


切り下げ


floor(val)
valの切り下げをおこないます。


数値に変換


number(any)
anyを数値に変換します。


四捨五入


round(val)
valを四捨五入します。


加算


sum(node)
nodeで指定されている値を加算し、数値で返却します。


<a>
    <b>10</b>
    <b>10</b>
</a>

sum(‘b’)の返却値は20です。


現在のノード取得


current()
現在位置のノードを取得します。


現在のノードの最下層ノードを取得


last()


nodeのローカル名(名前空間を外したもの)を取得


local-name(node)


現在のノード名を取得


name()


現在のノードの名前空間URIを取得


namespace-uri()


現在のノード位置を数値で取得


position()


boolean型への変換


boolean(any)
anyをboolean(true or false)に変換します。


falseの出力


false()
boolean型のfalseを出力します。


trueの出力


true()
boolean型のtrueを出力します。


否定


not(boolean)
booleanの否定値をbooleanで出力します。


ドキュメントルートの取得


document(uri)
uriのドキュメントルートを出力します。


xsltで解釈できるxlstノードかを判定


element-available(str)
返り値はbooleanです。


xsltで解釈できる関数かを判定


function-available(func)


ノードに固有の値を割り当て


generate-id(node)


該当するidのノードを取得


id(any)


キー値を返却


key(str any)
str内のハッシュに、anyで指定される表現を返却します。


XSLT固有値の取得


system-property(str)
str内のXSLT固有の値を取得します。


<!entity>タグ値を取得


unparsed-entity-uri()
<!entity>タグで指定されている値を取得します。


CSV出力のサンプルソース


CSV出力のサンプルソースを紹介します。


SpringMVCのコントローラクラスに実装することを想定しています。


@RequestMapping(params = "csvOut", produces = "text/csv")
public ModelAndView makeCsvOut(HttpServletResponse response, Form form)
        throws JAXBException, IOException {

    // DBから出力する情報を取得してDTOに格納
    CsvOutDTO dto = this.service.geCsvInfo(form);

    // DTOをXMLの形式に変換
    JAXBContext context = JAXBContext.newInstance(CsvOutDTO.class);
    Writer writer = new StringWriter();
    context.createMarshaller().marshal(dto, writer);

    // モデルに変換したXMLを格納
    String xslName = "CsxXsl";
    ModelAndView modelAndView = new ModelAndView(xslName);
    modelAndView.addObject(
        "xmlSource", new StringReader(writer.toString()));

    // HTTPレスポンスヘッダを作成
    response.setHeader("Content-Disposition", 
        "attachment; filename=csvFile.csv, "UTF-8"));

    // BOMを先頭に付加
    response.getOutputStream().write(CxFix.BOM);

    return modelAndView;
}

データベースからCSV出力する内容を取得することを想定しています。
「geCsvInfo」の中身は消略していますが、このメソッドの中身がDBアクセスしてDTOのインスタンスを返却するイメージです。


その後はサンプルの通りで、DTOを「JAXBContext」に渡せば、CSVファイルの出来上がり


次にXSLファイルのサンプルです。

上記のJavaソースで”CsvXsl”という名前で定義されたXSLファイルのサンプルになります。


<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns="http://www.w3.org/1999/xhtml">
    <xsl:output method="text" omit-xml-declaration="yes" 
        byte-order-mark="yes" encoding="UTF-8" />
  <xsl:template match="/csvList">
    <xsl:call-template name="template_csvList" />
  </xsl:template>
    <xsl:template name="template_csvList">
        <!-- ヘッダ情報 -->
        <xsl:text>カラム1,</xsl:text>
        <xsl:text>カラム2,</xsl:text>
        <xsl:text>カラム3,</xsl:text>
        <xsl:text>
    </xsl:text>
        <!-- データ情報 -->
        <xsl:value-of
            select="translate(translate(translate(
                ./entity/col1, '"', ' '), ',', ' '), '', ' ')" />
        <xsl:text>,</xsl:text>
        <xsl:value-of
            select="translate(translate(translate(
                ./entity/col2, '"', ' '), ',', ' '), '', ' ')" />
        <xsl:text>,</xsl:text>
        <xsl:value-of
            select="translate(
                translate(translate(./entity/col3, '"', ' '), 
                ',', ' '), '', ' ')" />
        <xsl:text>,</xsl:text>
    </xsl:template>
</xsl:stylesheet>

「entity」とは、前のJavaコードに記載していた、「CsvOutDTO」で使用しているエンティティクラスです。
1行データ毎に1エンティティのレコードを保持します。


CSVファイルは1行目は各項目名の説明で、実際のデータ出力は2行目からです。
XSLファイルで2行目以降を動的にすることで、「CsvOutDTO」に格納されているエンティティの数だけCSVデータ出力がおこなわれます


関数は「translate」しか使っていません。
ダブルクオテーションと改行コードを半角スペースに変換しています。


まとめ


いかがでしたでしょうか?


SpringMVCでCSV出力をおこなう場合に、参考にしてもらえればと思います。
特に、XSLの関数は助かります。
ちょっとした処理をJavaでおこなう必要がないので。


それではまた!


Javaで動画変換をおこなう。JAVE(Java Audio Video Encoder)でMP4に!

Javeでの画像変換

今回は、Javaで動画変換をおこなう方法を紹介します。
使用するライブラリは『JAVE』です。



あんまりメジャーではないライブラリのようですね。
でも、使い方がシンプルで、かつ、世の中に出回っているほとんどの動画ファイル形式に対応しているので、筆者は気に入っています。


具体的には、『mp4』に変換することを前提にサンプルプログラムを紹介していきます。


mp4に変更するのは特に理由はないですが、あえて理由をあげるとすれば、”圧縮率が一番よさそうだから”です。


検証環境


筆者が動作検証した環境は以下になります。


  • Windows7
  • Java1.8.06
  • JAVE-1.0.2

JAVE(Java Audio Video Encoder)について


JAVE(Java Audio Video Encoder)とは何か?ということになるのですが、公式サイトの説明を日本語化すると以下になります。


JAVE(Java Audio Video Encoder)ライブラリは、ffmpegプロジェクトのJavaラッパーです。
開発者は、JAVEを利用して、オーディオファイルとビデオファイルをある形式から別の形式にトランスコードできます。
例では、AVIファイルをMPEGファイルにトランスコードでき、DivXビデオストリームを(YouTubeのような)Flash FLVファイルに変更でき、WAVオーディオファイルをMP3またはOgg Vorbisに変換できます。
オーディオトラックとビデオトラックをトランスコードする場合、ビデオのサイズを変更したり、サイズや比率を変更したりできます。
JAVEでは、他の多くの形式、コンテナ、および操作がサポートされています。


動画操作で有名なライブラリは「ffmpeg」ですが、JAVEはffmpegをラッピングしているライブラリみたいです。


説明文をみるとように、様々なファイル形式に対応しています。
ベースはffmpegなので、当然っちゃ当然ですね。


JAVEでMP4に変換する


一通りの動画について変換を試してみました。
一覧でまとめました。
動画は基本的に圧縮されます。


拡張子

mp4変換時の圧縮率

.avi

70%

.mp4

70%

.m2ts

99%

.ts

27%

.mpeg

27%

.mpg

29%

.mkv

29%

.wmv

41%

.webm

42%

.ogm

180%

.mov

96%


JAVEを使用した動画変換処理


動画変換処理のサンプルコードを紹介します。
使い方は簡単で、変換処理の条件(コーデック、ビットレート、など)を属性情報として設定し、変換処理を実行します。
様々な形式に変換が可能(っぽい)ですが、MP4形式への変換サンプルコードになります。


import it.sauronsoftware.jave.AudioAttributes;
import it.sauronsoftware.jave.Encoder;
import it.sauronsoftware.jave.EncoderException;
import it.sauronsoftware.jave.EncodingAttributes;
import it.sauronsoftware.jave.InputFormatException;
import it.sauronsoftware.jave.VideoAttributes;
import it.sauronsoftware.jave.VideoSize;

import java.io.File;

public class JaveManage {

    private JaveManage() {
    }

    public static void main(String[] args) {
        System.out.println("start.");

        try {
            // 動画フォーマットを設定
            EncodingAttributes attrs = 
                new EncodingAttributes();

			// 動画圧縮変換時フォーマット
            attrs.setFormat("mp4");

            // 音声の変換情報を設定
            AudioAttributes audio = new AudioAttributes();
			// 動画圧縮変換時オーディオコーデック
            audio.setCodec("aac");
			// 動画圧縮変換時オーディオビットレート
            audio.setBitRate(256000);
			// 動画圧縮変換時オーディオチャンネル
            audio.setChannels(1);
            audio.setSamplingRate(88200);
			// 動画圧縮変換時オーディオサンプリングレート
            attrs.setAudioAttributes(audio);

            // 動画の変換情報を設定
            VideoAttributes video = new VideoAttributes();
			// 動画圧縮変換時ビデオコーデック
            video.setCodec("libx264");
			// 動画圧縮変換時ビデオビットレート
            video.setBitRate(2700000);
			// 動画圧縮変換時ビデオフレームレート
            video.setFrameRate(30);
			// 動画圧縮変換時ビデオ幅,動画圧縮変換時ビデオ高さ
            video.setSize(new VideoSize(1296, 768));
            attrs.setVideoAttributes(video);

            // 変換を実施
            File source = new File("source.wav");
            File dest = new File("source.mp4");
            Encoder encoder = new Encoder();
            encoder.encode(source, dest, attrs);

            // 変換結果を検証
            if (!dest.exists() || dest.length() == 0) {
                System.out.println("encode failer.");
            }
        } catch (EncoderException e) {
            System.out.println("Occured EncoderException.");
        } catch (Exception e) {
            System.out.println("Occured Exception.");
        }
        System.out.println("end.");
    }
}

設定値はソースコードに直接記述していますが、以下のパラメータを設定します。


  • オーディオビットレート:256000
  • オーディオチャンネル:1
  • サンプリングレート:88200
  • ビデオビットレート:2700000
  • フレームレート:30
  • サイズ:height:1296 width:768

まとめ


いかがでしたでしょうか?


JAVEはあんまりメジャーなライブラリではないようですね。
ググってもあんまり情報がないです。


使ってみた直観としては、使いやすい印象です。
「パラメータを設定して変換」っていうだけですからね。


皆さんが使う際に、参考にして頂ければと思います。


それではまた!



SpringMVCでトランザクション管理をおこなう。アノテーションとXMLの設定

SpringFrameWork

SpringMVCでは、アノテーションの設定をおこなうことだけでDBトランザクションの管理をおこなうことができます。


今回は、SpringMVCでトランザクション管理をおこなう方法について説明していきます


SpringMVCでは、トランザクションのスタート位置をアノテーションで指定することにより、トランザクションの開始を定義することができます
トランザクションのスタート位置を定義することにより、どのDB処理を一つのトランザクションとするかをコントロールすることができるということになります。


しかし、こういった制御をおこなうためには、アノテーションの準備とXMLへの設定が必要になります。


トランザクションとは


トランザクションとは何でしょうか?
ここでのトランザクションとは、“データベースのトランザクション”になります。
直訳すると『取引』ですね。


データベース処理では、1回のSQL発行ではデータの整合性が取れない場合が多々あります。
例えば、あるデータをシステムに登録しようとした場合、複数のテーブルにレコードの登録と更新が必要な場合。
テーブルが3つあり、あるデータをシステムに登録しようとした場合、テーブルAにはレコードの登録、テーブルBにはレコードの更新、テーブルCにもレコードの更新が必要だとします。
この場合、テーブルBのレコード更新が失敗したとすると、テーブルAのレコード登録は無かったことにしたいです。
具体的には”コミットしたくない”。


コミットは、全てのDB処理が完了してから(テーブルCへのレコード更新)おこないたいです。


トランザクションの管理

各テーブルへの処理を終わったタイミングでコミットしてしまうと、途中でエラーが発生した場合に、データの整合が取れていない状態でデータができあがってしまいます。
これを避けるために、3つの処理をまとめて考え、3つの処理が全て完了してからコミットします。
途中で、エラー等が発生した場合はロールバックして元に戻します。


この、複数のDB処理を一つにまとめることをトランザクションと呼びます。


どの処理をまとめて一つのトランザクションにするかを考えるのは、安定したシステムを作るのにきちんと考えておきたいポイントです。


common-database-context.xmlの定義


SpringMVCでトランザクション定義をおこなう場合は、「common-database-context.xml」への定義が必要になります。


まずはトランザクション管理するための基本定義です。
トランザクション管理するためのクラス定義と、DBコネクションのデータソース設定です。


「DataSourceTransactionManager」を使用可能な状態にし、「DataSourceTransactionManager」が使用するデータソースを定義します。


<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
    <property name="defaultTimeout" value="10" />
</bean>

プロパティ値として、タイムアウト値も定義します。
10秒です。


通常、SpringMVCで開発したアプリケーションは、Tomcat等のWEBアプリケーション上に配置することになります。
WEBアプリケーションが生成したコネクションプール内のDBコネクションを、SpringMVCが取得してアプリケーションが使用するのが通常になります。


次に、トランザクションを有効にするパッケージの定義とアノテーションの定義。
以下のように定義します。


<aop:config proxy-target-class="false">
    <aop:advisor advice-ref="txAdvice" pointcut="execution(public * jp.co.xxx..*Controller.*(..)) && @annotation(jp.co.xxx.annotation.DbTransaction)" />
</aop:config>

「pointcut」に、トランザクション管理対象のパッケージを定義します。
パッケージを「execution」括ります。


かつ、「@annotation」に、トランザクション開始を意味するアノテーション定義を記載します。
「jp.co.xxx.annotation.DbTransaction」が、アノテーションのJavaクラスになります。


トランザクション管理するアノテーション設定


ここまで準備した「common-database-context.xml」への定義とアノテーション定義で、トランザクション管理をおこなうことが可能になりました。
アノテーションを定義したメソッドがトランザクションの開始位置で、そのメソッドが完了したら自動的にコミットされます。
仮に、メソッドの途中で例外が発生してメソッドが中断された場合はロールバックされます。(コミットされません)
定義としては以下になります。


@DbTransaction
    public void xxx() {

通常は上記でトランザクション管理ができるのですが、個別にトランザクションの開始やコミット・ロールバックをおこないたい場合があります。
独自のトランザクション管理についても可能です。
以下のように、自分でイベントをプログラミングすればよいです。


// トランザクション管理を読み込み
@Autowired(required = false)
private PlatformTransactionManager transactionManager;

public void xxx() {

    // 処理開始
    TransactionStatus status = startTransaction();

        ・
        ・
        ・

    if (isDone) {
        // コミット
        transactionManager.commit(status);
    } else {
        // ロールバック
        transactionManager.rollback(status);
    }
}

まとめ


いかがでしたでしょうか?


SpringMVCでは、アノテーションの準備をするだけでトランザクション管理をおこなうことが可能になります。


システムの方針によるかと思うのですが、トランザクションの単位をどれにするかもいろいろです。
1つのコントローラメソッドについて1トランザクションにするか、サービスのメソッドについて1トランザクションにするか、
といった悩みどころもあります。


システムでルールを統一した上で、SpringMVCで開発を進めていくのがいいかと思います。


それではまた!