Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/api/DeletedDocument.java
### 代码内容：
/*
 * 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.api;

import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.Objects;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.security.authorization.Right;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.XWikiDeletedDocument;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.util.Programming;

/**
 * Information about a deleted document in the recycle bin.
 *
 * @version $Id$
 */
public class DeletedDocument extends Api
{
    private static final String ADMIN_RIGHT = "admin";

    /** Logging helper object. */
    private static final Logger LOGGER = LoggerFactory.getLogger(DeletedDocument.class);

    /**
     * The internal object wrapped by this API.
     */
    private final XWikiDeletedDocument deletedDoc;

    /**
     * Simple constructor, initializes a new API object with the current {@link com.xpn.xwiki.XWikiContext context} and
     * the specified protected {@link com.xpn.xwiki.doc.XWikiDeletedDocument deleted document} object.
     *
     * @param deletedDoc the internal object wrapped by this API
     * @param context the current request context
     */
    public DeletedDocument(XWikiDeletedDocument deletedDoc, XWikiContext context)
    {
        super(context);
        this.deletedDoc = deletedDoc;
    }

    /**
     * @return full name of document (ie: Main.WebHome)
     */
    public String getFullName()
    {
        return this.deletedDoc.getFullName();
    }

    /**
     * @return locale of document
     * @deprecated since 8.0M1, use {@link #getLocale()} instead
     */
    @Deprecated
    public String getLanguage()
    {
        return this.deletedDoc.getLanguage();
    }

    /**
     * @return locale of document
     * @since 8.0M1
     */
    public Locale getLocale()
    {
        return this.deletedDoc.getLocale();
    }

    /**
     * @return date of delete action
     */
    public Date getDate()
    {
        return this.deletedDoc.getDate();
    }

    /**
     * @return user which delete document
     */
    public String getDeleter()
    {
        return this.deletedDoc.getDeleter();
    }

    /**
     * @return the reference of the user who deleted this document
     * @since 11.5RC1
     */
    public DocumentReference getDeleterReference()
    {
        return this.deletedDoc.getDeleterReference();
    }

    /**
     * @return id of deleted document. id is unique only for this document.
     */
    public long getId()
    {
        return this.deletedDoc.getId();
    }

    /**
     * @return the id of the operation that deleted multiple documents at the same time, including this one
     * @since 9.4RC1
     */
    public String getBatchId()
    {
        return this.deletedDoc.getBatchId();
    }

    /**
     * Check if the current user has the right to restore the document.
     *
     * @return {@code true} if the current user can restore this document, {@code false} otherwise
     */
    public boolean canUndelete()
    {
        try {
            return hasAccessLevel(ADMIN_RIGHT, getFullName()) || hasAccessLevel("undelete", getFullName())
                || (Objects.equals(this.context.getUserReference(), getDeleterReference())
                    && hasAccess(Right.EDIT, getDocumentReference()));
        } catch (XWikiException ex) {
            // Public APIs should not throw exceptions
            LOGGER.warn("Exception while checking if entry [{}] can be restored from the recycle bin", getId(), ex);
            return false;
        }
    }

    /**
     * @return {@code true} if the current user can permanently delete this document, {@code false} otherwise
     * @xwiki.xwikicfg xwiki.store.recyclebin.adminWaitDays How many days should an administrator wait before being able
     *                 to permanently delete this document from the recycle bin. 0 by default.
     * @xwiki.xwikicfg xwiki.store.recyclebin.waitDays How many days should a normal user with "delete" right wait
     *                 before being able to permanently delete this document from the recycle bin. 7 by default.
     */
    public boolean canDelete()
    {
        try {
            XWikiDocument doc = new XWikiDocument();
            doc.setFullName(getFullName(), this.context);
            if (!hasAccessLevel("delete", getFullName())) {
                return false;
            }
            String waitdays;
            if (hasAccessLevel(ADMIN_RIGHT, getFullName())) {
                waitdays = getXWikiContext().getWiki().Param("xwiki.store.recyclebin.adminWaitDays", "0");
            } else {
                waitdays = getXWikiContext().getWiki().Param("xwiki.store.recyclebin.waitDays", "7");
            }
            int seconds = (int) (Double.parseDouble(waitdays) * 24 * 60 * 60 + 0.5);
            Calendar cal = Calendar.getInstance();
            cal.setTime(getDate());
            cal.add(Calendar.SECOND, seconds);
            return cal.before(Calendar.getInstance());
        } catch (Exception ex) {
            // Public APIs should not throw exceptions
            LOGGER.warn("Exception while checking if entry [{}] can be removed from the recycle bin", getId(), ex);
            return false;
        }
    }

    /**
     * @return original deleted document if user has programming rights, else {@code null}.
     */
    @Programming
    public XWikiDeletedDocument getDeletedDocument()
    {
        if (hasProgrammingRights()) {
            return this.deletedDoc;
        } else {
            return null;
        }
    }

    /**
     * @return the document as it is in the recycle bin if the user is allowed to restore it, {@code null} otherwise
     */
    public Document getDocument()
    {
        if (canUndelete()) {
            try {
                return new Document(this.deletedDoc.restoreDocument(null, this.context), this.context);
            } catch (XWikiException e) {
                LOGGER.warn("Failed to parse deleted document [{}]", getFullName(), e);
            }
        }

        return null;
    }

    /**
     * @return the document reference for the deleted document, including any locale information
     * @since 9.4RC1
     */
    public DocumentReference getDocumentReference()
    {
        return this.deletedDoc.getDocumentReference();
    }
}

## 内容2
### 文件路径：
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/doc/DocumentRevisionProvider.java
### 代码内容：
/*
 * 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.doc;

import org.xwiki.component.annotation.Role;
import org.xwiki.model.reference.DocumentReference;

import com.xpn.xwiki.XWikiException;

/**
 * Provide generic way of requesting a specific revision of a document from several sources (database, installed XAR
 * extension, etc.).
 *

 * The revision syntax is {@code <provider hint>:<revision>}, default provide being database (the version of the
 * document in the history).
 *
 * @version $Id$
 * @since 9.4RC1
 */
@Role
public interface DocumentRevisionProvider
{
    /**
     * Load the document in the provided revision.
     *
     * @param reference the reference of the document
     * @param revision the revision of the document
     * @return the {@link XWikiDocument} instance or null if none existing
     * @throws XWikiException when failing to load the document revision
     */
    XWikiDocument getRevision(DocumentReference reference, String revision) throws XWikiException;

    /**
     * Load the document in the provided revision.
     *
     * @param document the current document
     * @param revision the revision of the document
     * @return the {@link XWikiDocument} instance or null if none existing
     * @throws XWikiException when failing to load the document revision
     */
    XWikiDocument getRevision(XWikiDocument document, String revision) throws XWikiException;
}

## 内容3
### 文件路径：
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/doc/XWikiDeletedDocument.java
### 代码内容：
/*
 * 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.doc;

import java.io.IOException;
import java.util.Date;
import java.util.Locale;

import org.apache.commons.lang3.StringUtils;
import org.xwiki.localization.LocaleUtils;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.DocumentReferenceResolver;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.internal.store.hibernate.XWikiHibernateDeletedDocumentContent;
import com.xpn.xwiki.util.AbstractSimpleClass;
import com.xpn.xwiki.util.Util;
import com.xpn.xwiki.web.Utils;

/**
 * Archive of deleted document, stored in {@link com.xpn.xwiki.store.XWikiRecycleBinStoreInterface}. Immutable, because
 * we don't need modify deleted document.
 *
 * @version $Id$
 * @since 1.2M1
 */
public class XWikiDeletedDocument extends AbstractSimpleClass
{
    /**
     * Synthetic id.
     */
    private long id;

    /**
     * @see XWikiDocument#getFullName()
     */
    private String fullName;

    /**
     * @see XWikiDocument#getLocale()
     */
    private Locale locale;

    /**
     * date of delete action.
     */
    private Date date;

    /**
     * @see XWikiDeletedDocument#getDeleter()
     */
    private String deleter;

    private String xmlStore;

    private XWikiDeletedDocumentContent content;

    private String batchId;

    /**
     * Default constructor. Used only in hibernate.
     */
    protected XWikiDeletedDocument()
    {
    }

    /**
     * @param fullName the local reference of the document
     * @param locale the locale of the document
     * @param storeType - the way to store the document
     * @param deleter - user which delete document
     * @param deleteDate - date of delete action
     * @throws XWikiException if any error
     * @since 9.0RC1
     */
    private XWikiDeletedDocument(String fullName, Locale locale, String storeType, String deleter, Date deleteDate)
        throws XWikiException
    {
        this.fullName = fullName;
        this.locale = locale;
        this.deleter = deleter;
        this.date = deleteDate;
        this.xmlStore = storeType;
    }

    /**
     * @param doc - deleted document
     * @param deleter - user which delete document
     * @param deleteDate - date of delete action
     * @param context - used for environment
     * @throws XWikiException if any error
     */
    public XWikiDeletedDocument(XWikiDocument doc, String deleter, Date deleteDate, XWikiContext context)
        throws XWikiException
    {
        this(doc.getFullName(), doc.getLocale(), null, deleter, deleteDate);

        setDocument(doc, context);
    }

    /**
     * @param fullName the local reference of the document
     * @param locale the locale of the document
     * @param storeType the way to store the document
     * @param deleter the user who delete document
     * @param deleteDate date of delete action
     * @param content the stored deleted document
     * @throws XWikiException if any error
     * @since 9.0RC1
     */
    public XWikiDeletedDocument(String fullName, Locale locale, String storeType, String deleter, Date deleteDate,
        XWikiDeletedDocumentContent content) throws XWikiException
    {
        this(fullName, locale, storeType, deleter, deleteDate);

        this.content = content;
    }

    /**
     * @param fullName the local reference of the document
     * @param locale the locale of the document
     * @param storeType the way to store the document
     * @param deleter the user who delete document
     * @param deleteDate date of delete action
     * @param content the stored deleted document
     * @param batchId the id of the batch deletion
     * @throws XWikiException if any error
     * @since 9.4RC1
     */
    public XWikiDeletedDocument(String fullName, Locale locale, String storeType, String deleter, Date deleteDate,
        XWikiDeletedDocumentContent content, String batchId) throws XWikiException
    {
        this(fullName, locale, storeType, deleter, deleteDate, content);

        this.batchId = batchId;
    }

    /**
     * @return the synthetic id of this deleted document. unique only for document.
     */
    public long getId()
    {
        return this.id;
    }

    /**
     * @param id - the synthetic id to set. used only in Hibernate.
     */
    protected void setId(long id)
    {
        this.id = id;
    }

    /**
     * @return {@link XWikiDocument#getFullName()}
     */
    public String getFullName()
    {
        return this.fullName;
    }

    /**
     * @param docFullName - {@link XWikiDocument#getFullName()} to set
     */
    protected void setFullName(String docFullName)
    {
        this.fullName = docFullName;
    }

