JasperReportsでPDFを作成する

JavaでPDFを作成するJasperReportsの使い方について解説します。少し古いライブラリですが企業がバックについてますし、継続的にメンテナンスはされているようです。

JasperReportでPDFを作成するには、

  1. JasperReport Studioでテンプレートを作成する
  2. Javaプログラムでデータを流し込んでPDFを作成する

と言う手順を踏みます。JasperReport StudioではGUIからText、Imageといったパーツを配置しますが、裏ではjrxml形式のファイルを吐き出しています。このjrxmlをコンパイルした.jasperという拡張子のファイルをJavaのプログラムから読み込み、PDFを作成するというのが通常の手順です。

PDF出力のサンプルコードは次のようになります。大抵の場合はこれで事足りると思います。

// 入力パス、出力パス
Path in = ...
Path out = ...

// .jasperを読み込む
//
// .jrxmlを直接読み込むこともできる
//
// JasperReport report = 
//   JasperCompileManager.compileReport(in.toAbsolutePath().toString());
JasperReport report = 
  (JasperReport) JRLoader.loadObject(in.toAbsolutePath().toFile());

// パラメータ、データソースの準備
Map<String, Object> parameters = new HashMap<>();
Collection<Map<String, ?>> source = new ArrayList<>();
...

// パラメータ、データソースの流し込み
// ここでは JRMapCollectionDataSource を利用しているが、他にも JRBeanCollectionDataSource などがある。
// データソースが空の時は空の JRMapCollectionDataSource ではなく JREmptyDataSource を利用する。
// 空の JRMapCollectionDataSourc を利用すると JasperReports 6.7.0 では真っ白なPDFが作成され、エラーも発生しない問題があった。
JasperPrint print = JasperFillManager.fillReport(report, parameters, new JRMapCollectionDataSource(source));
// 書き出し
JasperExportManager.exportReportToPdfFile(print, out.toAbsolutePath().toString());

テンプレートへ流し込むデータには、「パラメータ」と「データソース」の2種類があります。データソースはDetailセクションに流し込む繰り返し構造のデータで、CSVファイルをイメージすると良いです。パラメータは主にDetailセクション以外で使われる埋め込みデータです。

テンプレートの作成

まずは空のテンプレートを見ていきましょう。

次の画像はJasperReports Studioで作った空のテンプレートファイルです。テンプレートはTitle, Page Header, Column Header, Detail1, Column Footer, Page Footer, Summaryのセクションからなっています。Title, Page Headerなどはそのままの意味で、タイトルやヘッダーを表示するところです。Column Header, Detail1, Column Footerがデータを流し込んで、テーブルとして表示する場所です。

f:id:tototoshi:20190302214117p:plain

jrxmlの方も見てみましょう。Title,Page,Header...といった構造がjrxmlにも反映されていることがわかります。

<?xml version="1.0" encoding="UTF-8"?>
<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="Blank_A4"
        pageWidth="595"
        pageHeight="842"
        columnWidth="555"
        leftMargin="20"
        rightMargin="20"
        topMargin="20"
        bottomMargin="20"
        uuid="86d21441-0199-44fa-8d1e-8b7170bc739f">
    <queryString>
        <![CDATA[]]>
    </queryString>
    <background>
        <band splitType="Stretch"/>
    </background>
    <title>
                <!-- bandタグで各セクションの高さを指定できる -->
        <band height="79" splitType="Stretch"/>
    </title>
    <pageHeader>
        <band height="35" splitType="Stretch"/>
    </pageHeader>
    <columnHeader>
        <band height="61" splitType="Stretch"/>
    </columnHeader>
    <detail>
        <band height="125" splitType="Stretch"/>
    </detail>
    <columnFooter>
        <band height="45" splitType="Stretch"/>
    </columnFooter>
    <pageFooter>
        <band height="54" splitType="Stretch"/>
    </pageFooter>
    <summary>
        <band height="42" splitType="Stretch"/>
    </summary>
</jasperReport>

各セクションは必須ではなく、不要であったら削除しても構いません。例えば何枚かに渡るレポートを作りたい時、最初の1枚は表紙になるかもしれません。その時はTitleセクションだけにして大丈夫です。

文字の埋め込み

雰囲気を掴むためにjrxmlを直接編集してみます。

文字の埋め込みは staticText タグを使います。

<title>
    <band height="79" splitType="Stretch">
        <staticText>
            <!-- reportElementタグで表示場所などを指定できる -->
            <reportElement x="0" y="0" width="100" height="50"/>
            <text><![CDATA[Invoice]]></text>
        </staticText>
    </band>
</title>

f:id:tototoshi:20190302214050p:plain

独自のフォントを埋め込む場合には設定の追加が必要です。特に日本語を利用する場合は必須です。今回はIPAフォントを利用します。

