如何使用 PDFBox 2.0.0 从 PDF 文件签署 InputStream
How to sign an InputStream from a PDF file with PDFBox 2.0.0
我想在不使用临时文件的情况下从 PDF 文件签署 InputStream。
在这里,我将 InputStream 转换为 File 并且工作正常:
InputStream inputStream = this.signatureObjPAdES.getSignatureDocument().getInputStream();
OutputStream outputStream = new FileOutputStream(new File("C:/temp.pdf"));
int read = 0;
byte[] bytes = new byte[1024];
while ((read = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, read);
}
PDDocument document = PDDocument.load(new File("C:/temp.pdf"));
...
document.addSignature(new PDSignature(this.dts.getDocumentTimeStamp()), this);
document.saveIncremental(new FileOutputStream("C:/result.pdf");
document.close();
但我想直接这样做:
PDDocument document = PDDocument.load(inputStream);
问题:在 运行
Exception in thread "main" java.lang.NullPointerException
at java.io.RandomAccessFile.<init>(Unknown Source)
at org.apache.pdfbox.io.RandomAccessBufferedFileInputStream.<init>(RandomAccessBufferedFileInputStream.java:77)
at org.apache.pdfbox.pdmodel.PDDocument.saveIncremental(PDDocument.java:961)
欢迎所有想法。
谢谢。
编辑:
它现在正在与 PDFBox 2.0.0 的发布一起工作。
原因
直接障碍在于方法PDDocument.saveIncremental()
本身:
public void saveIncremental(OutputStream output) throws IOException
{
InputStream input = new RandomAccessBufferedFileInputStream(incrementalFile);
COSWriter writer = null;
try
{
writer = new COSWriter(output, input);
writer.write(this, signInterface);
writer.close();
}
finally
{
if (writer != null)
{
writer.close();
}
}
}
第一行中使用的成员 incrementalFile
仅在带有 File
参数的 PDDocument.load
期间设置。
因此,无法使用此方法。
解决方法
幸运的是,方法 PDDocument.saveIncremental()
仅使用公开可用的方法和值,唯一的例外是 signInterface
,但您知道它的值,因为您在代码中的前一行中设置了它saveIncremental
调用:
document.addSignature(new PDSignature(this.dts.getDocumentTimeStamp()), this);
document.saveIncremental(new FileOutputStream("C:/result.pdf"));
因此,您可以在代码中执行等效的操作而不是调用 PDDocument.saveIncremental()
。
为此,您还需要 InputStream input
的替换值。它需要 return 一个内容与
中的 inputStream
相同的流
PDDocument document = PDDocument.load(inputStream);
因此您需要使用该流两次。由于您没有说 inputStream
是否可以重置,我们将首先将其复制到 byte[]
中,我们将其转发给 PDDocument.load
和 new COSWriter
.
因此,替换您的
PDDocument document = PDDocument.load(inputStream);
...
document.addSignature(new PDSignature(this.dts.getDocumentTimeStamp()), this);
document.saveIncremental(new FileOutputStream("C:/result.pdf"));
document.close();
来自
byte[] inputBytes = IOUtils.toByteArray(inputStream);
PDDocument document = PDDocument.load(new ByteArrayInputStream(inputBytes));
...
document.addSignature(new PDSignature(this.dts.getDocumentTimeStamp()), this);
saveIncremental(new FileOutputStream("C:/result.pdf"),
new ByteArrayInputStream(inputBytes), document, this);
document.close();
并在您的 class 中添加一个新方法 saveIncremental
,灵感来自原始 PDDocument.saveIncremental()
:
void saveIncremental(OutputStream output, InputStream input, PDDocument document, SignatureInterface signatureInterface) throws IOException
{
COSWriter writer = null;
try
{
writer = new COSWriter(output, input);
writer.write(document, signatureInterface);
writer.close();
}
finally
{
if (writer != null)
{
writer.close();
}
}
}
在旁边
我上面说了
As you have not said whether that inputStream
can be reset, we'll first copy it into a byte[]
which we forward both to PDDocument.load
and new COSWriter
.
实际上还有一个这样做的原因:COSWriter.doWriteSignature()
像这样检索原始 PDF 的长度:
long inLength = incrementalInput.available();
InputStream.available()
的文档指出:
Note that while some implementations of InputStream
will return the total number of bytes in the stream, many will not.
要重新使用inputStream
而不是像上面那样使用byte[]
和ByteArrayInputStream
,因此,inputStream
不仅需要支持reset()
但也需要成为少数InputStream
实现之一,其中return流中的总字节数为available
。
FileInputStream
和 ByteArrayInputStream
都 return 流中的总字节数为 available
.
使用通用 InputStream
而不是这两个时,可能还会有更多问题。
嘿Cyril Bremaud,你可以使用这种方法,因为PDDocument
class有3个重载构造函数,你可以继续,如果你愿意,只提供文件路径,它也会工作。但是为了能够将 InputStream
直接传递给 PDDocument
构造函数,请使用以下代码:
lStrInputPDFfile = "samples_pdf_signing\Country Calendar.pdf";
lOsPDFInput = new java.io.FileInputStream(lStrInputPDFfile);
jPDFDocument = new org.apache.pdfbox.pdmodel.PDDocument().load(lOsPDFInput);
但这也适用于我的情况:
lStrInputPDFfile = "samples_pdf_signing\Country Calendar.pdf";
jPDFDocument = new org.apache.pdfbox.pdmodel.PDDocument().load(lStrInputPDFfile);
注意:`InputStream 是 FileInputStream 的父 class,这就是上述代码有效的原因。
更新了我的代码,请再次检查。感谢 @mkl 指出这一点。
我想在不使用临时文件的情况下从 PDF 文件签署 InputStream。
在这里,我将 InputStream 转换为 File 并且工作正常:
InputStream inputStream = this.signatureObjPAdES.getSignatureDocument().getInputStream();
OutputStream outputStream = new FileOutputStream(new File("C:/temp.pdf"));
int read = 0;
byte[] bytes = new byte[1024];
while ((read = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, read);
}
PDDocument document = PDDocument.load(new File("C:/temp.pdf"));
...
document.addSignature(new PDSignature(this.dts.getDocumentTimeStamp()), this);
document.saveIncremental(new FileOutputStream("C:/result.pdf");
document.close();
但我想直接这样做:
PDDocument document = PDDocument.load(inputStream);
问题:在 运行
Exception in thread "main" java.lang.NullPointerException
at java.io.RandomAccessFile.<init>(Unknown Source)
at org.apache.pdfbox.io.RandomAccessBufferedFileInputStream.<init>(RandomAccessBufferedFileInputStream.java:77)
at org.apache.pdfbox.pdmodel.PDDocument.saveIncremental(PDDocument.java:961)
欢迎所有想法。
谢谢。
编辑: 它现在正在与 PDFBox 2.0.0 的发布一起工作。
原因
直接障碍在于方法PDDocument.saveIncremental()
本身:
public void saveIncremental(OutputStream output) throws IOException { InputStream input = new RandomAccessBufferedFileInputStream(incrementalFile); COSWriter writer = null; try { writer = new COSWriter(output, input); writer.write(this, signInterface); writer.close(); } finally { if (writer != null) { writer.close(); } } }
第一行中使用的成员 incrementalFile
仅在带有 File
参数的 PDDocument.load
期间设置。
因此,无法使用此方法。
解决方法
幸运的是,方法 PDDocument.saveIncremental()
仅使用公开可用的方法和值,唯一的例外是 signInterface
,但您知道它的值,因为您在代码中的前一行中设置了它saveIncremental
调用:
document.addSignature(new PDSignature(this.dts.getDocumentTimeStamp()), this);
document.saveIncremental(new FileOutputStream("C:/result.pdf"));
因此,您可以在代码中执行等效的操作而不是调用 PDDocument.saveIncremental()
。
为此,您还需要 InputStream input
的替换值。它需要 return 一个内容与
inputStream
相同的流
PDDocument document = PDDocument.load(inputStream);
因此您需要使用该流两次。由于您没有说 inputStream
是否可以重置,我们将首先将其复制到 byte[]
中,我们将其转发给 PDDocument.load
和 new COSWriter
.
因此,替换您的
PDDocument document = PDDocument.load(inputStream);
...
document.addSignature(new PDSignature(this.dts.getDocumentTimeStamp()), this);
document.saveIncremental(new FileOutputStream("C:/result.pdf"));
document.close();
来自
byte[] inputBytes = IOUtils.toByteArray(inputStream);
PDDocument document = PDDocument.load(new ByteArrayInputStream(inputBytes));
...
document.addSignature(new PDSignature(this.dts.getDocumentTimeStamp()), this);
saveIncremental(new FileOutputStream("C:/result.pdf"),
new ByteArrayInputStream(inputBytes), document, this);
document.close();
并在您的 class 中添加一个新方法 saveIncremental
,灵感来自原始 PDDocument.saveIncremental()
:
void saveIncremental(OutputStream output, InputStream input, PDDocument document, SignatureInterface signatureInterface) throws IOException
{
COSWriter writer = null;
try
{
writer = new COSWriter(output, input);
writer.write(document, signatureInterface);
writer.close();
}
finally
{
if (writer != null)
{
writer.close();
}
}
}
在旁边
我上面说了
As you have not said whether that
inputStream
can be reset, we'll first copy it into abyte[]
which we forward both toPDDocument.load
andnew COSWriter
.
实际上还有一个这样做的原因:COSWriter.doWriteSignature()
像这样检索原始 PDF 的长度:
long inLength = incrementalInput.available();
InputStream.available()
的文档指出:
Note that while some implementations of
InputStream
will return the total number of bytes in the stream, many will not.
要重新使用inputStream
而不是像上面那样使用byte[]
和ByteArrayInputStream
,因此,inputStream
不仅需要支持reset()
但也需要成为少数InputStream
实现之一,其中return流中的总字节数为available
。
FileInputStream
和 ByteArrayInputStream
都 return 流中的总字节数为 available
.
使用通用 InputStream
而不是这两个时,可能还会有更多问题。
嘿Cyril Bremaud,你可以使用这种方法,因为PDDocument
class有3个重载构造函数,你可以继续,如果你愿意,只提供文件路径,它也会工作。但是为了能够将 InputStream
直接传递给 PDDocument
构造函数,请使用以下代码:
lStrInputPDFfile = "samples_pdf_signing\Country Calendar.pdf";
lOsPDFInput = new java.io.FileInputStream(lStrInputPDFfile);
jPDFDocument = new org.apache.pdfbox.pdmodel.PDDocument().load(lOsPDFInput);
但这也适用于我的情况:
lStrInputPDFfile = "samples_pdf_signing\Country Calendar.pdf";
jPDFDocument = new org.apache.pdfbox.pdmodel.PDDocument().load(lStrInputPDFfile);
注意:`InputStream 是 FileInputStream 的父 class,这就是上述代码有效的原因。
更新了我的代码,请再次检查。感谢 @mkl 指出这一点。