1. Project Clover database Sat Feb 2 2019 06:45:20 CET
  2. Package com.xpn.xwiki.web

File ExportAction.java

 

Coverage histogram

../../../../img/srcFileCovDistChart8.png
56% of files have more coverage

Code metrics

72
206
8
3
570
367
52
0.25
25.75
2.67
6.5

Classes

Class Line # Actions
ExportAction 76 183 0% 42 72
0.713147471.3%
ExportAction.ExportFormat 86 0 - 0 0
-1.0 -
ExportAction.ExportArguments 99 23 0% 10 1
0.971428697.1%
 

Contributing tests

This file is covered by 2 tests. .

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.web;
21   
22    import java.io.IOException;
23    import java.io.UnsupportedEncodingException;
24    import java.lang.reflect.Type;
25    import java.net.URLDecoder;
26    import java.util.ArrayList;
27    import java.util.Collection;
28    import java.util.Collections;
29    import java.util.HashMap;
30    import java.util.LinkedHashMap;
31    import java.util.List;
32    import java.util.Map;
33   
34    import org.apache.commons.lang3.StringUtils;
35    import org.xwiki.filter.FilterException;
36    import org.xwiki.filter.input.InputFilterStream;
37    import org.xwiki.filter.input.InputFilterStreamFactory;
38    import org.xwiki.filter.instance.input.DocumentInstanceInputProperties;
39    import org.xwiki.filter.output.BeanOutputFilterStreamFactory;
40    import org.xwiki.filter.output.DefaultOutputStreamOutputTarget;
41    import org.xwiki.filter.output.OutputFilterStream;
42    import org.xwiki.filter.output.OutputFilterStreamFactory;
43    import org.xwiki.filter.type.FilterStreamType;
44    import org.xwiki.filter.xar.output.XAROutputProperties;
45    import org.xwiki.model.reference.DocumentReference;
46    import org.xwiki.model.reference.DocumentReferenceResolver;
47    import org.xwiki.model.reference.EntityReferenceSerializer;
48    import org.xwiki.model.reference.EntityReferenceSet;
49    import org.xwiki.model.reference.WikiReference;
50    import org.xwiki.query.Query;
51    import org.xwiki.query.QueryException;
52    import org.xwiki.query.QueryManager;
53    import org.xwiki.query.QueryParameter;
54    import org.xwiki.query.internal.DefaultQueryParameter;
55    import org.xwiki.security.authorization.AuthorizationManager;
56    import org.xwiki.security.authorization.Right;
57   
58    import com.xpn.xwiki.XWikiContext;
59    import com.xpn.xwiki.XWikiException;
60    import com.xpn.xwiki.doc.XWikiDocument;
61    import com.xpn.xwiki.export.html.HtmlPackager;
62    import com.xpn.xwiki.internal.export.OfficeExporter;
63    import com.xpn.xwiki.internal.export.OfficeExporterURLFactory;
64    import com.xpn.xwiki.pdf.api.PdfExport;
65    import com.xpn.xwiki.pdf.api.PdfExport.ExportType;
66    import com.xpn.xwiki.pdf.impl.PdfExportImpl;
67    import com.xpn.xwiki.pdf.impl.PdfURLFactory;
68    import com.xpn.xwiki.plugin.packaging.PackageAPI;
69    import com.xpn.xwiki.util.Util;
70   
71    /**
72    * Exports in XAR, PDF, HTML and all output formats supported by *Office (when an *Office Server is running).
73    *
74    * @version $Id: e5130687ed3bf98e48e3b68398854f656a9229dd $
75    */
 
