JavaでPDFを生成する場合、こういった方法や、http://allabout.co.jp/gm/gc/80691/ こういった方法http://www.atmarkit.co.jp/fjava/javatips/121jspservlet41.html がある。しかし、どちらも低レベル過ぎて、美しいビジネス文書や帳票を出力するにはワープロを作るくらいの気合が必要となる。
手っ取り早いのは、ExcelやWordの文書をApache POI経由で編集し、そいつをPDFにして出力出来れば、美しいPDFが任意のテンプレートで作れるというプランである。
最近のOfficeならPDF出力をできるので、もしMSマンセーな組織なら、Windowsサーバ上でExcelを常駐させて、COMなんちゃらを利用して実現するのが良いと思う。しかし僕はJava屋だ。サーバはLinuxが好きだ。というわけで、今回は、以下の組み合わせで実現したので、ポイントを抜粋して紹介します。
- アプリはWebアプリ、開発言語はJava
- テンプレートはExcel
- 帳票の生成はApache POI
- PDF化のエンジンはLibre Office (http://ja.libreoffice.org/)
- アプリとLibreOfficeの橋渡しはJodConvertor (http://www.artofsolving.com/opensource/jodconverter)
なお、POIのことについては割愛します。
まずはPOIなので、HSSFWorkbookを生成します。
final HSSFWorkbook book = new HSSFWorkbook(templateStream);
そして、bookを使って色々操作します(w
最後にoutputStreamをbook#writeに渡せばExcelファイルが出力されます。(ここまでは普通のPOIです)
book.write(out);
次はPDF化です。LibreOfficeをインストールして、インストールディレクトリの
program/soffice (Windowsの場合はsoffice.exe)に、次のオプションを付けて起動します。
-accept="socket,port=8100;urp;"
これでソケット通信を受け付けるようになります。
次に、jodConverterを用意しましょう。http://sourceforge.net/projects/jodconverter/files/JODConverter/2.2.2/ から jodconverter-webapp-2.2.2.zip をダウンロードして解凍します。zip中身にwarが入っているので、そいつをtomcatのwebappの下に配置したらすぐに使用できるようになります。
次に、アプリとjodConterverの連携です。幸い、jodConverterにはWebサービス機能があるのでそれを使うのが良さそうです。
ぐぐっても見つからないと思ったら本家に書いてありました(笑)
http://www.artofsolving.com/node/15
ということで、ざっくりこんな感じでユーティリティメソッドを作ります。
public int convertExcepToPdf(final InputStream in, OutputStream out) throws IOException {
HttpClient httpClient = new HttpClient();
PostMethod post = new PostMethod("http://localhost:8080/jodconverter-webapp-2.2.2/service");
post.setRequestHeader("Accept", "application/pdf");
post.setRequestHeader("Content-Type", "application/vnd.ms-excel");
post.setRequestBody(in);
try {
int status = httpClient.executeMethod(post);
if (status != HttpStatus.SC_OK) {
return status;
}
InputStream response = post.getResponseBodyAsStream();
byte[] buf = new byte[256];
for (int len; (len = response.read(buf)) != -1;) {
out.write(buf, 0, len);
}
return status;
} finally {
post.releaseConnection();
}
}
これで接続ができますが、POIのwriteはOutputStream、今回作った関数のパラメータはInputStreamという問題が残ります。
ByteArrayInputやOutputStreamを使ってメモリに全部記憶すればできますが、Excelファイルも大きめのファイルなので、メモリは節約したいです。かと言って、一旦ファイルに保存するのはなんともダサい感じです。HDDガリガリとかナンセンスです!
悩んだ結果、僕はPipedOutputStream, PipedInputStreamを使用しました。
final PipedOutputStream pipeout = new PipedOutputStream();
PipedInputStream pipeIn = new PipedInputStream(pipeout);
Thread t = new Thread(new Runnable() {
public void run() {
try {
try {
book.write(pipeout);
} finally {
pipeout.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
t.start();
PDFConverterConnector pdfC = new PDFConverterConnector();
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
try {
pdfC.convertDocumentFileToPdf(pipeIn, out);
} catch (Exception e) {
e.printStackTrace();
}
2011/05/26 追記
本番環境にインストールするに当たって、CentOS5に入れたのですが、バージョンが古くてLibreOfficeではなく、OpenOffice.orgになってしまったのと、コマンドラインから実行すると、
Set DISPLAY environment variable, use -display option or check permissions of your X-Server (See "man X" resp. "man xhost" for details)
というようなエラーが出てしまいました。これは、
yum install openoffice.org-headless
とすればOKでした。
参考:http://stackoverflow.com/questions/4004456/centos-server-openoffice-headless
追記(2014/04/26)
ここではJodConverter2を使っていますが、OpenOffice, LibreOffice側にメモリリーク問題があり、デーモンとして起動したOOo(LibreOffice)がクラッシュしてしまう問題があります。実際に私の現場でも、容量の大きいExcelファイルを連続して扱うとクラッシュしてしまう現象を観測し、現在は、JodConverter3に移行しています。
JodConverter3については、下記の記事を参照してください
https://code.google.com/p/jodconverter/wiki/WhatsNewInVersion3
JodConverter3はずっとbetaで開発が止まっていて作者によると
I started this project back in 2003, but I am no longer maintaining it. I moved the code here at GitHub in the hope that a well-maintained fork will emerge.
という立場のようです。すでに多数のforkされてpull requestもあるようです。
https://github.com/mirkonasato/jodconverter
私の現場では最新betaを使用していますが、とりたてて問題は発生していません。ただ、jodConverter3には便利なWebサービス機能がありませんので、自前でJodConnver2のものを元にWevServiceを作成しました。
その辺りを後日紹介したいと思います。
この記事は以前多くのブックマーク・コメントを頂きました。
これからブックマークされる方はこちら↓