はじめに
法務部門が複数の契約書PDFを受け取ると、各レビュアーが機密条項を保護するためにそれぞれパスワードを設定することがよくあります。パスワードが異なるため、ファイルを直接結合できず、単一のバインダーにまとめるのは悪夢のようです。手動で復号するのは時間がかかり、エラーが発生しやすく、特に数十件のPDFを扱う場合は大変です。
Password‑merge は、Java 用の GroupDocs.Merger ワークフローで、異種の PDF を解除し、マージ後に単一の認証情報で再保護します。このチュートリアルでは、保護された PDF の検出、解除、コンテンツのマージ、そしてオプションで統一パスワードのローテーションまでを解説します。
Merger API の設定方法、バイトストリームの処理方法、30 行未満の Java コードで安全な結合 PDF を生成する手順を学びます。
パスワードで保護されたPDFをマージすべき時は?
パスワードで保護された PDF をマージするのは、複数のパスワードを管理する手間を省きつつ、セキュリティポリシーを遵守した検索可能な単一アーカイブが必要なときに有効です。典型的なシナリオとしては、四半期ごとの財務報告バンドル、監査用の契約バインダー、各貢献者が異なるパスワードを設定した法的案件ファイルなどがあります。各ファイルをプログラムで解除し、統一パスワードを適用することで、アーカイブの安全性を保ちつつ、下流のレビュー工程を簡素化できます。この一連の操作は CI パイプラインで自動化でき、手作業に費やす時間を大幅に削減します。
前提条件
- Java 11 以上
- GroupDocs.Merger for Java 24.6 以上 (temporary license)
- 任意でパスワードが設定された PDF ファイルのセット
Maven でライブラリをインストールします:
mvn dependency:copy -Dartifact=com.groupdocs:groupdocs-merger:24.6
手順 1 – PDFがパスワードで保護されているか検出する
ファイルを解除しようとする前に、実際にパスワードが設定されているか確認します。不要な処理を防ぎ、どのファイルが認証情報を必要としているかログに残すことができます。
// Returns true if the PDF at `path` has an owner or user password
public boolean isDocumentProtected(String path, String password) {
Merger merger;
if (password == null || password.isEmpty()) {
merger = new Merger(path);
} else {
merger = new Merger(path, new LoadOptions(password));
}
try {
return merger.isPasswordSet();
} finally {
merger.dispose();
}
}
重要ポイント:
LoadOptionsは既知のパスワードを保持します。パスワードが無い場合は通常通りファイルを開きます。isPasswordSet()はオーナーパスワードとユーザーパスワードの両方に対して true を返します。- ネイティブリソースを解放するため、
Mergerインスタンスは必ずdispose()してください。
手順 2 – 各PDFのロックを解除し、生バイトを収集する
キーがファイルパス、値がパスワード(無い場合は null)となるマップを走査します。メソッドは解除された PDF を表すバイト配列のリストを返します。
public List<byte[]> unlockAll(Map<String, String> sources) throws IOException {
List<byte[]> unlocked = new ArrayList<>();
for (Map.Entry<String, String> e : sources.entrySet()) {
String path = e.getKey();
String password = e.getValue();
System.out.println("Unlocking (credentials=" +
(password != null ? "yes" : "no") + "): " + path);
if (password == null || password.isEmpty()) {
unlocked.add(Files.readAllBytes(Paths.get(path)));
} else {
LoadOptions opts = new LoadOptions(password);
ByteArrayOutputStream buf = new ByteArrayOutputStream();
Merger m = new Merger(path, opts);
try {
m.removePassword();
m.save(buf);
} finally {
m.dispose();
}
unlocked.add(buf.toByteArray());
}
}
return unlocked;
}
重要ポイント:
removePassword()で既存の保護を除去します。- 復号されたコンテンツは
ByteArrayOutputStreamに書き込まれ、メモリ上で扱います。 - パスワードが設定されていないファイルは直接読み取り、フローをシンプルに保ちます。
手順 3 – ロック解除したPDFをマージし、統一パスワードを適用する
最初の PDF ストリームから Merger を作成し、残りのストリームを結合します。最後に AddPasswordOptions で統一パスワードを設定します。
public void mergeAndProtect(List<byte[]> unlockedPdfs,
String unifiedPassword,
String outputPath) {
InputStream first = new ByteArrayInputStream(unlockedPdfs.get(0));
Merger merger = new Merger(first);
try {
for (int i = 1; i < unlockedPdfs.size(); i++) {
merger.join(new ByteArrayInputStream(unlockedPdfs.get(i)));
}
merger.addPassword(new AddPasswordOptions(unifiedPassword));
merger.save(outputPath);
} finally {
merger.dispose();
}
System.out.println("Merged output: " + new File(outputPath).getAbsolutePath());
System.out.println("Unified password: " + unifiedPassword);
}
重要ポイント:
addPasswordで最終 PDF を指定した認証情報で暗号化します。- すべての操作はメモリ上で行われ、最終ファイルだけがディスクに書き込まれます。
Mergerは必ずdispose()してネイティブハンドルを解放してください。
手順 4 – 既に保護されたPDFのパスワードをローテーションする(オプション)
組織で定期的なパスワード変更が求められる場合、ソースファイルを再マージせずに認証情報を更新できます。
public void rotateUnifiedPassword(String path,
String oldPassword,
String newPassword,
String outputPath) {
Merger merger = new Merger(path, new LoadOptions(oldPassword));
try {
merger.updatePassword(new UpdatePasswordOptions(newPassword));
merger.save(outputPath);
} finally {
merger.dispose();
}
System.out.println("Rotated output: " + new File(outputPath).getAbsolutePath());
System.out.println("New password: " + newPassword);
}
重要ポイント:
- 現在のパスワードで保護された PDF をロードします。
updatePasswordで新しい認証情報に置き換えます。- コンテンツを再処理しないため、処理は高速です。
実際の適用例
5 件の投資家契約書を統合した際、各契約書に異なるレビュアーパスワードが設定されていました。上記手順で全ファイルを解除し、単一のバインダーにマージ、社内ポリシーに合致した統一パスワードを適用しました。標準的なノートPC でも 2 分未満で完了しました。
ベストプラクティス
- パスワードは早期に検証:
isDocumentProtectedを使って手動対応が必要なファイルを事前にフラグ付けします。 - メモリ使用量を抑制: 大容量 PDF の場合は、すべてのバイト配列をメモリに保持せず、ディスクにストリームすることを検討してください。
- オブジェクトは速やかに破棄:
Mergerクラスはネイティブリソースを保持するため、finallyブロックで必ずdispose()を呼び出します。 - 開発時は一時ライセンスのみ使用: 本番環境では正式ライセンスを取得してください。
結論
GroupDocs.Merger for Java は、PDF コレクションの解除、マージ、再保護をシンプルに実現できる API を提供します。保護検出、解除、統一パスワードでのマージ、そしてオプションのパスワードローテーションという 4 つのステップに従うことで、手作業なしで安全な PDF バインダーを自動生成できます。
次のステップ:
- マージ後に PDF メタデータを設定するオプションを検討してください(documentation)。
- ブックマークを保持したまま PDF をマージする方法を学びます(API reference)。
- GitHub のサンプルプロジェクトで実装例を確認してください。