Pdf.java 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. package com.github.jhonnymertz.wkhtmltopdf.wrapper;
  2. import com.github.jhonnymertz.wkhtmltopdf.wrapper.configurations.WrapperConfig;
  3. import com.github.jhonnymertz.wkhtmltopdf.wrapper.exceptions.PDFExportException;
  4. import com.github.jhonnymertz.wkhtmltopdf.wrapper.page.Page;
  5. import com.github.jhonnymertz.wkhtmltopdf.wrapper.page.PageType;
  6. import com.github.jhonnymertz.wkhtmltopdf.wrapper.params.Param;
  7. import com.github.jhonnymertz.wkhtmltopdf.wrapper.params.Params;
  8. import org.apache.commons.io.FileUtils;
  9. import org.apache.commons.io.IOUtils;
  10. import org.apache.commons.lang3.StringUtils;
  11. import org.slf4j.Logger;
  12. import org.slf4j.LoggerFactory;
  13. import java.io.File;
  14. import java.io.IOException;
  15. import java.io.InputStream;
  16. import java.nio.file.Files;
  17. import java.nio.file.Path;
  18. import java.nio.file.Paths;
  19. import java.util.ArrayList;
  20. import java.util.Arrays;
  21. import java.util.List;
  22. import java.util.UUID;
  23. import java.util.concurrent.Callable;
  24. import java.util.concurrent.ExecutorService;
  25. import java.util.concurrent.Executors;
  26. import java.util.concurrent.Future;
  27. import java.util.concurrent.TimeUnit;
  28. /**
  29. * Represents a Pdf file
  30. */
  31. public class Pdf {
  32. private static final Logger logger = LoggerFactory.getLogger(Pdf.class);
  33. private static final String STDINOUT = "-";
  34. private final WrapperConfig wrapperConfig;
  35. private final Params params;
  36. private final Params tocParams;
  37. private final List<Page> pages;
  38. private boolean hasToc = false;
  39. /**
  40. * Timeout to wait while generating a PDF, in seconds
  41. */
  42. private int timeout = 10;
  43. private File tempDirectory;
  44. private String outputFilename = null;
  45. private List<Integer> successValues = new ArrayList<Integer>(Arrays.asList(0));
  46. @Deprecated
  47. /**
  48. * Default constructor
  49. * @deprecated Use the constructor with the WrapperConfig definition
  50. */
  51. public Pdf() {
  52. this(new WrapperConfig());
  53. }
  54. public Pdf(WrapperConfig wrapperConfig) {
  55. this.wrapperConfig = wrapperConfig;
  56. this.params = new Params();
  57. this.tocParams = new Params();
  58. this.pages = new ArrayList<Page>();
  59. logger.info("Initialized with {}", wrapperConfig);
  60. }
  61. /**
  62. * Add a page to the pdf
  63. *
  64. * @deprecated Use the specific type method to a better semantic
  65. */
  66. @Deprecated
  67. public void addPage(String source, PageType type) {
  68. this.pages.add(new Page(source, type));
  69. }
  70. /**
  71. * Add a page from an URL to the pdf
  72. */
  73. public void addPageFromUrl(String source) {
  74. this.pages.add(new Page(source, PageType.url));
  75. }
  76. /**
  77. * Add a page from a HTML-based string to the pdf
  78. */
  79. public void addPageFromString(String source) {
  80. this.pages.add(new Page(source, PageType.htmlAsString));
  81. }
  82. /**
  83. * Add a page from a file to the pdf
  84. */
  85. public void addPageFromFile(String source) {
  86. this.pages.add(new Page(source, PageType.file));
  87. }
  88. public void addToc() {
  89. this.hasToc = true;
  90. }
  91. public void addParam(Param param, Param... params) {
  92. this.params.add(param, params);
  93. }
  94. public void addTocParam(Param param, Param... params) {
  95. this.tocParams.add(param, params);
  96. }
  97. /**
  98. * Sets the timeout to wait while generating a PDF, in seconds
  99. */
  100. public void setTimeout(int timeout) {
  101. this.timeout = timeout;
  102. }
  103. /**
  104. * wkhtmltopdf often returns 1 to indicate some assets can't be found,
  105. * this can occur for protocol less links or in other cases. Sometimes you
  106. * may want to reject these with an exception which is the default, but in other
  107. * cases the PDF is fine for your needs. Call this method allow return values of 1.
  108. */
  109. public void setAllowMissingAssets() {
  110. if (!successValues.contains(1)) {
  111. successValues.add(1);
  112. }
  113. }
  114. public boolean getAllowMissingAssets() {
  115. return successValues.contains(1);
  116. }
  117. /**
  118. * In standard process returns 0 means "ok" and any other value is an error. However, wkhtmltopdf
  119. * uses the return value to also return warning information which you may decide to ignore (@see setAllowMissingAssets)
  120. *
  121. * @param successValues The full list of process return values you will accept as a 'success'.
  122. */
  123. public void setSuccessValues(List<Integer> successValues) {
  124. this.successValues = successValues;
  125. }
  126. /**
  127. * Sets the temporary folder to store files during the process
  128. * Default is provided by #File.createTempFile()
  129. * @param tempDirectory
  130. */
  131. public void setTempDirectory(File tempDirectory) {
  132. this.tempDirectory = tempDirectory;
  133. }
  134. /**
  135. * Executes the wkhtmltopdf into standard out and captures the results.
  136. * @param path The path to the file where the PDF will be saved.
  137. * @return
  138. * @throws IOException
  139. * @throws InterruptedException
  140. */
  141. public File saveAs(String path) throws IOException, InterruptedException {
  142. File file = new File(path);
  143. FileUtils.writeByteArrayToFile(file, getPDF());
  144. logger.info("PDF successfully saved in {}", file.getAbsolutePath());
  145. return file;
  146. }
  147. /**
  148. * Executes the wkhtmltopdf saving the results directly to the specified file path.
  149. * @param path The path to the file where the PDF will be saved.
  150. * @return
  151. * @throws IOException
  152. * @throws InterruptedException
  153. */
  154. public File saveAsDirect(String path)throws IOException, InterruptedException {
  155. File file = new File(path);
  156. outputFilename = file.getAbsolutePath();
  157. getPDF();
  158. return file;
  159. }
  160. public byte[] getPDF() throws IOException, InterruptedException, PDFExportException {
  161. ExecutorService executor = Executors.newFixedThreadPool(2);
  162. try {
  163. String command = getCommand();
  164. logger.debug("Generating pdf with: {}", command);
  165. Process process = Runtime.getRuntime().exec(getCommandAsArray());
  166. Future<byte[]> inputStreamToByteArray = executor.submit(streamToByteArrayTask(process.getInputStream()));
  167. Future<byte[]> outputStreamToByteArray = executor.submit(streamToByteArrayTask(process.getErrorStream()));
  168. process.waitFor();
  169. if (!successValues.contains( process.exitValue() )) {
  170. byte[] errorStream = getFuture(outputStreamToByteArray);
  171. logger.error("Error while generating pdf: {}", new String(errorStream));
  172. throw new PDFExportException(command, process.exitValue(), errorStream, getFuture(inputStreamToByteArray));
  173. } else {
  174. logger.debug("Wkhtmltopdf output:\n{}", new String(getFuture(outputStreamToByteArray)));
  175. }
  176. logger.info("PDF successfully generated with: {}", command);
  177. return getFuture(inputStreamToByteArray);
  178. } finally {
  179. logger.debug("Shutting down executor for wkhtmltopdf.");
  180. executor.shutdownNow();
  181. cleanTempFiles();
  182. }
  183. }
  184. protected String[] getCommandAsArray() throws IOException {
  185. List<String> commandLine = new ArrayList<String>();
  186. if (wrapperConfig.isXvfbEnabled()) {
  187. commandLine.addAll(wrapperConfig.getXvfbConfig().getCommandLine());
  188. }
  189. commandLine.add(wrapperConfig.getWkhtmltopdfCommand());
  190. commandLine.addAll(params.getParamsAsStringList());
  191. if (hasToc) {
  192. commandLine.add("toc");
  193. commandLine.addAll(tocParams.getParamsAsStringList());
  194. }
  195. for (Page page : pages) {
  196. if (page.getType().equals(PageType.htmlAsString)) {
  197. //htmlAsString pages are first store into a temp file, then the location is passed as parameter to
  198. // wkhtmltopdf, this is a workaround to avoid huge commands
  199. File temp = File.createTempFile("java-wkhtmltopdf-wrapper" + UUID.randomUUID().toString(), ".html", tempDirectory);
  200. FileUtils.writeStringToFile(temp, page.getSource(), "UTF-8");
  201. page.setFilePath(temp.getAbsolutePath());
  202. commandLine.add(temp.getAbsolutePath());
  203. } else {
  204. commandLine.add(page.getSource());
  205. }
  206. }
  207. commandLine.add( (null != outputFilename) ? outputFilename : STDINOUT);
  208. logger.debug("Command generated: {}", commandLine.toString());
  209. return commandLine.toArray(new String[commandLine.size()]);
  210. }
  211. private Callable<byte[]> streamToByteArrayTask(final InputStream input) {
  212. return new Callable<byte[]>() {
  213. public byte[] call() throws Exception {
  214. return IOUtils.toByteArray(input);
  215. }
  216. };
  217. }
  218. private byte[] getFuture(Future<byte[]> future) {
  219. try {
  220. return future.get(this.timeout, TimeUnit.SECONDS);
  221. } catch (Exception e) {
  222. throw new RuntimeException(e);
  223. }
  224. }
  225. private void cleanTempFiles() {
  226. logger.debug("Cleaning up temporary files...");
  227. for (Page page : pages) {
  228. if (page.getType().equals(PageType.htmlAsString)) {
  229. try {
  230. Path p = Paths.get(page.getFilePath());
  231. logger.debug("Delete temp file at: " + page.getFilePath() + " " + Files.deleteIfExists(p));
  232. } catch (IOException ex) {
  233. logger.warn("Couldn't delete temp file " + page.getFilePath());
  234. }
  235. }
  236. }
  237. }
  238. /**
  239. * Gets the final wkhtmltopdf command as string
  240. * @return the generated command from params
  241. * @throws IOException
  242. */
  243. public String getCommand() throws IOException {
  244. return StringUtils.join(getCommandAsArray(), " ");
  245. }
  246. }