Java tutorial
/* * 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; } }