76    public class ExportAction extends XWikiAction
77    {
78    /**
79    * Used to separate page arguments in excludes parameter
80    */
81    private static String PAGE_SEPARATOR = "&";
82   
83    /**
84    * Define the different format supported by the export.
85    */
 
86    private enum ExportFormat
87    {
88    XAR,
89    HTML,
90    OTHER
91    }
92   
93    /**
94    * Manage the arguments of the export and provides them in an object to simplify their usage in the different
95    * methods.
96    *
97    * @since 10.9
98    */
 
99    private class ExportArguments
100    {
101    /**
102    * Represent the pages to be included and excluded: keys are pattern of pages to include, values are list of
103    * pages to exclude
104    */
105    private Map<String, List<String>> exportPages;
106   
107    /**
108    * Name of the export
109    */
110    private String name;
111   
112    /**
113    * Description of the export
114    */
115    private String description;
116   
 
117  7 toggle ExportArguments(XWikiContext context, ExportFormat format) throws XWikiException
118    {
119  7 XWikiRequest request = context.getRequest();
120   
121  7 this.description = request.get("description");
122   
123  7 this.name = request.get("name");
124   
125  7 String[] pages = request.getParameterValues("pages");
126  7 String[] excludes = request.getParameterValues("excludes");
127   
128  7 if (StringUtils.isBlank(name) && !format.equals(ExportFormat.XAR)) {
129  2 this.name = context.getDoc().getFullName();
130    }
131   
132  7 this.exportPages = new LinkedHashMap<>();
133   
134  7 if (pages != null) {
135  16 for (int i = 0; i < pages.length; i++) {
136  10 List<String> excludedPages;
137   
138  10 if (excludes != null && i < excludes.length) {
139  8 excludedPages = this.decodePages(excludes[i], context);
140    } else {
141  2 excludedPages = Collections.emptyList();
142    }
143   
144  10 this.exportPages.put(pages[i], excludedPages);
145    }
146    }
147    }
148   
149    /**
150    * Decode an URIEncoded String and split it based on the {@link #PAGE_SEPARATOR}. Returns a list of decoded
151    * string.
152    *
153    * @param encodedString the string to decode.
154    * @param context the context used to retrieved the right character encoding.
155    * @return a list of decoded string.
156    * @throws XWikiException when there is a problem with the decoding process.
157    */
 
158  8 toggle private List<String> decodePages(String encodedString, XWikiContext context) throws XWikiException
159    {
160  8 List<String> listOfPages = new ArrayList<>();
161  8 for (String page : encodedString.split(PAGE_SEPARATOR)) {
162  11 try {
163  11 String decoded = URLDecoder.decode(page, context.getRequest().getCharacterEncoding());
164  11 if (!decoded.isEmpty()) {
165  6 listOfPages.add(decoded);
166    }
167    } catch (UnsupportedEncodingException e) {
168  0 throw new XWikiException(XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_APP_EXPORT,
169    "Failed to resolve pages to export", e);
170    }
171    }
172   
173  8 return listOfPages;
174    }
175    }
176   
 
177  11 toggle @Override
178    public String render(XWikiContext context) throws XWikiException
179    {
180  11 String defaultPage;
181   
182  11 try {
183  11 XWikiRequest request = context.getRequest();
184  11 String format = request.get("format");
185   
186  11 if ((format == null) || (format.equals("xar"))) {
187  5 defaultPage = exportXAR(context);
188  6 } else if (format.equals("html")) {
189  2 defaultPage = exportHTML(context);
190    } else {
191  4 defaultPage = export(format, context);
192    }
193    } catch (Exception e) {
194  0 throw new XWikiException(XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_APP_EXPORT,
195    "Exception while exporting", e);
196    }
197   
198  11 return defaultPage;
199    }
200   
201    /**
202    * Create ZIP archive containing wiki pages rendered in HTML, attached files and used skins.
203    *
204    * @param context the XWiki context.
205    * @return always return null.
206    * @throws XWikiException error when exporting HTML ZIP package.
207    * @throws IOException error when exporting HTML ZIP package.
208    * @since XWiki Platform 1.3M1
209    */
 
210  2 toggle private String exportHTML(XWikiContext context) throws XWikiException, IOException
211    {
212  2 ExportArguments exportArguments = new ExportArguments(context, ExportFormat.HTML);
213   
214  2 Collection<DocumentReference> pageList = resolvePages(exportArguments, context);
215  2 if (pageList.isEmpty()) {
216  0 return null;
217    }
218   
219  2 HtmlPackager packager = new HtmlPackager();
220   
221  2 if (exportArguments.name != null && exportArguments.name.trim().length() > 0) {
222  2 packager.setName(exportArguments.name);
223    }
224   
225  2 if (exportArguments.description != null) {
226  0 packager.setDescription(exportArguments.description);
227    }
228   
229  2 packager.addPageReferences(pageList);
230   
231  2 packager.export(context);
232   
233  2 return null;
234    }
235   
236    /**
237    * Extract the name of the wiki from a given String representing a document. Returns the name contained in the
238    * documentName or the wiki name of the current context.
239    *
240    * @param documentName a complete name of a document, which might contain the name of the wiki.
241    * @param context in case the documentName does not contain a wiki name, returns the context wiki name.
242    * @return the name of the documentName or by default the context wiki name.
243    */
 
244  16 toggle private String extractWikiName(String documentName, XWikiContext context)
245    {
246  16 String wikiName;
247  16 int index = documentName.indexOf(':');
248  16 if (index > 0) {
249  8 wikiName = documentName.substring(0, index);
250    } else {
251  8 wikiName = context.getWikiId();
252    }
253  16 return wikiName;
254    }
255   
256    /**
257    * Resolve the pages in the given context and return their references. This method uses the list of includedPages
258    * and excludedPages given in the exportArguments to build a proper query and resolve the pages after checking the
259    * user rights.
260    *
261    * @param arguments the arguments of the export to know the list of included/excluded pages to resolve.
262    * @param context the context to use.
263    * @return a collection of DocumentReference corresponding to the given criteria and which are viewable by the user.
264    */
 
265  7 toggle private Collection<DocumentReference> resolvePages(ExportArguments arguments, XWikiContext context)
266    throws XWikiException
267    {
268  7 List<DocumentReference> pageList = new ArrayList<>();
269   
270    // if there's no includedPages, the default is to return the current document
271  7 if (arguments.exportPages.isEmpty()) {
272  1 pageList.add(context.getDoc().getDocumentReference());
273   
274    // else we process the list of included/excluded pages
275    } else {
276  6 Map<String, Object[]> wikiQueries = new HashMap<>();
277   
278  6 for (Map.Entry<String, List<String>> export : arguments.exportPages.entrySet()) {
279  10 String includePage = export.getKey();
280  10 List<String> excludedPages = export.getValue();
281   
282  10 String wikiName = this.extractWikiName(includePage, context);
283   
284    // we only want the name of the document without its wikiName
285  10 if (includePage.startsWith(wikiName + ":")) {
286  5 includePage = includePage.substring(wikiName.length() + 1);
287    }
288   
289  10 StringBuffer where;
290  10 List<QueryParameter> params;
291   
292    // we didn't already made a query for this wiki
293    // so we create it
294  10 if (!wikiQueries.containsKey(wikiName)) {
295  6 Object[] query = new Object[2];
296  6 query[0] = where = new StringBuffer("where ( ");
297  6 query[1] = params = new ArrayList<>();
298  6 wikiQueries.put(wikiName, query);
299   
300    // we get back the query we started to continue it
301    } else {
302  4 Object[] query = wikiQueries.get(wikiName);
303  4 where = (StringBuffer) query[0];
304  4 params = (List<QueryParameter>) query[1];
305   
306  4 where.append("or ( ");
307    }
308   
309  10 where.append("doc.fullName like ?");
310  10 params.add(new DefaultQueryParameter(null).like(includePage));
311   
312    // if they exist we process the excludedPages associated with that include
313  10 if (!excludedPages.isEmpty()) {
314  9 for (int j = 0; j < excludedPages.size(); j++) {
315  6 String excludePage = excludedPages.get(j);
316   
317    // we check that the excludedPages are in the same wiki
318  6 String localwikiName = this.extractWikiName(excludePage, context);
319   
320  6 if (!localwikiName.equals(wikiName)) {
321  0 throw new XWikiException(XWikiException.MODULE_XWIKI_APP,
322    XWikiException.ERROR_XWIKI_APP_EXPORT,
323    String.format("The excludes argument [%s] makes reference to another wiki than its "
324    + "attached pages argument [%s]", excludePage, includePage));
325    }
326   
327  6 if (excludePage.startsWith(wikiName + ":")) {
328  3 excludePage = excludePage.substring(wikiName.length() + 1);
329    }
330   
331  6 where.append(" and doc.fullName not like ?");
332  6 params.add(new DefaultQueryParameter(null).like(excludePage));
333    }
334    }
335   
336    // don't forget to close the query statement
337  10 where.append(" ) ");
338    }
339   
340  6 DocumentReferenceResolver<String> resolver =
341    Utils.getComponent(DocumentReferenceResolver.TYPE_STRING, "current");
342   
343  6 QueryManager queryManager = Utils.getComponent(QueryManager.class);
344  6 AuthorizationManager authorizationManager = Utils.getComponent(AuthorizationManager.class);
345   
346  6 String database = context.getWikiId();
347  6 try {
348  6 for (Map.Entry<String, Object[]> entry : wikiQueries.entrySet()) {
349  6 String wikiName = entry.getKey();
350  6 Object[] query = entry.getValue();
351  6 String where = query[0].toString();
352  6 List<Object> params = (List<Object>) query[1];
353  6 Query dbQuery = queryManager.createQuery(where, Query.HQL);
354   
355  6 List<String> docsNames = dbQuery.setWiki(wikiName).bindValues(params).execute();
356   
357  6 for (String docName : docsNames) {
358  197 WikiReference wikiReference = new WikiReference(wikiName);
359  197 DocumentReference pageReference = resolver.resolve(docName, wikiReference);
360   
361  197 if (authorizationManager.hasAccess(Right.VIEW, context.getUserReference(), pageReference)) {
362  195 pageList.add(pageReference);
363    }
364    }
365    }
366    } catch (QueryException e) {
367  0 throw new XWikiException(XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_APP_EXPORT,
368    "Failed to resolve pages to export", e);
369    } finally {
370  6 context.setWikiId(database);
371    }
372    }
373  7 return pageList;
374    }
375   
 
376  4 toggle private String export(String format, XWikiContext context) throws XWikiException, IOException
377    {
378    // We currently use the PDF export infrastructure but we have to redesign the export code.
379  4 XWikiURLFactory urlFactory = new OfficeExporterURLFactory();
380  4 PdfExport exporter = new OfficeExporter();
381    // Check if the office exporter supports the specified format.
382  4 ExportType exportType = ((OfficeExporter) exporter).getExportType(format);
383    // Note 1: exportType will be null if no office server is started or it doesn't support the passed format
384    // Note 2: we don't use the office server for PDF exports since it doesn't work OOB. Instead we use FOP.
385  4 if ("pdf".equalsIgnoreCase(format)) {
386    // The export format is PDF or the office converter can't be used (either it doesn't support the specified
387    // format or the office server is not started).
388  4 urlFactory = new PdfURLFactory();
389  4 exporter = new PdfExportImpl();
390  4 exportType = ExportType.PDF;
391  0 } else if (exportType == null) {
392  0 context.put("message", "core.export.formatUnknown");
393  0 return "exception";
394    }
395   
396  4 urlFactory.init(context);
397  4 context.setURLFactory(urlFactory);
398  4 handleRevision(context);
399   
400  4 XWikiDocument doc = context.getDoc();
401  4 context.getResponse().setContentType(exportType.getMimeType());
402   
403    // Compute the name of the export. Since it's gong to be saved on the user's file system it needs to be a valid
404    // File name. Thus we use the "path" serializer but replace the "/" separator by "_" since we're not computing
405    // a directory hierarchy but a file name
406  4 EntityReferenceSerializer<String> serializer =
407    Utils.getComponent(EntityReferenceSerializer.TYPE_STRING, "path");
408  4 String filename = serializer.serialize(doc.getDocumentReference()).replaceAll("/", "_");
409    // Make sure we don't go over 255 chars since several filesystems don't support filename longer than that!
410  4 filename = StringUtils.abbreviateMiddle(filename, "__", 255);
411  4 context.getResponse().addHeader(
412    "Content-disposition",
413    String.format("inline; filename=%s.%s", filename, exportType.getExtension()));
414  4 exporter.export(doc, context.getResponse().getOutputStream(), exportType, context);
415   
416  4 return null;
417    }
418   
 
419  5 toggle private String exportXAR(XWikiContext context) throws XWikiException, IOException, FilterException
420    {
421  5 XWikiRequest request = context.getRequest();
422   
423  5 boolean history = Boolean.valueOf(request.get("history"));
424  5 boolean backup = Boolean.valueOf(request.get("backup"));
425  5 String author = request.get("author");
426  5 String licence = request.get("licence");
427  5 String version = request.get("version");
428   
429  5 ExportArguments exportArguments = new ExportArguments(context, ExportFormat.XAR);
430   
431  5 boolean all = exportArguments.exportPages.isEmpty();
432   
433  5 if (!context.getWiki().getRightService().hasWikiAdminRights(context)) {
434  0 context.put("message", "needadminrights");
435  0 return "exception";
436    }
437   
438  5 if (StringUtils.isEmpty(exportArguments.name)) {
439  0 if (all) {
440  0 exportArguments.name = "backup";
441    } else {
442  0 exportArguments.name = "export";
443    }
444    }
445   
446  5 if (context.getWiki().ParamAsLong("xwiki.action.export.xar.usefilter", 1) == 1) {
447    // Create input wiki stream
448  5 DocumentInstanceInputProperties inputProperties = new DocumentInstanceInputProperties();
449   
450    // We don't want to log the details
451  5 inputProperties.setVerbose(false);
452   
453  5 inputProperties.setWithJRCSRevisions(history);
454  5 inputProperties.setWithRevisions(false);
455   
456  5 EntityReferenceSet entities = new EntityReferenceSet();
457   
458  5 if (all) {
459  0 entities.includes(new WikiReference(context.getWikiId()));
460    } else {
461    // Find all page references and add them for processing
462  5 Collection<DocumentReference> pageList = resolvePages(exportArguments, context);
463  5 for (DocumentReference page : pageList) {
464  193 entities.includes(page);
465    }
466    }
467   
468  5 inputProperties.setEntities(entities);
469   
470  5 InputFilterStreamFactory inputFilterStreamFactory =
471    Utils.getComponent(InputFilterStreamFactory.class, FilterStreamType.XWIKI_INSTANCE.serialize());
472   
473  5 InputFilterStream inputFilterStream = inputFilterStreamFactory.createInputFilterStream(inputProperties);
474   
475    // Create output wiki stream
476  5 XAROutputProperties xarProperties = new XAROutputProperties();
477   
478    // We don't want to log the details
479  5 xarProperties.setVerbose(false);
480   
481  5 XWikiResponse response = context.getResponse();
482   
483  5 xarProperties.setTarget(new DefaultOutputStreamOutputTarget(response.getOutputStream()));
484  5 xarProperties.setPackageName(exportArguments.name);
485  5 if (exportArguments.description != null) {
486  0 xarProperties.setPackageDescription(exportArguments.description);
487    }
488  5 if (licence != null) {
489  0 xarProperties.setPackageLicense(licence);
490    }
491  5 if (author != null) {
492  0 xarProperties.setPackageAuthor(author);
493    }
494  5 if (version != null) {
495  0 xarProperties.setPackageVersion(version);
496    }
497  5 xarProperties.setPackageBackupPack(backup);
498  5 xarProperties.setPreserveVersion(backup || history);
499   
500  5 BeanOutputFilterStreamFactory<XAROutputProperties> xarFilterStreamFactory =
501    Utils.getComponent((Type) OutputFilterStreamFactory.class,
502    FilterStreamType.XWIKI_XAR_CURRENT.serialize());
503   
504  5 OutputFilterStream outputFilterStream = xarFilterStreamFactory.createOutputFilterStream(xarProperties);
505   
506    // Export
507  5 response.setContentType("application/zip");
508  5 response.addHeader("Content-disposition", "attachment; filename="
509    + Util.encodeURI(exportArguments.name, context) + ".xar");
510   
511  5 inputFilterStream.read(outputFilterStream.getFilter());
512   
513  5 inputFilterStream.close();
514  5 outputFilterStream.close();
515   
516    // Flush
517  5 response.getOutputStream().flush();
518   
519    // Indicate that we are done with the response so no need to add anything
520  5 context.setFinished(true);
521    } else {
522  0 PackageAPI export = ((PackageAPI) context.getWiki().getPluginApi("package", context));
523  0 if (export == null) {
524    // No Packaging plugin configured
525  0 return "exception";
526    }
527   
528  0 export.setWithVersions(history);
529   
530  0 if (author != null) {
531  0 export.setAuthorName(author);
532    }
533   
534  0 if (exportArguments.description != null) {
535  0 export.setDescription(exportArguments.description);
536    }
537   
538  0 if (licence != null) {
539  0 export.setLicence(licence);
540    }
541   
542  0 if (version != null) {
543  0 export.setVersion(version);
544    }
545   
546  0 export.setBackupPack(backup);
547   
548  0 export.setName(exportArguments.name);
549   
550  0 if (all) {
551  0 export.backupWiki();
552    } else {
553  0 Collection<DocumentReference> pageList = resolvePages(exportArguments, context);
554  0 for (DocumentReference pageReference : pageList) {
555  0 String defaultAction = request.get("action_" + pageReference.getName());
556  0 int iAction;
557  0 try {
558  0 iAction = Integer.parseInt(defaultAction);
559    } catch (Exception e) {
560  0 iAction = 0;
561    }
562  0 export.add(pageReference.getName(), iAction);
563    }
564  0 export.export();
565    }
566    }
567   
568  5 return null;
569    }
570    }