DataSpiderSDKによるPDFアダプタの実装

DataSpiderとブロックチェーン　その９

前回は、[NFT連携するためのDataSpiderによりシナリオ](https://qiita.com/dataspider-fan/items/56580cdf3452f4322580)について紹介しました。
今回からNFT連携に必要となるアダプタの実装について紹介したいと思います。

## やること
シナリオの❷ **(※[前回の記事で紹介しましたシナリオ](https://qiita.com/dataspider-fan/items/56580cdf3452f4322580)は以下の図を参照ください)** で必要となるPDF生成の実装していきます。
PDFを生成するアダプタを作成したいと思います。
![NFTシナリオ_vol1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849535/329d947e-deba-0625-de21-464b2fda8f57.png)

## 開発環境
前回から同様のWindows10上で行います。
DataSpiderもWindows版（体験版を使用）を用意ください。

## ライブラリの準備
今回は、あたらにPDF生成のためのJavaライブラリをインストールします。
iTextのライブラリを取得します。
Java1.8でコンパイルする場合は、こちらの[iText7 Githubから入手](https://github.com/itext/itext7)ください。
執筆時点では、iText Ver7.2.4を使用します。



### iText7のビルド
```
git clone https://github.com/itext/itext7.git
cd itext7
git checkout 7.2.4
mvn clean install -D maven.test.skip=true
```
:::note info
ビルドツール
Apache Maven 3.8.6
Maven home: C:\apache-maven-3.8.6-bin\apache-maven-3.8.6
Java version: 1.8.0_202, vendor: Oracle Corporation, runtime: C:\java\8\jre
Default locale: ja_JP, platform encoding: MS932
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"
:::

## iText ライブラリ群
```
itext7-barcodes-7.2.4.jar
itext7-commons-7.2.4.jar
itext7-font-asian-7.2.4.jar
itext7-forms-7.2.4.jar
itext7-hyph-7.2.4.jar
itext7-io-7.2.4.jar
itext7-kernel-7.2.4.jar
itext7-layout-7.2.4.jar
itext7-pdfa-7.2.4.jar
itext7-pdftest-7.2.4.jar
itext7-sign-7.2.4.jar
itext7-styled-xml-parser-7.2.4.jar
itext7-svg-7.2.4.jar
```

## iTextとは
PDFデータを生成するためのライブラリです。
フリー版（AGPLライセンス）と商用版が提供されています。
ライブラリは、Java言語と.NETの2種類があります。今回の検証では、Java言語用のライブラリを使用します。
公式ホームページ：https://itextpdf.com/products/itext-7
ナレッジサイトから色々な実装例を探すことができます。
ナレッジサイト：https://kb.itextpdf.com/home

## アダプタの実装

### PDFアダプタの利用イメージ
![NFTアダプタイメージ2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849535/f36d8cb6-b515-9ed2-d727-8a3d30d787e6.png)
**PDFアダプタの処理概要：**
複数件ある受取者名簿の内容を読み込み１行１ファイルずつPDFファイルを生成します。
+ プロパティ画面で、PDFに埋め込むロゴ画像、PDFファイルの出力先パス、PDFファイル生成時の接頭文字の設定欄を設けます。
+ PDFに埋め込む文字列を受け取ります。
+ 受け取った文字列と指定されたロゴ画像をPDFに埋め込みます。
+ 指定されたパスにPDFファイルを出力します。
+ 出力したファイル名をアドレスIDと共に後続のアダプタ向けの出力項目に格納します。

### PDF出力部分のソースコード
#### ソースの構成
PDF出力処理をPutTronGeneratePdfOperation.javaに実装します。
![PDFソース_1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849535/6d5c1e2d-24c1-7e70-b36f-fe3b51f0662c.png)

iTextのライブラリを参照ライブラリの外部jarとして追加します。
![PDFソース_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849535/80faa016-ec6c-f0e7-6e55-02d67e1db598.png)

#### PutTronGeneratePdfOperation.java
```java

@Override
public Map execute(Map inputData) throws Exception {
    Map res = new HashMap();

    FileManager fm = (FileManager) getContext().getProxy(FileManager.class);

    String imagePath = getImagePath();
    String pdfOutputPath = getPdfOutputPath();
    String pdfPrefix = getPdfPrefix();

    LocalDateTime now = LocalDateTime.now();
    DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy/MM/dd");
    String payoutDate = now.format(dateFormat);

    Object data = inputData.get(KEY_INPUT);

    LoggingContext log = getContext().log();

    // 出力側TableDataBuilderを生成する。
    TableDataBuilder builder = DataBuilderFactory.newTableDataBuilder(getColumnOutputTypes(), isLargeData());

    log.info(builder.toString());

    try (TableRowIterator rowIterator = DataParserFactory.newTableRowIterator(data)) {

        // 出力側項目の出力開始を宣言する
        builder.startConstruction();

        // 入力側レコードの繰り返し処理
        while (rowIterator.hasNext()) {
            Row record = rowIterator.next();

            builder.startRow(); // 出力側行単位のデータを格納するバッファ

            /*
             * 入力側データ読み込み
             */
            Column userIdColumn = record.getColumn(0);
            String userId = getColumnValue(userIdColumn);

            Column nameColumn = record.getColumn(1);
            String name = getColumnValue(nameColumn);

            Column addressColumn = record.getColumn(2);
            String publicAddress = getColumnValue(addressColumn);

            String dest = pdfOutputPath + "/" +  pdfPrefix + publicAddress + ".pdf";

            /*
             * PDF出力
             * 必要に応じてPDFテンプレートを読み込んでPDFを生成するロジックにする
             */
            int counter = 1;

            // 日本語対応のフォントを指定
            PdfFont fontDetail = PdfFontFactory.createFont("HeiseiKakuGo-W5", "UniJIS-UCS2-H");

            PdfWriter writer = new PdfWriter(fm.getOutputStream(dest));
            PdfDocument pdf = new PdfDocument(writer);
            pdf.setDefaultPageSize(PageSize.A6.rotate());
            Document doc = new Document(pdf);

            // SVGファイルのみ対応
            Image myLogo = SvgConverter.convertToImage(fm.getInputStream(imagePath), pdf);

            Paragraph p = new Paragraph("")
                    .add(myLogo);
            p.setTextAlignment(TextAlignment.CENTER);
            p.setVerticalAlignment(VerticalAlignment.TOP);
            doc.add(p);

            float[] columnWidths = {1, 4, 8};
            Table table = new Table(UnitValue.createPercentArray(columnWidths));
            Cell cell = new Cell(1, 3)
                    .add(new Paragraph("CERTIFICATE OF COMPLETION"))
                    .setWidth(1000)
                    .setFont(fontDetail)
                    .setFontSize(13)
                    .setFontColor(ColorConstants.WHITE)
                    .setBackgroundColor(ColorConstants.BLACK)
                    .setTextAlignment(TextAlignment.CENTER);

            table.addHeaderCell(cell);

            // 証明書の内容を１行ずつ生成する
            table.addCell(new Cell().setTextAlignment(TextAlignment.CENTER).add(new Paragraph(String.valueOf(counter++))));
            table.addCell(new Cell().setTextAlignment(TextAlignment.LEFT).add(new Paragraph("発行年月日").setFont(fontDetail).setFontSize(10)));
            table.addCell(new Cell().setTextAlignment(TextAlignment.LEFT).add(new Paragraph(payoutDate).setFontSize(10)));

            table.addCell(new Cell().setTextAlignment(TextAlignment.CENTER).add(new Paragraph(String.valueOf(counter++))));
            table.addCell(new Cell().setTextAlignment(TextAlignment.LEFT).add(new Paragraph("受講者").setFont(fontDetail).setFontSize(10)));
            table.addCell(new Cell().setTextAlignment(TextAlignment.LEFT).add(new Paragraph(name).setFont(fontDetail).setFontSize(10)));

            table.addCell(new Cell().setTextAlignment(TextAlignment.CENTER).add(new Paragraph(String.valueOf(counter++))));
            table.addCell(new Cell().setTextAlignment(TextAlignment.LEFT).add(new Paragraph("TRONアドレス").setFont(fontDetail).setFontSize(10)));
            table.addCell(new Cell().setTextAlignment(TextAlignment.LEFT).add(new Paragraph(publicAddress).setFontSize(10)));

            doc.add(table);

            doc.add(new Paragraph("上記受講者は、" + payoutDate + "をもって受講修了したことを証明します").setFont(fontDetail).setFontSize(10));

            doc.close();

            /*
             * 出力側項目格納
             */
            // UserID
            builder.column(userId);
            log.debug("UserID = " + userId);
            // senderAddress
            builder.column(publicAddress);
            log.debug("publicAddress = " + publicAddress);
            // fileName
            builder.column(pdfPrefix + publicAddress + ".pdf");
            log.debug("fileName = " + pdfPrefix + publicAddress + ".pdf");
            // 出力時間
            builder.column(payoutDate);

            builder.endRow();

        }

        builder.endConstruction();
        Object result = builder.getResult();

        res.put(KEY_OUTPUT, result);

    }

    return res;
}

```

### PDF生成アダプタの設定項目
#### PDF生成アダプタのプロパティ画面
![pdfアダプタ設定項目.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849535/8e43677d-03ff-66b9-1650-e62691829769.png)

#### TronGeneratePdfOperationFactory.java
画面のプロパティ情報を実装します。
```java
public abstract class TronGeneratePdfOperationFactory implements OperationFactory {

	@Override
	public OperationConfigurator createOperationConfigurator(
			OperationConfiguration conf,
			OperationContext context) throws Exception {

		OperationConfigurator configurator = new OperationConfigurator(conf, context);

		configurator.addGroup(new ParameterGroup(KEY_MUST_GROUP, MultiLangMessage.getString("group.type.label")));

		configurator.addSimpleParameter(KEY_MUST_GROUP, createImagePathParameter());
		configurator.addSimpleParameter(KEY_MUST_GROUP, createPdfOutputPathParameter());
		configurator.addSimpleParameter(KEY_MUST_GROUP, createPdfPrefixParameter());

		return configurator;
	}

	private SimpleParameter createImagePathParameter() {
		Fillin constraint = new Fillin();
		constraint.setLabel("PDFロゴパスファイル名");
		constraint.setRequired(false);
		constraint.setShortcut("L");
		constraint.setLength(1024);
		constraint.setDescription("1024 文字以内で設定してください");
		SimpleParameter param = new SimpleParameter(KEY_IMAGE_PATH, constraint);
		return param;
	}

	private SimpleParameter createPdfOutputPathParameter() {
		Fillin constraint = new Fillin();
		constraint.setLabel("PDF出力パス");
		constraint.setRequired(false);
		constraint.setShortcut("S");
		constraint.setLength(1024);
		constraint.setDescription("1024 文字以内で設定してください");
		SimpleParameter param = new SimpleParameter(KEY_PDF_OUTPUT_PATH, constraint);
		return param;
	}

	private SimpleParameter createPdfPrefixParameter() {
		Fillin constraint = new Fillin();
		constraint.setLabel("PDFファイルプレフィックス");
		constraint.setRequired(false);
		constraint.setShortcut("P");
		constraint.setLength(1024);
		constraint.setDescription("1024 文字以内で設定してください");
		SimpleParameter param = new SimpleParameter(KEY_PDF_PREFIX, constraint);
		return param;
	}

	String createInputSchema(OperationConfiguration conf) {
		TableSchemaGenerator generator = new TableSchemaGenerator();

		// 入力側の項目名称（固定）
		String address_id = KEY_INPUT_COLNAME_ADDRESS_ID;
		// 入力側の項目タイプ（固定）
		String address_id_type = TYPE_STRING;

		generator.addColumn(address_id, TableDataTypeUtil.parseType(address_id_type));

		// 入力側の項目名称（固定）
		String name = KEY_INPUT_COLNAME_NAME;
		// 入力側の項目タイプ（固定）
		String name_type = TYPE_STRING;

		generator.addColumn(name, TableDataTypeUtil.parseType(name_type));

		// 入力側の項目名称（固定）
		String address = KEY_INPUT_COLNAME_ADDRESS;
		// 入力側の項目タイプ（固定）
		String address_type = TYPE_STRING;

		generator.addColumn(address, TableDataTypeUtil.parseType(address_type));

		// 文字列として XMLSchema を返します。
		return generator.generateXMLSchema();
	}

	String createOutputSchema(OperationConfiguration conf) {
		TableSchemaGenerator generator = new TableSchemaGenerator();

		// 出力側の項目名称（固定）入力側のアドレスIDを格納
		String name = KEY_OUTPUT_COLNAME_ADDRESS_ID;
		// 出力側の項目タイプ（固定）
		String type = TYPE_STRING;
		generator.addColumn(name, TableDataTypeUtil.parseType(type));

		// 出力側の項目名称（固定）　入力側のアドレスを格納
		name = KEY_OUTPUT_COLNAME_ADDRESS;
		// 出力側の項目タイプ（固定）
		type = TYPE_STRING;
		generator.addColumn(name, TableDataTypeUtil.parseType(type));

		// 出力側の項目名称（固定）　PDF出力パスファイル名を格納
		name = KEY_OUTPUT_COLNAME_PDF_OUTPUT_PATH;
		// 出力側の項目タイプ（固定）
		type = TYPE_STRING;
		generator.addColumn(name, TableDataTypeUtil.parseType(type));

		// 出力側の項目名称（固定）　PDF発行日を格納
		name = KEY_OUTPUT_COLNAME_PDF_PAYOUT_DATE;
		// 出力側の項目タイプ（固定）
		type = TYPE_STRING;
		generator.addColumn(name, TableDataTypeUtil.parseType(type));

		// 文字列として XMLSchema を返します。
		return generator.generateXMLSchema();
	}
}
```
その他、画面から項目を取得するメソッドやアダプタアイコンを作成します。

詳細は、コンポーネントSDK 4.2 開発ガイドを参照ください。
+ 画面項目の実装：5.2. MessageXML Adapter の作成
+ アイコンの生成：18. オペレーションとグローバルリソースのアイコンファイルについて

## DataSpiderスクリプトの作成
PDF出力アイコンが今回実装したアダプタです。
その前後のアイコンのマッピング状態を以下に記します。
#### 全体像
![pdfスクリプト_1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849535/2b7c38b0-f27b-a056-0e0b-d7031dd4294c.png)

#### PDF出力マッピング（PDF出力アイコンの入力側マッピング）
![pdfスクリプト_2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849535/eb316961-4f31-c5c8-2f43-445f55f95769.png)

#### 出力結果マッピング（PDF出力アイコンの出力側マッピング）
![pdfスクリプト_3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849535/b6467c1e-6e2a-b905-1497-2fa8812e8d9c.png)

## 入出力ファイルと出力結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849535/827275f1-bfa1-2070-4de8-267a43e03eb6.png)

#### 証明書PDFファイル
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849535/95bda0e2-b5f9-87ca-598d-24d0235d3a21.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849535/619322ac-2e76-8662-8c24-8585e82e1469.png)

PDFアダプタの実装と動作結果は以上です。

## 次回は
[IPFS/NFT部分の実装](https://qiita.com/dataspider-fan/items/e88520027d67038d8be5)を行います。