/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package com.xpn.xwiki.web;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.xwiki.filter.FilterException;
import org.xwiki.filter.input.InputFilterStream;
import org.xwiki.filter.input.InputFilterStreamFactory;
import org.xwiki.filter.instance.input.DocumentInstanceInputProperties;
import org.xwiki.filter.output.BeanOutputFilterStreamFactory;
import org.xwiki.filter.output.DefaultOutputStreamOutputTarget;
import org.xwiki.filter.output.OutputFilterStream;
import org.xwiki.filter.output.OutputFilterStreamFactory;
import org.xwiki.filter.type.FilterStreamType;
import org.xwiki.filter.xar.output.XAROutputProperties;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.DocumentReferenceResolver;
import org.xwiki.model.reference.EntityReferenceSerializer;
import org.xwiki.model.reference.EntityReferenceSet;
import org.xwiki.model.reference.WikiReference;
import org.xwiki.query.Query;
import org.xwiki.query.QueryException;
import org.xwiki.query.QueryManager;
import org.xwiki.query.QueryParameter;
import org.xwiki.query.internal.DefaultQueryParameter;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.export.html.HtmlPackager;
import com.xpn.xwiki.internal.export.OfficeExporter;
import com.xpn.xwiki.internal.export.OfficeExporterURLFactory;
import com.xpn.xwiki.pdf.api.PdfExport;
import com.xpn.xwiki.pdf.api.PdfExport.ExportType;
import com.xpn.xwiki.pdf.impl.PdfExportImpl;
import com.xpn.xwiki.pdf.impl.PdfURLFactory;
import com.xpn.xwiki.plugin.packaging.PackageAPI;
import com.xpn.xwiki.util.Util;
/**
* Exports in XAR, PDF, HTML and all output formats supported by *Office (when an *Office Server is running).
*
* @version $Id: 5d5c235cd61622454104413949b858ba471bc244 $
*/
public class ExportAction extends XWikiAction
{
@Override
public String render(XWikiContext context) throws XWikiException
{
String defaultPage;
try {
XWikiRequest request = context.getRequest();
String format = request.get("format");
if ((format == null) || (format.equals("xar"))) {
defaultPage = exportXAR(context);
} else if (format.equals("html")) {
defaultPage = exportHTML(context);
} else {
defaultPage = export(format, context);
}
} catch (Exception e) {
throw new XWikiException(XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_APP_EXPORT,
"Exception while exporting", e);
}
return defaultPage;
}
/**
* Create ZIP archive containing wiki pages rendered in HTML, attached files and used skins.
*
* @param context the XWiki context.
* @return always return null.
* @throws XWikiException error when exporting HTML ZIP package.
* @throws IOException error when exporting HTML ZIP package.
* @since XWiki Platform 1.3M1
*/
private String exportHTML(XWikiContext context) throws XWikiException, IOException
{
XWikiRequest request = context.getRequest();
String description = request.get("description");
String name = request.get("name");
if (StringUtils.isBlank(name)) {
name = context.getDoc().getFullName();
}
Collection<DocumentReference> pageList = resolvePagesToExport(request.getParameterValues("pages"), context);
if (pageList.isEmpty()) {
return null;
}
HtmlPackager packager = new HtmlPackager();
if (name != null && name.trim().length() > 0) {
packager.setName(name);
}
if (description != null) {
packager.setDescription(description);
}
packager.addPageReferences(pageList);
packager.export(context);
return null;
}
private Collection<DocumentReference> resolvePagesToExport(String[] pages, XWikiContext context)
throws XWikiException
{
List<DocumentReference> pageList = new ArrayList<>();
if (pages == null || pages.length == 0) {
pageList.add(context.getDoc().getDocumentReference());
} else {
Map<String, Object[]> wikiQueries = new HashMap<>();
for (int i = 0; i < pages.length; ++i) {
String pattern = pages[i];
String wikiName;
if (pattern.contains(":")) {
int index = pattern.indexOf(':');
wikiName = pattern.substring(0, index);
pattern = pattern.substring(index + 1);
} else {
wikiName = context.getWikiId();
}
StringBuffer where;
List<QueryParameter> params;
if (!wikiQueries.containsKey(wikiName)) {
Object[] query = new Object[2];
query[0] = where = new StringBuffer("where ");
query[1] = params = new ArrayList<>();
wikiQueries.put(wikiName, query);
} else {
Object[] query = wikiQueries.get(wikiName);
where = (StringBuffer) query[0];
params = (List<QueryParameter>) query[1];
}
if (i > 0) {
where.append(" or ");
}
where.append("doc.fullName like ?");
params.add(new DefaultQueryParameter(null).like(pattern));
}
DocumentReferenceResolver<String> resolver =
Utils.getComponent(DocumentReferenceResolver.TYPE_STRING, "current");
QueryManager queryManager = Utils.getComponent(QueryManager.class);
String database = context.getWikiId();
try {
for (Map.Entry<String, Object[]> entry : wikiQueries.entrySet()) {
String wikiName = entry.getKey();
Object[] query = entry.getValue();
String where = query[0].toString();
List<Object> params = (List<Object>) query[1];
Query dbQuery = queryManager.createQuery(where, Query.HQL);
List<String> docsNames = dbQuery.setWiki(wikiName).bindValues(params).execute();
for (String docName : docsNames) {
String pageReference = wikiName + XWikiDocument.DB_SPACE_SEP + docName;
if (context.getWiki().getRightService().hasAccessLevel(
"view", context.getUser(), pageReference, context))
{
pageList.add(resolver.resolve(pageReference));
}
}
}
} catch (QueryException e) {
throw new XWikiException(XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_APP_EXPORT,
"Failed to resolve pages to export", e);
} finally {
context.setWikiId(database);
}
}
return pageList;
}
private String export(String format, XWikiContext context) throws XWikiException, IOException
{
// We currently use the PDF export infrastructure but we have to redesign the export code.
XWikiURLFactory urlFactory = new OfficeExporterURLFactory();
PdfExport exporter = new OfficeExporter();
// Check if the office exporter supports the specified format.
ExportType exportType = ((OfficeExporter) exporter).getExportType(format);
// Note 1: exportType will be null if no office server is started or it doesn't support the passed format
// Note 2: we don't use the office server for PDF exports since it doesn't work OOB. Instead we use FOP.
if ("pdf".equalsIgnoreCase(format)) {
// The export format is PDF or the office converter can't be used (either it doesn't support the specified
// format or the office server is not started).
urlFactory = new PdfURLFactory();
exporter = new PdfExportImpl();
exportType = ExportType.PDF;
} else if (exportType == null) {
context.put("message", "core.export.formatUnknown");
return "exception";
}
urlFactory.init(context);
context.setURLFactory(urlFactory);
handleRevision(context);
XWikiDocument doc = context.getDoc();
context.getResponse().setContentType(exportType.getMimeType());
// 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
// File name. Thus we use the "path" serializer but replace the "/" separator by "_" since we're not computing
// a directory hierarchy but a file name
EntityReferenceSerializer<String> serializer =
Utils.getComponent(EntityReferenceSerializer.TYPE_STRING, "path");
String filename = serializer.serialize(doc.getDocumentReference()).replaceAll("/", "_");
// Make sure we don't go over 255 chars since several filesystems don't support filename longer than that!
filename = StringUtils.abbreviateMiddle(filename, "__", 255);
context.getResponse().addHeader(
"Content-disposition",
String.format("inline; filename=%s.%s", filename, exportType.getExtension()));
exporter.export(doc, context.getResponse().getOutputStream(), exportType, context);
return null;
}
private String exportXAR(XWikiContext context) throws XWikiException, IOException, FilterException
{
XWikiRequest request = context.getRequest();
boolean history = Boolean.valueOf(request.get("history"));
boolean backup = Boolean.valueOf(request.get("backup"));
String author = request.get("author");
String description = request.get("description");
String licence = request.get("licence");
String version = request.get("version");
String name = request.get("name");
String[] pages = request.getParameterValues("pages");
boolean all = ArrayUtils.isEmpty(pages);
if (!context.getWiki().getRightService().hasWikiAdminRights(context)) {
context.put("message", "needadminrights");
return "exception";
}
if (StringUtils.isEmpty(name)) {
if (all) {
name = "backup";
} else {
name = "export";
}
}
if (context.getWiki().ParamAsLong("xwiki.action.export.xar.usefilter", 1) == 1) {
// Create input wiki stream
DocumentInstanceInputProperties inputProperties = new DocumentInstanceInputProperties();
// We don't want to log the details
inputProperties.setVerbose(false);
inputProperties.setWithJRCSRevisions(history);
inputProperties.setWithRevisions(false);
EntityReferenceSet entities = new EntityReferenceSet();
if (all) {
entities.includes(new WikiReference(context.getWikiId()));
} else {
// Find all page references and add them for processing
Collection<DocumentReference> pageList = resolvePagesToExport(pages, context);
for (DocumentReference page : pageList) {
entities.includes(page);
}
}
inputProperties.setEntities(entities);
InputFilterStreamFactory inputFilterStreamFactory =
Utils.getComponent(InputFilterStreamFactory.class, FilterStreamType.XWIKI_INSTANCE.serialize());
InputFilterStream inputFilterStream = inputFilterStreamFactory.createInputFilterStream(inputProperties);
// Create output wiki stream
XAROutputProperties xarProperties = new XAROutputProperties();
// We don't want to log the details
xarProperties.setVerbose(false);
XWikiResponse response = context.getResponse();
xarProperties.setTarget(new DefaultOutputStreamOutputTarget(response.getOutputStream()));
xarProperties.setPackageName(name);
if (description != null) {
xarProperties.setPackageDescription(description);
}
if (licence != null) {
xarProperties.setPackageLicense(licence);
}
if (author != null) {
xarProperties.setPackageAuthor(author);
}
if (version != null) {
xarProperties.setPackageVersion(version);
}
xarProperties.setPackageBackupPack(backup);
xarProperties.setPreserveVersion(backup || history);
BeanOutputFilterStreamFactory<XAROutputProperties> xarFilterStreamFactory =
Utils.getComponent((Type) OutputFilterStreamFactory.class,
FilterStreamType.XWIKI_XAR_CURRENT.serialize());
OutputFilterStream outputFilterStream = xarFilterStreamFactory.createOutputFilterStream(xarProperties);
// Export
response.setContentType("application/zip");
response.addHeader("Content-disposition", "attachment; filename=" + Util.encodeURI(name, context) + ".xar");
inputFilterStream.read(outputFilterStream.getFilter());
inputFilterStream.close();
outputFilterStream.close();
// Flush
response.getOutputStream().flush();
// Indicate that we are done with the response so no need to add anything
context.setFinished(true);
} else {
PackageAPI export = ((PackageAPI) context.getWiki().getPluginApi("package", context));
if (export == null) {
// No Packaging plugin configured
return "exception";
}
export.setWithVersions(history);
if (author != null) {
export.setAuthorName(author);
}
if (description != null) {
export.setDescription(description);
}
if (licence != null) {
export.setLicence(licence);
}
if (version != null) {
export.setVersion(version);
}
export.setBackupPack(backup);
export.setName(name);
if (all) {
export.backupWiki();
} else {
if (pages != null) {
for (String pageName : pages) {
String defaultAction = request.get("action_" + pageName);
int iAction;
try {
iAction = Integer.parseInt(defaultAction);
} catch (Exception e) {
iAction = 0;
}
export.add(pageName, iAction);
}
}
export.export();
}
}
return null;
}
}