まず、クラスパス上に jasperreports_extension.properties というファイルを作成し、フォントの読み込みのための設定と、フォント情報を記述したxmlファイルへのパス(fonts/fonts.xml)を指定します。

net.sf.jasperreports.extension.registry.factory.fonts=net.sf.jasperreports.engine.fonts.SimpleFontExtensionsRegistryFactory
net.sf.jasperreports.extension.simple.font.families.font=fonts/fonts.xml

fonts.xmlは次のように記述します。

<?xml version="1.0" encoding="UTF-8"?>
<fontFamilies>
    <fontFamily name="IPAexゴシック">
        <normal>fonts/ipaexg.ttf</normal>
        <pdfEncoding>Identity-H</pdfEncoding>
        <pdfEmbedded>true</pdfEmbedded>
    </fontFamily>
</fontFamilies>

その上で、jrxmlに textElement タグでフォントの名前を指定します。これで日本語フォントが利用可能になります。

<title>
    <band height="79" splitType="Stretch">
        <staticText>
            <!-- reportElementタグで表示場所などを指定できる -->
            <reportElement x="0" y="0" width="100" height="50"/>
            <textElement>
                <font fontName="IPAexゴシック" size="18"/>
            </textElement>
            <text><![CDATA[請求書]]></text>
        </staticText>
    </band>
</title>

f:id:tototoshi:20190302214028p:plain

なおフォントの使用にあたってはライセンスに注意してください。

パラメータの埋め込み

パラメータの埋め込みは textField タグを使います。textFieldExpression として $P{パラメータ名} を指定します。パラメータの名前はXMLの先頭で parameter タグで宣言しておきます。

<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Jaspersoft Studio version 6.6.0.final using JasperReports Library version 6.6.0  -->
<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="Blank_A4_1" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="f7adae20-db6a-4207-ae6a-75eb4a16e40e">
    <parameter name="title"/>
    <queryString>
        <![CDATA[]]>
    </queryString>
    <background>
        <band splitType="Stretch"/>
    </background>
    <title>
        <band height="79" splitType="Stretch">
            <textField>
                <reportElement x="0" y="0" width="100" height="50"/>
                <textElement verticalAlignment="Middle" textAlignment="Center">
                    <font fontName="IPAexゴシック" size="14"/>
                </textElement>
                <textFieldExpression><![CDATA[$P{title}]]></textFieldExpression>
            </textField>
        </band>
    </title>
    <pageHeader>
        <band height="35" splitType="Stretch"/>
    </pageHeader>
    <columnHeader>
        <band height="61" splitType="Stretch"/>
    </columnHeader>
    <detail>
        <band height="125" splitType="Stretch"/>
    </detail>
    <columnFooter>
        <band height="45" splitType="Stretch"/>
    </columnFooter>
    <pageFooter>
        <band height="54" splitType="Stretch"/>
    </pageFooter>
    <summary>
        <band height="42" splitType="Stretch"/>
    </summary>
</jasperReport>

テーブルの作成

次のようなテーブルを作成してみましょう。

f:id:tototoshi:20190302214004p:plain

columnHeaderに見出しの内容が、detailにテーブルの一行分の内容が入ります。

<columnHeader>{ここに見出しの内容が入る}</columnHeader>
<detail>{ここにテーブルの一行分の内容が入る}</detail>
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Jaspersoft Studio version 6.6.0.final using JasperReports Library version 6.6.0  -->
<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="Blank_A4"
        pageWidth="595"
        pageHeight="842"
        columnWidth="555"
        leftMargin="20"
        rightMargin="20"
        topMargin="20"
        bottomMargin="20"
        uuid="86d21441-0199-44fa-8d1e-8b7170bc739f">
    <queryString>
        <![CDATA[]]>
    </queryString>
    <field name="fruit"/>
    <field name="price"/>
    <columnHeader>
        <band height="30" splitType="Stretch">
            <staticText>
                <reportElement x="0" y="0" width="100" height="30"/>
                <textElement verticalAlignment="Middle" textAlignment="Center">
                    <font fontName="IPAexゴシック" size="14"/>
                </textElement>
                <text><![CDATA[商品]]></text>
            </staticText>
            <staticText>
                <reportElement x="100" y="0" width="100" height="30"/>
                <textElement verticalAlignment="Middle" textAlignment="Center">
                    <font fontName="IPAexゴシック" size="14"/>
                </textElement>
                <text><![CDATA[価格]]></text>
            </staticText>
        </band>
    </columnHeader>
    <detail>
        <band height="30" splitType="Stretch">
            <textField isBlankWhenNull="true">
                <reportElement x="0" y="0" width="100" height="30"/>
                <textElement verticalAlignment="Middle" textAlignment="Center">
                    <font fontName="IPAexゴシック" size="14"/>
                </textElement>
                <textFieldExpression><![CDATA[$F{fruit}]]></textFieldExpression>
            </textField>
            <textField isBlankWhenNull="true">
                <reportElement x="100" y="0" width="100" height="30"/>
                <textElement verticalAlignment="Middle" textAlignment="Center">
                    <font fontName="IPAexゴシック" size="14"/>
                </textElement>
                <textFieldExpression><![CDATA[$F{price}]]></textFieldExpression>
            </textField>
        </band>
    </detail>
