Servlet + JasperReports で動的に PDF を作成して Web サイトに表示してみるメモ

先日、[Servlet + Apache FOP ネタ](https://qiita.com/yamachan360/items/c028911b3eab49d0a281) とか [その続編](https://qiita.com/yamachan360/items/c985e7ad89d471db64df) とか投稿したのですが、知り合いから「[Jasper](https://www.jaspersoft.com/) 良さげだよ」との情報をいただきまして。ググったら情報多い！主流はコッチだったか… と。

既に日本語の情報は十分にある気もするので、備忘録として、簡単な実施メモにまとめてみました。

# JasperReports 概要

まず Jasper 系の PDF (帳票) 生成に関して、以下が理解の助けになりました。

* [JasperReportsとiReportで帳票の常識を理解しよう (1/4)](https://www.atmarkit.co.jp/ait/articles/1012/01/news143.html)
* [JavaでPDFのテンプレートを作成し、帳票作成を行う](https://www.whizz-tech.co.jp/3092/)
* [Jasperレポート作り方 ~iReport(基本編)~](https://qiita.com/Ky01_dev/items/f158219812e772c673a7)
* [Jaspersoft StudioプラグインをEclipseにインストールする](https://medium-company.com/jaspersoft-studio%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB/)

以下は公式もしくはダウンロードサイト。

* [JasperSoftStudio](https://www.jaspersoft.com/products/jaspersoft-studio)
* [JasperReports® Library](https://community.jaspersoft.com/project/jasperreports-library)
* [iReport-Designer for JasperReports](https://sourceforge.net/projects/ireport/)

LGPL のようですが、一応はライセンス周りを確認。

* [コミュニティ版と商用版の比較](https://community.jaspersoft.com/wiki/komiyuniteiban-toshang-yong-ban-nobi-jiao)
* [JasperReportsとiTextのライセンスについて](https://medium-company.com/jasperreports-itext-%e3%83%a9%e3%82%a4%e3%82%bb%e3%83%b3%e3%82%b9/)

# Jaspersoft StudioプラグインをEclipseにインストール

[iReportはjdk1.8に対応していない](https://qiita.com/taku_0115/items/e1eee9d0a93ca4d1089f) らしいので、今回は Jaspersoft Studio の Eclipse 用プラグインを利用してみます。

私の今のテスト環境 Eclipse 2020-09 で「Help -> Eclipse Marketplace」から Jaspersoft 6.16.0 をインストール。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193519/0c34b396-5e04-b344-b54d-87c36f0689c0.png)
ここで同意するライセンスは [Eclipse Foundation Software User Agreement](https://www.eclipse.org/legal/epl/notice.php) のみの模様。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193519/f71c28bf-bed3-eebe-d971-2a51e564fe4a.png)
インストール後は Eclipse が再起動するので、Welcome 画面に以下のような表示があればok。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193519/a2065891-6a49-ddcd-4da0-3d9b29136182.png)
後は新規作成メニューに追加された「Jasper Report」を新規作成すれば
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193519/763767bf-9893-a630-b754-9695bac174ee.png)
テンプレートの選択画面が出てきて、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193519/05be9a17-d035-fa99-9284-6049c4011f4f.png)
そして、こんな感じの帳票のデザイン画面が開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193519/37210abb-702e-4e9b-c7c7-ba78ad915ba6.png)

# 開発環境の準備

今回の開発環境はこんな感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193519/e74f931d-a5d2-0d35-5e70-74f924297759.png)

1) お馴染みのデータ入力用のシンプルな html ページ
2) 今回の主役、PDF を自動生成する Servlet
3) 先ほどインストールした Jaspersoft Studioプラグインで作成したテンプレート
4) 開発時に必要な JasperReports Library 用 jar ファイル
5) Servlet 実行時に必要な JasperReports Library 用 jar ファイル

4) ですが Jaspersoft Studioプラグインをインストール時に Eclipse に登録されていますので、ビルドパスへライブラリを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193519/0019cc46-736c-301b-0b78-c41b9d716785.png)
5) もプラグインをインストールしてあればPC内にあるはずなので、探して ``WEB-INF/lib`` 下にコピーもしくはリンクします。MVN リポジトリ [JasperReports Library](https://mvnrepository.com/artifact/net.sf.jasperreports/jasperreports) から入手してもかまいません。なお ``ipaexm.jar`` に関してはこの後で説明します。

# フォント設定

[こちら](https://www.whizz-tech.co.jp/3092/) の情報をもとに 「[IPAex明朝](https://moji.or.jp/ipafont/)」フォントをダウンロードし設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193519/04695ad9-7038-c43b-0077-99b5aca16365.png)
ここで大事なのは ``PDF Encoding`` 欄で ``Identity-H(Unicode with horizontal writing)`` を選択することです。これを忘れると日本語が出力されません。

フォントの設定が終わったら、一覧メニューで「Export」を実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193519/5c42b289-6e98-226b-d5bc-fb51b1cade13.png)
jar ファイルの保存画面になりますので ``WEB-INF/lib`` 下に ``ipaexm.jar`` というファイル名で保存します。これで開発環境で指定したフォント設定を、Servlet 実行環境に反映することができました。

# PDF テンプレートを作成する

Jaspersoft Studioプラグインで PDF テンプレートを作成します。今回は以下のようにシンプルなもので試しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193519/f2ec4890-d518-9408-3972-c72b21c45425.png)