    /**
     * @return the document reference for the deleted document, including any locale information
     * @since 9.4RC1
     */
    public DocumentReference getDocumentReference()
    {
        DocumentReference documentReference = getDocumentReferenceResolver().resolve(getFullName());

        Locale localeValue = getLocale();
        if (localeValue != null) {
            documentReference = new DocumentReference(documentReference, localeValue);
        }

        return documentReference;
    }

    private static DocumentReferenceResolver<String> getDocumentReferenceResolver()
    {
        return Utils.getComponent(DocumentReferenceResolver.TYPE_STRING, "currentmixed");
    }

    /**
     * @return {@link XWikiDocument#getLanguage()}
     * @deprecated since 8.0M1, use {@link #getLocale()} instead
     */
    @Deprecated
    public String getLanguage()
    {
        return getLocale().toString();
    }

    /**
     * @return {@link XWikiDocument#getLocale()}
     * @since 8.0M1
     */
    public Locale getLocale()
    {
        return this.locale != null ? this.locale : Locale.ROOT;
    }

    /**
     * @param locale - {@link XWikiDocument#getLanguage()} to set
     * @deprecated since 8.0M1
     */
    @Deprecated
    protected void setLanguage(String locale)
    {
        this.locale = LocaleUtils.toLocale(Util.normalizeLanguage(locale), Locale.ROOT);
    }

    /**
     * @return the date of delete action
     */
    public Date getDate()
    {
        return this.date;
    }

    /**
     * @param date - the date of delete action to set
     */
    protected void setDate(Date date)
    {
        this.date = date;
    }

    /**
     * @return the user which has removed the document
     */
    public String getDeleter()
    {
        return this.deleter;
    }

    /**
     * @return the reference of the user who deleted this document
     * @since 11.5RC1
     */
    public DocumentReference getDeleterReference()
    {
        return getDocumentReferenceResolver().resolve(this.deleter);
    }

    /**
     * @param deleter - the user which has removed the document to set
     */
    protected void setDeleter(String deleter)
    {
        this.deleter = deleter;
    }

    /**
     * @return the type of the store used for the content
     * @since 9.0RC1
     */
    public String getXmlStore()
    {
        return this.xmlStore;
    }

    /**
     * @param xmlStore the type of store (supported values are null/"hibernate" and "file")
     * @since 9.0RC1
     */
    protected void setXmlStore(String xmlStore)
    {
        this.xmlStore = xmlStore;
    }

