View Javadoc
1   /*
2    * Copyright (C) 2003-2012 eXo Platform SAS.
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 org.exoplatform.services.pdfviewer;
18  
19  import java.io.BufferedInputStream;
20  import java.io.BufferedOutputStream;
21  import java.io.File;
22  import java.io.FileInputStream;
23  import java.io.FileOutputStream;
24  import java.io.InputStream;
25  import java.io.OutputStream;
26  import java.io.Serializable;
27  import java.util.logging.Level;
28  import java.util.logging.Logger;
29  
30  import javax.jcr.Node;
31  
32  import org.apache.commons.lang.StringUtils;
33  import org.artofsolving.jodconverter.office.OfficeException;
34  import org.exoplatform.container.xml.InitParams;
35  import org.exoplatform.container.xml.ValueParam;
36  import org.exoplatform.services.cache.CacheService;
37  import org.exoplatform.services.cache.ExoCache;
38  import org.exoplatform.services.cms.impl.Utils;
39  import org.exoplatform.services.cms.jodconverter.JodConverterService;
40  import org.exoplatform.services.cms.mimetype.DMSMimeTypeResolver;
41  import org.exoplatform.services.jcr.RepositoryService;
42  import org.exoplatform.services.log.ExoLogger;
43  import org.exoplatform.services.log.Log;
44  import org.icepdf.core.pobjects.Document;
45  
46  /**
47   * Created by The eXo Platform SAS Author : Nguyen The Vinh From ECM Of
48   * eXoPlatform vinh_nguyen@exoplatform.com 6 Jul 2012
49   */
50  
51  public class PDFViewerService {
52    private static final Log               LOG                      = ExoLogger.getLogger(PDFViewerService.class.getName());
53  
54    private static final int               MAX_NAME_LENGTH          = 150;
55  
56    public static final long               DEFAULT_MAX_FILE_SIZE    = 10 * 1024 * 1024;
57  
58    public static final long               DEFAULT_MAX_PAGES        = 99;
59  
60    public static final String             MAX_FILE_SIZE_PARAM_NAME = "maxFileSize";
61  
62    public static final String             MAX_PAGES_PARAM_NAME     = "maxPages";
63  
64    private static final String CACHE_NAME = "ecms.PDFViewerService";
65  
66    private JodConverterService            jodConverter_;
67  
68    private ExoCache<Serializable, Object> pdfCache;
69  
70    private long                           maxFileSize;
71  
72    private long                           maxPages;
73  
74    public PDFViewerService(RepositoryService repositoryService,
75                            CacheService caService,
76                            JodConverterService jodConverter,
77                            InitParams initParams) throws Exception {
78      jodConverter_ = jodConverter;
79      pdfCache = caService.getCacheInstance(CACHE_NAME);
80  
81      maxFileSize = DEFAULT_MAX_FILE_SIZE;
82      maxPages = DEFAULT_MAX_PAGES;
83      if (initParams != null) {
84        ValueParam maxFileSizeValueParam = initParams.getValueParam(MAX_FILE_SIZE_PARAM_NAME);
85        if (maxFileSizeValueParam != null) {
86          try {
87            maxFileSize = Long.parseLong(maxFileSizeValueParam.getValue()) * 1024 * 1024;
88          } catch (NumberFormatException e) {
89            LOG.warn("Parameter " + MAX_FILE_SIZE_PARAM_NAME + " for document preview is not a valid number ("
90                + maxFileSizeValueParam.getValue() + "), default value will be used (" + DEFAULT_MAX_FILE_SIZE + ")");
91          }
92        }
93        ValueParam maxPagesValueParam = initParams.getValueParam(MAX_PAGES_PARAM_NAME);
94        if (maxPagesValueParam != null) {
95          try {
96            maxPages = Long.parseLong(maxPagesValueParam.getValue());
97          } catch (NumberFormatException e) {
98            LOG.warn("Parameter " + MAX_PAGES_PARAM_NAME + " for document preview is not a valid number ("
99                + maxPagesValueParam.getValue() + "), default value will be used (" + MAX_PAGES_PARAM_NAME + ")");
100         }
101       }
102     }
103   }
104 
105   public long getMaxFileSize() {
106     return maxFileSize;
107   }
108 
109   public long getMaxPages() {
110     return maxPages;
111   }
112 
113   public ExoCache<Serializable, Object> getCache() {
114     return pdfCache;
115   }
116 
117   /**
118    * Init pdf document from InputStream in nt:file node
119    * 
120    * @param currentNode
121    * @param repoName
122    * @return
123    * @throws Exception
124    */
125   public Document initDocument(Node currentNode, String repoName) throws Exception {
126     return buildDocumentImage(getPDFDocumentFile(currentNode, repoName), currentNode.getName());
127   }
128 
129   public Document buildDocumentImage(File input, String name) {
130     Document document = new Document();
131 
132     // Turn off Log of org.icepdf.core.pobjects.Document to avoid printing error
133     // stack trace in case viewing
134     // a PDF file which use new Public Key Security Handler.
135     // TODO: Remove this statement after IcePDF fix this
136     Logger.getLogger(Document.class.toString()).setLevel(Level.OFF);
137 
138     if (input == null)
139       return null;
140 
141     // Capture the page image to file
142     try {
143       // cut the file name if name is too long, because OS allows only file with
144       // name < 250 characters
145       name = reduceFileNameSize(name);
146       FileInputStream fis = new FileInputStream(input);
147       document.setInputStream(new BufferedInputStream(fis), name);
148       return document;
149     } catch (Exception ex) {
150       LOG.warn("Failed to build Document image from pdf file " + name);
151       if (LOG.isDebugEnabled()) {
152         LOG.debug(ex);
153       }
154       return null;
155     }
156   }
157 
158   /**
159    * Write PDF data to file
160    * 
161    * @param currentNode
162    * @param repoName
163    * @return
164    * @throws Exception
165    */
166   public File getPDFDocumentFile(Node currentNode, String repoName) throws Exception {
167     String wsName = currentNode.getSession().getWorkspace().getName();
168     String uuid = currentNode.getUUID();
169     StringBuilder bd = new StringBuilder();
170     StringBuilder bd1 = new StringBuilder();
171     StringBuilder bd2 = new StringBuilder();
172     bd.append(repoName).append("/").append(wsName).append("/").append(uuid);
173     bd1.append(bd).append("/jcr:lastModified");
174     bd2.append(bd).append("/jcr:baseVersion");
175     String path = (String) pdfCache.get(new ObjectKey(bd.toString()));
176     String lastModifiedTime = (String) pdfCache.get(new ObjectKey(bd1.toString()));
177     String cachedBaseVersion = (String) pdfCache.get(new ObjectKey(bd2.toString()));
178     File content = null;
179     String name = currentNode.getName().replaceAll(":", "_");
180     Node contentNode = currentNode.getNode("jcr:content");
181 
182     String lastModified = Utils.getJcrContentLastModified(currentNode);
183     String baseVersion = Utils.getJcrContentBaseVersion(currentNode);
184     if (path == null || !(content = new File(path)).exists() || !lastModified.equals(lastModifiedTime) ||
185             !StringUtils.equals(baseVersion, cachedBaseVersion)) {
186       String mimeType = contentNode.getProperty("jcr:mimeType").getString();
187       InputStream input = new BufferedInputStream(contentNode.getProperty("jcr:data").getStream());
188       // Create temp file to store converted data of nt:file node
189       if (name.indexOf(".") > 0)
190         name = name.substring(0, name.lastIndexOf("."));
191       // cut the file name if name is too long, because OS allows only file with
192       // name < 250 characters
193       name = reduceFileNameSize(name);
194       content = File.createTempFile(name + "_tmp", ".pdf");
195 
196       // Convert to pdf if need
197       String extension = DMSMimeTypeResolver.getInstance().getExtension(mimeType);
198       if ("pdf".equals(extension)) {
199         read(input, new BufferedOutputStream(new FileOutputStream(content)));
200       } else {
201         // create temp file to store original data of nt:file node
202         File in = File.createTempFile(name + "_tmp", "." + extension);
203         read(input, new BufferedOutputStream(new FileOutputStream(in)));
204         long fileSize = in.length(); // size in byte
205         if (LOG.isDebugEnabled()) {
206           LOG.debug("File '" + currentNode.getPath() + "' of " + fileSize + " B. Size limit for preview: "
207               + (getMaxFileSize() / (1024 * 1024)) + " MB");
208         }
209         if (fileSize <= getMaxFileSize()) {
210           try {
211             boolean success = jodConverter_.convert(in, content, "pdf");
212             // If the converting failed then delete the content of temporary
213             // file
214             if (!success) {
215               content.delete();
216               content = null;
217             }
218 
219           } catch (OfficeException connection) {
220             content.delete();
221             content = null;
222             if (LOG.isErrorEnabled()) {
223               LOG.error("Exception when using Office Service", connection);
224             }
225           } finally {
226             in.delete();
227           }
228         } else {
229           LOG.info("File '" + currentNode.getPath() + "' is too big for preview.");
230           content.delete();
231           content = null;
232           in.delete();
233         }
234       }
235       if (content != null && content.exists()) {
236         if (contentNode.hasProperty("jcr:lastModified")) {
237           pdfCache.put(new ObjectKey(bd.toString()), content.getPath());
238           pdfCache.put(new ObjectKey(bd1.toString()), lastModified);
239         }
240         pdfCache.put(new ObjectKey(bd2.toString()), baseVersion);
241       }
242     }
243     return content;
244   }
245 
246   /**
247    * reduces the file name size. If the length is > 150, return the first 150
248    * characters, else, return the original value
249    * 
250    * @param name the name
251    * @return the reduced name
252    */
253   private String reduceFileNameSize(String name) {
254     return (name != null && name.length() > MAX_NAME_LENGTH) ? name.substring(0, MAX_NAME_LENGTH) : name;
255   }
256 
257   private void read(InputStream is, OutputStream os) throws Exception {
258     int bufferLength = 1024;
259     int readLength = 0;
260     while (readLength > -1) {
261       byte[] chunk = new byte[bufferLength];
262       readLength = is.read(chunk);
263       if (readLength > 0) {
264         os.write(chunk, 0, readLength);
265       }
266     }
267     os.flush();
268     os.close();
269   }
270 }