如何使用PDFBox 2.0.0从PDF文件中签署InputStream

Cyr*_*aud 5 java pdf signing inputstream pdfbox

我想在不使用临时文件的情况下从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();
Run Code Online (Sandbox Code Playgroud)

但我想直接这样做:

PDDocument document = PDDocument.load(inputStream);
Run Code Online (Sandbox Code Playgroud)

问题:跑步时

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)
Run Code Online (Sandbox Code Playgroud)

欢迎所有想法.
谢谢.

编辑:它现在正在使用PDFBox 2.0.0的发布.

mkl*_*mkl 3

原因

直接的障碍在于方法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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

PDDocument.java

第一行中使用的成员仅在带有参数的incrementalFile过程中设置。PDDocument.loadFile

因此,不能使用该方法。

解决方法

幸运的是,该方法PDDocument.saveIncremental()仅使用公开可用的方法和值signInterface,但您知道它的值,因为您在调用之前的代码行中设置了它saveIncremental

document.addSignature(new PDSignature(this.dts.getDocumentTimeStamp()), this);
document.saveIncremental(new FileOutputStream("C:/result.pdf"));
Run Code Online (Sandbox Code Playgroud)

因此,PDDocument.saveIncremental()您可以在代码中执行等效操作,而不是调用。

为此,您还需要 的替换值InputStream input。它需要返回一个与inputStream您的内容相同的流

PDDocument document = PDDocument.load(inputStream);
Run Code Online (Sandbox Code Playgroud)

所以你需要使用该流两次。由于您没有说是否inputStream可以重置,我们将首先将其复制到byte[]我们转发到PDDocument.load和 的a 中new COSWriter

因此,替换你的

PDDocument document = PDDocument.load(inputStream);

...

document.addSignature(new PDSignature(this.dts.getDocumentTimeStamp()), this);
document.saveIncremental(new FileOutputStream("C:/result.pdf"));
document.close();
Run Code Online (Sandbox Code Playgroud)

经过

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();
Run Code Online (Sandbox Code Playgroud)

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在一边

我上面说了

由于您没有说是否inputStream可以重置,我们将首先将其复制到byte[]我们转发到PDDocument.load和 的a 中new COSWriter

实际上这样做还有另一个原因:COSWriter.doWriteSignature()像这样检索原始 PDF 的长度:

long inLength = incrementalInput.available();
Run Code Online (Sandbox Code Playgroud)

COSWriter.java

不过,国家的文件InputStream.available()

请注意,虽然 的某些实现InputStream将返回流中的字节总数,但许多实现不会返回。

因此,要重用而不是像上面inputStream那样使用 abyte[]和s,不仅需要支持,而且还需要成为将流中的总字节数返回为 的少数实现之一。ByteArrayInputStreaminputStreamreset()InputStreamavailable

FileInputStream并且ByteArrayInputStream两者都将流中的总字节数返回为available

InputStream使用泛型而不是这两个时可能还会出现更多问题。