1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package com.xpn.xwiki.pdf.impl

File FileSystemURLFactory.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart0.png
83% of files have more coverage

Code metrics

16
77
12
1
299
183
29
0.38
6.42
12
2.42

Classes

Class Line # Actions
FileSystemURLFactory 55 77 0% 29 105
0.00%
 

Contributing tests

No tests hitting this source file were found.

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package com.xpn.xwiki.pdf.impl;
21   
22    import java.io.File;
23    import java.io.FileOutputStream;
24    import java.io.IOException;
25    import java.io.InputStream;
26    import java.io.UnsupportedEncodingException;
27    import java.net.URL;
28    import java.net.URLEncoder;
29    import java.util.List;
30    import java.util.Map;
31   
32    import org.apache.commons.io.FilenameUtils;
33    import org.apache.commons.io.IOUtils;
34    import org.apache.commons.lang3.StringUtils;
35    import org.slf4j.Logger;
36    import org.slf4j.LoggerFactory;
37    import org.xwiki.model.reference.DocumentReference;
38   
39    import com.xpn.xwiki.XWiki;
40    import com.xpn.xwiki.XWikiContext;
41    import com.xpn.xwiki.doc.XWikiAttachment;
42    import com.xpn.xwiki.doc.XWikiDocument;
43    import com.xpn.xwiki.internal.model.LegacySpaceResolver;
44    import com.xpn.xwiki.web.Utils;
45    import com.xpn.xwiki.web.XWikiServletURLFactory;
46   
47    /**
48    * Special URL Factory used during exports, which stores referenced attachments and resources on the filesystem, in a
49    * temporary folder, so that they can be included in the export result. The returned URLs point to these resources as
50    * {@code file://} links, and not as {@code http://} links.
51    *
52    * @version $Id: 933bcf4d94a01236f8630d2e0226999025024959 $
53    * @since 5.0RC1
54    */
 
