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

File HtmlPackager.java

 

Coverage histogram

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

Code metrics

20
108
13
1
439
229
26
0.24
8.31
13
2

Classes

Class Line # Actions
HtmlPackager 63 108 0% 26 141
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.export.html;
21   
22    import java.io.File;
23    import java.io.FileFilter;
24    import java.io.FileInputStream;
25    import java.io.IOException;
26    import java.lang.reflect.Type;
27    import java.util.Collection;
28    import java.util.HashSet;
29    import java.util.Set;
30    import java.util.zip.ZipEntry;
31    import java.util.zip.ZipOutputStream;
32   
33    import org.apache.commons.io.IOUtils;
34    import org.apache.commons.io.filefilter.NotFileFilter;
35    import org.apache.commons.io.filefilter.SuffixFileFilter;
36    import org.apache.commons.io.filefilter.TrueFileFilter;
37    import org.apache.commons.lang3.RandomStringUtils;
38    import org.slf4j.Logger;
39    import org.slf4j.LoggerFactory;
40    import org.xwiki.context.Execution;
41    import org.xwiki.context.ExecutionContext;
42    import org.xwiki.context.ExecutionContextException;
43    import org.xwiki.context.ExecutionContextManager;
44    import org.xwiki.environment.Environment;
45    import org.xwiki.model.reference.DocumentReference;
46    import org.xwiki.model.reference.DocumentReferenceResolver;
47    import org.xwiki.model.reference.EntityReference;
48    import org.xwiki.url.URLContextManager;
49   
50    import com.xpn.xwiki.XWikiContext;
51    import com.xpn.xwiki.XWikiException;
52    import com.xpn.xwiki.doc.XWikiDocument;
53    import com.xpn.xwiki.util.Util;
54    import com.xpn.xwiki.web.ExportURLFactory;
55    import com.xpn.xwiki.web.Utils;
56   
57    /**
58    * Create a ZIP package containing a range of HTML pages with skin and attachment dependencies.
59    *
60    * @version $Id: f1ce5d07595a6357db0df7e4001920da3071f32c $
61    * @since XWiki Platform 1.3M1
62    */
 