</jasperReport>

テーブルに埋め込むデータは「パラメータ」ではなく主に「データソース」です。データソースはコレクション形式のデータで、そのコレクション内の一つの要素がテーブル一行分のデータとなります。コレクション内の要素はその中にいくつかのフィールドを持つことになります。 今回の例の場合、データソースは「fruit」、「price」というフィールドを持つ要素のコレクションです。

データソースの内容を埋め込むには、$F{フィールドの名前} という記法を使います。フィールドの名前は field タグであらかじめ宣言しておく必要があります。

これをレンダリングするJavaプログラムは次のようになります。

Path in = ...;
Path out = ...;

JasperReport report = (JasperReport) JRLoader.loadObject(in.toAbsolutePath().toFile());

Map<String, Object> parameters = new HashMap<>();
Collection<Map<String, ?>> source = new ArrayList<>();

Map<String, Object> row1 = new HashMap<>();
row1.put("fruit", "りんご");
row1.put("price", "100");
source.add(row1);
Map<String, Object> row2 = new HashMap<>();
row2.put("fruit", "みかん");
row2.put("price", "50");
source.add(row2);
Map<String, Object> row3 = new HashMap<>();
row3.put("fruit", "バナナ");
row3.put("price", "30");
source.add(row3);

JasperPrint print = JasperFillManager.fillReport(report, parameters, new JRMapCollectionDataSource(source));
JasperExportManager.exportReportToPdfFile(print, out.toAbsolutePath().toString());

罫線や背景色の調整

reportElement タグで背景色をつけたり、 box タグで罫線を引いたりすることができます。

    <columnHeader>
        <band height="30" splitType="Stretch">
            <staticText>
                <reportElement x="0" y="0" width="100" height="30" mode="Opaque" backcolor="rgba(200, 200, 200, 0.5)"/>
                <box>
                    <topPen lineWidth="1"/>
                    <leftPen lineWidth="1"/>
                    <bottomPen lineWidth="1"/>
                    <rightPen lineWidth="1"/>
                </box>
                <textElement verticalAlignment="Middle" textAlignment="Center">
                    <font fontName="IPAexゴシック" size="14"/>
                </textElement>
                <text><![CDATA[商品]]></text>
            </staticText>
            <staticText>
                <reportElement x="100" y="0" width="100" height="30" mode="Opaque" backcolor="rgba(200, 200, 200, 0.5)"/>
                <box>
                    <topPen lineWidth="1"/>
                    <leftPen lineWidth="1"/>
                    <bottomPen lineWidth="1"/>
                    <rightPen lineWidth="1"/>
                </box>
                <textElement verticalAlignment="Middle" textAlignment="Center">
                    <font fontName="IPAexゴシック" size="14"/>
                </textElement>
                <text><![CDATA[価格]]></text>
            </staticText>
        </band>
    </columnHeader>
    <detail>
        <band height="30" splitType="Stretch">
            <textField isBlankWhenNull="true">
                <reportElement x="0" y="0" width="100" height="30"/>
                <box>
                    <topPen lineWidth="0"/>
                    <leftPen lineWidth="1"/>
                    <bottomPen lineWidth="1"/>
                    <rightPen lineWidth="1"/>
                </box>
                <textElement verticalAlignment="Middle" textAlignment="Center">
                    <font fontName="IPAexゴシック" size="14"/>
                </textElement>
                <textFieldExpression><![CDATA[$F{fruit}]]></textFieldExpression>
            </textField>
            <textField isBlankWhenNull="true">
                <reportElement x="100" y="0" width="100" height="30"/>
                <box>
                    <topPen lineWidth="0"/>
                    <leftPen lineWidth="1"/>
                    <bottomPen lineWidth="1"/>
                    <rightPen lineWidth="1"/>
                </box>
                <textElement verticalAlignment="Middle" textAlignment="Center">
                    <font fontName="IPAexゴシック" size="14"/>
                </textElement>
                <textFieldExpression><![CDATA[$F{price}]]></textFieldExpression>
            </textField>
        </band>
    </detail>

f:id:tototoshi:20190302213926p:plain

とまあここまでくるとJasperReports StudioのGUIも使いながらデザインするのが楽だと思います。

画像、線、矩形表示などのタグもありますが、基本的な考えは変わらないので割愛します。 より詳しくはドキュメントのJasperReports Library Ultimate Guideを参照してください。

https://community.jaspersoft.com/documentation

