2021年4月11日日曜日

PDFBoxで書き込みパスワードを設定する

 kingFisherでは、Javaで生成したPDFをメール送付又は、ダウンロードできることを考えていますが、受け取り者が編集できてしまうのは具合が悪く、読み取りはフリーですが、更新は不可とするためパスワードを付与してみます。

実現するには、org.apache.pdfbox.pdmodel.encryption.AccessPermissionと、org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicyクラスを使用します。

指定方法は以下


//制限を設定
AccessPermission objAp = new AccessPermission();
objAp.setCanAssembleDocument(false);
objAp.setCanExtractContent(false);
objAp.setCanExtractForAccessibility(false);
objAp.setCanFillInForm(false);
objAp.setCanModify(false);
objAp.setCanModifyAnnotations(false);
objAp.setCanPrint(false);
objAp.setCanPrintDegraded(false);
// ap.setReadOnly();
StandardProtectionPolicy spp = new StandardProtectionPolicy("12345", "", objAp);
spp.setEncryptionKeyLength(128);
document.protect(spp);


具体例を示しますと、まず引数にファイル名を渡し、PDF生成部分を呼び出す部分

File pdfOut = new File("C:\\Work\\testpdf001secure.pdf");
createPdf(pdfOut);

呼び出されるPDF作成部分

	protected void createPdf(File pdfOut) throws IOException{
		// 空のドキュメントオブジェクトを作成します
		try(PDDocument document = new PDDocument()) {
			//制限を設定
			AccessPermission objAp = new AccessPermission();
			objAp.setCanAssembleDocument(false);
			objAp.setCanExtractContent(false);
			objAp.setCanExtractForAccessibility(false);
			objAp.setCanFillInForm(false);
			objAp.setCanModify(false);
			objAp.setCanModifyAnnotations(false);
			objAp.setCanPrint(false);
			objAp.setCanPrintDegraded(false);
            // ap.setReadOnly();
            StandardProtectionPolicy spp = new StandardProtectionPolicy("12345", "", objAp);
            spp.setEncryptionKeyLength(128);
            document.protect(spp);

			// 新しいページのオブジェクトを作成します
			PDRectangle rectangle = PDRectangle.A4;
			PDPage page = new PDPage(rectangle);
			document.addPage(page);
			try (TrueTypeCollection ttcG = new TrueTypeCollection(new File("C:/Windows/Fonts/msgothic.ttc"));
				 TrueTypeCollection ttcM = new TrueTypeCollection(new File("C:/Windows/Fonts/msmincho.ttc"))) {

	            TrueTypeFont ttfG = ttcG.getFontByName("MS-Gothic");
	            PDFont fontG = PDType0Font.load(document, ttfG, true);
	            TrueTypeFont ttfM = ttcM.getFontByName("MS-Mincho");
	            PDFont fontM = PDType0Font.load(document, ttfM, true);

	            try(PDPageContentStream contentStream = new PDPageContentStream(document, page)){
	            	//文字出力
	            	contentStream.beginText();
					contentStream.setFont(fontG, 14);
					contentStream.newLineAtOffset(10, rectangle.getHeight() - 40);
					contentStream.showText( "この文字はフォント MSゴシックです" );
					contentStream.endText();
	            	//文字出力
					contentStream.beginText();
					contentStream.setFont(fontM, 14);
					contentStream.newLineAtOffset(10, rectangle.getHeight() - 70);
					contentStream.showText( "この文字はフォント MS明朝です" );
					contentStream.endText();
	            }
				// ドキュメントを保存します
				document.save(pdfOut);
			}
		}
	}


作成されたPDFを開きプロパティを見てみると、パスワードが設定されているのが分かります



2021年4月8日木曜日

決定した環境でPDFBoxが正常に動くか検証

JavaプログラムからPDFを出力するライブライはいくつかありますが、今回は無償で商用利用可能なライセンスとなっているApache PDFBox(検証時最新は3.0)を検証します。

なおPDFBoxはApache Commons-Logging1.2を必要としますので、こちらも併せてダウンロードします。

今回のタスク

  • Apache PDFBoxのライブラリをダウンロード
  • Apache Commons-Loggingライブラリをダウンロード
  • Java ServletからPDFをブラウザに返却するServletクラスを作成して検証
  • PDFに出力する文字には、フォントMS明朝、MSゴシックを使ってみる
  • servletを実行するOSはWindowsとする
それでは始めましょう

まずはApache PDFBoxのライブラリをダウンロードします

 https://pdfbox.apache.org/へアクセスします

