I want to sign a InputStream from a PDF file without using a temporary file.
Here I convert InputStream to File and this work fine :
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();
But I want to do this directly :
PDDocument document = PDDocument.load(inputStream);
Problem: at run
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)
All ideas are welcome.
Thank you.
EDIT: It's now working with the release of PDFBox 2.0.0.
The immediate hindrance is in the method PDDocument.saveIncremental()
itself:
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(); } } }
(PDDocument.java)
The member incrementalFile
used in the first line is only set during a PDDocument.load
with a File
parameter.
Thus, this method cannot be used.
Fortunately the method PDDocument.saveIncremental()
only uses methods and values publicly available with the sole exception of signInterface
, but you know the value of it because you set it in your code in the line right before the saveIncremental
call:
document.addSignature(new PDSignature(this.dts.getDocumentTimeStamp()), this);
document.saveIncremental(new FileOutputStream("C:/result.pdf"));
Thus, instead of calling PDDocument.saveIncremental()
you can do the equivalent in your code.
To do so you furthermore need a replacement value for the InputStream input
. It needs to return a stream with the identical content as inputStream
in your
PDDocument document = PDDocument.load(inputStream);
So you need to use that stream twice. 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
.
Thus, replace your
PDDocument document = PDDocument.load(inputStream);
...
document.addSignature(new PDSignature(this.dts.getDocumentTimeStamp()), this);
document.saveIncremental(new FileOutputStream("C:/result.pdf"));
document.close();
by
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();
and add a new method saveIncremental
to your class inspired by the original 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();
}
}
}
I said above
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
.
Actually there is another reason to do so: COSWriter.doWriteSignature()
retrieves the length of the original PDF like this:
long inLength = incrementalInput.available();
(COSWriter.java)
The documentation of InputStream.available()
states, though:
Note that while some implementations of
InputStream
will return the total number of bytes in the stream, many will not.
To re-use inputStream
instead of using a byte[]
and ByteArrayInputStream
s as above, therefore, inputStream
not only needs to support reset()
but also needs to be one of the few InputStream
implementations which return the total number of bytes in the stream as available
.
FileInputStream
and ByteArrayInputStream
both do return the total number of bytes in the stream as available
.
There may still be more issues when using generic InputStream
s instead of these two.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With