1) は単純にタイトルを表示しているだけです。``Static Text`` を配置して、適当なタイトルを入力し、さきほど設定した日本語フォントを指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193519/1739dd7c-fd7f-cc7f-62d4-ad681868ad62.png)

2) は ``Current Date`` を配置しただけです。日本語ではありませんが、表示を統一するためフォントだけ指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193519/d3210174-4762-7d00-4162-4775c2b0746a.png)

3) が動的に設定するテキストになり、最も大事な部分です。まず右の要素ツリーで ``MY_MESSAGE`` という名称の独自パラメーターを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193519/2872b33a-0995-8008-efa8-922e2b9ab767.png)
指定する名称と型(今回は文字列)は以下のような感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193519/1f9881d3-2f36-5a43-3843-ad952df1cd15.png)
次に ``Text Field`` 要素を追加します。表示内容を指定する Expression Editor では追加した独自パラメーターの ``MY_MESSAGE`` を選択します。あ、フォント指定もお忘れなく。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193519/c8e67fe2-dd32-7a86-88a5-98c3cbed2d14.png)

さて、これでテンプレートは完成しました。以下はプレビューを実行した様子です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193519/1efb59cb-4e3e-34bc-d073-ad8e30682d1c.png)

保存した ``sample1.jrxml`` ファイルは以下のような感じ。

```xml:sample1.jrxml
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Jaspersoft Studio version 6.16.0.final using JasperReports Library version 6.16.0-48579d909b7943b64690c65c71e07e0b80981928  -->
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="sample1" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="3fe7289f-d8d2-4a65-9b55-c9aa20be3391">
	<property name="com.jaspersoft.studio.data.defaultdataadapter" value="One Empty Record"/>
	<parameter name="MY_MESSAGE" class="java.lang.String" isForPrompting="false"/>
	<queryString>
		<![CDATA[]]>
	</queryString>
	<background>
		<band splitType="Stretch"/>
	</background>
	<title>
		<band height="79" splitType="Stretch">
			<staticText>
				<reportElement x="80" y="20" width="400" height="30" uuid="b771d8a0-efcc-4c72-a8eb-f66f1b7bc644"/>
				<textElement textAlignment="Center">
					<font fontName="IPAex明朝" size="16"/>
				</textElement>
				<text><![CDATA[JasperReports PDF サンプル]]></text>
			</staticText>
		</band>
	</title>
	<pageHeader>
		<band height="49" splitType="Stretch">
			<textField pattern="MMMMM dd, yyyy">
				<reportElement x="370" y="10" width="180" height="30" uuid="cf6034bc-e2c5-4db9-806d-e40ed1bc65a5"/>
				<textElement textAlignment="Right">
					<font fontName="IPAex明朝"/>
				</textElement>
				<textFieldExpression><![CDATA[new java.util.Date()]]></textFieldExpression>
			</textField>
		</band>
	</pageHeader>
	<columnHeader>
		<band height="61" splitType="Stretch"/>
	</columnHeader>
	<detail>
		<band height="125" splitType="Stretch">
			<textField>
				<reportElement x="0" y="10" width="550" height="100" uuid="0298349c-4284-4697-b6ad-792637b8e76e"/>
				<textElement>
					<font fontName="IPAex明朝"/>
				</textElement>
				<textFieldExpression><![CDATA[$P{MY_MESSAGE}.toString()]]></textFieldExpression>
			</textField>
		</band>
	</detail>
	<columnFooter>
		<band height="45" splitType="Stretch"/>
	</columnFooter>
	<pageFooter>
		<band height="54" splitType="Stretch"/>
	</pageFooter>
	<summary>
		<band height="42" splitType="Stretch"/>
	</summary>
</jasperReport>
```

保存した ``sample1.jrxml`` ファイルを右クリックメニューからコンパイルし、sample1.jasper ファイルを生成しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193519/194c0fa1-3b79-ee72-3b32-ccb7e0d20fea.png)

# 入力用ページ

