Webエンジニア susumuis の技術ブログ

このブログの内容は個人の見解であり、所属する組織の公式見解ではありません

JavaでPDF生成する方法(LibreOffice, jodConverterによる方法)

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が好きだ。というわけで、今回は、以下の組み合わせで実現したので、ポイントを抜粋して紹介します。

なお、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();
}

無事PDFが出力されたのを見ると感激しますね!

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を作成しました。
その辺りを後日紹介したいと思います。