ファイルアップロード

今回はファイルアップロードについて解説します。
Servlet3.0から強力なファイルアップロード機能がついたので、それを受けてJSF2.2からは、h:inputFileタグが使えるようになりました。これでファイルアップロードがとても簡単になりました。

h:inputFileによるファイルアップロードの方法

(注意)アップロードしたファイルの名前が日本語の場合、GlassFishがUTF-8のエンコードを行うように指定していないとファイル名が文字化けします。glassfish-web.xmlを作成して、末尾に、

<parameter-encoding default-charset=”UTF-8″/>

を追加することを忘れないようにしてください(「わかりやすいJavaEE」 P15~17を参照)。

JSFページの書き方

基本的なパターンでは、ウェブは次のように、h:inputFileタグとh:commandButtonを書くだけです。ただし、ファイルデータを送信するので、h:formタグには enctype=”multipart/form-data” を付ける必要があります。

       <h:form id="form" enctype="multipart/form-data">
           <h:inputFile id="file" value="#{bb.file}" />
           <h:commandButton value="アップロード" action="#{bb.upload()}"/>
       </h:form>

#{bb.file}は、バッキングビーンのフィールド変数で、アップロードしたファイルをPart型のオブジェクトとして受け取ります。また、#{bb.upload()}は、fileをディスクに保管するメソッドです。

バッキングビーンの書き方

では、バッキングビーンを見てみましょう。

@Named
@RequestScoped
public class Bb {
    private Part file; // 受け取ったファイルデータ
    public void upload(){
        // ファイルとして保存する処理
    }
    // ゲッター、セッター、その他(省略)
}

あっさりしていますね。ただ、フィールド変数が、private Part file; となっています。

Partは、MIME形式で受け取ったファイルデータを表すインタフェースです。ファイルデータ本体や、ファイル名、サイズ、ContentTypeなどの情報を返すメソッドを持っています。

Partインタフェースのメソッド

Partの持つ全メソッドは次の通りです(JavaEE7 API)。

  • void  delete() —– テンポラリファイルなどを削除する
  • String getContentType() —– ContentTypeを返す
  • String getHeader(String name) —– nameで指定したヘッダーを返す
  • Collection<String> getHeaderNames() —– すべてのヘッダーを返す
  • Collection<String> getHeaders(String name) — nameで指定したPartのヘッダー
  • InputStream getInputStream() —– ファイルを得るためのInputStreamを返す
  • String getName() — Mimeパートの名前を返す
  • long getSize() — ファイルサイズを返す
  • String getSubmittedFileName() — 送信されたファイル名を返す
  • void write(String fileName) — Servletで使用できる簡易なファイル保存メソッド

ファイルとして保存する処理

次は、上のソースで省略したuploadメソッドの処理です。

Part型の file を使って、アップロードされたファイルをディレクトリに保存します。

// ファイルとして保存する処理
try{
  InputStream in = file.getInputStream(); // (1)ストリームを得る
  String filepath = getRealPath("resources/files");// (2)保存するディレクトリのパス
  String filename = file.getSubmittedFileName();  // (3)ファイル名を得る
  Files.copy(in, new File(filepath ,filename).toPath()); // (4)ファイルとして保管
}catch(IOException e){
  //
}

(1)で取得するInputStreamは、ファイルデータを入力するために使います。

(2)は、ファイルの保管場所の作成です。この例では、/resources/files に保管しますが、これはアプリケーション内の相対パスなので、getRealPathメソッドで絶対パスに変換しておきます。getRealPathメソッドは簡単なユーティリティメソッドです。内容は最後に掲載したソースコードを見てください。

(3)は、送信されたファイル名を取得します。オリジナルのファイル名です。

(4)は、java.nio.fileパッケージのFilesクラスが持つ、copyメソッドを使って、入力ストリームinから、すべてのバイトを取り出し、ファイルにコピーします。ファイルは、Path型で指定するので、new File(filepath ,filename)でFileオブジェクトを作成し、そのtoPathメソッドでPath型のオブジェクトを作成しています。

  • static long copy(InputStream in , Path target)  — Filesクラスのメソッド

byte型の配列やテキストとして受け取るには

ファイルデータをbyte型の配列に受け取ればよい場合は、次のようにします。