Dowunloadをクリックします

下のほうへスクロールします

pdfbox-app-3.0.0-RC1.jarをクリックします

Apacheのサイトにリダイレクトされるので、ここでHTTPS://downloads.apache.org/をクリックします

下のほうへスクロールしていきます

pdfboxがありました。ここをクリックします

バージョンごとにまとめられています。ここで3.0.0-RC1のフォルダをクリックします

ライブラリにたどり着きました。最低限ひつような「fontbox-xxx.jar」と「pdfbox-xxx.jar」をダウンロードします。

続けて、commons-loggingのダウンロードです https://commons.apache.org/proper/commons-logging/download_logging.cgi

下へスクロールします

Binariesの中、commons-logging-1.2-bin.zipをダウンロードします

zipを解凍し、commons-logging-1.2.jarを取り出します。
最終的に、上記に示す3つのjarを使用しますので、これをビルドパスに通してください。

Java Servletを作成します(完成形は下記)
package sample01;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.fontbox.ttf.TrueTypeCollection;
import org.apache.fontbox.ttf.TrueTypeFont;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType0Font;

@WebServlet("/S02")
public class Servlet02 extends HttpServlet {

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

		response.setContentType("application/pdf");
		response.setHeader("Cache-Control","pdfs");
		response.setHeader("Pragma","pdfs");
		try(OutputStream out = response.getOutputStream()){
			createPdf(out);
		}

	}

	protected void createPdf(OutputStream out) throws IOException{
		// 空のドキュメントオブジェクトを作成します
		try(PDDocument document = new PDDocument()) {
			// 新しいページのオブジェクトを作成します
			PDRectangle rectangle = PDRectangle.A4;
			PDPage page = new PDPage(rectangle);
			document.addPage(page);
			try (TrueTypeCollection ttcG = new TrueTypeCollection(new File("C:/Windows/Fonts/msgothic.ttc"));
				 TrueTypeCollection ttcM = new TrueTypeCollection(new File("C:/Windows/Fonts/msmincho.ttc"))) {

	            TrueTypeFont ttfG = ttcG.getFontByName("MS-Gothic");
	            PDFont fontG = PDType0Font.load(document, ttfG, true);
	            TrueTypeFont ttfM = ttcM.getFontByName("MS-Mincho");
	            PDFont fontM = PDType0Font.load(document, ttfM, true);

	            try(PDPageContentStream contentStream = new PDPageContentStream(document, page)){
	            	//文字出力
	            	contentStream.beginText();
					contentStream.setFont(fontG, 14);
					contentStream.newLineAtOffset(10, rectangle.getHeight() - 40);
					contentStream.showText( "この文字はフォント MSゴシックです" );
					contentStream.endText();
	            	//文字出力
					contentStream.beginText();
					contentStream.setFont(fontM, 14);
					contentStream.newLineAtOffset(10, rectangle.getHeight() - 70);
					contentStream.showText( "この文字はフォント MS明朝です" );
					contentStream.endText();
	            }
				// ドキュメントを保存します
				document.save(out);
			}
		}
	}
}

ブラウザから、http://localhost:8080/sample01/S02 へアクセスします
上手く、PDFが表示できました。
PDFBoxは無償で利用できますが、GUIでフォーマットを作成したりといったものはありませんので、座標を計算してコーディングするといったひと手間が必要です。



2021年3月30日火曜日

Javaスレッドセーフ

Javaのプログラムはマルチスレッドで実行できる。特にServletやJSPなどはマルチスレッドで動かすことが前提なので、マルチスレッドで実行しても問題が発生しない様にスレッドセーフを意識してコーディングする必要がある。

例えば、Webアプリケーションでユーザー情報をスレッドセーフで無い変数に格納しているプログラムがあった場合、タイミングによって他のユーザーの情報が自分の画面に表示されたりといった重大な不具合を混入させてしまう恐れがあり、さらに単体テストでは気づき難い。


インスタンス変数はメソッドの外に定義された変数、クラス変数はインスタンス変数にstaticを付加した変数で、いずれも並行実行した場合、他の実行スレッドから参照される可能性がある。

下記のコードでは、ローカル変数だけがスレッドセーフ。


public class Sample01 {

	private String str01;           //インスタンス変数
	
	private static String str02;    //クラス変数
	
	public static void main(String[] args) {

		String str03;             //ローカル変数
				
	}

}