55    public class FileSystemURLFactory extends XWikiServletURLFactory
56    {
57    /** Logging helper object. */
58    private static final Logger LOGGER = LoggerFactory.getLogger(FileSystemURLFactory.class);
59   
60    /** Segment separator used in the collision-free key generation. */
61    private static final char SEPARATOR = '/';
62   
63    private LegacySpaceResolver legacySpaceResolver = Utils.getComponent(LegacySpaceResolver.class);
64   
 
65  0 toggle @Override
66    public URL createAttachmentURL(String filename, String spaces, String name, String action, String querystring,
67    String wiki, XWikiContext context)
68    {
69  0 try {
70  0 return getURL(wiki, spaces, name, filename, null, context);
71    } catch (Exception ex) {
72  0 LOGGER.warn("Failed to save image for PDF export", ex);
73  0 return super.createAttachmentURL(filename, spaces, name, action, null, wiki, context);
74    }
75    }
76   
 
77  0 toggle @Override
78    public URL createAttachmentRevisionURL(String filename, String spaces, String name, String revision, String wiki,
79    XWikiContext context)
80    {
81  0 try {
82  0 return getURL(wiki, spaces, name, filename, revision, context);
83    } catch (Exception ex) {
84  0 LOGGER.warn("Failed to save image for PDF export: " + ex.getMessage());
85  0 return super.createAttachmentRevisionURL(filename, spaces, name, revision, wiki, context);
86    }
87    }
88   
 
89  0 toggle @Override
90    public URL createSkinURL(String filename, String skin, XWikiContext context)
91    {
92  0 try {
93  0 Map<String, File> usedFiles = getFileMapping(context);
94  0 String key = getSkinfileKey(filename, skin);
95  0 if (!usedFiles.containsKey(key)) {
96  0 if (!copyResource("/skins/" + skin + '/' + filename, key, usedFiles, context)) {
97    // The resource does not exist, just return a http:// URL
98  0 return super.createSkinURL(filename, skin, context);
99    }
100    }
101  0 return usedFiles.get(key).toURI().toURL();
102    } catch (Exception ex) {
103    // Shouldn't happen
104  0 return super.createSkinURL(filename, skin, context);
105    }
106    }
107   
 
108  0 toggle @Override
109    public URL createResourceURL(String filename, boolean forceSkinAction, XWikiContext context)
110    {
111  0 try {
112  0 Map<String, File> usedFiles = getFileMapping(context);
113  0 String key = getResourceKey(filename);
114  0 if (!usedFiles.containsKey(key)) {
115  0 if (!copyResource("/resources/" + filename, key, usedFiles, context)) {
116  0 return super.createResourceURL(filename, forceSkinAction, context);
117    }
118    }
119  0 return usedFiles.get(key).toURI().toURL();
120    } catch (Exception ex) {
121    // Shouldn't happen
122  0 return super.createResourceURL(filename, forceSkinAction, context);
123    }
124    }
125   
 
126  0 toggle @Override
127    public String getURL(URL url, XWikiContext context)
128    {
129  0 if (url == null) {
130  0 return "";
131    }
132  0 return url.toString();
133    }
134   
135    /**
136    * Store the requested attachment on the filesystem and return a {@code file://} URL where FOP can access that file.
137    *
138    * @param wiki the name of the owner document's wiki
139    * @param spaces a serialized space reference which can contain one or several spaces (e.g. "space1.space2"). If
140    * a space name contains a dot (".") it must be passed escaped as in "space1\.with\.dot.space2"
141    * @param name the name of the owner document
142    * @param filename the name of the attachment
143    * @param revision an optional attachment version
144    * @param context the current request context
145    * @return a {@code file://} URL where the attachment has been stored
146    * @throws Exception if the attachment can't be retrieved from the database and stored on the filesystem
147    */
 
148  0 toggle private URL getURL(String wiki, String spaces, String name, String filename, String revision, XWikiContext context)
149    throws Exception
150    {
151  0 Map<String, File> usedFiles = getFileMapping(context);
152  0 List<String> spaceNames = this.legacySpaceResolver.resolve(spaces);
153  0 String key = getAttachmentKey(spaceNames, name, filename, revision);
154  0 if (!usedFiles.containsKey(key)) {
155  0 File file = getTemporaryFile(key, context);
156  0 LOGGER.debug("Temporary PDF export file [{}]", file.toString());
157  0 XWikiDocument doc = context.getWiki().getDocument(new DocumentReference(
158    StringUtils.defaultString(wiki, context.getWikiId()), spaceNames, name), context);
159  0 XWikiAttachment attachment = doc.getAttachment(filename);
160  0 if (StringUtils.isNotEmpty(revision)) {
161  0 attachment = attachment.getAttachmentRevision(revision, context);
162    }
163  0 FileOutputStream fos = new FileOutputStream(file);
164  0 IOUtils.copy(attachment.getContentInputStream(context), fos);
165  0 fos.close();
166  0 usedFiles.put(key, file);
167    }
168  0 return usedFiles.get(key).toURI().toURL();
169    }
170   
171    /**
172    * Copy a resource from the filesystem into a temporary file and map this resulting file to the requested resource
173    * location.
174    *
175    * @param resourceName the name of the file to copy, possibly including a path to it, for example
176    * {@code icons/silk/add.png}
177    * @param key the collision-free identifier of the resource
178    * @param usedFiles the mapping of resource keys to temporary files where to put the resulting temporary file
179    * @param context the current request context
180    * @return {@code true} if copying the resource succeeded and the new temporary file was mapped to the resource key,
181    * {@code false} otherwise
182    */
 
183  0 toggle private boolean copyResource(String resourceName, String key, Map<String, File> usedFiles, XWikiContext context)
184    {
185  0 try {
186  0 InputStream data = context.getWiki().getResourceAsStream(resourceName);
187  0 if (data != null) {
188    // Copy the resource to a temporary file
189  0 File file = getTemporaryFile(key, context);
190  0 FileOutputStream fos = new FileOutputStream(file);
191  0 IOUtils.copy(data, fos);
192  0 fos.close();
193  0 usedFiles.put(key, file);
194  0 return true;
195    }
196    } catch (Exception ex) {
197    // Can't access the resource, let's hope FOP can handle the http:// URL
198    }
199  0 return false;
200    }
201   
202    /**
203    * Computes a safe identifier for an attachment, guaranteed to be collision-free.
204    *
205    * @param name the name of the owner document
206    * @param filename the name of the attachment
207    * @param revision an optional attachment version
208    * @return an identifier for this attachment
209    */
 
210  0 toggle private String getAttachmentKey(List<String> spaceNames, String name, String filename, String revision)
211    {
212  0 StringBuilder builder = new StringBuilder();
213   
214  0 try {
215  0 builder.append("attachment").append(SEPARATOR);
216  0 for (String spaceName : spaceNames) {
217  0 builder.append(URLEncoder.encode(spaceName, XWiki.DEFAULT_ENCODING));
218  0 builder.append(SEPARATOR);
219    }
220  0 builder.append(URLEncoder.encode(name, XWiki.DEFAULT_ENCODING)).append(SEPARATOR);
221  0 builder.append(URLEncoder.encode(filename, XWiki.DEFAULT_ENCODING)).append(SEPARATOR);
222  0 builder.append(URLEncoder.encode(StringUtils.defaultString(revision), XWiki.DEFAULT_ENCODING));
223  0 return builder.toString();
224    } catch (UnsupportedEncodingException e) {
225    // This should never happen, UTF-8 is always available
226  0 throw new RuntimeException(String.format("Failed to compute unique Attachment key for spaces [%s[, "
227    + "page [%s], filename [%s], revision [%s], while exporting.", StringUtils.join(spaceNames, ", "),
228    name, filename, revision), e);
229    }
230    }
231   
232    /**
233    * Computes a safe identifier for a resource file, guaranteed to be collision-free.
234    *
235    * @param filename the name of the file, possibly including a path to it, for example {@code icons/silk/add.png}
236    * @return an identifier for this file
237    */
 
238  0 toggle private String getResourceKey(String filename)
239    {
240  0 try {
241  0 return "resource" + SEPARATOR + URLEncoder.encode(filename, XWiki.DEFAULT_ENCODING);
242    } catch (UnsupportedEncodingException ex) {
243    // This should never happen, UTF-8 is always available
244  0 return filename;
245    }
246    }
247   
248    /**
249    * Computes a safe identifier for a skin filename, guaranteed to be collision-free.
250    *
251    * @param filename the name of the file, possibly including a path to it, for example {@code css/colors/black.css}
252    * @param skin the name of the skin where the file is expected to be
253    * @return an identifier for this file
254    */
 
255  0 toggle private String getSkinfileKey(String filename, String skin)
256    {
257  0 try {
258  0 return "skin" + SEPARATOR + URLEncoder.encode(skin, XWiki.DEFAULT_ENCODING) + SEPARATOR
259    + URLEncoder.encode(filename, XWiki.DEFAULT_ENCODING);
260    } catch (UnsupportedEncodingException ex) {
261    // This should never happen, UTF-8 is always available
262  0 return skin + SEPARATOR + filename;
263    }
264    }
265   
266    /**
267    * Retrieve the Map that relates resource keys to their corresponding temporary file.
268    *
269    * @param context the current request context
270    * @return the mapping as it was found in the context (read-write)
271    */
 
272  0 toggle private Map<String, File> getFileMapping(XWikiContext context)
273    {
274  0 @SuppressWarnings("unchecked")
275    Map<String, File> usedFiles = (Map<String, File>) context.get("pdfexport-file-mapping");
276  0 return usedFiles;
277    }
278   
279    /**
280    * Create a new temporary file for the given resource key and return it.
281    *
282    * @param key the resource key, needed for getting the file extension, if any
283    * @param context the current request context
284    * @return a new empty file
285    * @throws java.io.IOException if creating the file fails
286    */
 
287  0 toggle private File getTemporaryFile(String key, XWikiContext context) throws IOException
288    {
289  0 File tempdir = (File) context.get("pdfexportdir");
290  0 String prefix = "pdf";
291  0 String suffix = "." + FilenameUtils.getExtension(key);
292  0 try {
293  0 return File.createTempFile(prefix, suffix, tempdir);
294    } catch (IOException e) {
295  0 throw new IOException("Failed to create temporary PDF export file with prefix [" + prefix + "], suffix ["
296    + suffix + "] in directory [" + tempdir + "]", e);
297    }
298    }
299    }