入力用ページは [これまでと同じ](https://qiita.com/yamachan360/items/c028911b3eab49d0a281) ですので、説明は省きますね。フォーム部分は以下のような感じ。

```html:index.html
<div class="container">
    <h2>Simple JasperReports sample with Servlet</h2>
    <form method="POST" action="/test03/pdfServlet">
        <div class="form-group">
            <label for="i_fname">Document format</label>
            <input type="text" class="form-control" id="i_fname" name="i_fname" value="sample1">
            <label for="i_body">Body mwssage</label>
            <input type="text" class="form-control" id="i_body" name="i_body" value="Sample body text...">
        </div>
        <button type="submit" class="btn btn-primary">Submit</button>
    </form>
</div>
```

# Servlet

さて、今回のコアとなる Servelt 部分ですが、実は過去の [Apache FOP 版](https://qiita.com/yamachan360/items/c028911b3eab49d0a281#servlet-%E3%83%9A%E3%83%BC%E3%82%B8) とあまり変わらなかったりします。呼び出しているライブラリが異なるぐらい？実際のコードを見てもらったほうが理解し易いとおもいます。

```java:pdfServlet.java
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.sf.jasperreports.engine.JREmptyDataSource;
import net.sf.jasperreports.engine.JasperRunManager;

@WebServlet("/pdfServlet")
public class pdfServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    public pdfServlet() {
        super();
    }

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doPost(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	    try {
	    	request.setCharacterEncoding("utf-8");
	    	String i_fname = request.getParameter("i_fname");
	    	String i_body = request.getParameter("i_body");
	    	File jasperFile = new File(getServletConfig().getServletContext().getRealPath("/" + i_fname + ".jasper"));
	    	HashMap<String, Object> params = new HashMap<String, Object>();
	    	params.put("MY_MESSAGE", i_body);
	    	byte[] bytes = JasperRunManager.runReportToPdf(jasperFile.getPath(), params, new JREmptyDataSource());
	        response.setContentType("application/pdf");
	        response.setContentLength(bytes.length);
	        response.setHeader("Content-Disposition", "inline");
	        ServletOutputStream out = response.getOutputStream();
	        out.write(bytes);
	        out.flush();
	    } catch (Exception ex) {
	        throw new ServletException(ex);
	    }
	}

}
```

ポイントは ``params.put("MY_MESSAGE", i_body);`` ですね。PDF テンプレートを作成した際に設定した独自パラメーターに対し、表示用の値をここで渡してあげるわけです。

今回はテンプレートをファイルから読み込んでいますが、DB 化については [続編のほう](https://qiita.com/yamachan360/items/c985e7ad89d471db64df) を参照してみてください。コンパイル後はバイナリ形式であることに留意すれば、ほぼ同じ考え方で対応できるとおもいます。

# 実行してみよう

さて、まずはいつものように index.html を開いてテキストを入力し、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193519/e0427592-b0bb-6fb7-0ee0-7816e934dac7.png)
「Submit」で入力したテキストが埋め込まれた PDF が表示されることを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193519/6e48de76-0da3-9754-fbee-01badc561501.png)
うん、問題なさそうですね。日本語のタイトルも、今日の日付も表示されています。そして本文の部分には、フォームで入力した日本語を含んだテキストが、ちゃんと埋め込まれているのを確認できました。

# 生成された PDF について

今回のサンプルプログラムで生成した PDF ファイルを以下の URL に置きました。

http://rinco.jp/test/jasper1.pdf

いまのところ、以下の環境で表示の確認をしています。もし文字化けとかしちゃう環境などありましたら、コメントいただけると助かります！

* Google Chrome on Windows 10
* Edge on Windows 10 (IEで開いてもこちら起動する)
* Safari on Mac
* Safari on iPhone
* Kindle Fire HD (Silk ブラウザで表示するとKindleアプリが起動し表示)

# ライセンス

この投稿に含まれる私の作成した全てのコードは [Creative Commons Zero ライセンス](https://creativecommons.org/publicdomain/zero/1.0/deed.ja) とします。自由にお使いください。

# おまけ：表示・非表示をコントロールする

PDFで表を生成するサンプルを作成したところ「データが無い場合、ヘッダだけの空の表はみっともない。この場合は表自体を非表示にできないか」という相談がありました。こんな場合に使えるのが、各要素にある ``Print When Expression`` 欄です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193519/5b46486b-1791-46b0-9533-0ce5d773ad63.png)
ここに式を書いておくと、その式が ``true`` の場合にその要素が表示されます。``false`` であれば非表示です。例えば今回のサンプルでメッセージの表示欄に以下のような式を設定してみると
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193519/c606689e-32e5-f5bd-faaf-3644a55bcf85.png)
10文字以下の短いメッセージを入力すると、PDFに表示されなくなります。

表に使用する場合は、表自体、もしくは表を含むセクション全体の ``Print When Expression`` 欄に、表示行数を参照する条件演算子を設定すると良さそうです。

# Enjoy!

以上、Servlet を用いて Web サイトで、JasperReports を用いた PDF の動的生成を試してみました。これをベースに、いろいろ機能を追加して遊んでみてください。

Apache FOP も JasperReports も Servlet から使うぶんには、それほど違いがない気がします。でもテンプレートを作成する部分、JasperReports 用プラグインによる GUI ベースの設定は圧倒的に楽ですね。なので、JasperReports のほうを強く推したいとオモイマス。

それではまた！