ちょっと紛らわしいのですが、下記compはstaticメソッドですが、スレッドセーフです。なぜかというと、インスタンス変数、クラス変数いずれも使用していない為です。

public class Sample01 {

	public static int comp(int arg0) {

		return arg0++;
				
	}

}

2021年3月28日日曜日

Eclipse DBViewerプラグインでCSVファイル出力

KingFisherプロジェクトとは脱線しますが、ちょっとレガシーなデータベースInterbase/Firebirdからデータの引っ越し案件が入り、調査したところデータベースに標準であるisqlではcvs出力機能が無いようでしたので、EclipseのDBViewerプラグインを使用して出力してみました。

このプラグインの良いところはJDBCドライバさえあれば同じインターフェースで様々なデータベースへアクセスできる点です。

最新のEclipse All in One(2021)には「データ・ソース・エクスプローラー」というプラグインはインストール済みですが、データベースが古いせいか、うまくいきませんでしたので、DBViewerプラグインをインストールして試みます。

インストールはこちらのサイトを参考にさせていただきました。

https://qiita.com/nkojima/items/2a865b819071e7943761


手順は

DBViewerのビューを開きます
メニュー「ウインドウ」-「ビューの表示」-「その他」をクリックします

「DBViewerプラグイン」の中から「DBツリー・ビュー」をクリックします

DBツリー・ビューが表示されました

ここからデータベースへ接続します
まず「登録」をクリックします

JDBCドライバを指定します
「ファイルの追加」をクリックします

接続対象のjdbcドライバを選択して、開くをクリックします
ここから接続に必要な情報の登録をしていきます
次へをクリックします
接続に必要な情報を登録して次へをクリックします
必要であればオプション情報を登録し完了をクリックします

無事接続できました。
テーブルの情報も見えています
CSVファイル出力したいテーブルを選択し、右クリック、CSV出力をクリックします

ファイルの格納場所とファイル名を指定して、保存をクリックします。
カンマ区切りのCSVファイルが保存されました。





2021年3月24日水曜日

決定した環境でJavaMailが正常に動くか検証

 Javaプログラムからemailを送信するライブラリがjavamailです。今回構築する環境でjavamailが正しく動作するか、検証したいと思います。

なおOpenJDK 11 以降ではjava.activation パッケージが削除されています。ただ JavaMail は内部でこれを使用していますので、javax.activationライブラリを別途用意する必要があります。

今回のタスク

  • javamailのライブラリをダウンロード
  • javax.activationライブラリをダウンロード
  • Apache commons-emailライブラリをダウンロード
  • email送信クラスを作成して検証
それでは始めましょう

まずはjavamailのライブラリをダウンロードします

https://javaee.github.io/javamail/ へアクセスします

少し下にスクロールします

Download javamail release の中からjavax.mail.jarリンクをクリックしてライブラリを取得しビルドパスに追加します

続けてjavax.activationライブラリの取得です
ここへアクセスします

Files のJar リンクをクリックしてライブラリを取得しビルドパスに追加します

続けて、Apache commons emailライブラリを取得です
https://commons.apache.org/proper/commons-email/ ここへアクセスします

Downloadをクリックします

今回はWindows環境にセットアップしますのでcommons-email-1.5-bin.zipをクリックします
ダウンロードされたzip内の、commons-email-1.5.jarをビルドパスに追加します

ここから検証プログラムを作成していきます

package sample02;

import org.apache.commons.mail.DefaultAuthenticator;
import org.apache.commons.mail.Email;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.SimpleEmail;

public class Emailtest {

	public void sendEmail() {
		try {
			Email email = new SimpleEmail();
			email.setHostName("xxx.xxx.co.jp");
			email.setSmtpPort(587);
			email.setAuthenticator(new DefaultAuthenticator("xxx@xxx.co.jp", "password"));
			email.setSSLOnConnect(false);
			email.setCharset("ISO-2022-JP");
			email.setFrom("xxx@xxx.co.jp", "ウエブマスター", "ISO-2022-JP");
			email.setSubject("テストメール");
			email.setMsg("テストメールの本文です");
			email.addTo("yyy@yyy.com", "なかさん", "ISO-2022-JP");
			email.send();
		} catch (EmailException e) {
			e.printStackTrace();
		}
	}

}
 
シンプルですね上記サンプルのxxx,yyyは適宜置き換えてくださいね


package sample02;

public class Execute {

	public static void main(String[] args) {
		Emailtest obj = new Emailtest();
		obj.sendEmail();

	}

}
実行用クラスを準備してEclipseから実行します
送信先に指定したアドレスにメールが送信されました。

