/*
* 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.util.Arrays;
import java.util.List;
import java.util.Locale;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xwiki.job.Job;
import org.xwiki.job.JobException;
import org.xwiki.job.JobExecutor;
import org.xwiki.refactoring.job.RefactoringJobs;
import org.xwiki.refactoring.job.RestoreRequest;
import org.xwiki.refactoring.script.RefactoringScriptService;
import org.xwiki.script.service.ScriptService;
import com.xpn.xwiki.XWiki;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.api.DeletedDocument;
import com.xpn.xwiki.doc.XWikiDeletedDocument;
import com.xpn.xwiki.doc.XWikiDocument;
/**
* Action for restoring documents from the recycle bin.
*
* @version $Id: cbbbdc7223e68b142243dd80922786763a662e5a $
* @since 1.2M1
*/
public class UndeleteAction extends XWikiAction
{
private static final String ID_PARAMETER = "id";
private static final String SHOW_BATCH_PARAMETER = "showBatch";
private static final String INCLUDE_BATCH_PARAMETER = "includeBatch";
private static final String CONFIRM_PARAMETER = "confirm";
private static final String ASYNC_PARAM = "async";
private static final String TRUE = "true";
private static final String VIEW_ACTION = "view";
private static final Logger LOGGER = LoggerFactory.getLogger(UndeleteAction.class);
@Override
public boolean action(XWikiContext context) throws XWikiException
{
XWikiRequest request = context.getRequest();
XWikiResponse response = context.getResponse();
XWikiDocument doc = context.getDoc();
// If the provided DeletedDocument ID is invalid, for any reason, redirect to view mode to see the document does
// not exist screen.
XWikiDeletedDocument deletedDocument = getDeletedDocument(context);
if (deletedDocument == null) {
sendRedirect(response, doc.getURL(VIEW_ACTION, context));
return false;
}
// Note: Check the ID validity before checking the showBatch parameter so that we validate the ID before
// displaying any restore batch UI.
// If showBatch=true and confirm=true then restore the page w/o the batch. If not, the render action will go to
// the "restore" UI so that the user can confirm. That "restore" UI will then call the action again with
// confirm=true.
if (TRUE.equals(request.getParameter(SHOW_BATCH_PARAMETER))
&& !TRUE.equals(request.getParameter(CONFIRM_PARAMETER))) {
return true;
}
// CSRF prevention
if (!csrfTokenCheck(context)) {
return false;
}
// If the current user is not allowed to restore, render the "accessdenied" template.
DeletedDocument deletedDocumentAPI = new DeletedDocument(deletedDocument, context);
if (!deletedDocumentAPI.canUndelete()) {
return true;
}
boolean redirected = false;
if (deletedDocument != null) {
redirected = restoreDocument(deletedDocument, context);
}
// Redirect to the undeleted document. Make sure to redirect to the proper translation.
if (!redirected) {
String queryString = getRedirectQueryString(context, deletedDocument.getLocale());
sendRedirect(response, doc.getURL(VIEW_ACTION, queryString, context));
redirected = true;
}
return !redirected;
}
private XWikiDeletedDocument getDeletedDocument(XWikiContext context) throws XWikiException
{
XWikiDeletedDocument result = null;
XWikiRequest request = context.getRequest();
XWiki xwiki = context.getWiki();
String sindex = request.getParameter(ID_PARAMETER);
try {
long index = Long.parseLong(sindex);
result = xwiki.getDeletedDocument(index, context);
} catch (Exception e) {
LOGGER.error("Failed to get deleted document with ID [{}]", sindex, e);
}
return result;
}
private boolean restoreDocument(XWikiDeletedDocument deletedDocument, XWikiContext context) throws XWikiException
{
Job restoreJob = startRestoreJob(deletedDocument, context);
// If the user asked for an asynchronous action...
if (isAsync(context.getRequest())) {
List<String> jobId = restoreJob.getRequest().getId();
// We redirect to the view action and accept the edge case when the restored document's rights might prevent
// the restoring user to view the result. In that case, an admin must be contacted to fix the rights.
sendRedirect(context.getResponse(),
Utils.getRedirect(VIEW_ACTION, "xpage=restore&jobId=" + serializeJobId(jobId), context));
// A redirect has been performed.
return true;
}
// Otherwise...
try {
restoreJob.join();
} catch (InterruptedException e) {
throw new XWikiException(String.format("Failed to restore [%s] from batch [%s]",
deletedDocument.getFullName(), deletedDocument.getBatchId()), e);
}
// No redirect has been performed.
return false;
}
private String serializeJobId(List<String> jobId)
{
return StringUtils.join(jobId, "/");
}
private Job startRestoreJob(XWikiDeletedDocument deletedDocument, XWikiContext context) throws XWikiException
{
XWikiRequest request = context.getRequest();
RefactoringScriptService refactoring =
(RefactoringScriptService) Utils.getComponent(ScriptService.class, "refactoring");
RestoreRequest restoreRequest = null;
if (TRUE.equals(request.getParameter(INCLUDE_BATCH_PARAMETER))) {
// Restore the entire batch, including the current document.
String batchId = deletedDocument.getBatchId();
restoreRequest = refactoring.createRestoreRequest(batchId);
} else {
// Restore just the current document.
restoreRequest = refactoring.createRestoreRequest(Arrays.asList(deletedDocument.getId()));
}
restoreRequest.setInteractive(isAsync(request));
try {
JobExecutor jobExecutor = Utils.getComponent(JobExecutor.class);
return jobExecutor.execute(RefactoringJobs.RESTORE, restoreRequest);
} catch (JobException e) {
throw new XWikiException(
String.format("Failed to schedule the restore job for deleted document [%s], id [%s] of batch [%s]",
deletedDocument.getFullName(), deletedDocument.getId(), deletedDocument.getBatchId()),
e);
}
}
private boolean isAsync(XWikiRequest request)
{
return TRUE.equals(request.get(ASYNC_PARAM));
}
private String getRedirectQueryString(XWikiContext context, Locale deletedDocumentLocale)
{
String result = null;
XWiki xwiki = context.getWiki();
if (deletedDocumentLocale != null && xwiki.isMultiLingual(context)) {
result = String.format("language=%s", deletedDocumentLocale);
}
return result;
}
@Override
public String render(XWikiContext context) throws XWikiException
{
String result = null;
XWikiRequest request = context.getRequest();
// If showBatch=true and user confirmation is required, display the "restore" UI.
if (TRUE.equals(request.getParameter(SHOW_BATCH_PARAMETER))
&& !TRUE.equals(request.getParameter(CONFIRM_PARAMETER))) {
result = "restore";
}
// If the current user is not allowed to restore, display the "accessdenied" template.
XWikiDeletedDocument deletedDocument = getDeletedDocument(context);
if (deletedDocument != null) {
// Note: Checking for null because when the document is actually restored, it may no longer be in the
// recycle bin by the time render() gets called.
DeletedDocument deletedDocumentAPI = new DeletedDocument(deletedDocument, context);
if (!deletedDocumentAPI.canUndelete()) {
return "accessdenied";
}
}
return result;
}
}