001/* 
002 * Copyright (C) 2016 Hobrasoft s.r.o.
003 *
004 * This program is free software: you can redistribute it and/or modify
005 * it under the terms of the GNU Affero General Public License as published by
006 * the Free Software Foundation, either version 3 of the License, or
007 * (at your option) any later version.
008 *
009 * This program is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012 * GNU Affero General Public License for more details.
013 *
014 * You should have received a copy of the GNU Affero General Public License
015 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
016 */
017package cz.hobrasoft.pdfmu.operation;
018
019import com.itextpdf.text.pdf.PdfStamper;
020import cz.hobrasoft.pdfmu.PdfmuUtils;
021import static cz.hobrasoft.pdfmu.error.ErrorType.ATTACH_ATTACHMENT_EQUALS_OUTPUT;
022import static cz.hobrasoft.pdfmu.error.ErrorType.ATTACH_FAIL;
023import cz.hobrasoft.pdfmu.jackson.EmptyResult;
024import cz.hobrasoft.pdfmu.operation.args.InOutPdfArgs;
025import java.io.File;
026import java.io.IOException;
027import java.util.AbstractMap.SimpleEntry;
028import java.util.logging.Logger;
029import java.util.regex.Matcher;
030import java.util.regex.Pattern;
031import net.sourceforge.argparse4j.impl.Arguments;
032import net.sourceforge.argparse4j.inf.Namespace;
033import net.sourceforge.argparse4j.inf.Subparser;
034
035/**
036 * Attaches one or more files to a PDF document
037 *
038 * @author <a href="mailto:filip.bartek@hobrasoft.cz">Filip Bartek</a>
039 */
040public class OperationAttach extends OperationCommon {
041
042    private static final Logger logger = Logger.getLogger(OperationAttach.class.getName());
043
044    private final String metavarIn = "IN.pdf";
045    private final InOutPdfArgs inout = new InOutPdfArgs(metavarIn);
046
047    @Override
048    public Subparser configureSubparser(Subparser subparser) {
049        String help = "Attach a file to a PDF document";
050
051        subparser.help(help)
052                .description(help)
053                .defaultHelp(true);
054
055        inout.addArguments(subparser);
056
057        String metavarAttachment = "ATTACHMENT";
058
059        // TODO: Try to reuse InPdfArgs
060        subparser.addArgument("attachment")
061                .help(String.format("file to attach to %s", metavarIn))
062                .metavar(metavarAttachment)
063                .type(Arguments.fileType().acceptSystemIn());
064        subparser.addArgument("-r", "--rename")
065                .help(String.format("attachment filename shown in the output PDF document (default: <%s>)", metavarAttachment))
066                .metavar("FILENAME")
067                .type(String.class);
068        subparser.addArgument("-d", "--description")
069                .help("attachment description shown in the output PDF document (default: <none>)")
070                .type(String.class);
071
072        return subparser;
073    }
074
075    @Override
076    public void execute(Namespace namespace) throws OperationException {
077        inout.setFromNamespace(namespace);
078
079        File file = namespace.get("attachment");
080
081        File outFile = inout.getOut().getFile();
082        assert outFile != null;
083        if (outFile.equals(file)) {
084            throw new OperationException(ATTACH_ATTACHMENT_EQUALS_OUTPUT,
085                    PdfmuUtils.sortedMap(
086                            new SimpleEntry<String, Object>("outputFile", outFile),
087                            new SimpleEntry<String, Object>("attachmentFile", file)));
088        }
089
090        String description = namespace.getString("description");
091        String fileDisplay = namespace.getString("rename");
092
093        if (fileDisplay == null) {
094            fileDisplay = file.getName();
095        }
096
097        try {
098            inout.open();
099            execute(inout.getPdfStamper(), description, file.getPath(), fileDisplay);
100            inout.close(true);
101        } finally {
102            inout.close(false);
103        }
104
105        writeResult(new EmptyResult());
106    }
107
108    private static final Pattern filenameWithExtension = Pattern.compile(".*\\.[^\\.]+");
109
110    private static void execute(PdfStamper stp, String description, String file, String fileDisplay) throws OperationException {
111        {
112            assert stp != null;
113            assert file != null;
114            assert fileDisplay != null; // We use the attachment file name by default
115
116            logger.info(String.format("Attached file: %s", file));
117            logger.info(String.format("Description: %s", (description != null ? description : "<none>")));
118            logger.info(String.format("Display name: %s", fileDisplay));
119            {
120                Matcher m = filenameWithExtension.matcher(fileDisplay);
121                if (!m.matches()) {
122                    logger.warning("Display name does not contain a file extension. Adobe Reader XI does not allow opening or saving such attachment.");
123                }
124            }
125        }
126        try {
127            stp.addFileAttachment(description, null, file, fileDisplay);
128        } catch (IOException ex) {
129            throw new OperationException(ATTACH_FAIL, ex,
130                    PdfmuUtils.sortedMap(new String[]{"file"}, new Object[]{file}));
131        }
132        logger.info(String.format("The file \"%s\" has been attached.", file));
133    }
134
135    private static Operation instance = null;
136
137    public static Operation getInstance() {
138        if (instance == null) {
139            instance = new OperationAttach();
140        }
141        return instance;
142    }
143
144    private OperationAttach() {
145        // Singleton
146    }
147
148}