2021年3月23日火曜日

EclipseでJUnitを使用したユニットテストを実施

 Eclipse All in One にはユニットテストが実行できるJUnitが最初から組み込まれています。今回は、これを使ってユニットテストを実施する手順を纏めました。

Eclipseのセットアップおよびテスト対象のクラスについてはこの回で作成済みのものを使用します。

ただし、サーブレットクラスをテストするには、JUnit単独ではできないため、今回はユニットテスト用に、一部ソースを書き換えます。(改変後のgetTextメソッドをテストします)

package sample01;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/S01")
public class Servlet01 extends HttpServlet {

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

		response.setContentType("text/html");
		PrintWriter out = response.getWriter();
		out.println(getText());

	}

	protected String getText() {
		StringBuffer sb = new StringBuffer();
		sb.append("<html><head></head><body>");
		sb.append("<p>Simple Application</p>");
		sb.append("<p>Servlet01</p>");
		sb.append("</body></html>");
		return  sb.toString();
	}
}


それでは、始めて行きましょう。

リリース時にテストクラスを除外しやすくなるなどのメリットがありますのでテストクラス専用のソースフォルダを作成します。

プロジェクト上で右クリック「新規」-「その他」をクリックします

「Java」-「ソースフォルダ」を選択して次へをクリックします

テスト専用フォルダのフォルダ名(今回はtestsrcとしました)を指定して完了をクリックします

「testsrc」が作成されています(パッケージエクスプローラーで確認できます)

ここからテストクラスを作成していきます
テスト対象のクラス上で右クリックし「新規」-「その他」をクリックします
「Junitテストケース」を選択して次へをクリックします

ソース・フォルダを先ほど作成したテストクラス専用のtestsrcとし、名前はテスト対象クラスにTestの文字を付加したもの、テスト元クラスは最初に選択したクラスが設定されている状態で次へをクリックします

テスト対象のメソッドはgetTextなので、これをチェックで選択し完了をクリックします

対象のプロジェクトのビルドパスにJUnitが無い場合、このダイアログが表示されるので、OKボタンをクリックしビルドパスに追加します

テスト対象のクラスに対になるようにテストクラスが作成されました。
続けて、テストクラスを実装します作成されたテストクラスを次の様に変更してください。
メソッドassertEquals([x],[y])は、xとyが同じ場合OKとなる
※テストNGの様子が分かるように、わざとchk変数の先頭に「!」を入れています

package sample01;

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;

class Servlet01Test {

	@Test
	void testGetText() {
		final String chk =
		"!<html><head></head><body><p>Simple Application</p><p>Servlet01</p></body></html>");


		Servlet01 obj = new Servlet01();
		assertEquals(chk,obj.getText());
	}

}
ここからユニットテストを実行します
テストクラス上で右クリックし、「実行」-「JUnitテスト」をクリックします

ユニットテストが実行されて、エラーが1件発生している状態です

JUnitビューの「org.opentest4j...」部分をダブルクリックすると比較ダイアログが表示され先頭1文字が想定と異なることが確認できます

テストクラスの変数chkの先頭「!」を削除して再実行すると、エラーなく終了することが出来ました。


2021年3月22日月曜日

EclipseでwebアプリケーションのDebug

 JavaのIDEであるEclipseを使ってWebアプリケーションをデバックする方法を纏めました

Eclipseのセットアップはこの回で完了していることが前提。

作成したソースコードにブレークポイントを設定します
(行ナンバーの場所でダブルクリック)

デバッグ・モードでサーバーを始動をクリックします

アプリケーションサーバーTomcatが起動します

ブラウザを立ち上げてhttp://localhost:8080/sample01/S01へアクセスします

パースペクティブがデバッグに切り替わります
先ほど設定したブレイクポイントで一時停止しています。
カーソルをオブジェクトに合わせるとポップアップが表示され、変数の内容が細かく確認できます

デバッグ情報が取得できたら、継続してプログラムを実行するため、再開をクリックします

レスポンスを返却した状態

ブラウザにもレスポンスが帰りました

一通りの確認が完了したら、サーバーを停止をクリックしサーバーを停止します




PDFBoxで書き込みパスワードを設定する

 kingFisherでは、Javaで生成したPDFをメール送付又は、ダウンロードできることを考えていますが、受け取り者が編集できてしまうのは具合が悪く、読み取りはフリーですが、更新は不可とするためパスワードを付与してみます。 実現するには、org.apache.pdfbox.p...