byte[] data = new byte[(int) file.getSize()];   // byte配列を作成
try {
    InputStream in = file.getInputStream();     // ストリームからbyte配列
    in.read(data);                              // に、入力する
} catch (IOException ex) {
  // 
}

※アップロードしたファイルをデータベースに保管するには、byte配列のdataをそのまま保管すればOKです。適当なエンティティを作成し、@Lob @Basic(FetchType.LAZY)を付けてdataのフィールドを定義してください。(「わかりやすいJavaEE」P.438を参照)

また、テキストファイルをアップロードし、中身のテキストを文字列として受け取るには次のようにします。なお、例ではUTF-8で作成したデータだったので、UTF-8にエンコードして文字列化しています。

byte[] data = new byte[(int) file.getSize()];   // 配列を作成
try {
    InputStream in = file.getInputStream();     // ストリームからbyte配列
    in.read(data);                              // に、入力する
} catch (IOException ex) {
    // 
}
String text = new String(data, "UTF-8"); // バイト配列から文字列を生成

ソースコード

NetBeans用のプロジェクトファイルを次からダウンロードしてください。すぐに、ファイルアップロードを試すことができます。

ファイルアップロードの例題プロジェクト

また、ソースコードは次のようです。

index.xhtml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html">
    <h:outputStylesheet library="css" name="mystyle.css"/>
    <h:head>
        <title>基本的なファイルアップロード</title>
    </h:head>
    <h:body>
       <h:form id="form" enctype="multipart/form-data">
           <h:inputFile id="file" value="#{bb.file}" />
           <h:commandButton value="アップロード" action="#{bb.upload()}"/>
       </h:form>
    </h:body>
</html>

Bb.java(バッキングビーン)

package beans;
import java.io.File;
import javax.servlet.http.Part;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
import javax.faces.context.FacesContext;
import javax.servlet.ServletContext;

@Named
@RequestScoped
public class Bb {
    private Part file;      // アップロードされたファイル
    public void upload(){   // ファイルに保存
        try {
            InputStream in = file.getInputStream();
            String filepath = getRealPath("resources/files");// 絶対パスに変換
            String filename = file.getSubmittedFileName();
            Files.copy(in, new File(filepath ,filename).toPath()); // ファイル保管
        } catch (IOException ex) {
            Logger.getLogger(Bb.class.getName()).log(Level.SEVERE, ex.toString());
        }
    }
    /**
     * アプリケーションルートからの相対パスを絶対パスに変換する
     * @param path  アプリケーションルートのリソースからの相対パス(ex. /resources/~)
     * @return      絶対パス
     */
    public String getRealPath(String path) {
        ServletContext ctx = (ServletContext) FacesContext.getCurrentInstance()
                       .getExternalContext().getContext();
        return ctx.getRealPath(path);
    }    
    // セッターとゲッター
    public Part getFile() {
        return file;
    }
    public void setFile(Part file) {
        this.file = file;
    }
}

まとめ

ファイルアップロードは、確かに、簡単になりました。今回の解説を応用すると、サーバーにファイルを送って、添付ファイル付きのHTMLメールを送信することも簡単に実現できるでしょう。tkxmailライブラリの解説を参照してください。

ただ、それでも大きな不満が1つあります。それは、アップロードの進行状況を表示するプログレスパーを扱えないことです。

これまでは、ブラウザから、XMLHttpRequestとJavaScriptを使ってファイルをアップロードすると、プログレスバーは簡単に表示できました。しかし、h:inputFile タグを使うと、もはやJavaScriptの入り込む余地はありません。どうしたらいいのでしょうか?

結論から言うと、プログレスバーを表示する必要があるならh:inputFileは使わないことです。その代わり、ウェブページ側は普通の inputタグを使って、JavaScriptでファイルをアップロードします。JQueryを使うとスマートにできます。

また、サーバー側には、ファイルアップロード専用のサーブレットをおき、JSFのページに書いたJavaScriptで呼び出します。JSFとサーブレットのハイブリッドですね。

そこで、次回(JPAの連載終了後になります)はハイブリッドなファイルアップロードの方法を解説します。もちろんプログレスバーも付いています。

読者になる

コメントをどうぞ

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

%d人のブロガーが「いいね」をつけました。