View Javadoc
1   /* 
2    * Copyright (C) 2016 Hobrasoft s.r.o.
3    *
4    * This program is free software: you can redistribute it and/or modify
5    * it under the terms of the GNU Affero General Public License as published by
6    * the Free Software Foundation, either version 3 of the License, or
7    * (at your option) any later version.
8    *
9    * This program is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU Affero General Public License for more details.
13   *
14   * You should have received a copy of the GNU Affero General Public License
15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16   */
17  package cz.hobrasoft.pdfmu.operation.args;
18  
19  import com.itextpdf.text.DocumentException;
20  import com.itextpdf.text.pdf.PdfReader;
21  import com.itextpdf.text.pdf.PdfStamper;
22  import cz.hobrasoft.pdfmu.PdfmuUtils;
23  import static cz.hobrasoft.pdfmu.error.ErrorType.OUTPUT_CLOSE;
24  import static cz.hobrasoft.pdfmu.error.ErrorType.OUTPUT_EXISTS_FORCE_NOT_SET;
25  import static cz.hobrasoft.pdfmu.error.ErrorType.OUTPUT_NOT_SPECIFIED;
26  import static cz.hobrasoft.pdfmu.error.ErrorType.OUTPUT_OPEN;
27  import static cz.hobrasoft.pdfmu.error.ErrorType.OUTPUT_STAMPER_CLOSE;
28  import static cz.hobrasoft.pdfmu.error.ErrorType.OUTPUT_STAMPER_OPEN;
29  import static cz.hobrasoft.pdfmu.error.ErrorType.OUTPUT_WRITE;
30  import cz.hobrasoft.pdfmu.operation.OperationException;
31  import java.io.ByteArrayOutputStream;
32  import java.io.File;
33  import java.io.FileNotFoundException;
34  import java.io.FileOutputStream;
35  import java.io.IOException;
36  import java.io.OutputStream;
37  import java.util.AbstractMap.SimpleEntry;
38  import java.util.logging.Logger;
39  import net.sourceforge.argparse4j.impl.Arguments;
40  import net.sourceforge.argparse4j.inf.ArgumentParser;
41  import net.sourceforge.argparse4j.inf.Namespace;
42  
43  /**
44   * The methods must be called in the following order:
45   * <ol>
46   * <li>{@link #addArguments(ArgumentParser)}
47   * <li>{@link #setFromNamespace(Namespace)}
48   * <li>{@link #setDefaultFile(File)} (optional)
49   * <li>{@link #open(PdfReader, boolean, char)}
50   * <li>{@link #close()}
51   * </ol>
52   *
53   * @author <a href="mailto:filip.bartek@hobrasoft.cz">Filip Bartek</a>
54   */
55  public class OutPdfArgs implements ArgsConfiguration, AutoCloseable {
56  
57      private static final Logger logger = Logger.getLogger(OutPdfArgs.class.getName());
58  
59      private final String metavarIn;
60      private final String metavarOut = "OUT.pdf";
61      private final boolean allowAppend;
62  
63      public OutPdfArgs(String metavarIn, boolean allowAppend) {
64          this.metavarIn = metavarIn;
65          this.allowAppend = allowAppend;
66      }
67  
68      @Override
69      public void addArguments(ArgumentParser parser) {
70          parser.addArgument("-o", "--out")
71                  .help(String.format("output PDF document (default: <%s>)", metavarIn))
72                  .metavar(metavarOut)
73                  .type(Arguments.fileType());
74  
75          if (allowAppend) {
76              parser.addArgument("--append")
77                      .help("append to the document, creating a new revision. If this option is disabled, the operation invalidates all the existing signatures.")
78                      .type(boolean.class)
79                      .setDefault(true);
80          }
81  
82          parser.addArgument("-f", "--force")
83                  .help(String.format("overwrite %s if it exists", metavarOut))
84                  .type(boolean.class)
85                  .action(Arguments.storeTrue());
86      }
87  
88      private File file = null;
89      private boolean overwrite = false;
90      private boolean append = false;
91  
92      @Override
93      public void setFromNamespace(Namespace namespace) {
94          file = namespace.get("out");
95          overwrite = namespace.getBoolean("force");
96  
97          if (allowAppend) {
98              append = namespace.getBoolean("append");
99          } else {
100             append = false;
101         }
102     }
103 
104     /**
105      * Set the target file if it has not been set by
106      * {@link #setFromNamespace(Namespace)}.
107      *
108      * @param file the default file to be used in case none was specified by
109      * {@link #setFromNamespace(Namespace)}
110      */
111     public void setDefaultFile(File file) {
112         if (this.file == null) {
113             logger.info("Output file has not been specified. Assuming in-place operation.");
114             this.file = file;
115         }
116     }
117 
118     private ByteArrayOutputStream os;
119     private PdfStamper stp;
120 
121     private void openOs() throws OperationException {
122         assert os == null;
123 
124         // Initialize the array length to the file size
125         // because the whole file will have to fit in the array anyway.
126         os = new ByteArrayOutputStream((int) file.length());
127     }
128 
129     private void openStpSignature(PdfReader pdfReader, char pdfVersion) throws OperationException {
130         assert os != null;
131         assert stp == null;
132 
133         try {
134             // digitalsignatures20130304.pdf : Code sample 2.17
135             // TODO?: Make sure version is high enough
136             stp = PdfStamper.createSignature(pdfReader, os, pdfVersion, null, append);
137         } catch (DocumentException | IOException ex) {
138             throw new OperationException(OUTPUT_STAMPER_OPEN, ex,
139                     PdfmuUtils.sortedMap(new SimpleEntry<String, Object>("outputFile", file)));
140         }
141     }
142 
143     private void openStpNew(PdfReader pdfReader, char pdfVersion) throws OperationException {
144         assert os != null;
145         assert stp == null;
146 
147         // Open the PDF stamper
148         try {
149             stp = new PdfStamper(pdfReader, os, pdfVersion, append);
150         } catch (DocumentException | IOException ex) {
151             throw new OperationException(OUTPUT_STAMPER_OPEN, ex,
152                     PdfmuUtils.sortedMap(new SimpleEntry<String, Object>("outputFile", file)));
153         }
154     }
155 
156     /**
157      * Returns a {@link PdfStamper} associated with the internal buffer. Using a
158      * buffer instead of an actual file means that the operation can be rolled
159      * back completely, leaving the output file untouched. Call {@link #close}
160      * to save the content of the buffer to the output file.
161      *
162      * @param pdfReader the input {@link PdfReader} to operate on
163      * @param signature shall we be signing the document?
164      * @param pdfVersion the last character of the PDF version number ('2' to
165      * '7'), or '\0' to keep the original version
166      * @return a {@link PdfStamper} that uses pdfReader as the source
167      *
168      * @throws OperationException if an error occurs
169      */
170     public PdfStamper open(PdfReader pdfReader, boolean signature, char pdfVersion) throws OperationException {
171         if (file == null) {
172             throw new OperationException(OUTPUT_NOT_SPECIFIED);
173         }
174         assert file != null;
175 
176         logger.info(String.format("Output file: %s", file));
177         if (file.exists()) {
178             logger.info("Output file already exists.");
179             if (overwrite) {
180                 logger.info("Will overwrite the output file (--force flag is set).");
181             } else {
182                 throw new OperationException(OUTPUT_EXISTS_FORCE_NOT_SET,
183                         PdfmuUtils.sortedMap(new SimpleEntry<String, Object>("outputFile", file)));
184             }
185         }
186 
187         openOs();
188 
189         if (signature) {
190             openStpSignature(pdfReader, pdfVersion);
191         } else {
192             openStpNew(pdfReader, pdfVersion);
193         }
194 
195         return stp;
196     }
197 
198     /**
199      * Writes the content of the internal buffer to the output file.
200      *
201      * @throws OperationException if an error occurs when closing the
202      * {@link PdfStamper} or when writing to the output file
203      */
204     @Override
205     public void close() throws OperationException {
206         close(false);
207     }
208 
209     public void close(boolean success) throws OperationException {
210         if (stp != null) {
211             // Only attempt to close the stamper if the operation has succeeded.
212             if (success) {
213                 try {
214                     stp.close();
215                 } catch (DocumentException | IOException ex) {
216                     throw new OperationException(OUTPUT_STAMPER_CLOSE, ex,
217                             PdfmuUtils.sortedMap(new SimpleEntry<String, Object>("outputFile", file)));
218                 }
219             }
220             stp = null;
221         }
222 
223         if (os != null) {
224             if (success) {
225                 assert file != null;
226                 logger.info(String.format("Writing the output of the operation to the output file: %s", file));
227 
228                 // Save the content of `os` to `file`.
229                 { // fileOs
230                     OutputStream fileOs = null;
231                     try {
232                         fileOs = new FileOutputStream(file);
233                     } catch (FileNotFoundException ex) {
234                         throw new OperationException(OUTPUT_OPEN, ex,
235                                 PdfmuUtils.sortedMap(new SimpleEntry<String, Object>("outputFile", file)));
236 
237                     }
238                     assert fileOs != null;
239                     try {
240                         assert os != null;
241                         os.writeTo(fileOs);
242                     } catch (IOException ex) {
243                         throw new OperationException(OUTPUT_WRITE, ex,
244                                 PdfmuUtils.sortedMap(new SimpleEntry<String, Object>("outputFile", file)));
245                     }
246                     try {
247                         fileOs.close();
248                     } catch (IOException ex) {
249                         throw new OperationException(OUTPUT_CLOSE, ex,
250                                 PdfmuUtils.sortedMap(new SimpleEntry<String, Object>("outputFile", file)));
251                     }
252                 }
253             }
254             os = null;
255         }
256     }
257 
258     public PdfStamper getPdfStamper() {
259         return stp;
260     }
261 
262     public File getFile() {
263         return file;
264     }
265 
266 }