63    public class HtmlPackager
64    {
65    private static final Logger LOGGER = LoggerFactory.getLogger(HtmlPackager.class);
66   
67    /**
68    * A point.
69    */
70    private static final String POINT = ".";
71   
72    /**
73    * Name of the context property containing the document.
74    */
75    private static final String CONTEXT_TDOC = "tdoc";
76   
77    /**
78    * Name of the Velocity context property containing the document.
79    */
80    private static final String VCONTEXT_DOC = "doc";
81   
82    /**
83    * Name of the Velocity context property containing the document.
84    */
85    private static final String VCONTEXT_CDOC = "cdoc";
86   
87    /**
88    * Name of the Velocity context property containing the document.
89    */
90    private static final String VCONTEXT_TDOC = CONTEXT_TDOC;
91   
92    /**
93    * The separator in an internal zip path.
94    */
95    private static final String ZIPPATH_SEPARATOR = "/";
96   
97    /**
98    * The name of the package for which packager append ".zip".
99    */
100    private String name = "html.export";
101   
102    /**
103    * A description of the package.
104    */
105    private String description = "";
106   
107    /**
108    * The pages to export. A {@link Set} of page name.
109    */
110    private Set<String> pages = new HashSet<String>();
111   
112    /**
113    * Used to get the temporary directory.
114    */
115    private Environment environment = Utils.getComponent((Type) Environment.class);
116   
117    /**
118    * Modify the name of the package for which packager append ".zip".
119    *
120    * @param name the name of the page.
121    */
 
122  0 toggle public void setName(String name)
123    {
124  0 this.name = name;
125    }
126   
127    /**
128    * @return the name of the package for which packager append ".zip".
129    */
 
130  0 toggle public String getName()
131    {
132  0 return this.name;
133    }
134   
135    /**
136    * Modify the description of the package.
137    *
138    * @param description the description of the package.
139    */
 
140  0 toggle public void setDescription(String description)
141    {
142  0 this.description = description;
143    }
144   
145    /**
146    * @return the description of the package.
147    */
 
148  0 toggle public String getDescription()
149    {
150  0 return this.description;
151    }
152   
153    /**
154    * Add a page to export.
155    *
156    * @param page the name of the page to export.
157    */
 
158  0 toggle public void addPage(String page)
159    {
160  0 this.pages.add(page);
161    }
162   
163    /**
164    * Add a range of pages to export.
165    *
166    * @param pages a range of pages to export.
167    */
 
168  0 toggle public void addPages(Collection<String> pages)
169    {
170  0 this.pages.addAll(pages);
171    }
172   
173    /**
174    * Add rendered document to ZIP stream.
175    *
176    * @param pageName the name (used with {@link com.xpn.xwiki.XWiki#getDocument(String, XWikiContext)}) of the page to
177    * render.
178    * @param zos the ZIP output stream.
179    * @param context the XWiki context.
180    * @throws XWikiException error when rendering document.
181    * @throws IOException error when rendering document.
182    */
 
183  0 toggle private void renderDocument(String pageName, ZipOutputStream zos, XWikiContext context)
184    throws XWikiException, IOException
185    {
186  0 DocumentReferenceResolver<String> resolver =
187    Utils.getComponent(DocumentReferenceResolver.TYPE_STRING, "current");
188  0 DocumentReference docReference = resolver.resolve(pageName);
189  0 XWikiDocument doc = context.getWiki().getDocument(docReference, context);
190   
191  0 if (doc.isNew()) {
192    // Skip non-existing documents.
193  0 return;
194    }
195   
196  0 String zipname = doc.getDocumentReference().getWikiReference().getName();
197  0 for (EntityReference space : doc.getDocumentReference().getSpaceReferences()) {
198  0 zipname += POINT + space.getName();
199    }
200  0 zipname += POINT + doc.getDocumentReference().getName();
201  0 String language = doc.getLanguage();
202  0 if (language != null && language.length() != 0) {
203  0 zipname += POINT + language;
204    }
205   
206  0 zipname += ".html";
207   
208  0 ZipEntry zipentry = new ZipEntry(zipname);
209  0 zos.putNextEntry(zipentry);
210   
211  0 String originalDatabase = context.getWikiId();
212  0 try {
213  0 context.setWikiId(doc.getDocumentReference().getWikiReference().getName());
214  0 context.setDoc(doc);
215   
216  0 XWikiDocument tdoc = doc.getTranslatedDocument(context);
217  0 context.put(CONTEXT_TDOC, tdoc);
218   
219  0 String content = evaluateDocumentContent(context);
220   
221  0 zos.write(content.getBytes(context.getWiki().getEncoding()));
222  0 zos.closeEntry();
223    } finally {
224  0 context.setWikiId(originalDatabase);
225    }
226    }
227   
 
228  0 toggle private String evaluateDocumentContent(XWikiContext context) throws IOException
229    {
230  0 context.getWiki().getPluginManager().beginParsing(context);
231  0 Utils.enablePlaceholders(context);
232  0 String content;
233  0 try {
234  0 content = context.getWiki().evaluateTemplate("view.vm", context);
235  0 content = Utils.replacePlaceholders(content, context);
236    } finally {
237  0 Utils.disablePlaceholders(context);
238    }
239  0 content = context.getWiki().getPluginManager().endParsing(content.trim(), context);
240  0 return content;
241    }
242   
243    /**
244    * Init provided {@link ExportURLFactory} and add rendered documents to ZIP stream.
245    *
246    * @param zos the ZIP output stream.
247    * @param tempdir the directory where to copy attached files.
248    * @param urlf the {@link com.xpn.xwiki.web.XWikiURLFactory} used to render the documents.
249    * @param context the XWiki context.
250    * @throws XWikiException error when render documents.
251    * @throws IOException error when render documents.
252    */
 
253  0 toggle private void renderDocuments(ZipOutputStream zos, File tempdir, ExportURLFactory urlf, XWikiContext context)
254    throws XWikiException, IOException
255    {
256  0 ExecutionContextManager ecim = Utils.getComponent(ExecutionContextManager.class);
257  0 Execution execution = Utils.getComponent(Execution.class);
258   
259  0 try {
260  0 XWikiContext renderContext = context.clone();
261  0 renderContext.put("action", "view");
262   
263  0 ExecutionContext executionContext = ecim.clone(execution.getContext());
264   
265    // Bridge with old XWiki Context, required for legacy code.
266  0 executionContext.setProperty("xwikicontext", renderContext);
267   
268    // Push a clean new Execution Context since we don't want the main Execution Context to be used for
269    // rendering the HTML pages to export. It's cleaner to isolate it as we do. Note that the new
270    // Execution Context automatically gets initialized with a new Velocity Context by
271    // the VelocityRequestInitializer class.
272  0 execution.pushContext(executionContext);
273   
274  0 try {
275    // Set the URL Factories/Serializer to use
276  0 urlf.init(this.pages, tempdir, renderContext);
277  0 renderContext.setURLFactory(urlf);
278  0 Utils.getComponent(URLContextManager.class).setURLFormatId("filesystem");
279   
280  0 for (String pageName : this.pages) {
281  0 renderDocument(pageName, zos, renderContext);
282    }
283    } finally {
284  0 execution.popContext();
285    }
286    } catch (ExecutionContextException e) {
287  0 throw new XWikiException(XWikiException.MODULE_XWIKI_EXPORT, XWikiException.ERROR_XWIKI_INIT_FAILED,
288    "Failed to initialize Execution Context", e);
289    }
290    }
291   
292    /**
293    * Apply export and create the ZIP package.
294    *
295    * @param context the XWiki context used to render pages.
296    * @throws IOException error when creating the package.
297    * @throws XWikiException error when render the pages.
298    */
 
299  0 toggle public void export(XWikiContext context) throws IOException, XWikiException
300    {
301  0 context.getResponse().setContentType("application/zip");
302  0 context.getResponse().addHeader("Content-disposition",
303    "attachment; filename=" + Util.encodeURI(this.name, context) + ".zip");
304  0 context.setFinished(true);
305   
306  0 ZipOutputStream zos = new ZipOutputStream(context.getResponse().getOutputStream());
307   
308  0 File dir = this.environment.getTemporaryDirectory();
309  0 File tempdir = new File(dir, RandomStringUtils.randomAlphanumeric(8));
310  0 tempdir.mkdirs();
311  0 File attachmentDir = new File(tempdir, "attachment");
312  0 attachmentDir.mkdirs();
313   
314    // Create custom URL factory
315  0 ExportURLFactory urlf = new ExportURLFactory();
316   
317    // Render pages to export
318  0 renderDocuments(zos, tempdir, urlf, context);
319   
320    // Add required skins to ZIP file
321  0 for (String skinName : urlf.getFilesystemExportContext().getNeededSkins()) {
322  0 addSkinToZip(skinName, zos, urlf.getFilesystemExportContext().getExportedSkinFiles(), context);
323    }
324   
325    // Copy generated files in the ZIP file.
326  0 addDirToZip(tempdir, TrueFileFilter.TRUE, zos, "", null);
327   
328  0 zos.setComment(this.description);
329   
330    // Finish ZIP file
331  0 zos.finish();
332  0 zos.flush();
333   
334    // Delete temporary directory
335  0 deleteDirectory(tempdir);
336    }
337   
338    /**
339    * Delete a directory and all with all it's content.
340    *
341    * @param directory the directory to delete.
342    */
 
343  0 toggle private static void deleteDirectory(File directory)
344    {
345  0 if (!directory.isDirectory()) {
346  0 return;
347    }
348   
349  0 File[] files = directory.listFiles();
350   
351  0 if (files == null) {
352  0 return;
353    }
354   
355  0 for (File file : files) {
356  0 if (file.isDirectory()) {
357  0 deleteDirectory(file);
358  0 continue;
359    }
360   
361  0 file.delete();
362    }
363   
364  0 directory.delete();
365    }
366   
367    /**
368    * Add skin to the package in sub-directory "skins".
369    *
370    * @param skinName the name of the skin.
371    * @param out the ZIP output stream where to put the skin.
372    * @param context the XWiki context.
373    * @throws IOException error when adding the skin to package.
374    */
 
375  0 toggle private static void addSkinToZip(String skinName, ZipOutputStream out, Collection<String> exportedSkinFiles,
376    XWikiContext context) throws IOException
377    {
378  0 File file = new File(context.getWiki().getEngineContext().getRealPath("/skins/" + skinName));
379   
380    // Don't include vm and LESS files by default
381  0 FileFilter filter = new NotFileFilter(new SuffixFileFilter(new String[] { ".vm", ".less", "skin.properties" }));
382   
383  0 addDirToZip(file, filter, out, "skins" + ZIPPATH_SEPARATOR + skinName + ZIPPATH_SEPARATOR, exportedSkinFiles);
384    }
385   
386    /**
387    * Add a directory and all its sub-directories to the package.
388    *
389    * @param directory the directory to add.
390    * @param filter the files to include or exclude from the copy
391    * @param out the ZIP output stream where to put the skin.
392    * @param basePath the path where to put the directory in the package.
393    * @throws IOException error when adding the directory to package.
394    */
 
395  0 toggle private static void addDirToZip(File directory, FileFilter filter, ZipOutputStream out, String basePath,
396    Collection<String> exportedSkinFiles) throws IOException
397    {
398  0 if (LOGGER.isDebugEnabled()) {
399  0 LOGGER.debug("Adding dir [" + directory.getPath() + "] to the Zip file being generated.");
400    }
401   
402  0 if (!directory.isDirectory()) {
403  0 return;
404    }
405   
406  0 File[] files = directory.listFiles(filter);
407   
408  0 if (files == null) {
409  0 return;
410    }
411   
412  0 for (File file : files) {
413  0 if (file.isDirectory()) {
414  0 addDirToZip(file, filter, out, basePath + file.getName() + ZIPPATH_SEPARATOR, exportedSkinFiles);
415    } else {
416  0 String path = basePath + file.getName();
417   
418  0 if (exportedSkinFiles != null && exportedSkinFiles.contains(path)) {
419  0 continue;
420    }
421   
422  0 FileInputStream in = new FileInputStream(file);
423   
424  0 try {
425    // Starts a new Zip entry. It automatically closes the previous entry if present.
426  0 out.putNextEntry(new ZipEntry(path));
427   
428  0 try {
429  0 IOUtils.copy(in, out);
430    } finally {
431  0 out.closeEntry();
432    }
433    } finally {
434  0 in.close();
435    }
436    }
437    }
438    }
439    }