JasperReportsはテーブルが1つのPDF、例えば見積書や請求書のような書類を作成するのに特化しているようにも思いましたが、SubReportという機能を利用することでより複雑なレイアウトにも対応できそうです。

IntelliJ IDEA を無料で使う方法

IntelliJ IDEA は Community Edition であれば無料で使えますが、 Ultimate Edition になると大体初期2万+維持費1万/年くらいの課金をする必要があります。

機能比較

これをどう見るかは人によると思いますが、自分は Scala はほとんど Emacs で書いてしまって、たまに気分で IntelliJ IDEA を使うくらいのノリなので少し高く感じています。

しかし、実は Ultimate Edition をタダで使う方法が存在します。Open Source License というヤツです。

IntelliJ IDEA 14.x Open Source License

Open Source License はオープンソースのプロジェクトの開発に使用できるライセンスです。ライセンスの発行には審査が必要です。 条件は、

  • プロジェクトのリーダー、または常にコミットしていること
  • プロジェクトがオープンソースの定義を満たしていること
  • 資金援助などを受けていないこと
  • アクティブに開発されていること
  • コミュニティがアクティブであること
  • ウェブサイトを持っていること
  • 定期的にリリースがされていること

と、一見厳しそうですが、コミュニティとかウェブサイトがどうとかは GitHubリポジトリや issues がその役目を果たしますし、規模とかはあまり関係なく、真面目に開発してる感さえあれば大丈夫だと思います。

というわけで、今回は tototoshi/scala-csv で申請してライセンスを発行してもらえました。ライセンスの発行には大体数日必要らしいですが、以前 PhpStorm のライセンスを tototoshi/staticmock に対して発行してもらったときは1ヶ月くらいかかった(絶対忘れてただろ)ので気長に待ちましょう。

Open Source License の有効期間は1年で、更新のときは期限切れの直前にメールでお願いする必要があるようです。

というわけで、自信のない人もとりあえず申請してみればよいと思います。JetBrains もそれなりに儲けてそうだし皆さんがタダで使っても潰れないでしょう。

追記: はてぶコメントで補足してくれてる方がいますが、仕事には使えないので会社に Commercial License を買ってもらいましょう

Flyway は複数人での開発に向かないという誤解について

というように、Flyway は複数人開発に向かないという噂をたまに聞くのですが、多分誤解です。

誤解が生まれるのは公式ドキュメントで Flyway のマイグレーションスクリプトのファイル名として、V1__Add_new_table.sql みたいなファイル名が例に出されており、これが V2__V3__ のような名前をつける必要があるという印象を与えるためかと思われます。

ドキュメントを良く読むと、バージョンのルールは

  • One or more numeric parts
  • Separated by a dot (.) or an underscore (_)
  • Underscores are replaced by dots at runtime
  • Leading zeroes are ignored in each part

なので、もう少し柔軟です。だから rails のようにタイムスタンプベースのファイル名付けちゃえば良いです。

sql
├── V20150127114055__create_user_table.sql
├── V20150127114322__add_country_column.sql
└── V20150127114323__add_age_column.sql

このようにタイムスタンプベースのバージョン付けを行うとブランチをマージしたときなどに順番が狂うという問題が発生しますが、 outOfOrder という設定があるのでこいつを on にしてやれば OK です。

WEB+DB PRESS VOL.84 の Flyway の記事にもなかったので書いてみました。

Heroku で JDK のバージョンを指定する

Heroku でサポートされている JDK は 1.6, 1.7, 1.8 です。 今ではデフォルトは 1.8 ですが、古いアプリではどうやらそのまま 1.6 が使われているようです。

JDK のバージョンを指定したいときには system.properties というファイルを使います。

java.runtime.version=1.8

このファイルをコミットし、PATH を設定します。

APP_PATH=`heroku config:get PATH`
heroku config:set PATH=/app/.jdk/bin:$APP_PATH

で、あとはこれを git push heroku master するだけで、JDK のバージョンアップができます。

% git push heroku master
Fetching repository, done.
Counting objects: 4, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 324 bytes | 0 bytes/s, done.
Total 3 (delta 1), reused 0 (delta 0)

-----> Play 2.x - Scala app detected
-----> Installing OpenJDK 1.8...done

参考: Updating Existing Java Apps to Use Java 7 | Heroku Dev Center

Google App Engineを試してみました。

Google Plugin for Eclipseいいですね。
ローカルでのテスト用にWebサーバ内蔵してるし、
デプロイもボタンひとつでやってくれる。


以下、メモ

応用情報技術者試験合格しました。

四月受けた応用情報技術者試験合格してました。
点数的にはちょっといまいちでした。
午後問題のオブジェクト指向とDBで結構間違えた。
仕事柄ここだけは全部当たらないとお話にならないんだけど。


秋はネットワーク試験受けようと思います。