OutPdfArgs.java
/*
* Copyright (C) 2016 Hobrasoft s.r.o.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cz.hobrasoft.pdfmu.operation.args;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import cz.hobrasoft.pdfmu.PdfmuUtils;
import static cz.hobrasoft.pdfmu.error.ErrorType.OUTPUT_CLOSE;
import static cz.hobrasoft.pdfmu.error.ErrorType.OUTPUT_EXISTS_FORCE_NOT_SET;
import static cz.hobrasoft.pdfmu.error.ErrorType.OUTPUT_NOT_SPECIFIED;
import static cz.hobrasoft.pdfmu.error.ErrorType.OUTPUT_OPEN;
import static cz.hobrasoft.pdfmu.error.ErrorType.OUTPUT_STAMPER_CLOSE;
import static cz.hobrasoft.pdfmu.error.ErrorType.OUTPUT_STAMPER_OPEN;
import static cz.hobrasoft.pdfmu.error.ErrorType.OUTPUT_WRITE;
import cz.hobrasoft.pdfmu.operation.OperationException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.AbstractMap.SimpleEntry;
import java.util.logging.Logger;
import net.sourceforge.argparse4j.impl.Arguments;
import net.sourceforge.argparse4j.inf.ArgumentParser;
import net.sourceforge.argparse4j.inf.Namespace;
/**
* The methods must be called in the following order:
* <ol>
* <li>{@link #addArguments(ArgumentParser)}
* <li>{@link #setFromNamespace(Namespace)}
* <li>{@link #setDefaultFile(File)} (optional)
* <li>{@link #open(PdfReader, boolean, char)}
* <li>{@link #close()}
* </ol>
*
* @author <a href="mailto:filip.bartek@hobrasoft.cz">Filip Bartek</a>
*/
public class OutPdfArgs implements ArgsConfiguration, AutoCloseable {
private static final Logger logger = Logger.getLogger(OutPdfArgs.class.getName());
private final String metavarIn;
private final String metavarOut = "OUT.pdf";
private final boolean allowAppend;
public OutPdfArgs(String metavarIn, boolean allowAppend) {
this.metavarIn = metavarIn;
this.allowAppend = allowAppend;
}
@Override
public void addArguments(ArgumentParser parser) {
parser.addArgument("-o", "--out")
.help(String.format("output PDF document (default: <%s>)", metavarIn))
.metavar(metavarOut)
.type(Arguments.fileType());
if (allowAppend) {
parser.addArgument("--append")
.help("append to the document, creating a new revision. If this option is disabled, the operation invalidates all the existing signatures.")
.type(boolean.class)
.setDefault(true);
}
parser.addArgument("-f", "--force")
.help(String.format("overwrite %s if it exists", metavarOut))
.type(boolean.class)
.action(Arguments.storeTrue());
}
private File file = null;
private boolean overwrite = false;
private boolean append = false;
@Override
public void setFromNamespace(Namespace namespace) {
file = namespace.get("out");
overwrite = namespace.getBoolean("force");
if (allowAppend) {
append = namespace.getBoolean("append");
} else {
append = false;
}
}
/**
* Set the target file if it has not been set by
* {@link #setFromNamespace(Namespace)}.
*
* @param file the default file to be used in case none was specified by
* {@link #setFromNamespace(Namespace)}
*/
public void setDefaultFile(File file) {
if (this.file == null) {
logger.info("Output file has not been specified. Assuming in-place operation.");
this.file = file;
}
}
private ByteArrayOutputStream os;
private PdfStamper stp;
private void openOs() throws OperationException {
assert os == null;
// Initialize the array length to the file size
// because the whole file will have to fit in the array anyway.
os = new ByteArrayOutputStream((int) file.length());
}
private void openStpSignature(PdfReader pdfReader, char pdfVersion) throws OperationException {
assert os != null;
assert stp == null;
try {
// digitalsignatures20130304.pdf : Code sample 2.17
// TODO?: Make sure version is high enough
stp = PdfStamper.createSignature(pdfReader, os, pdfVersion, null, append);
} catch (DocumentException | IOException ex) {
throw new OperationException(OUTPUT_STAMPER_OPEN, ex,
PdfmuUtils.sortedMap(new SimpleEntry<String, Object>("outputFile", file)));
}
}
private void openStpNew(PdfReader pdfReader, char pdfVersion) throws OperationException {
assert os != null;
assert stp == null;
// Open the PDF stamper
try {
stp = new PdfStamper(pdfReader, os, pdfVersion, append);
} catch (DocumentException | IOException ex) {
throw new OperationException(OUTPUT_STAMPER_OPEN, ex,
PdfmuUtils.sortedMap(new SimpleEntry<String, Object>("outputFile", file)));
}
}
/**
* Returns a {@link PdfStamper} associated with the internal buffer. Using a
* buffer instead of an actual file means that the operation can be rolled
* back completely, leaving the output file untouched. Call {@link #close}
* to save the content of the buffer to the output file.
*
* @param pdfReader the input {@link PdfReader} to operate on
* @param signature shall we be signing the document?
* @param pdfVersion the last character of the PDF version number ('2' to
* '7'), or '\0' to keep the original version
* @return a {@link PdfStamper} that uses pdfReader as the source
*
* @throws OperationException if an error occurs
*/
public PdfStamper open(PdfReader pdfReader, boolean signature, char pdfVersion) throws OperationException {
if (file == null) {
throw new OperationException(OUTPUT_NOT_SPECIFIED);
}
assert file != null;
logger.info(String.format("Output file: %s", file));
if (file.exists()) {
logger.info("Output file already exists.");
if (overwrite) {
logger.info("Will overwrite the output file (--force flag is set).");
} else {
throw new OperationException(OUTPUT_EXISTS_FORCE_NOT_SET,
PdfmuUtils.sortedMap(new SimpleEntry<String, Object>("outputFile", file)));
}
}
openOs();
if (signature) {
openStpSignature(pdfReader, pdfVersion);
} else {
openStpNew(pdfReader, pdfVersion);
}
return stp;
}
/**
* Writes the content of the internal buffer to the output file.
*
* @throws OperationException if an error occurs when closing the
* {@link PdfStamper} or when writing to the output file
*/
@Override
public void close() throws OperationException {
close(false);
}
public void close(boolean success) throws OperationException {
if (stp != null) {
// Only attempt to close the stamper if the operation has succeeded.
if (success) {
try {
stp.close();
} catch (DocumentException | IOException ex) {
throw new OperationException(OUTPUT_STAMPER_CLOSE, ex,
PdfmuUtils.sortedMap(new SimpleEntry<String, Object>("outputFile", file)));
}
}
stp = null;
}
if (os != null) {
if (success) {
assert file != null;
logger.info(String.format("Writing the output of the operation to the output file: %s", file));
// Save the content of `os` to `file`.
{ // fileOs
OutputStream fileOs = null;
try {
fileOs = new FileOutputStream(file);
} catch (FileNotFoundException ex) {
throw new OperationException(OUTPUT_OPEN, ex,
PdfmuUtils.sortedMap(new SimpleEntry<String, Object>("outputFile", file)));
}
assert fileOs != null;
try {
assert os != null;
os.writeTo(fileOs);
} catch (IOException ex) {
throw new OperationException(OUTPUT_WRITE, ex,
PdfmuUtils.sortedMap(new SimpleEntry<String, Object>("outputFile", file)));
}
try {
fileOs.close();
} catch (IOException ex) {
throw new OperationException(OUTPUT_CLOSE, ex,
PdfmuUtils.sortedMap(new SimpleEntry<String, Object>("outputFile", file)));
}
}
}
os = null;
}
}
public PdfStamper getPdfStamper() {
return stp;
}
public File getFile() {
return file;
}
}