    /**
     * Only used in Hibernate.
     *
     * @return xml serialization of {@link XWikiDocument}
     */
    public String getXml()
    {
        if (this.content != null) {
            try {
                return this.content.getContentAsString();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        // Return empty String instead of null because this field is configured as not null at database level
        return "";
    }

    /**
     * Only used in Hibernate.
     *
     * @param xml - xml serialization of {@link XWikiDocument}
     */
    protected void setXml(String xml)
    {
        if (StringUtils.isNotEmpty(xml)) {
            try {
                this.content = new XWikiHibernateDeletedDocumentContent(xml);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * Export {@link XWikiDocument} to {@link XWikiDeletedDocument}.
     *
     * @param doc - the deleted document
     * @param context - used in {@link XWikiDocument#toXML(XWikiContext)}
     * @throws XWikiException in error in {@link XWikiDocument#toXML(XWikiContext)}
     * @deprecated since 9.0RC1, use
     *             {@link XWikiDeletedDocument#XWikiDeletedDocument(String, Locale, String, String, Date, XWikiDeletedDocumentContent)
     *             instead}
     */
    @Deprecated
    protected void setDocument(XWikiDocument doc, XWikiContext context) throws XWikiException
    {
        this.content = new XWikiHibernateDeletedDocumentContent(doc);
    }

    /**
     * @return restored document
     * @param doc optional object where to put the document data, if not null
     * @param context the current {@link XWikiContext context}
     * @throws XWikiException if error in {@link XWikiDocument#fromXML(String)}
     */
    public XWikiDocument restoreDocument(XWikiDocument doc, XWikiContext context) throws XWikiException
    {
        if (this.content == null) {
            throw new XWikiException(XWikiException.MODULE_XWIKI_DOC, XWikiException.ERROR_DOC_XML_PARSING,
                "Cannot find any content for the deleted document [" + this.fullName + " (" + this.locale + ")]");
        }

        try {
            return this.content.getXWikiDocument(doc);
        } catch (IOException e) {
            throw new XWikiException(XWikiException.MODULE_XWIKI_DOC, XWikiException.ERROR_DOC_XML_PARSING,
                "Error restoring document", e, null);
        }
    }

    /**
     * @return restored document
     * @param context the current {@link XWikiContext context}
     * @throws XWikiException if error in {@link XWikiDocument#fromXML(String)}
     * @since 9.0RC1
     */
    public XWikiDocument restoreDocument(XWikiContext context) throws XWikiException
    {
        return restoreDocument(null, context);
    }

    /**
     * @param batchId batch operation ID to set
     * @since 9.4RC1
     */
    protected void setBatchId(String batchId)
    {
        this.batchId = batchId;
    }

    /**
     * @return the id of the operation that deleted multiple documents at the same time, including this one
     * @since 9.4RC1
     */
    public String getBatchId()
    {
        return batchId;
    }
}

## 内容4
### 文件路径：
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/doc/DefaultDocumentRevisionProvider.java
### 代码内容：
/*
 * 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.internal.doc;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;

import org.xwiki.component.annotation.Component;
import org.xwiki.component.manager.ComponentLookupException;
import org.xwiki.component.manager.ComponentManager;
import org.xwiki.model.reference.DocumentReference;

import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.DocumentRevisionProvider;
import com.xpn.xwiki.doc.XWikiDocument;

/**
 * The default implementation of {@link DocumentRevisionProvider}.
 *

 * The main job of {@link DefaultDocumentRevisionProvider} is to call the right {@link DocumentRevisionProvider}
 * depending on the revision prefix.
 *
 * @version $Id$
 * @since 9.3rc1
 */
@Component
@Singleton
public class DefaultDocumentRevisionProvider extends AbstractDocumentRevisionProvider
{
    @Inject
    @Named("context")
    private Provider<ComponentManager> componentManagerProvider;

    @Inject
    @Named("database")
    private DocumentRevisionProvider databaseDocumentRevisionProvider;

    @Override
    public XWikiDocument getRevision(DocumentReference reference, String revision) throws XWikiException
    {
        // Parse the version
        String revisionPrefix = null;
        if (revision != null) {
            int revisionPrefixIndex = revision.indexOf(':');
            if (revisionPrefixIndex > 0) {
                revisionPrefix = revision.substring(0, revisionPrefixIndex);
            }
        }
        String shortRevision;
        if (revisionPrefix != null) {
            shortRevision = revision.substring(revisionPrefix.length() + 1);
        } else {
            shortRevision = revision;
        }

        // Find the provider
        DocumentRevisionProvider provider = this.databaseDocumentRevisionProvider;
        if (revisionPrefix != null) {
            ComponentManager componentManager = this.componentManagerProvider.get();
            if (componentManager.hasComponent(DocumentRevisionProvider.class, revisionPrefix)) {
                try {
                    provider = componentManager.getInstance(DocumentRevisionProvider.class, revisionPrefix);
                } catch (ComponentLookupException e) {
                    throw new XWikiException("Failed to get revision provider for revision [" + revision + "]", e);
                }
            }
        }

        // Load the document revision
        return provider.getRevision(reference, shortRevision);
    }
}

## 内容5
### 文件路径：
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/doc/DeletedDocumentRevisionProvider.java
### 代码内容：
/*
 * 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.internal.doc;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;

import org.xwiki.component.annotation.Component;
import org.xwiki.model.reference.DocumentReference;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.DocumentRevisionProvider;
import com.xpn.xwiki.doc.XWikiDeletedDocument;
import com.xpn.xwiki.doc.XWikiDocument;

/**
 * Get deleted document revisions from the database.
 *
 * @version $Id$
 * @since 9.4RC1
 */
@Component
@Named("deleted")
@Singleton
public class DeletedDocumentRevisionProvider implements DocumentRevisionProvider
{
    @Inject
    private Provider<XWikiContext> xcontextProvider;

    @Override
    public XWikiDocument getRevision(DocumentReference reference, String revision) throws XWikiException
    {
        XWikiContext xcontext = this.xcontextProvider.get();

        XWikiDeletedDocument deletedDocument = xcontext.getWiki().getDeletedDocument(Long.valueOf(revision), xcontext);

        // Only local the document if it matches the asked document reference
        if (deletedDocument != null
            && (reference == null || deletedDocument.getDocumentReference().equals(reference))) {
            return deletedDocument.restoreDocument(xcontext);
        }

        return null;

    }

    @Override
    public XWikiDocument getRevision(XWikiDocument document, String revision) throws XWikiException
    {
        return getRevision(document != null ? document.getDocumentReferenceWithLocale() : null, revision);
    }
}

## 内容6
### 文件路径：
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/store/XWikiHibernateRecycleBinStore.java
### 代码内容：
/*
 * 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.store;

import java.util.Date;
import java.util.List;
import java.util.Locale;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.commons.lang3.reflect.FieldUtils;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.slf4j.Logger;
import org.xwiki.component.annotation.Component;
import org.xwiki.component.manager.ComponentLookupException;
import org.xwiki.component.manager.ComponentManager;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.XWikiDeletedDocument;
import com.xpn.xwiki.doc.XWikiDeletedDocumentContent;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.internal.store.StoreConfiguration;
import com.xpn.xwiki.internal.store.hibernate.XWikiHibernateDeletedDocumentContent;

/**
 * Realization of {@link XWikiRecycleBinStoreInterface} for Hibernate store.
 *
 * @version $Id$
 */
@Component
@Named(XWikiHibernateBaseStore.HINT)
@Singleton
public class XWikiHibernateRecycleBinStore extends XWikiHibernateBaseStore implements XWikiRecycleBinStoreInterface
{
    /**
     * {@link HibernateCallback} used to retrieve from the recycle bin store the deleted versions of a document.
     */
    private static class DeletedDocumentsHibernateCallback implements HibernateCallback<XWikiDeletedDocument[]>
    {
        /**
         * The document whose versions are retrieved from the recycle bin store.
         */
        private XWikiDocument document;

        /**
         * Creates a new call-back for the given document.
         *
         * @param document the document whose deleted versions you want to retrieve from the recycle bin store
         */
        DeletedDocumentsHibernateCallback(XWikiDocument document)
        {
            this.document = document;
        }

        @Override
        public XWikiDeletedDocument[] doInHibernate(Session session) throws HibernateException, XWikiException
        {
            CriteriaBuilder builder = session.getCriteriaBuilder();
            CriteriaQuery<XWikiDeletedDocument> query = builder.createQuery(XWikiDeletedDocument.class);
            Root<XWikiDeletedDocument> root = query.from(XWikiDeletedDocument.class);

            query.select(root);

            Predicate[] predicates = new Predicate[2];

            predicates[0] = builder.equal(root.get(FULL_NAME_FIELD), this.document.getFullName());

            // Note: We need to support databases who treats empty strings as NULL like Oracle. For those checking
            // for equality when the string is empty is not going to work and thus we need to handle the special
            // empty case separately.
            Locale language = this.document.getLocale();
            Path<String> languageProperty = root.get(LANGUAGE_PROPERTY_NAME);
            if (language.equals(Locale.ROOT)) {
                predicates[1] = builder.or(builder.equal(languageProperty, ""), builder.isNull(languageProperty));
            } else {
                predicates[1] = builder.equal(languageProperty, language);
            }

            query.where(predicates);

            query.orderBy(builder.desc(root.get("date")));

            List<XWikiDeletedDocument> deletedVersions = session.createQuery(query).getResultList();

            return deletedVersions.toArray(new XWikiDeletedDocument[deletedVersions.size()]);
        }
    }

    /**
     * {@link HibernateCallback} used to retrieve from the recycle bin store the deleted document versions from a given
     * batch.
     */
    private static class DeletedDocumentsBatchHibernateCallback implements HibernateCallback<XWikiDeletedDocument[]>
    {
        private String batchId;

        /**
         * Creates a new call-back for the given batch.
         *
         * @param batchId the ID of the batch of deleted documents you want to retrieve from the recycle bin store
         */
        DeletedDocumentsBatchHibernateCallback(String batchId)
        {
            this.batchId = batchId;
        }

        @Override
        public XWikiDeletedDocument[] doInHibernate(Session session) throws HibernateException, XWikiException
        {
            CriteriaBuilder builder = session.getCriteriaBuilder();
            CriteriaQuery<XWikiDeletedDocument> query = builder.createQuery(XWikiDeletedDocument.class);
            Root<XWikiDeletedDocument> root = query.from(XWikiDeletedDocument.class);

            query.select(root);

            query.where(builder.equal(root.get("batchId"), batchId));

            query.orderBy(builder.asc(root.get(FULL_NAME_FIELD)));

            List<XWikiDeletedDocument> deletedVersions = session.createQuery(query).getResultList();

            return deletedVersions.toArray(new XWikiDeletedDocument[deletedVersions.size()]);
        }
    }

    private static final String FULL_NAME_FIELD = "fullName";

    /**
     * Name of the language property in the Hibernate mapping.
     */
    private static final String LANGUAGE_PROPERTY_NAME = "language";

    @Inject
    private StoreConfiguration storeConfiguration;

    @Inject
    private ComponentManager componentManager;

    @Inject
    private Logger logger;

    /**
     * @param context used for environment
     * @deprecated 1.6M1. Use ComponentManager#getInstance(XWikiRecycleBinStoreInterface.class) instead.
     */
    @Deprecated
    public XWikiHibernateRecycleBinStore(XWikiContext context)
    {
        super(context.getWiki(), context);
    }

    /**
     * Empty constructor needed for component manager.
     */
    public XWikiHibernateRecycleBinStore()
    {
    }

    private XWikiRecycleBinContentStoreInterface getDefaultXWikiRecycleBinContentStore() throws XWikiException
    {
        try {
            return this.storeConfiguration.getXWikiRecycleBinContentStore();
        } catch (ComponentLookupException e) {
            throw new XWikiException(XWikiException.MODULE_XWIKI_STORE, XWikiException.ERROR_XWIKI_UNKNOWN,
                "Failed to lookup recycle bin content store", e);
        }
    }

    private XWikiRecycleBinContentStoreInterface getXWikiRecycleBinContentStore(String storeType)
    {
        if (storeType != null && !storeType.equals(HINT)) {
            try {
                return this.componentManager.getInstance(XWikiRecycleBinContentStoreInterface.class, storeType);
            } catch (ComponentLookupException e) {
                this.logger.warn("Can't find recycle bin content store for type [{}]", storeType, e);
            }
        }

        return null;
    }

    private XWikiDeletedDocument resolveDeletedDocumentContent(XWikiDeletedDocument deletedDocument,
        boolean bTransaction) throws XWikiException
    {
        XWikiRecycleBinContentStoreInterface contentStore =
            getXWikiRecycleBinContentStore(deletedDocument.getXmlStore());

        if (contentStore != null) {
            XWikiDeletedDocumentContent content =
                contentStore.get(deletedDocument.getDocumentReference(), deletedDocument.getId(), bTransaction);

            try {
                FieldUtils.writeDeclaredField(deletedDocument, "content", content, true);
            } catch (IllegalAccessException e) {
                throw new XWikiException(XWikiException.MODULE_XWIKI_STORE, XWikiException.ERROR_XWIKI_UNKNOWN,
                    "Failed to set deleted document content", e);
            }
        }

        return deletedDocument;
    }

    private XWikiDeletedDocument createXWikiDeletedDocument(XWikiDocument doc, String deleter, Date date,
        XWikiRecycleBinContentStoreInterface contentStore, String batchId) throws XWikiException
    {
        XWikiDeletedDocument trashdoc;

        String storeType = null;
        XWikiDeletedDocumentContent deletedDocumentContent = null;

        if (contentStore != null) {
            storeType = contentStore.getHint();
        } else {
            deletedDocumentContent = new XWikiHibernateDeletedDocumentContent(doc);
        }

        trashdoc = new XWikiDeletedDocument(doc.getFullName(), doc.getLocale(), storeType, deleter, date,
            deletedDocumentContent, batchId);

        return trashdoc;
    }

    private void deleteDeletedDocumentContent(XWikiDeletedDocument deletedDocument, boolean bTransaction)
        throws XWikiException
    {
        XWikiRecycleBinContentStoreInterface contentStore =
            getXWikiRecycleBinContentStore(deletedDocument.getXmlStore());

        if (contentStore != null) {
            contentStore.delete(deletedDocument.getDocumentReference(), deletedDocument.getId(), bTransaction);
        }
    }

    @Override
    public void saveToRecycleBin(XWikiDocument doc, String deleter, Date date, XWikiContext inputxcontext,
        boolean bTransaction) throws XWikiException
    {
        saveToRecycleBin(doc, deleter, date, null, inputxcontext, bTransaction);
    }

    @Override
    public void saveToRecycleBin(XWikiDocument doc, String deleter, Date date, String batchId,
        XWikiContext inputxcontext, boolean bTransaction) throws XWikiException
    {
        XWikiContext context = getExecutionXContext(inputxcontext, true);

        try {
            executeWrite(context, session -> {
                XWikiRecycleBinContentStoreInterface contentStore = getDefaultXWikiRecycleBinContentStore();

                XWikiDeletedDocument trashdoc = createXWikiDeletedDocument(doc, deleter, date, contentStore, batchId);

                // Hibernate store.
                long index = ((Number) session.save(trashdoc)).longValue();

                // External store
                if (contentStore != null) {
                    contentStore.save(doc, index, bTransaction);
                }

                return null;
            });
        } finally {
            restoreExecutionXContext();
        }
    }

    @Override
    public XWikiDocument restoreFromRecycleBin(final XWikiDocument doc, final long index,
        final XWikiContext inputxcontext, boolean bTransaction) throws XWikiException
    {
        return restoreFromRecycleBin(index, inputxcontext, bTransaction);
    }

    @Override
    public XWikiDocument restoreFromRecycleBin(long index, XWikiContext inputxcontext, boolean bTransaction)
        throws XWikiException
    {
        XWikiContext context = getExecutionXContext(inputxcontext, true);

        try {
            XWikiDeletedDocument deletedDocument = getDeletedDocument(index, context, bTransaction);
            return deletedDocument.restoreDocument(context);
        } finally {
            restoreExecutionXContext();
        }
    }

    @Override
    public XWikiDeletedDocument getDeletedDocument(XWikiDocument doc, final long index, XWikiContext context,
        boolean bTransaction) throws XWikiException
    {
        return getDeletedDocument(index, context, bTransaction);
    }

    @Override
    public XWikiDeletedDocument getDeletedDocument(long index, XWikiContext context, boolean bTransaction)
        throws XWikiException
    {
        return getDeletedDocument(index, context, true, bTransaction);
    }

    private XWikiDeletedDocument getDeletedDocument(final long index, XWikiContext context, boolean resolve,
        boolean bTransaction) throws XWikiException
    {
        return executeRead(context, session -> {
            XWikiDeletedDocument deletedDocument = session.get(XWikiDeletedDocument.class, Long.valueOf(index));

            if (deletedDocument != null && resolve) {
                deletedDocument = resolveDeletedDocumentContent(deletedDocument, false);
            }

            return deletedDocument;
        });
    }

    @Override
    public XWikiDeletedDocument[] getAllDeletedDocuments(XWikiDocument doc, XWikiContext context, boolean bTransaction)
        throws XWikiException
    {
        XWikiDeletedDocument[] deletedDocuments = executeRead(context, new DeletedDocumentsHibernateCallback(doc));

        // Resolve deleted document content if needed
        for (int i = 0; i < deletedDocuments.length; ++i) {
            deletedDocuments[i] = resolveDeletedDocumentContent(deletedDocuments[i], bTransaction);
        }

        return deletedDocuments;
    }

    @Override
    public Long[] getAllDeletedDocumentsIds(XWikiContext context, int limit) throws XWikiException
    {
        return executeRead(context, session -> {
            org.hibernate.query.Query<Long> query =
                session.createQuery("SELECT id FROM XWikiDeletedDocument ORDER BY date DESC", Long.class);

            if (limit > 0) {
                query.setMaxResults(limit);
            }

            List<Long> deletedDocIds = query.list();
            Long[] result = new Long[deletedDocIds.size()];
            return deletedDocIds.toArray(result);
        });
    }

    @Override
    public Long getNumberOfDeletedDocuments(XWikiContext context) throws XWikiException
    {

        return executeRead(context, session -> {
            org.hibernate.query.Query<Long> query =
                session.createQuery("SELECT count(id) FROM XWikiDeletedDocument", Long.class);

            return query.uniqueResult();
        });
    }

    @Override
    public XWikiDeletedDocument[] getAllDeletedDocuments(String batchId, XWikiContext context, boolean bTransaction)
        throws XWikiException
    {
        return getAllDeletedDocuments(batchId, true, context, bTransaction);
    }

    @Override
    public XWikiDeletedDocument[] getAllDeletedDocuments(String batchId, boolean withContent, XWikiContext context,
        boolean bTransaction) throws XWikiException
    {
        XWikiDeletedDocument[] deletedDocuments =
            executeRead(context, new DeletedDocumentsBatchHibernateCallback(batchId));

        // Resolve deleted document content if needed
        if (withContent) {
            for (int i = 0; i < deletedDocuments.length; ++i) {
                XWikiDeletedDocument deletedDocument = deletedDocuments[i];
                deletedDocuments[i] = resolveDeletedDocumentContent(deletedDocument, bTransaction);
            }
        }

        return deletedDocuments;
    }

    @Override
    public void deleteFromRecycleBin(XWikiDocument doc, final long index, XWikiContext context, boolean bTransaction)
        throws XWikiException
    {
        deleteFromRecycleBin(index, context, bTransaction);
    }

    @Override
    public void deleteFromRecycleBin(final long index, XWikiContext context, boolean bTransaction) throws XWikiException
    {
        executeWrite(context, session -> {
            XWikiDeletedDocument deletedDocument = getDeletedDocument(index, context, false, bTransaction);

            // Delete metadata
            session.delete(deletedDocument);

            // Delete content
            deleteDeletedDocumentContent(deletedDocument, bTransaction);

            return null;
        });
    }
}

## 内容7
### 文件路径：
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/store/XWikiRecycleBinStoreInterface.java
### 代码内容：
/*
 * 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.store;

import java.util.Date;

import org.xwiki.component.annotation.Role;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.XWikiDeletedDocument;
import com.xpn.xwiki.doc.XWikiDocument;

/**
 * Interface for RecycleBin feature (XWIKI-543) store system.
 *
 * @version $Id$
 * @since 1.2M1
 */
@Role
public interface XWikiRecycleBinStoreInterface
{
    /**
     * Save document to recycle bin.
     *
     * @param doc - document to save
     * @param deleter - the user which delete document
     * @param date - date of delete action
     * @param bTransaction - should use old transaction(false) or create new (true)
     * @param context - used while saving
     * @throws XWikiException if error in saving
     */
    void saveToRecycleBin(XWikiDocument doc, String deleter, Date date, XWikiContext context, boolean bTransaction)
        throws XWikiException;

    /**
     * Save document to recycle bin.
     *
     * @param doc - document to save
     * @param deleter - the user which delete document
     * @param date - date of delete action
     * @param batchId - id of the operation that deleted multiple documents at the same time, useful when trying to
     *            revert the operation. {@code null} or empty values are ignored
     * @param bTransaction - should use old transaction(false) or create new (true)
     * @param context - used while saving
     * @throws XWikiException if error in saving
     * @since 9.4RC1
     */
    default void saveToRecycleBin(XWikiDocument doc, String deleter, Date date, String batchId, XWikiContext context,
        boolean bTransaction) throws XWikiException
    {
        // XXX: The current signature does not return the saved document index so we have no way of setting the batchId
        // to the save document. This means we can`t completely respect the method`s contract, but at least the most
        // important part of the work is done and the document can be individually restored.
        saveToRecycleBin(doc, deleter, date, context, bTransaction);
    }

    /**
     * @return restored document from recycle bin
     * @param doc - document to restore
     * @param index - what deleted document to restore. see {@link XWikiDeletedDocument#getId()}
     * @param context - used while loading
     * @param bTransaction - should use old transaction(false) or create new (true)
     * @throws XWikiException if error while loading
     * @deprecated since 9.4RC1. The document parameter is useless and gets in the way. Use
     *             {@link #restoreFromRecycleBin(long, XWikiContext, boolean)} instead.
     */
    @Deprecated
    XWikiDocument restoreFromRecycleBin(XWikiDocument doc, long index, XWikiContext context, boolean bTransaction)
        throws XWikiException;

    /**
     * @param index - what deleted document to restore. see {@link XWikiDeletedDocument#getId()}
     * @param context - used while loading
     * @param bTransaction - should use old transaction(false) or create new (true)
     * @return the restored document from recycle bin
     * @throws XWikiException if error while loading
     * @since 9.4RC1
     */
    default XWikiDocument restoreFromRecycleBin(long index, XWikiContext context, boolean bTransaction)
        throws XWikiException
    {
        // XXX: Depending on how an older implementation handled the XWikiDocument argument, it's relatively safer to
        // pass an empty document than null. However, if the document's reference is actually used, the result might be
        // unpredictable.
        return restoreFromRecycleBin(new XWikiDocument(), index, context, bTransaction);
    }

    /**
     * @return specified deleted document from recycle bin. null if not found.
     * @param doc - deleted document
     * @param index - what deleted document to restore. see {@link XWikiDeletedDocument#getId()}
     * @param context - used while loading
     * @param bTransaction - should use old transaction(false) or create new (true)
     * @throws XWikiException if error while loading
     * @deprecated since 9.4RC1. The document parameter is useless and gets in the way. Use
     *             {@link #getDeletedDocument(long, XWikiContext, boolean)} instead.
     */
    @Deprecated
    XWikiDeletedDocument getDeletedDocument(XWikiDocument doc, long index, XWikiContext context, boolean bTransaction)
        throws XWikiException;

    /**
     * @param index - what deleted document to restore. See {@link XWikiDeletedDocument#getId()}
     * @param context - used while loading
     * @param bTransaction - should use old transaction(false) or create new (true)
     * @return specified deleted document from recycle bin or {@code null} if not found.
     * @throws XWikiException if error while loading
     * @since 9.4RC1
     */
    default XWikiDeletedDocument getDeletedDocument(long index, XWikiContext context, boolean bTransaction)
        throws XWikiException
    {
        // XXX: Depending on how an older implementation handled the XWikiDocument argument, it's relatively safer to
        // pass an empty document than null. However, if the document's reference is actually used, the result might be
        // unpredictable.
        return getDeletedDocument(new XWikiDocument(), index, context, bTransaction);
    }

    /**
     * @return info about all delete actions of specific document. sorted by date.
     * @param doc - the deleted document
     * @param context - used to load
     * @param bTransaction - should use old transaction(false) or create new (true)
     * @throws XWikiException - if error in loading
     */
    XWikiDeletedDocument[] getAllDeletedDocuments(XWikiDocument doc, XWikiContext context, boolean bTransaction)
        throws XWikiException;

    /**
     * Get all the deleted documents ID or a specified number. Sorted by date.
     * @param context - used to load the deleted documents id.
     * @param limit - if > 0 then all deleted documents id are returned. Else the specified number.
     * @return an array of IDs of deleted documents.
     * @throws XWikiException - if error in loading
     * @since 10.10RC1
     */
    default Long[] getAllDeletedDocumentsIds(XWikiContext context, int limit) throws XWikiException
    {
        return new Long[0];
    }

    /**
     * @param context - used to realize the query.
     * @return the number of deleted documents in the recycle bin.
     * @throws XWikiException - if error in loading.
     * @since 10.10RC1
     */
    default Long getNumberOfDeletedDocuments(XWikiContext context) throws XWikiException
    {
        return -1L;
    }

    /**
     * @return info about all documents that were deleted in the same batch, as part of the same operation
     * @param batchId - id of the operation that deleted multiple documents at the same time; useful when trying to
     *            revert the operation
     * @param context - used to load
     * @param bTransaction - should use old transaction(false) or create new (true)
     * @throws XWikiException - if error in loading
     * @since 9.4RC1
     */
    default XWikiDeletedDocument[] getAllDeletedDocuments(String batchId, XWikiContext context, boolean bTransaction)
        throws XWikiException
    {
        // Return no results as default implementation.
        return new XWikiDeletedDocument[0];
    }

    /**
     * @param batchId - id of the operation that deleted multiple documents at the same time; useful when trying to
     *            revert the operation
     * @param withContent - {@code true} if the deleted document's content should also be loaded; {@code false} if
     * @param context - used to load
     * @param bTransaction - should use old transaction(false) or create new (true)
     * @return info about all documents that were deleted in the same batch, as part of the same operation
     * @throws XWikiException - if error in loading
     * @since 9.4RC1
     */
    default XWikiDeletedDocument[] getAllDeletedDocuments(String batchId, boolean withContent, XWikiContext context,
        boolean bTransaction) throws XWikiException
    {
        // Return no results as default implementation.
        return new XWikiDeletedDocument[0];
    }

    /**
     * Permanently delete document from recycle bin.
     *
     * @param doc - document to delete
     * @param index - which instance document in recycle bin to delete
     * @param context - used for environment
     * @param bTransaction - should use old transaction(false) or create new (true)
     * @throws XWikiException if any error
     * @deprecated since 9.4RC1. The document parameter is useless and gets in the way. Use
     *             {@link #deleteFromRecycleBin(long, XWikiContext, boolean)} instead.
     */
    @Deprecated
    void deleteFromRecycleBin(XWikiDocument doc, long index, XWikiContext context, boolean bTransaction)
        throws XWikiException;

    /**
     * Permanently delete document from recycle bin.
     *
     * @param index - which instance document in recycle bin to delete
     * @param context - used for environment
     * @param bTransaction - should use old transaction(false) or create new (true)
     * @throws XWikiException if any error
     * @since 9.4RC1
     */
    default void deleteFromRecycleBin(long index, XWikiContext context, boolean bTransaction) throws XWikiException
    {
        // XXX: Depending on how an older implementation handled the XWikiDocument argument, it's relatively safer to
        // pass an empty document than null. However, if the document's reference is actually used, the result might be
        // unpredictable.
        deleteFromRecycleBin(new XWikiDocument(), index, context, bTransaction);
    }
}

## 内容8
### 文件路径：
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/XWikiAction.java
### 代码内容：
/*
 * 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.net.URL;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Vector;

import javax.inject.Inject;
import javax.inject.Named;
import javax.script.ScriptContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.velocity.VelocityContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xwiki.bridge.event.ActionExecutedEvent;
import org.xwiki.bridge.event.ActionExecutingEvent;
import org.xwiki.component.descriptor.ComponentDescriptor;
import org.xwiki.component.manager.ComponentLookupException;
import org.xwiki.component.manager.ComponentManager;
import org.xwiki.component.util.DefaultParameterizedType;
import org.xwiki.container.Container;
import org.xwiki.container.Request;
import org.xwiki.container.servlet.ServletContainerException;
import org.xwiki.container.servlet.ServletContainerInitializer;
import org.xwiki.container.servlet.ServletRequest;
import org.xwiki.context.Execution;
import org.xwiki.context.ExecutionContext;
import org.xwiki.csrf.CSRFToken;
import org.xwiki.internal.web.DocExistValidator;
import org.xwiki.job.event.status.JobProgressManager;
import org.xwiki.job.internal.DefaultJobProgress;
import org.xwiki.localization.ContextualLocalizationManager;
import org.xwiki.localization.LocaleUtils;
import org.xwiki.model.EntityType;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.DocumentReferenceResolver;
import org.xwiki.model.reference.EntityReference;
import org.xwiki.model.reference.EntityReferenceProvider;
import org.xwiki.model.reference.EntityReferenceSerializer;
import org.xwiki.model.reference.EntityReferenceValueProvider;
import org.xwiki.model.reference.SpaceReference;
import org.xwiki.model.validation.EntityNameValidationConfiguration;
import org.xwiki.model.validation.EntityNameValidationManager;
import org.xwiki.observation.ObservationManager;
import org.xwiki.observation.WrappedThreadEventListener;
import org.xwiki.rendering.async.AsyncContext;
import org.xwiki.rendering.internal.transformation.MutableRenderingContext;
import org.xwiki.rendering.syntax.Syntax;
import org.xwiki.rendering.transformation.RenderingContext;
import org.xwiki.resource.NotFoundResourceHandlerException;
import org.xwiki.resource.ResourceReferenceHandler;
import org.xwiki.resource.ResourceReferenceManager;
import org.xwiki.resource.ResourceType;
import org.xwiki.resource.entity.EntityResourceReference;
import org.xwiki.resource.internal.DefaultResourceReferenceHandlerChain;
import org.xwiki.script.ScriptContextManager;
import org.xwiki.security.authorization.ContextualAuthorizationManager;
import org.xwiki.security.authorization.Right;
import org.xwiki.stability.Unstable;
import org.xwiki.template.TemplateManager;
import org.xwiki.velocity.VelocityManager;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.xpn.xwiki.XWiki;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.internal.web.LegacyAction;
import com.xpn.xwiki.monitor.api.MonitorPlugin;
import com.xpn.xwiki.objects.BaseObject;
import com.xpn.xwiki.plugin.fileupload.FileUploadPlugin;
import com.xpn.xwiki.redirection.RedirectionFilter;

/**
 *

 * Root class for most XWiki actions. It provides a common framework that allows actions to execute just the specific
 * action code, handling the extra activities, such as preparing the context and retrieving the document corresponding
 * to the URL.
 *

 *

 * It defines two methods, {@link #action(XWikiContext)} and {@link #render(XWikiContext)}, that should be overridden by
 * specific actions. {@link #action(XWikiContext)} should contain the processing part of the action.
 * {@link #render(XWikiContext)} should return the name of a template that should be rendered, or manually write to the
 * {@link XWikiResponse response} stream.
 *

 *

 * Serving a request goes through the following phases:
 *

 *

 *
Wrapping the request and response object in XWiki specific wrappers

 *
Prepare the request {@link XWikiContext XWiki-specific context}

 *
Initialize/retrieve the XWiki object corresponding to the requested wiki

 *
Handle file uploads

 *
Prepare the velocity context

 *
Prepare the document objects corresponding to the requested URL

 *
Send action pre-notifications to listeners

 *
Run the overridden {@link #action(XWikiContext)}

 *
If {@link #action(XWikiContext)} returns true, run the overridden {@link #render(XWikiContext)}

 *
If {@link #render(XWikiContext)} returned a string (template name), render the template with that name

 *
Send action post-notifications to listeners

 *

 *

 * During this process, also handle specific errors, like when a document does not exist, or the user does not have the
 * right to perform the current action.
 *

 */
public abstract class XWikiAction implements LegacyAction
{
    public static final String ACTION_PROGRESS = "actionprogress";

    private static final Logger LOGGER = LoggerFactory.getLogger(XWikiAction.class);

    /**
     * Actions that need to be resolved on the main wiki instead of the current non-existing wiki. This is used to be
     * able to render the skin even on a wiki that doesn't exist.
     */
    private static final List<String> ACTIONS_IGNORED_WHEN_WIKI_DOES_NOT_EXIST =
        Arrays.asList("skin", "ssx", "jsx", "download");

    @Inject
    protected ComponentDescriptor<LegacyAction> componentDescriptor;

    @Inject
    protected Container container;

    @Inject
    protected Execution execution;

    @Inject
    protected ObservationManager observation;

    @Inject
    @Named("context")
    private ComponentManager componentManager;

    /**
     * Indicate if the action allow asynchronous display (among which the XWiki initialization).
     */
    protected boolean waitForXWikiInitialization = true;

    @Inject
    @Named("currentmixed")
    private DocumentReferenceResolver<String> currentmixedReferenceResolver;

    @Inject
    private ContextualAuthorizationManager autorization;

    private ContextualLocalizationManager localization;

    private JobProgressManager progress;

    private ScriptContextManager scriptContextManager;

    private EntityNameValidationManager entityNameValidationManager;

    private EntityNameValidationConfiguration entityNameValidationConfiguration;

    private EntityReferenceSerializer<String> localSerializer;

    /**
     * @return the class of the XWikiForm in charge of parsing the request
     * @since 13.0
     */
    @Unstable
    protected Class<? extends XWikiForm> getFormClass()
    {
        return null;
    }

    protected ContextualLocalizationManager getLocalization()
    {
        if (this.localization == null) {
            this.localization = Utils.getComponent(ContextualLocalizationManager.class);
        }

        return this.localization;
    }

    /**
     * @since 12.10.6
     * @since 13.2RC1
     */
    protected DocumentReferenceResolver<String> getCurrentMixedDocumentReferenceResolver()
    {
        return this.currentmixedReferenceResolver;
    }

    /**
     * @since 12.10.6
     * @since 13.2RC1
     */
    protected ContextualAuthorizationManager getContextualAuthorizationManager()
    {
        return this.autorization;
    }

    protected String localizePlainOrKey(String key, Object... parameters)
    {
        return StringUtils.defaultString(getLocalization().getTranslationPlain(key, parameters), key);
    }

    protected JobProgressManager getProgress()
    {
        if (this.progress == null) {
            this.progress = Utils.getComponent(JobProgressManager.class);
        }

        return this.progress;
    }

    protected EntityNameValidationManager getEntityNameValidationManager()
    {
        if (this.entityNameValidationManager == null) {
            this.entityNameValidationManager = Utils.getComponent(EntityNameValidationManager.class);
        }
        return this.entityNameValidationManager;
    }

    protected EntityNameValidationConfiguration getEntityNameValidationConfiguration()
    {
        if (this.entityNameValidationConfiguration == null) {
            this.entityNameValidationConfiguration = Utils.getComponent(EntityNameValidationConfiguration.class);
        }

        return this.entityNameValidationConfiguration;
    }

    protected EntityReferenceSerializer<String> getLocalSerializer()
    {
        if (this.localSerializer == null) {
            this.localSerializer = Utils.getComponent(EntityReferenceSerializer.TYPE_STRING, "local");
        }
        return this.localSerializer;
    }

    /**
     * @return the current unmodified {@link ScriptContext} instance
     * @since 8.3M1
     */
    protected ScriptContext getCurrentScriptContext()
    {
        if (this.scriptContextManager == null) {
            this.scriptContextManager = Utils.getComponent(ScriptContextManager.class);
        }

        return this.scriptContextManager.getCurrentScriptContext();
    }

    @Override
    public void execute(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws Exception
    {
        XWikiContext context = null;

        try {
            // Initialize the XWiki Context which is the main object used to pass information across
            // classes/methods. It's also wrapping the request, response, and all container objects
            // in general.
            context = initializeXWikiContext(servletRequest, servletResponse);

            // From this line forward all information can be found in the XWiki Context.
            execute(context);
        } finally {
            if (context != null) {
                cleanupComponents();
            }
        }
    }

    /**
     * Ensure that the given entity reference is valid according to the configured name strategy. Always returns true if
     * the name strategy is not found.
     *
     * @param entityReference the entity reference name to validate
     * @return {@code true} if the entity reference name is valid according to the name strategy.
     * @since 12.0RC1
     */
    protected boolean isEntityReferenceNameValid(EntityReference entityReference)
    {
        if (this.getEntityNameValidationManager().getEntityReferenceNameStrategy() != null
            && this.getEntityNameValidationConfiguration().useValidation()) {
            if (!this.getEntityNameValidationManager().getEntityReferenceNameStrategy().isValid(entityReference)) {
                Object[] args = {getLocalSerializer().serialize(entityReference)};
                XWikiException invalidNameException = new XWikiException(XWikiException.MODULE_XWIKI_STORE,
                    XWikiException.ERROR_XWIKI_APP_DOCUMENT_NAME_INVALID,
                    "Cannot create document {0} because its name does not respect the name strategy of the wiki.", null,
                    args);
                ScriptContext scontext = getCurrentScriptContext();
                scontext.setAttribute("invalidNameReference", entityReference, ScriptContext.ENGINE_SCOPE);
                scontext.setAttribute("createException", invalidNameException, ScriptContext.ENGINE_SCOPE);
                return false;
            }
        }
        return true;
    }

    /**
     * Write an error response to an ajax request.
     *
     * @param httpStatusCode The status code to set on the response.
     * @param message The message that should be displayed.
     * @param context the context.
     */
    protected void writeAjaxErrorResponse(int httpStatusCode, String message, XWikiContext context)
    {
        try {
            context.getResponse().setContentType("text/plain");
            context.getResponse().setStatus(httpStatusCode);
            context.getResponse().setCharacterEncoding(context.getWiki().getEncoding());
            context.getResponse().getWriter().print(message);
        } catch (IOException e) {
            LOGGER.error("Failed to send error response to AJAX save and continue request.", e);
        }
    }

    public void execute(XWikiContext context) throws Exception
    {
        MonitorPlugin monitor = null;
        FileUploadPlugin fileupload = null;
        DefaultJobProgress actionProgress = null;
        String docName = "";

        boolean debug = StringUtils.equals(context.getRequest().get("debug"), "true");

        String sasync = context.getRequest().get("async");

        try {
            String action = context.getAction();

            // Start progress
            if (debug) {
                actionProgress = new DefaultJobProgress(context.getURL().toExternalForm());
                this.observation.addListener(new WrappedThreadEventListener(actionProgress));

                // Register the action progress in the context
                ExecutionContext econtext = this.execution.getContext();
                if (econtext != null) {
                    econtext.setProperty(XWikiAction.ACTION_PROGRESS, actionProgress);
                }
            }

            getProgress().pushLevelProgress(2, this);

            getProgress().startStep(this, "Get XWiki instance");

            // Initialize context.getWiki() with the main wiki
            XWiki xwiki;

            // Verify that the requested wiki exists
            try {
                // Don't show init screen if async is forced to false
                xwiki = XWiki.getXWiki(this.waitForXWikiInitialization || StringUtils.equals(sasync, "false"), context);

                // If XWiki is still initializing display initialization template
                if (xwiki == null) {
                    // Display initialization template
                    renderInit(context);

                    // Initialization template has been displayed, stop here.
                    return;
                }
            } catch (XWikiException e) {
                // If the wiki asked by the user doesn't exist, then we first attempt to use any existing global
                // redirects. If there are none, then we display the specific error template.
                if (e.getCode() == XWikiException.ERROR_XWIKI_DOES_NOT_EXIST) {
                    xwiki = XWiki.getMainXWiki(context);

                    // Initialize the url factory
                    XWikiURLFactory urlf = xwiki.getURLFactoryService().createURLFactory(context.getMode(), context);
                    context.setURLFactory(urlf);

                    // Initialize the velocity context and its bindings so that it may be used in the velocity templates
                    // that we
                    // are parsing below.
                    VelocityManager velocityManager = Utils.getComponent(VelocityManager.class);
                    VelocityContext vcontext = velocityManager.getVelocityContext();

                    if (!sendGlobalRedirect(context.getResponse(), context.getURL().toString(), context)) {
                        // Starting XWiki 5.0M2, 'xwiki.virtual.redirect' was removed. Warn users still using it.
                        if (!StringUtils.isEmpty(context.getWiki().Param("xwiki.virtual.redirect"))) {
                            LOGGER.warn(String.format("%s %s", "'xwiki.virtual.redirect' is no longer supported.",
                                "Please update your configuration and/or see XWIKI-8914 for more details."));
                        }

                        // Display the error template only for actions that are not ignored
                        if (!ACTIONS_IGNORED_WHEN_WIKI_DOES_NOT_EXIST.contains(action)) {

                            // Add localization resources to the context
                            xwiki.prepareResources(context);

                            // Set the main home page in the main space of the main wiki as the current requested entity
                            // since we cannot set the non existing one as it would generate errors obviously...
                            EntityReferenceValueProvider valueProvider =
                                Utils.getComponent(EntityReferenceValueProvider.class);
                            xwiki.setPhonyDocument(new DocumentReference(valueProvider.getDefaultValue(EntityType.WIKI),
                                valueProvider.getDefaultValue(EntityType.SPACE),
                                valueProvider.getDefaultValue(EntityType.DOCUMENT)), context, vcontext);

                            // Parse the error template
                            Utils.parseTemplate(context.getWiki().Param("xwiki.wiki_exception", "wikidoesnotexist"),
                                context);

                            // Error template was displayed, stop here.
                            return;
                        }

                        // At this point, we allow regular execution of the ignored action because even if the wiki
                        // does not exist, we still need to allow UI resources to be retrieved (from the filesystem
                        // and the main wiki) or our error template will not be rendered properly.

                        // Proceed with serving the main wiki

                    } else {
                        // Global redirect was executed, stop here.
                        return;
                    }
                } else {
                    LOGGER.error("Uncaught exception during XWiki initialisation:", e);
                    throw e;
                }
            }

            // Send global redirection (if any)
            if (sendGlobalRedirect(context.getResponse(), context.getURL().toString(), context)) {
                return;
            }

            XWikiURLFactory urlf = xwiki.getURLFactoryService().createURLFactory(context.getMode(), context);
            context.setURLFactory(urlf);

            // Handle ability to enter space URLs and convert them to page URLs (Nested Documents)
            if (redirectSpaceURLs(action, urlf, xwiki, context)) {
                return;
            }

            String sajax = context.getRequest().get("ajax");
            boolean ajax = false;
            if (sajax != null && !sajax.trim().equals("") && !sajax.equals("0")) {
                ajax = true;
            }
            context.put("ajax", ajax);

            boolean async = false;
            if (StringUtils.isNotEmpty(sasync)) {
                async = sasync.equals("true");
            } else {
                // By default allow asynchronous rendering for "human oriented" actions which are not executing an ajax
                // request
                async = !ajax && !this.waitForXWikiInitialization;
            }
            Utils.getComponent(AsyncContext.class).setEnabled(async);

            // Any error before this will be treated using a redirection to an error page

            if (monitor != null) {
                monitor.startTimer("request");
            }

            getProgress().startStep(this, "Execute request");

            VelocityManager velocityManager = Utils.getComponent(VelocityManager.class);
            VelocityContext vcontext = velocityManager.getVelocityContext();

            getProgress().pushLevelProgress(7, this);

            boolean eventSent = false;
            try {
                getProgress().startStep(this, "Prepare documents and put them in the context");

                // Prepare documents and put them in the context
                if (!xwiki.prepareDocuments(context.getRequest(), context, vcontext)) {
                    return;
                }

                // Start monitoring timer
                monitor = (MonitorPlugin) xwiki.getPlugin("monitor", context);
                if (monitor != null) {
                    monitor.startRequest("", context.getAction(), context.getURL());
                    monitor.startTimer("multipart");
                }

                getProgress().startStep(this, "Parses multipart");

                // Parses multipart so that params in multipart are available for all actions
                fileupload = Utils.handleMultipart(context.getRequest().getHttpServletRequest(), context);
                if (monitor != null) {
                    monitor.endTimer("multipart");
                }

                if (monitor != null) {
                    monitor.setWikiPage(context.getDoc().getFullName());
                }

                getProgress().startStep(this, "Send [" + context.getAction() + "] action start event");

                // For the moment we're sending the XWiki context as the data, but this will be
                // changed in the future, when the whole platform will be written using components
                // and there won't be a need for the context.
                try {
                    ActionExecutingEvent event = new ActionExecutingEvent(context.getAction());
                    this.observation.notify(event, context.getDoc(), context);
                    eventSent = true;
                    if (event.isCanceled()) {
                        // Action has been canceled
                        // TODO: do something special ?
                        return;
                    }
                } catch (Throwable ex) {
                    LOGGER.error("Cannot send action notifications for document [" + context.getDoc()
                        + " using action [" + context.getAction() + "]", ex);
                }

                if (monitor != null) {
                    monitor.endTimer("prenotify");
                }

                // Call the Actions

                getProgress().startStep(this, "Search and execute entity resource handler");

                // Call the new Entity Resource Reference Handler.
                ResourceReferenceHandler entityResourceReferenceHandler = Utils.getComponent(
                    new DefaultParameterizedType(null, ResourceReferenceHandler.class, ResourceType.class), "bin");
                EntityResourceReference entityResourceReference =
                    (EntityResourceReference) Utils.getComponent(ResourceReferenceManager.class).getResourceReference();

                // We save the current action set since:
                // - by default the action is set to "view" for Extensions not installed as root and contributing some
                // new Entity Action (see https://jira.xwiki.org/browse/XWIKI-15182).
                // - we want to set back the action in case no ResourceReferenceHandler was found to handle the URL
                // TODO: Remove once https://jira.xwiki.org/browse/XWIKI-14947 is fixed
                String originalAction = context.getAction();
                try {
                    // Force the action in the context because of https://jira.xwiki.org/browse/XWIKI-15182.
                    // TODO: Remove once https://jira.xwiki.org/browse/XWIKI-14947 is fixed
                    context.setAction(entityResourceReference.getAction().getActionName());
                    entityResourceReferenceHandler.handle(entityResourceReference,
                        DefaultResourceReferenceHandlerChain.EMPTY);
                    // Don't let the old actions kick in!
                    return;
                } catch (NotFoundResourceHandlerException e) {
                    // No Entity Resource Action has been found. Don't do anything and let it go through
                    // so that the old Action system kicks in...
                    // Put back the action, because of https://jira.xwiki.org/browse/XWIKI-15182
                    // TODO: Remove once https://jira.xwiki.org/browse/XWIKI-14947 is fixed
                    context.setAction(originalAction);
                }

                getProgress().startStep(this, "Execute action render");

                // Handle the XWiki.RedirectClass object that can be attached to the current document
                boolean hasRedirect = handleRedirect(context);

                // Then call the old Actions for backward compatibility (and because a lot of them have not been
                // migrated to new Actions yet).
                String renderResult = null;
                XWikiDocument doc = context.getDoc();
                docName = doc.getFullName();
                if (!hasRedirect && action(context)) {
                    renderResult = render(context);
                }

                if (renderResult != null) {
                    // check for doc existence
                    if (shouldReturnDocDoesNotExist(doc, context)) {
                        String page = Utils.getPage(context.getRequest(), "docdoesnotexist");

                        getProgress().startStep(this, "Execute template [" + page + "]");
                        Utils.parseTemplate(page, context);
                    } else {
                        String page = Utils.getPage(context.getRequest(), renderResult);

                        getProgress().startStep(this, "Execute template [" + page + "]");
                        Utils.parseTemplate(page, !page.equals("direct"), context);
                    }
                }
                return;
            } catch (Throwable e) {
                if (e instanceof IOException) {
                    e = new XWikiException(XWikiException.MODULE_XWIKI_APP,
                        XWikiException.ERROR_XWIKI_APP_SEND_RESPONSE_EXCEPTION, "Exception while sending response", e);
                }

                if (!(e instanceof XWikiException)) {
                    e = new XWikiException(XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_UNKNOWN,
                        "Uncaught exception", e);
                }

                try {
                    XWikiException xex = (XWikiException) e;
                    if (xex.getCode() == XWikiException.ERROR_XWIKI_APP_SEND_RESPONSE_EXCEPTION) {
                        // Connection aborted from the client side, there's not much we can do on the server side. We
                        // simply ignore it.
                        LOGGER.debug("Connection aborted", e);
                        // We don't write any other message to the response, as the connection is broken, anyway.
                        return;
                    } else if (xex.getCode() == XWikiException.ERROR_XWIKI_ACCESS_DENIED) {
                        Utils.parseTemplate(context.getWiki().Param("xwiki.access_exception", "accessdenied"), context);
                        return;
                    } else if (xex.getCode() == XWikiException.ERROR_XWIKI_USER_INACTIVE
                        || xex.getCode() == XWikiException.ERROR_XWIKI_USER_DISABLED) {
                        if (xex.getCode() == XWikiException.ERROR_XWIKI_USER_DISABLED) {
                            context.put("cause", "disabled");
                        }
                        // In case of user disabled or inactive, the resources are actually forbidden.
                        context.getResponse().setStatus(HttpServletResponse.SC_FORBIDDEN);
                        Utils.parseTemplate(context.getWiki().Param("xwiki.user_exception", "userinactive"), context);

                        return;
                    } else if (xex.getCode() == XWikiException.ERROR_XWIKI_APP_ATTACHMENT_NOT_FOUND) {
                        context.put("message", "attachmentdoesnotexist");
                        Utils.parseTemplate(
                            context.getWiki().Param("xwiki.attachment_exception", "attachmentdoesnotexist"), context);
                        return;
                    } else if (xex.getCode() == XWikiException.ERROR_XWIKI_APP_URL_EXCEPTION) {
                        vcontext.put("message", localizePlainOrKey("platform.core.invalidUrl"));
                        xwiki.setPhonyDocument(xwiki.getDefaultSpace(context) + "." + xwiki.getDefaultPage(context),
                            context, vcontext);
                        context.getResponse().setStatus(HttpServletResponse.SC_BAD_REQUEST);
                        Utils.parseTemplate(context.getWiki().Param("xwiki.invalid_url_exception", "error"), context);
                        return;
                    }
                    // Note: We don't use the vcontext variable computed above since apparently the velocity context
                    // can have changed in between. Thus we get it again to be sure we're setting the binding in the
                    // right one.
                    velocityManager.getVelocityContext().put("exp", e);
                    if (LOGGER.isWarnEnabled()) {
                        // Don't log "Broken Pipe" exceptions since they're not real errors and we don't want to pollute
                        // the logs with unnecessary stack traces. It just means the client side has cancelled the
                        // connection.
                        if (ExceptionUtils.getRootCauseMessage(e).equals("IOException: Broken pipe")) {
                            return;
                        }
                        LOGGER.warn("Uncaught exception: " + e.getMessage(), e);
                    }
                    // If the request is an AJAX request, we don't return a whole HTML page, but just the exception
                    // inline.
                    String exceptionTemplate = ajax ? "exceptioninline" : "exception";
                    Utils.parseTemplate(Utils.getPage(context.getRequest(), exceptionTemplate), context);
                    return;
                } catch (XWikiException ex) {
                    if (ex.getCode() == XWikiException.ERROR_XWIKI_APP_SEND_RESPONSE_EXCEPTION) {
                        LOGGER.error("Connection aborted");
                    }
                } catch (Exception e2) {
                    // I hope this never happens
                    LOGGER.error("Uncaught exceptions (inner): ", e);
                    LOGGER.error("Uncaught exceptions (outer): ", e2);
                }
                return;
            } finally {
                // Let's make sure we have flushed content and closed
                try {
                    context.getResponse().getWriter().flush();
                } catch (Throwable e) {
                    // This might happen if the connection was closed, for example.
                    // If we can't flush, then there's nothing more we can send to the client.
                }

                if (monitor != null) {
                    monitor.endTimer("request");
                    monitor.startTimer("notify");
                }

                if (eventSent) {
                    // For the moment we're sending the XWiki context as the data, but this will be
                    // changed in the future, when the whole platform will be written using components
                    // and there won't be a need for the context.
                    try {
                        this.observation.notify(new ActionExecutedEvent(context.getAction()), context.getDoc(), context);
                    } catch (Throwable ex) {
                        LOGGER.error("Cannot send action notifications for document [" + docName + " using action ["
                            + context.getAction() + "]", ex);
                    }
                }

                if (monitor != null) {
                    monitor.endTimer("notify");
                }

                getProgress().startStep(this, "Cleanup database connections");

                // Make sure we cleanup database connections
                // There could be cases where we have some
                xwiki.getStore().cleanUp(context);

                getProgress().popLevelProgress(this);
            }
        } finally {
            // End request
            if (monitor != null) {
                monitor.endRequest();
            }

            // Stop progress
            if (actionProgress != null) {
                getProgress().popLevelProgress(this);

                this.observation.removeListener(actionProgress.getName());
            }

            if (fileupload != null) {
                fileupload.cleanFileList(context);
            }
        }
    }

    /**
     * Check if the given document exists or not and if it should return a 404 based on the context. A {@link
     * DocExistValidator} with an hint matching the current action is used to check if the document exists. When no
     * {@link DocExistValidator} is found, the response is always {@code false} When a {@link DocExistValidator} is
     * found, the result is delegated to {@link DocExistValidator#docExist(XWikiDocument, XWikiContext)}.
     *
     * @param doc the doc for which to check it exists or not
     * @param context the current context
     * @return {@code true} if we should return a 404
     * @throws ComponentLookupException if an error occurs when instantiating a {@link DocExistValidator}
     */
    private boolean shouldReturnDocDoesNotExist(XWikiDocument doc, XWikiContext context) throws ComponentLookupException
    {
        boolean result = false;
        String action = context.getAction();
        if (this.componentManager.hasComponent(DocExistValidator.class, action)) {
            result = this.componentManager.<DocExistValidator>getInstance(DocExistValidator.class, action)
                .docExist(doc, context);
        }
        return result;
    }

    private void renderInit(XWikiContext xcontext) throws Exception
    {
        RenderingContext renderingContext = Utils.getComponent(RenderingContext.class);
        MutableRenderingContext mutableRenderingContext =
            renderingContext instanceof MutableRenderingContext ? (MutableRenderingContext) renderingContext : null;

        if (mutableRenderingContext != null) {
            mutableRenderingContext.push(renderingContext.getTransformation(), renderingContext.getXDOM(),
                renderingContext.getDefaultSyntax(), "init.vm", renderingContext.isRestricted(), Syntax.XHTML_1_0);
        }

        xcontext.getResponse().setStatus(202);
        xcontext.getResponse().setContentType("text/html; charset=UTF-8");

        try {
            Utils.getComponent(TemplateManager.class).render("init.vm", xcontext.getResponse().getWriter());
        } finally {
            if (mutableRenderingContext != null) {
                mutableRenderingContext.pop();
            }
        }

        xcontext.getResponse().flushBuffer();

        xcontext.setFinished(true);
    }

    protected XWikiContext initializeXWikiContext(HttpServletRequest servletRequest,
        HttpServletResponse servletResponse)
        throws XWikiException, ServletException, InstantiationException, IllegalAccessException
    {
        XWikiForm form;
        if (getFormClass() != null) {
            form = getFormClass().newInstance();
        } else {
            form = null;
        }

        return initializeXWikiContext(servletRequest, servletResponse, form);
    }

    /**
     * @return the name to put in the {@link XWikiContext}, by default the component role hint is used
     * @since 13.0
     */
    @Unstable
    protected String getName()
    {
        return this.componentDescriptor.getRoleHint();
    }

    protected XWikiContext initializeXWikiContext(HttpServletRequest servletRequest,
        HttpServletResponse servletResponse, XWikiForm form) throws XWikiException, ServletException
    {
        String action = getName();

        XWikiRequest request = new XWikiServletRequest(servletRequest);
        XWikiResponse response = new XWikiServletResponse(servletResponse);
        XWikiContext context = Utils.prepareContext(action, request, response,
            new XWikiServletContext(servletRequest.getServletContext()));

        if (form != null) {
            form.reset(request);
        }

        // Add the form to the context
        context.setForm(form);

        // Initialize the Container component which is the new way of transporting the Context in the new
        // component architecture.
        initializeContainerComponent(context);

        return context;
    }

    protected void initializeContainerComponent(XWikiContext context) throws ServletException
    {
        // Initialize the Container fields (request, response, session).
        // Note that this is a bridge between the old core and the component architecture.
        // In the new component architecture we use ThreadLocal to transport the request,
        // response and session to components which require them.
        // In the future this Servlet will be replaced by the XWikiPlexusServlet Servlet.
        ServletContainerInitializer containerInitializer = Utils.getComponent(ServletContainerInitializer.class);

        try {
            containerInitializer.initializeRequest(context.getRequest().getHttpServletRequest(), context);
            containerInitializer.initializeResponse(context.getResponse());
            containerInitializer.initializeSession(context.getRequest().getHttpServletRequest());
        } catch (ServletContainerException e) {
            throw new ServletException("Failed to initialize Request/Response or Session", e);
        }
    }

    protected void cleanupComponents()
    {
        // We must ensure we clean the ThreadLocal variables located in the Container and Execution
        // components as otherwise we will have a potential memory leak.
        container.removeRequest();
        container.removeResponse();
        container.removeSession();
        execution.removeContext();
    }

    public String getRealPath(String path)
    {
        Request request = this.container.getRequest();

        if (request instanceof ServletRequest) {
            return ((ServletRequest) request).getHttpServletRequest().getServletContext().getRealPath(path);
        }

        return null;
    }

    // hook
    public boolean action(XWikiContext context) throws XWikiException
    {
        return true;
    }

    // hook
    public String render(XWikiContext context) throws XWikiException
    {
        return null;
    }

    /**
     * Indicate if the action support redirection. The default value is {@code false}.
     *
     * @return {@code true} if the action supports redirections, {@code false} otherwise
     * @since 14.0RC1
     */
    @Unstable
    protected boolean supportRedirections()
    {
        return false;
    }

    protected void handleRevision(XWikiContext context) throws XWikiException
    {
        String rev = context.getRequest().getParameter("rev");
        if (rev != null) {
            context.put("rev", rev);
            XWikiDocument doc = (XWikiDocument) context.get("doc");
            XWikiDocument tdoc = (XWikiDocument) context.get("tdoc");
            // if the doc is deleted and we request a specific language, we have to set the locale so we can retrieve
            // properly the document revision.
            if (rev.startsWith("deleted") && !StringUtils.isEmpty(context.getRequest().getParameter("language"))
                && doc == tdoc) {
                Locale locale = LocaleUtils.toLocale(context.getRequest().getParameter("language"), Locale.ROOT);
                tdoc = new XWikiDocument(tdoc.getDocumentReference(), locale);
            }
            XWikiDocument rdoc =
                (!doc.getLocale().equals(tdoc.getLocale())) ? doc : context.getWiki().getDocument(doc, rev, context);

            XWikiDocument rtdoc =
                (doc.getLocale().equals(tdoc.getLocale())) ? rdoc : context.getWiki().getDocument(tdoc, rev, context);

            context.put("tdoc", rtdoc);
            context.put("cdoc", rdoc);
            context.put("doc", rdoc);
        }
    }

    /**
     * Send redirection based on a regexp pattern (if any) set at the main wiki level. To enable this feature you must
     * add xwiki.preferences.redirect=1 to your xwiki.cfg.
     *
     * @param response the servlet response
     * @param url url of the request
     * @param context the XWiki context
     * @return true if a redirection has been sent
     */
    protected boolean sendGlobalRedirect(XWikiResponse response, String url, XWikiContext context) throws Exception
    {
        if ("1".equals(context.getWiki().Param("xwiki.preferences.redirect"))) {
            // Note: This implementation is not performant at all and will slow down the wiki as the number
            // of redirects increases. A better implementation would use a cache of redirects and would use
            // the notification mechanism to update the cache when the XWiki.XWikiPreferences document is
            // modified.
            XWikiDocument globalPreferences = context.getWiki().getDocument("xwiki:XWiki.XWikiPreferences", context);
            Vector<BaseObject> redirects = globalPreferences.getObjects("XWiki.GlobalRedirect");

            if (redirects != null) {
                for (BaseObject redir : redirects) {
                    if (redir != null) {
                        String p = redir.getStringValue("pattern");
                        if (p != null && url.matches(p)) {
                            String dest = redir.getStringValue("destination");
                            response.sendRedirect(url.replaceAll(p, dest));
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    /**
     * Perform a redirect to the given URL.
     * @param response the response to use to perform the redirect
     * @param url the location of the redirect
     * @throws XWikiException in case of IOException when performing the redirect.
     */
    protected void sendRedirect(XWikiResponse response, String url) throws XWikiException
    {
        try {
            if (url != null) {
                response.sendRedirect(response.encodeRedirectURL(url));
            }
        } catch (IOException e) {
            Object[] args = {url};
            throw new XWikiException(XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_APP_REDIRECT_EXCEPTION,
                "Exception while sending redirect to page {0}", e, args);
        }
    }

    /**
     * Gets the translated version of a document, in the specified language. If the translation does not exist, a new
     * document translation is created. If the requested language does not correspond to a translation (is not defined
     * or is the same as the main document), then the main document is returned.
     *
     * @param doc the main (default, untranslated) document to translate
     * @param language the requested document language
     * @param context the current request context
     * @return the translated document, or the original untranslated document if the requested language is not a
     *         translation
     * @throws XWikiException if the translation cannot be retrieved from the database
     */
    protected XWikiDocument getTranslatedDocument(XWikiDocument doc, String language, XWikiContext context)
        throws XWikiException
    {
        XWikiDocument tdoc;
        if (StringUtils.isBlank(language) || language.equals("default") || language.equals(doc.getDefaultLanguage())) {
            tdoc = doc;
        } else {
            tdoc = doc.getTranslatedDocument(language, context);
            if (tdoc == doc) {
                tdoc = new XWikiDocument(doc.getDocumentReference());
                tdoc.setLanguage(language);
                tdoc.setStore(doc.getStore());
            }
            tdoc.setTranslation(1);
        }
        return tdoc;
    }

    /**
     * Perform CSRF check and redirect to the resubmission page if needed. Throws an exception if the access should be
     * denied, returns false if the check failed and the user will be redirected to a resubmission page.
     *
     * @param context current xwiki context containing the request
     * @return true if the check succeeded, false if resubmission is needed
     * @throws XWikiException if the check fails
     */
    protected boolean csrfTokenCheck(XWikiContext context) throws XWikiException
    {
        return csrfTokenCheck(context, false);
    }

    /**
     * Perform CSRF check and redirect to the resubmission page if needed. Throws an exception if the access should be
     * denied, returns false if the check failed and the user will be redirected to a resubmission page.
     *
     * @param context current xwiki context containing the request
     * @param jsonAnswer if true, returns a JSON answer in case of AJAX request: allow to process it properly on client.
     * @return true if the check succeeded, false if resubmission is needed
     * @throws XWikiException if the check fails
     * @since 11.3RC1
     */
    protected boolean csrfTokenCheck(XWikiContext context, boolean jsonAnswer) throws XWikiException
    {
        final boolean isAjaxRequest = Utils.isAjaxRequest(context);
        CSRFToken csrf = Utils.getComponent(CSRFToken.class);
        try {
            String token = context.getRequest().getParameter("form_token");
            if (!csrf.isTokenValid(token)) {
                if (isAjaxRequest) {
                    if (jsonAnswer) {
                        Map<String, String> jsonObject = new LinkedHashMap<>();
                        jsonObject.put("errorType", "CSRF");
                        jsonObject.put("resubmissionURI", csrf.getRequestURI());
                        jsonObject.put("newToken", csrf.getToken());
                        this.answerJSON(context, HttpServletResponse.SC_FORBIDDEN, jsonObject);
                    } else {
                        final String csrfCheckFailedMessage = localizePlainOrKey("core.editors.csrfCheckFailed");
                        writeAjaxErrorResponse(HttpServletResponse.SC_FORBIDDEN, csrfCheckFailedMessage, context);
                    }
                } else {
                    sendRedirect(context.getResponse(), csrf.getResubmissionURL());
                }

                return false;
            }
        } catch (XWikiException exception) {
            // too bad
            throw new XWikiException(XWikiException.MODULE_XWIKI_ACCESS, XWikiException.ERROR_XWIKI_ACCESS_DENIED,
                "Access denied, secret token verification failed", exception);
        }
        return true;
    }

    /**
     * In order to let users enter URLs to Spaces we do the following when receiving {@code /A/B} (where A and B are
     * spaces):
     *

     *
check that the action is "view" (we only support this for the view action since otherwise this would break
     * apps written before this concept was introduced in XWiki 7.2M1)

     *
if A.B exists then continue

     *
if A.B doesn't exist then forward to A.B.WebHome

     *

     * In order to disable this redirect you should provide the {@code spaceRedirect=false} Query String parameter and
     * value.
     *
     * @since 7.2M1
     */
    private boolean redirectSpaceURLs(String action, XWikiURLFactory urlf, XWiki xwiki, XWikiContext context)
        throws Exception
    {
        if ("view".equals(action) && !"false".equalsIgnoreCase(context.getRequest().getParameter("spaceRedirect"))) {
            DocumentReference reference = xwiki.getDocumentReference(context.getRequest(), context);
            if (!xwiki.exists(reference, context)) {
                String defaultDocumentName = Utils.getComponent(EntityReferenceProvider.class)
                    .getDefaultReference(EntityType.DOCUMENT).getName();
                // Avoid an infinite loop by ensuring we're not on a WebHome already
                if (!reference.getName().equals(defaultDocumentName)) {
                    // Consider the reference as a Space Reference and Construct a new reference to the home of that
                    // Space. Then generate the URL for it and forward to it
                    SpaceReference spaceReference = new SpaceReference(reference.getName(), reference.getParent());
                    // Extract the anchor
                    String anchor = new URL(context.getRequest().getRequestURL().toString()).getRef();
                    URL forwardURL = urlf.createURL(getLocalSerializer().serialize(spaceReference), defaultDocumentName,
                        action, context.getRequest().getQueryString(), anchor,
                        spaceReference.getWikiReference().getName(), context);
                    // Since createURL() contain the webapp context and since RequestDispatcher should not contain it,
                    // we need to remove it!
                    String webappContext = xwiki.getWebAppPath(context);
                    String relativeURL = urlf.getURL(forwardURL, context);
                    relativeURL = '/' + StringUtils.substringAfter(relativeURL, webappContext);
                    context.getRequest().getRequestDispatcher(relativeURL).forward(context.getRequest(),
                        context.getResponse());
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Answer to a request with a JSON content.
     *
     * @param context the current context of the request.
     * @param status the status code to send back.
     * @param answer the content of the JSON answer.
     * @throws XWikiException in case of error during the serialization of the JSON.
     */
    protected void answerJSON(XWikiContext context, int status, Map<String, String> answer) throws XWikiException
    {
        ObjectMapper mapper = new ObjectMapper();

        try {
            String jsonAnswerAsString = mapper.writeValueAsString(answer);
            context.getResponse().setContentType("application/json");
            context.getResponse().setContentLength(jsonAnswerAsString.length());
            context.getResponse().setStatus(status);
            context.getResponse().setCharacterEncoding(context.getWiki().getEncoding());
            context.getResponse().getWriter().print(jsonAnswerAsString);
            context.setResponseSent(true);
        } catch (IOException e) {
            throw new XWikiException("Error while sending JSON answer.", e);
        }
    }

    /**
     * Make sure to set the right length (or nothing) in the response.
     *
     * @param response the response
     * @param length the length to set in the response
     * @since 11.10
     * @since 10.11.10
     * @since 11.3.6
     */
    protected void setContentLength(XWikiResponse response, long length)
    {
        // Set the content length in the response
        response.setContentLengthLong(length);
    }

    /**
     * Helper used resolve the template passed to the action if the current user have access to it.
     *
     * @param template the template to copy
     * @return the reference of the template if not empty and the current user have access to it
     * @since 12.10.6
     * @since 13.2RC1
     */
    protected DocumentReference resolveTemplate(String template)
    {
        if (StringUtils.isNotBlank(template)) {
            DocumentReference templateReference = getCurrentMixedDocumentReferenceResolver().resolve(template);

            // Make sure the current user have access to the template document before copying it
            if (getContextualAuthorizationManager().hasAccess(Right.VIEW, templateReference)) {
                return templateReference;
            }
        }

        return null;
    }

    /**
     * Helper used by various actions to initialize a document by copying a template to it.
     *
     * @param document the document to update
     * @param template the template to copy
     * @param context the XWiki context
     * @return true if the document was updated, false otherwise (for example when the current user does not have view
     *         right on the template document)
     * @throws XWikiException when failing to copy the template
     * @since 12.10.6
     * @since 13.2RC1
     */
    @Unstable
    protected boolean readFromTemplate(XWikiDocument document, String template, XWikiContext context)
        throws XWikiException
    {
        DocumentReference templateReference = resolveTemplate(template);

        if (templateReference != null) {
            document.readFromTemplate(templateReference, context);

            return true;
        }

        return false;
    }

    /**
     * Loop over the {@link RedirectionFilter} components until one of them perform a redirection. If none of the does,
     * the action continues normally.
     *
     * @param context the current wiki content
     * @return {@code true} if a redirection has been performed, {@code false} otherwise
     * @throws XWikiException in case of error during the execution of a redirection filter
     */
    private boolean handleRedirect(XWikiContext context) throws XWikiException
    {
        // If no redirection are expected, this step is skipped.
        if (this.supportRedirections()) {
            try {
                for (RedirectionFilter filter : this.componentManager.<RedirectionFilter>getInstanceList(
                    RedirectionFilter.class)) {
                    if (filter.redirect(context)) {
                        return true;
                    }
                }
            } catch (ComponentLookupException e) {
                throw new XWikiException("Failed to resolve the redirection filters list", e);
            }
        }
        return false;
    }
}

## 内容9
### 文件路径：
xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/main/resources/templates/recyclebinlist.vm
### 代码内容：
## ---------------------------------------------------------------------------
## 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.
## ---------------------------------------------------------------------------
### Show list of deleted documents
#set($dds = $xwiki.getDeletedDocuments($tdoc.fullName, $tdoc.locale))
#displayDeletedDocuments($dds, 'docs')

#if ($doc.documentReference.name == 'WebHome')
  ## Also display the list of terminal documents sharing the same location.
  ## eg. If the current document is: "A.B.WebHome", we should also display any deleted terminal page called "A.B".
  ## This is important because when the user delete a terminal document and go back to the location, she should see
  ## the page she have just deleted, and not an empty page because A.B.WebHome has never existed...
  #set($dds = $xwiki.getDeletedDocuments($tdoc.space, $tdoc.locale))
  #displayDeletedDocuments($dds, 'terminal-docs', 'core.recyclebin.showListTerminalPagesMsg')
#end

#*
 * Display the given list of deleted documents
 * @param $list the list to display
 * @param $className the name of the css class to apply to the list
 * @param $message (optional) the translation key for the message which introduce the list
 *#
#macro (displayDeletedDocuments $list $className $message)
  #if (!$message)
    #set ($message = 'core.recyclebin.showlistmsg')
  #end
  #if($list && $list.size() > 0)
    #set ($canDelete = $list[0].canDelete())
    #set ($canRestore = $list[0].canUndelete())

$escapetool.xml($services.localization.render($message))

         $escapetool.xml($services.localization.render('core.recyclebin.deleter'))
         $escapetool.xml($services.localization.render('core.recyclebin.deleteDate'))
         #if ($canRestore)
           $escapetool.xml($services.localization.render('core.recyclebin.batchId'))
         #end
         $escapetool.xml($services.localization.render('core.recyclebin.actions'))

       #foreach($dd in $list)

         $xwiki.getUserName($dd.getDeleter())

             $xwiki.formatDate($dd.getDate())

         #if ($canRestore)
           $!{dd.batchId}
         #end

           #if($canRestore)

               $escapetool.xml($services.localization.render('core.recyclebin.restore'))

           #end

          #if($canDelete)

            $escapetool.xml($services.localization.render('core.recyclebin.delete'))

          #end

       #end

  #end
#end

Output:
【漏洞类型】权限检查不足
【严重性】High
【漏洞摘要】原始代码中，`DeletedDocument`类的`canUndelete`和`canDelete`方法存在权限检查不足的问题。`canUndelete`方法允许任何具有`ADMIN_RIGHT`、`undelete`权限或删除该文档的用户进行恢复，这可能导致未授权用户恢复或删除重要文档。`canDelete`方法中，对于删除操作的权限检查依赖于配置参数，但未验证用户输入的合法性，这可能导致通过修改配置参数绕过权限检查。
【关键代码模式】`canUndelete`和`canDelete`方法中的权限检查逻辑。
【修复方法说明】修复方法通过引入`checkAccess`和`hasAccess`方法，确保在访问或操作删除文档时进行严格的权限检查。