レベル: 中級 小川 直人 (naogawa@fsi.co.jp), 主任, 富士ソフト株式会社
2009年 6月 19日 JasperReportsは、SQLを発行し、その結果をレポーティングすることができます。しかし、XQueryはサポートしていません。簡単な拡張で、JasperReportsでXQueryが使えるようになります。
イントロダクション
JasperReportsはとても優れた帳票ツールです。機能が豊富で、拡張が容易です。たとえば、JasperReportsはデータベースからデータを取得するSQLを指定することができます。JasperReportsにデータベースコネクションを渡せば、指定したSQLを発行し、データを取得し、帳票を作成することができます。これにはJavaコーディングが不要です。しかし、残念ながら、JasperReportsはXQueryを発行することができません。PureXMLに格納されたXML文書から帳票を作るには、XML文書を取得するJavaコーディングが必要ですが、 JasperReportsの強力な拡張機能を利用すれば、JasperReportsでXQueryを扱えるようになります。
システム要件とシナリオ
この記事のソースは、次の環境で実行、テストされています。
Table 1.環境
| Module | Version |
|---|
| OS | Windows XP Professinal Version 2002 Service Pack 3 | | Java | SUN JVM 1.6.0_13 | | Database | DB2 v9.5.200.315 Fix Pax 2 (Express-C) | | JasperReports | 3.1.2(jasperreports-3.1.2-project.zipのダウンロードをお勧めします。依存するjarが含まれているからです。) |
DB2では、SAMPLEデータベースが提供されています。そのデータベースのCustomerテーブルを利用することにします。まず、テーブル定義を確認しましょう。
Listing 1. SAMPLEデータベースのCustomerテーブルの定義
|--------10--------20--------30--------40--------50--------60--------70--------80--------|
db2 =>describe table customer
Data type Column
Column name schema Data type name Length Scale Nulls
------------------------------- --------- ------------------- ---------- ----- ------
CID SYSIBM BIGINT 8 0 No
INFO SYSIBM XML 0 0 Yes
HISTORY SYSIBM XML 0 0 Yes
-------------------------------------------------------------------------------------
|
CustomerテーブルのINFO列に顧客情報がXML型として格納されています。この顧客情報XMLから、顧客ID、顧客名、電話番号、電話タイプを表形式でPDF出力することを考えていきましょう。最終的なレポートは以下に示すFigure 1.のようなPDFになります。
Figure 1. Customerテーブルの情報を一覧化したPDFファイル
カスタマイズのアイデア
カスタマイズの方法はとてもシンプルです。まず、XQueryステートメントを含む帳票定義ファイル(.jrxmlファイル)を作成します。次にカスタムのQuery Executerを作成します。このQuery Executerで、jrxmlファイルからXQueryを読み込み、発行し、XML文書をデータベースから取得し、その文書をXMLDataSourceに渡します。XMLDataSourceはJasperReportsに実装されていますので、後は実行するのみです。
XPath DataSource
カスタマイズの作業に取り掛かる前に、XMLマッピングについて確認しましょう。次のXQueryをPureXMLで発行するとします。
Listing 2. SAMPLEデータベースから顧客情報を取得するXQuery
|--------10--------20--------30--------40--------50--------60--------70--------80--------|
xquery
let $inst := db2-fn:xmlcolumn("CUSTOMER.INFO")
return <customers>{$inst}
</customers>
|
このXQueryから次のXML文書が取得できます。
Listing 3. Listing 2の結果
|--------10--------20--------30--------40--------50--------60--------70--------80--------|
<?xml version="1.0" encoding="UTF-8" ?>
<customers>
<customerinfo xmlns="http://posample.org" Cid="1000">
<name>Kathy Smith</name>
<addr country="Canada">
<street>5 Rosewood</street>
<city>Toronto</city>
<prov-state>Ontario</prov-state>
<pcode-zip>M6W 1E6</pcode-zip>
</addr>
<phone type="work">416-555-1358</phone>
</customerinfo>
<customerinfo xmlns="http://posample.org" Cid="1001">
<name>Kathy Smith</name>
<addr country="Canada">
<street>25 EastCreek</street>
<city>Markham</city>
<prov-state>Ontario</prov-state>
<pcode-zip>N9C 3T6</pcode-zip>
</addr>
<phone type="work">905-555-7258</phone>
</customerinfo>
<!-- 他のcustomerinfoは省略 -->
</customers>
|
ここで、Listing 3.と同様なXML文書があり、この文書のファイル名をmydata.xmlと仮定しましょう。このXML文書を帳票にマッピングするために、次のような帳票定義ファイルが必要となります。ここでは詳細には立ち入りませんが、この帳票定義ファイルはiReportを使用して作成しました。
Listing 4. 帳票定義ファイル(reportXPath.jrxml)
|--------10--------20--------30--------40--------50--------60--------70--------80--------|
<?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="report name" pageWidth="595" pageHeight="842" columnWidth="535"
leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20">
<queryString language="xPath"> <!-- **(1)** -->
<![CDATA[/customers/customerinfo]]>
</queryString>
<field name="Cid" class="java.lang.String">
<fieldDescription><![CDATA[@Cid]]></fieldDescription> <!-- **(2-1)** -->
</field>
<field name="name" class="java.lang.String">
<fieldDescription><![CDATA[normalize-space(name/child::text())]]></fieldDescription>
<!-- **(2-2)** -->
</field>
<field name="phone" class="java.lang.String">
<fieldDescription><![CDATA[normalize-space(phone/child::text())]]></fieldDescription>
<!-- **(2-3)** -->
</field>
<field name="type" class="java.lang.String">
<fieldDescription><![CDATA[phone/@type]]></fieldDescription> <!-- **(2-4)** -->
</field>
<background>
<band/>
</background>
<title>
<band height="79"/>
</title>
<pageHeader>
<band height="35"/>
</pageHeader>
<columnHeader>
<band height="61"/>
</columnHeader>
<detail>
<band height="23">
<textField>
<reportElement x="97" y="3" width="100" height="20"/>
<textElement/>
<textFieldExpression class="java.lang.String"><![CDATA[$F{name}]]>
</textFieldExpression>
</textField>
<textField>
<reportElement x="0" y="3" width="100" height="20"/>
<textElement/>
<textFieldExpression class="java.lang.String"><![CDATA[$F{Cid}]]>
</textFieldExpression>
</textField>
<textField>
<reportElement x="197" y="3" width="100" height="20"/>
<textElement/>
<textFieldExpression class="java.lang.String"><![CDATA[$F{phone}]]>
</textFieldExpression>
</textField>
<textField>
<reportElement x="297" y="3" width="100" height="20"/>
<textElement/>
<textFieldExpression class="java.lang.String"><![CDATA[$F{type}]]>
</textFieldExpression>
</textField>
</band>
</detail>
<columnFooter>
<band height="45"/>
</columnFooter>
<pageFooter>
<band height="54"/>
</pageFooter>
<summary>
<band height="42"/>
</summary>
</jasperReport>
|
帳票定義でポイントとなるのは、上記(1)で示したqueryString要素です。このqueryString要素のlanguage要素の値はxPathです。これは、JasperReportsがXPath用のDataSourceを使うことを意味しています。queryString要素の内容は、XML文書中のどのノードの集合を基準とするかを示しています。この基準からどのノードを取得するかをfieldDescription要素にて指定します(上記(2-1)から(2-4)を参照)。以下に述べるListing 6.でqueryString要素を修正します。
さて、PDFを生成するために、次のようなJavaコードが必要です。
Listing 5. PDF出力するコード(ReportXMLFile.java)
|--------10--------20--------30--------40--------50--------60--------70--------80--------|
package com.example;
import java.util.HashMap;
import java.util.Map;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.query.JRXPathQueryExecuterFactory;
import net.sf.jasperreports.engine.util.JRLoader;
import net.sf.jasperreports.engine.util.JRXmlUtils;
import org.w3c.dom.Document;
public class ReportXMLFile {
@SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception {
JasperReport jasperReport;
JasperPrint jasperPrint;
jasperReport = JasperCompileManager
.compileReport("reports/reportXPath.jrxml");//---------(1)
Map params = new HashMap();
Document document = JRXmlUtils.parse(JRLoader
.getLocationInputStream("data/mydata.xml"));
params.put(JRXPathQueryExecuterFactory.PARAMETER_XML_DATA_DOCUMENT,
document);//-------------------------------------------(2)
jasperPrint = JasperFillManager.fillReport(jasperReport, params);
JasperExportManager.exportReportToPdfFile(jasperPrint,
"reports/pdfXPath.pdf");//-----------------------------(3)
}
}
|
このコーディングは次の通りになります。
- reportXPath.jrxmlファイルをコンパイルします。
- XML文書をJasperReportsに設定します。
- PDFを生成します。
これで、mydata.xmlファイルがある前提のもと、Figure 1.のPDFが生成されます。
JasperReportsのカスタマイズ
上記の準備を踏まえ、JasperReportsをカスタマイズしましょう。
まず、帳票定義ファイル(.jrxml)から始めましょう。XQueryステートメントを記述する必要があるので、Listing 4.のqueryString要素を次のように変更します。
Listing 6. XQueryを発行する記述(reportXQuery.jrxmlの一部)
|--------10--------20--------30--------40--------50--------60--------70--------80--------|
<property name="basepath" value="/customers/customerinfo"/> <!-- **(1)** -->
<queryString language="XQuery"> <!-- **(2)** -->
<![CDATA[xquery let $inst := db2-fn:xmlcolumn("CUSTOMER.INFO") return <customers>{$inst}
</customers>]]>
</queryString>
|
Listing 6.の(2)を見てください。queryString要素のlanguage属性にXQueryを指定します。この要素の内容にListing 2.で示したXQueryを記述します。次にListing 6.の(1)を見てください。listing 4.で示していたXML文書中のどのノードの集合を基準とするかを示す情報(language属性にXPath属性をもつqueryString要素)を、property要素の属性に記述を移動しました。property要素のname属性は、これから作成するQueryExecutorにて使用します。なお、JasperReportsのDTDを読むと、property要素を使って、任意の情報をJasperReportsに渡せることが分かります。
次に、XQueryを発行するためのQueryExecutorを作成しましょう。このカスタムクラスを、XQueryQueryExecutorと呼ぶことにします。XQueryQueryExecutorクラスは、JRQueryExecutorインターフェースをインプリメントする必要があります。実装は下記のようになります。
Listing 7. XQueryQueryExecutor.java
|--------10--------20--------30--------40--------50--------60--------70--------80--------|
package com.example;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map;
import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRDataset;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRParameter;
import net.sf.jasperreports.engine.JRValueParameter;
import net.sf.jasperreports.engine.data.JRXmlDataSource;
import net.sf.jasperreports.engine.query.JRQueryExecuter;
public class XQueryQueryExecuter implements JRQueryExecuter {
private Connection connection;
protected final JRDataset dataset;
@SuppressWarnings("unchecked")
private final Map parametersMap;
@SuppressWarnings("unchecked")
public XQueryQueryExecuter(JRDataset dataset, Map parameters) {
this.dataset = dataset;
this.parametersMap = parameters;
JRValueParameter parameter = (JRValueParameter) this.parametersMap
.get(JRParameter.REPORT_CONNECTION);
this.connection = (Connection) parameter.getValue();
}
@Override
public boolean cancelQuery() throws JRException {
return false;
}
@Override
public void close() {
}
@Override
public JRDataSource createDatasource() throws JRException {
JRDataSource dataSource = null;
String query = dataset.getQuery().getText();//------------------------------(1)
Statement statement = null;
try {
statement = this.connection.createStatement();
ResultSet rs = statement.executeQuery(query);//----------------------------(2)
rs.next();
InputStream is = rs.getAsciiStream(1);
String expression = dataset.getPropertiesMap().getProperty("basepath");//--(3)
dataSource = new JRXmlDataSource(is, expression);//------------------------(4)
} catch (SQLException e) {
throw new JRException(e);
}
return dataSource;
}
}
|
コンストラクタでは、渡されたパラメータとデータベースコネクションをこのクラスのフィールドに設定します。処理の中心となるcreateDataSourceメソッドを確認しましょう。
- JRDataSetよりXQueryのテキストを取得します。これはListing 6.のqueryString要素の内容です。(Listing 6. (2)を参照)
- XQueryを発行します。結果セットよりXML文書のInputStreamを取得します。XMLは結果セットの1行目の1列目にあるので、nextメソッド呼び出し後に、getAsciiStreamメソッドにパラメータ1を渡しています。
- JRDataSetよりXML文書中のどのノードの集合を基準とするかを示す情報を取得します。これはListing 6.のproperty要素のvalue属性です。(Listing 6. (1)を参照)
- 上記2., 3.で取得した内容を使い、JRXmlDataSourceのインスタンスを生成します。
次に、ファクトリクラスを準備する必要があります。ファクトリクラスは、JRQueryExecutorFactoryインターフェースを実装します。
Listing 8. XQueryExecutorFactory.java
|--------10--------20--------30--------40--------50--------60--------70--------80--------|
package com.example;
import java.util.Map;
import net.sf.jasperreports.engine.JRDataset;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.query.JRQueryExecuter;
import net.sf.jasperreports.engine.query.JRQueryExecuterFactory;
public class XQueryExcecuterFactory implements JRQueryExecuterFactory {
@Override
public JRQueryExecuter createQueryExecuter(JRDataset dataset, Map parameters)
throws JRException {
return new XQueryQueryExecuter(dataset, parameters);
}
@Override
public Object[] getBuiltinParameters() {
return null;
}
@Override
public boolean supportsQueryParameterType(String className) {
return false;
}
}
|
このクラスの解説は特に必要ないでしょう。
最後に、JasperReportsを実行するコード(ReportXQuery.java)を作成しましょう。SAMPLEデータベースはローカルマシンに存在するとします。このデータベースのユーザ名、パスワードはそれぞれfoo,barとします。
Listing 9. ReportXQuery.java
|--------10--------20--------30--------40--------50--------60--------70--------80--------|
package com.example;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.HashMap;
import java.util.Map;
import net.sf.jasperreports.engine.JRParameter;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.util.JRProperties;
public class ReportXQuery {
@SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception {
JRProperties.setProperty(
"net.sf.jasperreports.query.executer.factory.XQuery",
"com.example.XQueryExcecuterFactory");-----------(1)
JasperReport jasperReport;
JasperPrint jasperPrint;
jasperReport = JasperCompileManager
.compileReport("reports/reportXQuery.jrxml");//
Map params = new HashMap();
Connection con = getConnection();
params.put(JRParameter.REPORT_CONNECTION, con);//-----------(2)
jasperPrint = JasperFillManager.fillReport(jasperReport, params);
JasperExportManager.exportReportToPdfFile(jasperPrint,
"reports/pdfXQuery.pdf");
con.close();
}
private static Connection getConnection() throws Exception {
Class.forName("com.ibm.db2.jcc.DB2Driver");
String url = "jdbc:db2://127.0.0.1:50000/sample";
String user = "foo";
String password = "bar";
Connection con = null;
con = DriverManager.getConnection(url, user, password);
return con;
}
}
|
コーディングはListing 5.とほとんど同じですが、相違点を解説します。
- JaspreReportsのインスタンスに、先ほど作成したファクトリクラスを設定します。ここで設定するキーのnet.sf.jasperreports.query.executer.factory.XQueryは、JRQueryExecuterFactoryインターフェースのQUERY_EXECUTER_FACTORY_PREFIXで定義された変数の内容にqueryString要素のlanguage属性をピリオドで連結したものです。
- データベースコネクションをJaspreReportsのインスタンスに設定します。
終わりに
SAMPLEデータベースを開始し、ReportXQuery(Listing 9.)を実行してください。Figure 1.で示したPDFが得られます。もちろん、今回作成したXMLファイルやJavaクラス、それからDB2のJDBCドライバへのクラスパスの設定を忘れずに。
このように、ちょっとした工夫で、JasperReportsとpureXMLをシームレスに連携させることができ、pureXMLのポテンシャルを実感することができると言えるでしょう。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| JasperReprots_XQuery | jasperReprots_XQuery.zip | 7.86KB | HTTP |
|---|
参考文献
著者について  | |  | 富士ソフト株式会社IT事業本部勤務。社内のJava,XML関連のプロジェクトへの支援作業に従事。 |
記事の評価
|