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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xwiki.component.annotation.Component;

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

/**
 * Administration xwiki action.
 *
 * @version $Id$
 */
@Component
@Named("admin")
@Singleton
public class AdminAction extends XWikiAction
{
    /** The logger. */
    private static final Logger LOGGER = LoggerFactory.getLogger(AdminAction.class);

    /**
     * Default constructor.
     */
    public AdminAction()
    {
        this.waitForXWikiInitialization = false;
    }

    @Override
    protected Class<? extends XWikiForm> getFormClass()
    {
        return EditForm.class;
    }

    @Override
    public String render(XWikiContext context) throws XWikiException
    {
        XWikiRequest request = context.getRequest();
        String content = request.getParameter("content");
        XWikiDocument doc = context.getDoc();
        XWikiForm form = context.getForm();

        synchronized (doc) {
            XWikiDocument tdoc = (XWikiDocument) context.get("tdoc");
            EditForm peform = (EditForm) form;
            String parent = peform.getParent();
            if (parent != null) {
                doc.setParent(parent);
            }
            String creator = peform.getCreator();
            if (creator != null) {
                doc.setCreator(creator);
            }
            String defaultTemplate = peform.getDefaultTemplate();
            if (defaultTemplate != null) {
                doc.setDefaultTemplate(defaultTemplate);
            }
            String defaultLanguage = peform.getDefaultLanguage();
            if ((defaultLanguage != null) && !defaultLanguage.equals("")) {
                doc.setDefaultLanguage(defaultLanguage);
            }
            if (doc.getDefaultLanguage().equals("")) {
                doc.setDefaultLanguage(context.getWiki().getLanguagePreference(context));
            }

            String language = context.getWiki().getLanguagePreference(context);
            String languagefromrequest = context.getRequest().getParameter("language");
            String languagetoedit =
                ((languagefromrequest == null) || (languagefromrequest.equals(""))) ? language : languagefromrequest;

            if ((languagetoedit == null) || (languagetoedit.equals("default"))) {
                languagetoedit = "";
            }
            if (doc.isNew() || (doc.getDefaultLanguage().equals(languagetoedit))) {
                languagetoedit = "";
            }

            if (languagetoedit.equals("")) {
                // In this case the created document is going to be the default document
                tdoc = doc;
                context.put("tdoc", doc);
                if (doc.isNew()) {
                    doc.setDefaultLanguage(language);
                    doc.setLanguage("");
                }
            } else {
                // If the translated doc object is the same as the doc object
                // this means the translated doc did not exists so we need to create it
                if ((tdoc == doc)) {
                    tdoc = new XWikiDocument(doc.getDocumentReference());
                    tdoc.setLanguage(languagetoedit);
                    tdoc.setContent(doc.getContent());
                    tdoc.setSyntax(doc.getSyntax());
                    tdoc.setAuthor(context.getUser());
                    tdoc.setStore(doc.getStore());
                    context.put("tdoc", tdoc);
                }
            }

            XWikiDocument tdoc2 = tdoc.clone();
            if (content != null && !content.isEmpty()) {
                tdoc2.setContent(content);
            }
            context.put("tdoc", tdoc2);
            try {
                tdoc2.readFromTemplate(peform, context);
            } catch (XWikiException e) {
                if (e.getCode() == XWikiException.ERROR_XWIKI_APP_DOCUMENT_NOT_EMPTY) {
                    context.put("exception", e);
                    return "docalreadyexists";
                }
            }

            /* Setup a lock */
            try {
                XWikiLock lock = tdoc.getLock(context);
                if ((lock == null) || (lock.getUserName().equals(context.getUser())) || (peform.isLockForce())) {
                    tdoc.setLock(context.getUser(), context);
                }
            } catch (Exception e) {
                // Lock should never make XWiki fail
                // But we should log any related information
                LOGGER.error("Exception while setting up lock", e);
            }
        }

        return "admin";
    }
}

## 内容2
### 文件路径：
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/CreateAction.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.util.HashMap;
import java.util.Locale;
import java.util.Map;

import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.script.ScriptContext;

import org.apache.commons.lang3.StringUtils;
import org.xwiki.component.annotation.Component;
import org.xwiki.csrf.CSRFToken;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.DocumentReferenceResolver;
import org.xwiki.model.reference.EntityReferenceSerializer;
import org.xwiki.model.reference.SpaceReference;
import org.xwiki.security.authorization.ContextualAuthorizationManager;
import org.xwiki.security.authorization.Right;

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.objects.BaseObject;
import com.xpn.xwiki.util.Util;

/**
 * Create document action.
 *
 * @version $Id$
 * @since 2.4M2
 */
@Component
@Named("create")
@Singleton
public class CreateAction extends XWikiAction
{
    /**
     * The name of the create.vm template to render.
     */
    private static final String CREATE_TEMPLATE = "create";

    /**
     * The name of the parent parameter.
     */
    private static final String PARENT = "parent";

    /**
     * The name of the space reference parameter.
     */
    private static final String SPACE_REFERENCE = "spaceReference";

    /**
     * The name parameter.
     */
    private static final String NAME = "name";

    /**
     * The name of the template field inside the template provider, or the template parameter which can be sent
     * directly, without passing through the template provider.
     */
    private static final String TEMPLATE = "template";

    /**
     * Internal name for a flag determining if we are creating a Nested Space or a terminal document.
     */
    private static final String IS_SPACE = "isSpace";

    /**
     * Space homepage document name.
     */
    private static final String WEBHOME = "WebHome";

    /**
     * Local entity reference serializer hint.
     */
    private static final String LOCAL_SERIALIZER_HINT = "local";

    /**
     * Current entity reference resolver hint.
     */
    private static final String CURRENT_MIXED_RESOLVER_HINT = "currentmixed";

    /**
     * The action to perform when creating a new page from a template.
     *
     * @version $Id$
     */
    private enum ActionOnCreate
    {
        /**
         * Go to edit mode without saving.
         */
        EDIT("edit"),

        /**
         * Save and then go to edit mode.
         */
        SAVE_AND_EDIT("saveandedit"),

        /**
         * Save and then go to view mode.
         */
        SAVE_AND_VIEW("saveandview");

        private static final Map<String, ActionOnCreate> BY_ACTION = new HashMap<>();

        static {
            for (ActionOnCreate actionOnCreate : values()) {
                BY_ACTION.put(actionOnCreate.action, actionOnCreate);
            }
        }

        private final String action;

        ActionOnCreate(String action)
        {
            this.action = action;
        }

        public static ActionOnCreate valueOfAction(String action)
        {
            return BY_ACTION.get(action);
        }
    }

    /**
     * Default constructor.
     */
    public CreateAction()
    {
        this.waitForXWikiInitialization = false;
    }

    @Override
    public String render(XWikiContext context) throws XWikiException
    {
        CreateActionRequestHandler handler = new CreateActionRequestHandler(context);

        // Read the request and extract the passed information.
        handler.processRequest();

        // Save the determined values so we have them available in the action template.
        ScriptContext scontext = getCurrentScriptContext();
        scontext.setAttribute(SPACE_REFERENCE, handler.getSpaceReference(), ScriptContext.ENGINE_SCOPE);
        scontext.setAttribute(NAME, handler.getName(), ScriptContext.ENGINE_SCOPE);
        scontext.setAttribute(IS_SPACE, handler.isSpace(), ScriptContext.ENGINE_SCOPE);
        // put the available templates on the context, for the .vm to not compute them again
        scontext.setAttribute("availableTemplateProviders", handler.getAvailableTemplateProviders(),
            ScriptContext.ENGINE_SCOPE);
        scontext.setAttribute("recommendedTemplateProviders", handler.getRecommendedTemplateProviders(),
            ScriptContext.ENGINE_SCOPE);

        DocumentReference newDocumentReference = handler.getNewDocumentReference();
        if (newDocumentReference == null) {
            // There is information still missing, go back to the template and fill it.
            return CREATE_TEMPLATE;
        }

        // Check if the creation in the spaceReference is allowed.
        if (!handler.isTemplateProviderAllowedToCreateInCurrentSpace()) {
            // The selected template provider is not usable in the selected location. Go back to the template and pick
            // something else.
            return CREATE_TEMPLATE;
        }

        // Checking the rights to create the new document.
        // Note: Note checking the logical spaceReference, but the space of the final actual document reference, since
        // that is where we are creating the new document.
        checkRights(newDocumentReference.getLastSpaceReference(), context);

        // Check if the document to create already exists and if it respects the name strategy
        XWikiDocument newDocument = context.getWiki().getDocument(newDocumentReference, context);
        if (handler.isDocumentAlreadyExisting(newDocument) || handler.isDocumentPathTooLong(newDocumentReference)
            || !this.isEntityReferenceNameValid(newDocumentReference)) {
            return CREATE_TEMPLATE;
        }

        // Verify if the "type" of document to create has been set, even if we currently do not use it in the action.
        // The goal is let the user be able to chose it, which have some consequences in the UI (thanks to javascript).
        // See: https://jira.xwiki.org/browse/XWIKI-12580
        // Note: we do not need the "type" if we have a template provider: the type of the new document will be the type
        // of the template.
        // TODO: handle this type in doCreate() that we call above (see: https://jira.xwiki.org/browse/XWIKI-12585).
        if (StringUtils.isBlank(handler.getType()) && !handler.hasTemplate()) {
            return CREATE_TEMPLATE;
        }

        // create is finally valid, can be executed
        doCreate(context, newDocument, handler.isSpace(), handler.getTemplateProvider());

        return null;
    }

    /**
     * @param context the XWiki context
     * @param spaceReference the reference of the space where the new document will be created
     * @throws XWikiException in case the permission to create a new document in the specified space is denied
     */
    private void checkRights(SpaceReference spaceReference, XWikiContext context) throws XWikiException
    {
        ContextualAuthorizationManager authManager = Utils.getComponent(ContextualAuthorizationManager.class);
        if (!authManager.hasAccess(Right.EDIT, spaceReference)) {
            Object[] args = { spaceReference.toString(), context.getUser() };
            throw new XWikiException(XWikiException.MODULE_XWIKI_ACCESS, XWikiException.ERROR_XWIKI_ACCESS_DENIED,
                "The creation of a document into the space {0} has been denied to user {1}", null, args);
        }
    }

    /**
     * Actually executes the create, after all preconditions have been verified.
     *
     * @param context the context of this action
     * @param newDocument the document to be created
     * @param isSpace whether the document is a space webhome or a page
     * @param templateProvider the template provider to create from
     * @throws XWikiException in case anything goes wrong accessing xwiki documents
     */
    private void doCreate(XWikiContext context, XWikiDocument newDocument, boolean isSpace, BaseObject templateProvider)
        throws XWikiException
    {
        XWikiRequest request = context.getRequest();
        XWikiDocument doc = context.getDoc();

        String parent = getParent(request, doc, isSpace, context);

        // get the title of the page to create, as specified in the parameters
        String title = getTitle(request, newDocument, isSpace);

        // get the template from the template parameter, to allow creation directly from template, without
        // forcing to create a template provider for each template creation
        String template = getTemplate(templateProvider, request);

        // Read from the template provide the action to perform when creating the page.
        ActionOnCreate actionOnCreate = getActionOnCreate(templateProvider);

        String action = null;
        if (actionOnCreate == ActionOnCreate.SAVE_AND_EDIT) {
            initAndSaveDocument(context, newDocument, title, template, parent);
            action = newDocument.getDefaultEditMode(context);
        } else {
            action = actionOnCreate == ActionOnCreate.SAVE_AND_VIEW ? "save" : getEditMode(template, context);
        }

        // Perform a redirection to the selected action of the document to create.
        String redirectParams = getRedirectParameters(parent, title, template, actionOnCreate);
        String redirectURL = newDocument.getURL(action, redirectParams, context);
        redirectURL = context.getResponse().encodeRedirectURL(redirectURL);
        if (context.getRequest().getParameterMap().containsKey("ajax")) {
            // If this template is displayed from a modal popup, send a header in the response notifying that a
            // redirect must be performed in the calling page.
            context.getResponse().setHeader("redirect", redirectURL);
        } else {
            // Perform the redirect
            sendRedirect(context.getResponse(), redirectURL);
        }
    }

    /**
     * @return the resolver uses to resolve references received in request parameters
     */
    private DocumentReferenceResolver<String> getCurrentMixedDocumentReferenceResolver()
    {
        return Utils.getComponent(DocumentReferenceResolver.TYPE_STRING, CURRENT_MIXED_RESOLVER_HINT);
    }

    /**
     * Initialize and save the new document before editing it. Follow the steps done by the Save action.
     *
     * @param context the XWiki context
     * @param newDocument the document being created
     * @param title the document title
     * @param template the template to copy
     * @param parent the parent document
     * @throws XWikiException if copying the template or saving the document fails
     */
    private void initAndSaveDocument(XWikiContext context, XWikiDocument newDocument, String title, String template,
        String parent) throws XWikiException
    {
        XWiki xwiki = context.getWiki();
        DocumentReferenceResolver<String> resolver = getCurrentMixedDocumentReferenceResolver();

        // Set the locale and default locale, considering that we're creating the original version of the document
        // (not a translation).
        newDocument.setLocale(Locale.ROOT);
        if (newDocument.getDefaultLocale() == Locale.ROOT) {
            newDocument.setDefaultLocale(xwiki.getLocalePreference(context));
        }

        // Copy the template.
        DocumentReference templateReference = resolver.resolve(template);
        newDocument.readFromTemplate(templateReference, context);

        // Set the parent field.
        if (!StringUtils.isEmpty(parent)) {
            DocumentReference parentReference = resolver.resolve(parent);
            newDocument.setParentReference(parentReference);
        }

        // Set the document title
        if (title != null) {
            newDocument.setTitle(title);
        }

        // Set the author and creator.
        DocumentReference currentUserReference = context.getUserReference();
        newDocument.setAuthorReference(currentUserReference);
        newDocument.setCreatorReference(currentUserReference);

        // Make sure the user is allowed to make this modification
        xwiki.checkSavingDocument(currentUserReference, newDocument, context);

        xwiki.saveDocument(newDocument, context);
    }

    private String getRedirectParameters(String parent, String title, String template, ActionOnCreate actionOnCreate)
    {
        if (actionOnCreate == ActionOnCreate.SAVE_AND_EDIT) {
            // We don't need to pass any parameters because the document is saved before the redirect using the
            // parameter values.
            return null;
        }

        String redirectParams = "template=" + Util.encodeURI(template, null);
        if (parent != null) {
            redirectParams += "&parent=" + Util.encodeURI(parent, null);
        }
        if (title != null) {
            redirectParams += "&title=" + Util.encodeURI(title, null);
        }
        if (actionOnCreate == ActionOnCreate.SAVE_AND_VIEW) {
            // Add the CSRF token because we redirect to save action.
            CSRFToken csrf = Utils.getComponent(CSRFToken.class);
            redirectParams += "&form_token=" + Util.encodeURI(csrf.getToken(), null);
        }

        return redirectParams;
    }

    /**
     * @param templateProvider the set template provider, if any
     * @param request the request on which to fallback
     * @return the string reference of the document to use as template or {@code ""} if none set
     */
    private String getTemplate(BaseObject templateProvider, XWikiRequest request)
    {
        String result = "";

        if (templateProvider != null) {
            result = templateProvider.getStringValue(TEMPLATE);
        } else if (request.getParameter(TEMPLATE) != null) {
            result = request.getParameter(TEMPLATE);
        }

        return result;
    }

    /**
     * @param request the current request for which this action is executed
     * @param doc the current document
     * @param isSpace {@code true} if the request is to create a space, {@code false} if a page should be created
     * @param context the XWiki context
     * @return the serialized reference of the parent to create the document for
     */
    private String getParent(XWikiRequest request, XWikiDocument doc, boolean isSpace, XWikiContext context)
    {
        // This template can be passed a parent document reference in parameter (using the "parent" parameter).
        // If a parent parameter is passed, use it to set the parent when creating the new Page or Space.
        // If no parent parameter was passed:
        // * use the current document
        // ** if we're creating a new page and if the current document exists or
        // * use the Main space's WebHome
        // ** if we're creating a new page and the current document does not exist.
        String parent = request.getParameter(PARENT);
        if (StringUtils.isEmpty(parent)) {
            EntityReferenceSerializer<String> localSerializer =
                Utils.getComponent(EntityReferenceSerializer.TYPE_STRING, LOCAL_SERIALIZER_HINT);

            if (doc.isNew()) {
                // Use the Main space's WebHome.
                Provider<DocumentReference> defaultDocumentReferenceProvider =
                    Utils.getComponent(DocumentReference.TYPE_PROVIDER);

                DocumentReference parentRef =
                    defaultDocumentReferenceProvider.get().setWikiReference(context.getWikiReference());

                parent = localSerializer.serialize(parentRef);
            } else {
                // Use the current document.
                DocumentReference parentRef = doc.getDocumentReference();

                parent = localSerializer.serialize(parentRef);
            }
        }

        return parent;
    }

    /**
     * @param request the current request for which this action is executed
     * @param newDocument the document to be created
     * @param isSpace {@code true} if the request is to create a space, {@code false} if a page should be created
     * @return the title of the page to be created. If no request parameter is set, the page name is returned for a new
     *         page and the space name is returned for a new space
     */
    private String getTitle(XWikiRequest request, XWikiDocument newDocument, boolean isSpace)
    {
        String title = request.getParameter("title");
        if (StringUtils.isEmpty(title)) {
            if (isSpace) {
                title = newDocument.getDocumentReference().getLastSpaceReference().getName();
            } else {
                title = newDocument.getDocumentReference().getName();
                // Avoid WebHome titles for pages that are really space homepages.
                if (WEBHOME.equals(title)) {
                    title = newDocument.getDocumentReference().getLastSpaceReference().getName();
                }
            }
        }

        return title;
    }

    /**
     * @param templateProvider the template provider for this creation
     * @return {@code true} if the created document should be saved on create, before editing, {@code false} otherwise
     */
    private ActionOnCreate getActionOnCreate(BaseObject templateProvider)
    {
        if (templateProvider != null) {
            String action = templateProvider.getStringValue("action");
            ActionOnCreate actionOnCreate = ActionOnCreate.valueOfAction(action);
            if (actionOnCreate != null) {
                return actionOnCreate;
            }
        }

        // Default action when creating a page from a template.
        return ActionOnCreate.EDIT;
    }

    /**
     * @param template the template to create document from
     * @param resolver the resolver to use to resolve the template document reference
     * @param context the context of the current request
     * @return the default edit mode for a document created from the passed template
     * @throws XWikiException in case something goes wrong accessing template document
     */
    private String getEditMode(String template, XWikiContext context)
        throws XWikiException
    {
        // Determine the edit action (edit/inline) for the newly created document, if a template is passed it is
        // used to determine the action. Default is 'edit'.
        String editAction = ActionOnCreate.EDIT.name().toLowerCase();
        XWiki xwiki = context.getWiki();
        if (!StringUtils.isEmpty(template)) {
            DocumentReference templateReference = getCurrentMixedDocumentReferenceResolver().resolve(template);
            if (xwiki.exists(templateReference, context)) {
                editAction = xwiki.getDocument(templateReference, context).getDefaultEditMode(context);
            }
        }

        return editAction;
    }
}

## 内容3
### 文件路径：
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/EditAction.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 javax.inject.Named;
import javax.inject.Singleton;
import javax.script.ScriptContext;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xwiki.component.annotation.Component;
import org.xwiki.rendering.syntax.Syntax;

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

/**
 * Initializes a document before it is edited.
 *
 * @version $Id$
 */
@Component
@Named("edit")
@Singleton
public class EditAction extends XWikiAction
{
    /**
     * The object used for logging.
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(EditAction.class);

    /**
     * Default constructor.
     */
    public EditAction()
    {
        this.waitForXWikiInitialization = false;
    }

    @Override
    protected Class<? extends XWikiForm> getFormClass()
    {
        return EditForm.class;
    }

    @Override
    public String render(XWikiContext context) throws XWikiException
    {
        try {
            XWikiDocument editedDocument = prepareEditedDocument(context);
            maybeLockDocument(editedDocument, context);
        } catch (XWikiException e) {
            if (e.getCode() == XWikiException.ERROR_XWIKI_APP_DOCUMENT_NOT_EMPTY) {
                context.put("exception", e);
                return "docalreadyexists";
            } else {
                throw e;
            }
        }

        // Make sure object property fields are displayed in edit mode.
        // See XWikiDocument#display(String, BaseObject, XWikiContext)
        // TODO: Revisit the display mode after the inline action is removed. Is the display mode still needed when
        // there is only one edit action?
        context.put("display", "edit");
        return "edit";
    }

    /**
     * Determines the edited document (translation) and updates it based on the template specified on the request and
     * any additional request parameters that overwrite the default values from the template.
     *
     * @param context the XWiki context
     * @return the edited document
     * @throws XWikiException if something goes wrong
     */
    protected XWikiDocument prepareEditedDocument(XWikiContext context) throws XWikiException
    {
        // Determine the edited document (translation).
        XWikiDocument editedDocument = getEditedDocument(context);
        EditForm editForm = (EditForm) context.getForm();

        // Update the edited document based on the template specified on the request.
        editedDocument.readFromTemplate(editForm, context);

        // The default values from the template can be overwritten by additional request parameters.
        updateDocumentTitleAndContentFromRequest(editedDocument, context);
        editedDocument.readObjectsFromForm(editForm, context);

        // Set the current user as creator, author and contentAuthor when the edited document is newly created to avoid
        // using XWikiGuest instead (because those fields were not previously initialized).
        if (editedDocument.isNew()) {
            editedDocument.setCreatorReference(context.getUserReference());
            editedDocument.setAuthorReference(context.getUserReference());
            editedDocument.setContentAuthorReference(context.getUserReference());
        }

        // Expose the edited document on the XWiki context and the Velocity context.
        putDocumentOnContext(editedDocument, context);

        return editedDocument;
    }

    /**
     * There are three important use cases:
     * <ul>
     * <li>editing or creating the original translation (for the default language)</li>
     * <li>editing an existing document translation</li>
     * <li>creating a new translation.</i>
     * </ul>
     * Most of the code deals with the really bad way the default language can be specified (empty string, 'default' or
     * a real language code).
     *
     * @param context the XWiki context
     * @return the edited document translation based on the language specified on the request
     * @throws XWikiException if something goes wrong
     */
    private XWikiDocument getEditedDocument(XWikiContext context) throws XWikiException
    {
        XWikiDocument doc = context.getDoc();
        boolean hasTranslation = doc != context.get("tdoc");

        // We have to clone the context document because it is cached and the changes we are going to make are valid
        // only for the duration of the current request.
        doc = doc.clone();
        context.put("doc", doc);

        EditForm editForm = (EditForm) context.getForm();
        doc.readDocMetaFromForm(editForm, context);

        String language = context.getWiki().getLanguagePreference(context);
        if (doc.isNew() && doc.getDefaultLanguage().equals("")) {
            doc.setDefaultLanguage(language);
        }

        String languageToEdit = StringUtils.isEmpty(editForm.getLanguage()) ? language : editForm.getLanguage();

        // If no specific language is set or if it is "default" then we edit the current doc.
        if (languageToEdit == null || languageToEdit.equals("default")) {
            languageToEdit = "";
        }
        // If the document is new or if the language to edit is the default language then we edit the default
        // translation.
        if (doc.isNew() || doc.getDefaultLanguage().equals(languageToEdit)) {
            languageToEdit = "";
        }
        // If the doc does not exist in the language to edit and the language was not explicitly set in the URL then
        // we edit the default document translation. This prevents use from creating unneeded translations.
        if (!hasTranslation && StringUtils.isEmpty(editForm.getLanguage())) {
            languageToEdit = "";
        }

        // Initialize the translated document.
        XWikiDocument tdoc;
        if (languageToEdit.equals("")) {
            // Edit the default document translation (default language).
            tdoc = doc;
            if (doc.isNew()) {
                doc.setDefaultLanguage(language);
                doc.setLanguage("");
            }
        } else if (!hasTranslation && context.getWiki().isMultiLingual(context)) {
            // Edit a new translation.
            tdoc = new XWikiDocument(doc.getDocumentReference());
            tdoc.setLanguage(languageToEdit);
            tdoc.setDefaultLocale(doc.getDefaultLocale());
            // Mark the translation. It's important to know whether a document is a translation or not, especially
            // for the sheet manager which needs to access the objects using the default document not one of its
            // translations.
            tdoc.setTitle(doc.getTitle());
            tdoc.setContent(doc.getContent());
            tdoc.setSyntax(doc.getSyntax());
            tdoc.setAuthorReference(context.getUserReference());
            tdoc.setStore(doc.getStore());
        } else {
            // Edit an existing translation. Clone the translated document object to be sure that the changes we are
            // going to make will last only for the duration of the current request.
            tdoc = ((XWikiDocument) context.get("tdoc")).clone();
        }

        return tdoc;
    }

    /**
     * Updates the title and content of the given document with values taken from the 'title' and 'content' request
     * parameters or based on the document section specified on the request.
     *
     * @param document the document whose title and content should be updated
     * @param context the XWiki context
     * @throws XWikiException if something goes wrong
     */
    private void updateDocumentTitleAndContentFromRequest(XWikiDocument document, XWikiContext context)
        throws XWikiException
    {
        // Check if section editing is enabled and if a section is specified.
        boolean sectionEditingEnabled = context.getWiki().hasSectionEdit(context);
        int sectionNumber = sectionEditingEnabled ? NumberUtils.toInt(context.getRequest().getParameter("section")) : 0;
        getCurrentScriptContext().setAttribute("sectionNumber", sectionNumber, ScriptContext.ENGINE_SCOPE);

        // Update the edited content.
        EditForm editForm = (EditForm) context.getForm();
        if (editForm.getContent() != null) {
            document.setContent(editForm.getContent());
        } else if (sectionNumber > 0) {
            document.setContent(document.getContentOfSection(sectionNumber));
        }

        // Update the edited title.
        if (editForm.getTitle() != null) {
            document.setTitle(editForm.getTitle());
        } else if (sectionNumber > 0 && document.getSections().size() > 0) {
            // The edited content is either the content of the specified section or the content provided on the
            // request. We assume the content provided on the request is meant to overwrite the specified section.
            // In both cases the document content is currently having one section, so we can take its title.
            String sectionTitle = document.getDocumentSection(1).getSectionTitle();
            if (StringUtils.isNotBlank(sectionTitle)) {
                // We cannot edit the page title while editing a page section so this title is for display only.
                String sectionPlainTitle = document.getRenderedContent(sectionTitle, document.getSyntax().toIdString(),
                    Syntax.PLAIN_1_0.toIdString(), context);
                document.setTitle(localizePlainOrKey("core.editors.content.titleField.sectionEditingFormat",
                    document.getRenderedTitle(Syntax.PLAIN_1_0, context), sectionNumber, sectionPlainTitle));
            }
        }
    }

    /**
     * Exposes the given document in the XWiki context and the Velocity context under the 'tdoc' and 'cdoc' keys.
     *
     * @param document the document to expose
     * @param context the XWiki context
     */
    private void putDocumentOnContext(XWikiDocument document, XWikiContext context)
    {
        context.put("tdoc", document);
        // Old XWiki applications that are still using the inline action might expect the cdoc (content document) to be
        // properly set on the context. Let's expose the given document also as cdoc for backward compatibility.
        context.put("cdoc", context.get("tdoc"));
    }

    /**
     * Locks the given document unless it is already locked by a different user and the current user didn't request to
     * force the lock.
     *
     * @param document the document to lock
     * @param context the XWiki context
     */
    private void maybeLockDocument(XWikiDocument document, XWikiContext context)
    {
        try {
            XWikiLock lock = document.getLock(context);
            EditForm editForm = (EditForm) context.getForm();
            if (lock == null || lock.getUserName().equals(context.getUser()) || editForm.isLockForce()) {
                document.setLock(context.getUser(), context);
            }
        } catch (Exception e) {
            // Lock should never make XWiki fail, but we should log any related information.
            LOGGER.error("Exception while setting up lock", e);
        }
    }
}

## 内容4
### 文件路径：
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/InlineAction.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 javax.inject.Named;
import javax.inject.Singleton;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xwiki.component.annotation.Component;

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

/**
 * @deprecated use {@link EditAction} with {@code editor=inline} in the query string instead since 3.2
 */
@Component
@Named("inline")
@Singleton
@Deprecated
public class InlineAction extends XWikiAction
{
    private static final Logger LOGGER = LoggerFactory.getLogger(InlineAction.class);

    @Override
    protected Class<? extends XWikiForm> getFormClass()
    {
        return EditForm.class;
    }

    @Override
    public String render(XWikiContext context) throws XWikiException
    {
        XWikiDocument doc = context.getDoc();

        synchronized (doc) {
            XWikiForm form = context.getForm();

            XWikiDocument cdoc = (XWikiDocument) context.get("cdoc");
            if (cdoc == null) {
                cdoc = doc;
            }

            EditForm peform = (EditForm) form;

            XWikiDocument doc2 = doc.clone();
            context.put("doc", doc2);

            String parent = peform.getParent();
            if (parent != null) {
                doc2.setParent(parent);
            }
            String creator = peform.getCreator();
            if (creator != null) {
                doc2.setCreator(creator);
            }
            String defaultLanguage = peform.getDefaultLanguage();
            if ((defaultLanguage != null) && !defaultLanguage.equals("")) {
                doc2.setDefaultLanguage(defaultLanguage);
            }
            if (doc2.getDefaultLanguage().equals("")) {
                doc2.setDefaultLanguage(context.getWiki().getLanguagePreference(context));
            }
            try {
                doc2.readFromTemplate(peform, context);
            } catch (XWikiException e) {
                if (e.getCode() == XWikiException.ERROR_XWIKI_APP_DOCUMENT_NOT_EMPTY) {
                    return "docalreadyexists";
                }
            }

            if (doc == cdoc) {
                context.put("cdoc", doc2);
            } else {
                XWikiDocument cdoc2 = cdoc.clone();
                cdoc2.readFromTemplate(peform, context);
                context.put("cdoc", cdoc2);
            }

            doc2.readFromForm((EditForm) form, context);

            // Set the current user as creator, author and contentAuthor when the edited document is newly created
            // to avoid using XWikiGuest instead (because those fields were not previously initialized).
            // This is needed for the script right, as guest doesn't have it and this would block the execution of
            // scripts in newly created documents even if the user creating the document has the right.
            if (doc2.isNew()) {
                doc2.setCreatorReference(context.getUserReference());
                doc2.setAuthorReference(context.getUserReference());
                doc2.setContentAuthorReference(context.getUserReference());
            }

            /* Setup a lock */
            try {
                XWikiLock lock = doc.getLock(context);
                if ((lock == null) || (lock.getUserName().equals(context.getUser())) || (peform.isLockForce())) {
                    doc.setLock(context.getUser(), context);
                }
            } catch (Exception e) {
                // Lock should never make XWiki fail
                // But we should log any related information
                LOGGER.error("Exception while setting up lock", e);
            }
        }

        // Make sure object property fields are displayed in edit mode.
        // See XWikiDocument#display(String, BaseObject, XWikiContext)
        context.put("display", "edit");

        return "inline";
    }
}

## 内容5
### 文件路径：
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/web/SaveAction.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.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;

import javax.inject.Named;
import javax.inject.Singleton;
import javax.script.ScriptContext;

import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.suigeneris.jrcs.diff.DifferentiationFailedException;
import org.suigeneris.jrcs.diff.delta.Delta;
import org.suigeneris.jrcs.rcs.Version;
import org.xwiki.component.annotation.Component;
import org.xwiki.configuration.ConfigurationSource;
import org.xwiki.diff.ConflictDecision;
import org.xwiki.job.Job;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.DocumentReferenceResolver;
import org.xwiki.model.reference.EntityReference;
import org.xwiki.refactoring.job.CreateRequest;
import org.xwiki.refactoring.script.RefactoringScriptService;
import org.xwiki.script.service.ScriptService;
import org.xwiki.store.merge.MergeConflictDecisionsManager;
import org.xwiki.store.merge.MergeDocumentResult;
import org.xwiki.store.merge.MergeManager;

import com.xpn.xwiki.XWiki;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.DocumentRevisionProvider;
import com.xpn.xwiki.doc.MetaDataDiff;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.doc.XWikiLock;
import com.xpn.xwiki.doc.merge.MergeConfiguration;
import com.xpn.xwiki.internal.mandatory.RedirectClassDocumentInitializer;
import com.xpn.xwiki.objects.ObjectDiff;

/**
 * Action used for saving and proceeding to view the saved page.
 * <p>
 * Used as a generic action for saving documents.
 *
 * @version $Id$
 */
@Component
@Named("save")
@Singleton
public class SaveAction extends PreviewAction
{
    /** The identifier of the save action. */
    public static final String ACTION_NAME = "save";

    protected static final String ASYNC_PARAM = "async";

    /** Logger. */
    private static final Logger LOGGER = LoggerFactory.getLogger(SaveAction.class);

    /**
     * The key to retrieve the saved object version from the context.
     */
    private static final String SAVED_OBJECT_VERSION_KEY = "SaveAction.savedObjectVersion";

    /**
     * The context key to know if a document has been merged for saving it.
     */
    private static final String MERGED_DOCUMENTS = "SaveAction.mergedDocuments";

    /**
     * Parameter value used with forceSave to specify that the merge should be performed even if there is conflicts.
     */
    private static final String FORCE_SAVE_MERGE = "merge";

    /**
     * Parameter value used with forceSave to specify that no merge should be done but the current document should
     * override the previous one.
     */
    private static final String FORCE_SAVE_OVERRIDE = "override";

    private DocumentRevisionProvider documentRevisionProvider;

    private MergeManager mergeManager;

    private MergeConflictDecisionsManager conflictDecisionsManager;

    public SaveAction()
    {
        this.waitForXWikiInitialization = true;
    }

    /**
     * Saves the current document, updated according to the parameters sent in the request.
     *
     * @param context The current request {@link XWikiContext context}.
     * @return <code>true</code> if there was an error and the response needs to render an error page,
     *         <code>false</code> if the document was correctly saved.
     * @throws XWikiException If an error occured: cannot communicate with the storage module, or cannot update the
     *             document because the request contains invalid parameters.
     */
    public boolean save(XWikiContext context) throws XWikiException
    {
        XWiki xwiki = context.getWiki();
        XWikiRequest request = context.getRequest();
        XWikiDocument doc = context.getDoc();
        EditForm form = (EditForm) context.getForm();

        // Check save session
        int sectionNumber = 0;
        if (request.getParameter("section") != null && xwiki.hasSectionEdit(context)) {
            sectionNumber = Integer.parseInt(request.getParameter("section"));
        }

        if (doc.isNew() && !this.isEntityReferenceNameValid(doc.getDocumentReference())) {
            context.put("message", "entitynamevalidation.create.invalidname");
            context.put("messageParameters",
                new Object[] { getLocalSerializer().serialize(doc.getDocumentReference())});
            return true;
        }

        XWikiDocument originalDoc = doc;

        // We need to clone this document first, since a cached storage would return the same object for the
        // following requests, so concurrent request might get a partially modified object, or worse, if an error
        // occurs during the save, the cached object will not reflect the actual document at all.
        doc = doc.clone();

        String language = form.getLanguage();
        // FIXME Which one should be used: doc.getDefaultLanguage or
        // form.getDefaultLanguage()?
        // String defaultLanguage = ((EditForm) form).getDefaultLanguage();
        XWikiDocument tdoc;

        if (doc.isNew() || (language == null) || (language.equals("")) || (language.equals("default"))
            || (language.equals(doc.getDefaultLanguage()))) {
            // Saving the default document translation.
            // Need to save parent and defaultLanguage if they have changed
            tdoc = doc;
        } else {
            tdoc = doc.getTranslatedDocument(language, context);
            if ((tdoc == doc) && xwiki.isMultiLingual(context)) {
                // Saving a new document translation.
                tdoc = new XWikiDocument(doc.getDocumentReference());
                tdoc.setLanguage(language);
                tdoc.setStore(doc.getStore());
                // In that specific case, we want the original doc to be the translation document so that we
                // never raised a conflict.
                originalDoc = tdoc;
            } else if (tdoc != doc) {
                // Saving an existing document translation (but not the default one).
                // Same as above, clone the object retrieved from the store cache.
                originalDoc = tdoc;
                tdoc = tdoc.clone();
            }
        }

        if (doc.isNew()) {
            doc.setLocale(Locale.ROOT);
            if (doc.getDefaultLocale() == Locale.ROOT) {
                doc.setDefaultLocale(xwiki.getLocalePreference(context));
            }
        }

        try {
            tdoc.readFromTemplate(form.getTemplate(), context);
        } catch (XWikiException e) {
            if (e.getCode() == XWikiException.ERROR_XWIKI_APP_DOCUMENT_NOT_EMPTY) {
                context.put("exception", e);
                return true;
            }
        }

        // Convert the content and the meta data of the edited document and its translations if the syntax has changed
        // and the request is asking for a syntax conversion. We do this after applying the template because the
        // template may have content in the previous syntax that needs to be converted. We do this before applying the
        // changes from the submitted form because it may contain content that was already converted.
        if (form.isConvertSyntax() && !tdoc.getSyntax().toIdString().equals(form.getSyntaxId())) {
            convertSyntax(tdoc, form.getSyntaxId(), context);
        }

        if (sectionNumber != 0) {
            XWikiDocument sectionDoc = tdoc.clone();
            sectionDoc.readFromForm(form, context);
            String sectionContent = sectionDoc.getContent() + "\n";
            String content = tdoc.updateDocumentSection(sectionNumber, sectionContent);
            tdoc.setContent(content);
            tdoc.setComment(sectionDoc.getComment());
            tdoc.setMinorEdit(sectionDoc.isMinorEdit());
        } else {
            tdoc.readFromForm(form, context);
        }

        // TODO: handle Author
        String username = context.getUser();
        tdoc.setAuthor(username);
        if (tdoc.isNew()) {
            tdoc.setCreator(username);
        }

        // Make sure we have at least the meta data dirty status
        tdoc.setMetaDataDirty(true);

        // Validate the document if we have xvalidate=1 in the request
        if ("1".equals(request.getParameter("xvalidate"))) {
            boolean validationResult = tdoc.validate(context);
            // If the validation fails we should show the "Inline form" edit mode
            if (validationResult == false) {
                // Set display context to 'edit'
                context.put("display", "edit");
                // Set the action used by the "Inline form" edit mode as the context action. See #render(XWikiContext).
                context.setAction(tdoc.getDefaultEditMode(context));
                // Set the document in the context
                context.put("doc", doc);
                context.put("cdoc", tdoc);
                context.put("tdoc", tdoc);
                // Force the "Inline form" edit mode.
                getCurrentScriptContext().setAttribute("editor", "inline", ScriptContext.ENGINE_SCOPE);

                return true;
            }
        }

        // Remove the redirect object if the save request doesn't update it. This allows users to easily overwrite
        // redirect place-holders that are created when we move pages around.
        if (tdoc.getXObject(RedirectClassDocumentInitializer.REFERENCE) != null
            && request.getParameter("XWiki.RedirectClass_0_location") == null) {
            tdoc.removeXObjects(RedirectClassDocumentInitializer.REFERENCE);
        }

        // We only proceed on the check between versions in case of AJAX request, so we currently stay in the edit form
        // This can be improved later by displaying a nice UI with some merge options in a sync request.
        // For now we don't want our user to loose their changes.
        if (isConflictCheckEnabled() && Utils.isAjaxRequest(context)
            && request.getParameter("previousVersion") != null) {
            if (isConflictingWithVersion(context, originalDoc, tdoc)) {
                return true;
            }
        }

        // Make sure the user is allowed to make this modification
        xwiki.checkSavingDocument(context.getUserReference(), tdoc, tdoc.getComment(), tdoc.isMinorEdit(), context);

        // We get the comment to be used from the document
        // It was read using readFromForm
        xwiki.saveDocument(tdoc, tdoc.getComment(), tdoc.isMinorEdit(), context);

        context.put(SAVED_OBJECT_VERSION_KEY, tdoc.getRCSVersion());

        Job createJob = startCreateJob(tdoc.getDocumentReference(), form);
        if (createJob != null) {
            if (isAsync(request)) {
                if (Utils.isAjaxRequest(context)) {
                    // Redirect to the job status URL of the job we have just launched.
                    sendRedirect(context.getResponse(), String.format("%s/rest/jobstatus/%s?media=json",
                        context.getRequest().getContextPath(), serializeJobId(createJob.getRequest().getId())));
                }

                // else redirect normally and the operation will eventually finish in the background.
                // Note: It is preferred that async mode is called in an AJAX request that can display the progress.
            } else {
                // Sync mode, default, wait for the work to finish.
                try {
                    createJob.join();
                } catch (InterruptedException e) {
                    throw new XWikiException(String.format(
                        "Interrupted while waiting for template [%s] to be processed when creating the document [%s]",
                        form.getTemplate(), tdoc.getDocumentReference()), e);
                }
            }
        } else {
            // Nothing more to do, just unlock the document.
            XWikiLock lock = tdoc.getLock(context);
            if (lock != null) {
                tdoc.removeLock(context);
            }
        }

        return false;
    }

    private boolean isConflictCheckEnabled()
    {
        ConfigurationSource configurationSource = Utils.getComponent(ConfigurationSource.class, "xwikiproperties");
        return configurationSource.getProperty("edit.conflictChecking.enabled", true);
    }

    private DocumentRevisionProvider getDocumentRevisionProvider()
    {
        if (this.documentRevisionProvider == null) {
            this.documentRevisionProvider = Utils.getComponent(DocumentRevisionProvider.class);
        }

        return this.documentRevisionProvider;
    }

    private MergeManager getMergeManager()
    {
        if (this.mergeManager == null) {
            this.mergeManager = Utils.getComponent(MergeManager.class);
        }

        return this.mergeManager;
    }

    private MergeConflictDecisionsManager getConflictDecisionsManager()
    {
        if (this.conflictDecisionsManager == null) {
            this.conflictDecisionsManager = Utils.getComponent(MergeConflictDecisionsManager.class);
        }

        return this.conflictDecisionsManager;
    }

    /**
     * Retrieve the conflict decisions made from the request and fill the conflict decision manager with them. We handle
     * two list of parameters here: - mergeChoices: those parameters are on the form [conflict id]=[choice] where the
     * choice is defined by the {@link ConflictDecision.DecisionType} values. - customChoices: those parameters are on
     * the form [conflict id]=[encoded string] where the encoded string is actually the desired value to solve the
     * conflict.
     */
    private void recordConflictDecisions(XWikiContext context, DocumentReference documentReference)
    {
        XWikiRequest request = context.getRequest();
        String[] mergeChoices = request.getParameterValues("mergeChoices");
        String[] customChoices = request.getParameterValues("customChoices");

        // Build a map indexed by the conflict ids and whose values are the actual decoded custom values.
        Map<String, String> customChoicesMap = new HashMap<>();
        if (customChoices != null) {
            for (String customChoice : customChoices) {
                String[] splittedCustomChoiceInfo = customChoice.split("=");
                String conflictReference = splittedCustomChoiceInfo[0];
                String customValue = customChoice.substring(conflictReference.length() + 1);
                try {
                    customValue = URLDecoder.decode(customValue, request.getCharacterEncoding());
                } catch (UnsupportedEncodingException e) {
                    LOGGER.error("Error while decoding a custom value decision.", e);
                }
                customChoicesMap.put(conflictReference, customValue);
            }
        }
        if (mergeChoices != null) {
            for (String choice : mergeChoices) {
                String[] splittedChoiceInfo = choice.split("=");
                String conflictReference = splittedChoiceInfo[0];
                String selectedChoice = splittedChoiceInfo[1];
                List<String> customValue = null;

                ConflictDecision.DecisionType decisionType =
                    ConflictDecision.DecisionType.valueOf(selectedChoice.toUpperCase());

                if (decisionType == ConflictDecision.DecisionType.CUSTOM) {
                    customValue = Collections.singletonList(customChoicesMap.get(conflictReference));
                }
                getConflictDecisionsManager().recordDecision(documentReference, context.getUserReference(),
                    conflictReference, decisionType, customValue);
            }
        }
    }

    /**
     * Check if the version of the document being saved is conflicting with another version. This check is done by
     * getting the "previousVersion" parameter from the request and comparing it with latest version of the document. If
     * the current version of the document is not the same as the previous one, a diff is computed on the document
     * content: a conflict is detected only if the contents are different.
     *
     * @param context the current context of the request.
     * @param originalDoc the original version of the document being modified that will be saved (i.e. before content
     *            changes). We don't retrieve it through context since it can be a translation.
     * @return true in case of conflict. If it's true, the answer is immediately sent to the client.
     */
    private boolean isConflictingWithVersion(XWikiContext context, XWikiDocument originalDoc, XWikiDocument modifiedDoc)
        throws XWikiException
    {
        XWikiRequest request = context.getRequest();

        // in case of force save we skip the check.
        if (FORCE_SAVE_OVERRIDE.equals(request.getParameter("forceSave"))) {
            return false;
        }

        // the document is new we don't have to check the version date or anything
        if ("true".equals(request.getParameter("isNew")) && originalDoc.isNew()) {
            return false;
        }

        // TODO The check of the previousVersion should be done at a lower level or with a semaphore since
        // another job might have saved a different version of the document
        Version previousVersion = new Version(request.getParameter("previousVersion"));
        Version latestVersion = originalDoc.getRCSVersion();

        Date editingVersionDate = new Date(Long.parseLong(request.getParameter("editingVersionDate")));
        Date latestVersionDate = originalDoc.getDate();

        // we ensure that nobody edited the document between the moment the user started to edit and now
        if (!latestVersion.equals(previousVersion) || latestVersionDate.after(editingVersionDate)) {
            try {
                XWikiDocument previousDoc =
                    getDocumentRevisionProvider().getRevision(originalDoc, previousVersion.toString());

                // We also check that the previousDoc revision exists to avoid an exception if it has been deleted
                // Note that if we're here and the request says that the document is new, it's not necessarily a
                // conflict: we might be in the case where the doc has been created during the edition because of
                // an image added to it, without updating the client. So it's still accurate to check that the diff
                // hasn't changed.
                if (!originalDoc.isNew() && previousDoc != null) {
                    // if changes between previousVersion and latestVersion didn't change the content, it means it's ok
                    // to save the current changes.
                    List<Delta> contentDiff =
                        originalDoc.getContentDiff(previousVersion.toString(), latestVersion.toString(), context);

                    // we also need to check the object diff, to be sure there's no conflict with the inline form.
                    List<List<ObjectDiff>> objectDiff =
                        originalDoc.getObjectDiff(previousVersion.toString(), latestVersion.toString(), context);

                    // we finally check the metadata: we want to get a conflict if the title changed, or the syntax,
                    // the default language etc.
                    // However we have to filter out the author: we don't care if the author reference changed and it's
                    // actually most certainly the case if we are here.
                    List<MetaDataDiff> metaDataDiff =
                        originalDoc.getMetaDataDiff(previousVersion.toString(), latestVersion.toString(), context);

                    List<MetaDataDiff> filteredMetaDataDiff = new ArrayList<>();
                    for (MetaDataDiff dataDiff : metaDataDiff) {
                        if (!dataDiff.getField().equals("author")) {
                            filteredMetaDataDiff.add(dataDiff);
                        }
                    }

                    if (contentDiff.isEmpty() && objectDiff.isEmpty() && filteredMetaDataDiff.isEmpty()) {
                        return false;
                    } else {
                        MergeConfiguration mergeConfiguration = new MergeConfiguration();

                        // We need the reference of the user and the document in the config to retrieve
                        // the conflict decision in the MergeManager.
                        mergeConfiguration.setUserReference(context.getUserReference());
                        mergeConfiguration.setConcernedDocument(modifiedDoc.getDocumentReferenceWithLocale());

                        // The modified doc is actually the one we should save, so it's ok to modify it directly
                        // and better for performance.
                        mergeConfiguration.setProvidedVersionsModifiables(true);

                        // We need to retrieve the conflict decisions that might have occurred from the request.
                        recordConflictDecisions(context, modifiedDoc.getDocumentReferenceWithLocale());

                        MergeDocumentResult mergeDocumentResult =
                            getMergeManager().mergeDocument(previousDoc, originalDoc, modifiedDoc, mergeConfiguration);

                        // Be sure to not keep the conflict decisions we might have made if new conflicts occurred
                        // we don't want to pollute the list of decisions.
                        getConflictDecisionsManager().removeConflictDecisionList(
                            modifiedDoc.getDocumentReferenceWithLocale(), context.getUserReference());

                        // If we don't get any conflict, or if we want to force the merge even with conflicts,
                        // then we pursue to save the document.
                        if (FORCE_SAVE_MERGE.equals(request.getParameter("forceSave"))
                            || !mergeDocumentResult.hasConflicts()) {
                            context.put(MERGED_DOCUMENTS, "true");
                            return false;

                            // If we got merge conflicts and we don't want to force it, then we record the conflict in
                            // order to allow fixing them independently.
                        } else {
                            getConflictDecisionsManager().recordConflicts(modifiedDoc.getDocumentReferenceWithLocale(),
                                context.getUserReference(),
                                mergeDocumentResult.getConflicts(MergeDocumentResult.DocumentPart.CONTENT));
                        }
                    }
                }

                // if the revision has been deleted or if the content/object diff is not empty
                // we have a conflict.
                // TODO: Improve it to return the diff between the current version and the latest recorder
                Map<String, String> jsonObject = new LinkedHashMap<>();
                jsonObject.put("previousVersion", previousVersion.toString());
                jsonObject.put("previousVersionDate", editingVersionDate.toString());
                jsonObject.put("latestVersion", latestVersion.toString());
                jsonObject.put("latestVersionDate", latestVersionDate.toString());
                this.answerJSON(context, HttpStatus.SC_CONFLICT, jsonObject);
                return true;
            } catch (DifferentiationFailedException e) {
                throw new XWikiException("Error while loading the diff", e);
            }
        }
        return false;
    }

    @Override
    public boolean action(XWikiContext context) throws XWikiException
    {
        // CSRF prevention
        if (!csrfTokenCheck(context, true)) {
            return false;
        }

        if (save(context)) {
            return true;
        }

        // forward to view
        if (Utils.isAjaxRequest(context)) {
            Map<String, String> jsonAnswer = new LinkedHashMap<>();
            Version newVersion = (Version) context.get(SAVED_OBJECT_VERSION_KEY);
            jsonAnswer.put("newVersion", newVersion.toString());
            if ("true".equals(context.get(MERGED_DOCUMENTS))) {
                jsonAnswer.put("mergedDocument", "true");
            }
            answerJSON(context, HttpStatus.SC_OK, jsonAnswer);
        } else {
            sendRedirect(context.getResponse(), Utils.getRedirect("view", context));
        }
        return false;
    }

    @Override
    public String render(XWikiContext context) throws XWikiException
    {
        XWikiException e = (XWikiException) context.get("exception");
        if ((e != null) && (e.getCode() == XWikiException.ERROR_XWIKI_APP_DOCUMENT_NOT_EMPTY)) {
            return "docalreadyexists";
        }

        if ("edit".equals(context.get("display"))) {
            // When form validation (xvalidate) fails the save action forwards to the "Inline form" edit mode. In this
            // case the context action is not "save" anymore because it was changed in #save(XWikiContext). The context
            // action should be the action used by the "Inline form" edit mode (either "edit" or "inline").
            return context.getAction();
        }

        return "exception";
    }

    private boolean isAsync(XWikiRequest request)
    {
        return "true".equals(request.get(ASYNC_PARAM));
    }

    private Job startCreateJob(EntityReference entityReference, EditForm editForm) throws XWikiException
    {
        if (StringUtils.isBlank(editForm.getTemplate())) {
            // No template specified, nothing more to do.
            return null;
        }

        // If a template is set in the request, then this is a create action which needs to be handled by a create job,
        // but skipping the target document, which is now already saved by the save action.

        RefactoringScriptService refactoring =
            (RefactoringScriptService) Utils.getComponent(ScriptService.class, "refactoring");

        CreateRequest request = refactoring.getRequestFactory().createCreateRequest(Arrays.asList(entityReference));
        request.setCheckAuthorRights(false);
        // Set the target document.
        request.setEntityReferences(Arrays.asList(entityReference));
        // Set the template to use.
        DocumentReferenceResolver<String> resolver =
            Utils.getComponent(DocumentReferenceResolver.TYPE_STRING, "currentmixed");
        EntityReference templateReference = resolver.resolve(editForm.getTemplate());
        request.setTemplateReference(templateReference);
        // We`ve already created and populated the fields of the target document, focus only on the remaining children
        // specified in the template.
        request.setSkippedEntities(Arrays.asList(entityReference));

        Job createJob = refactoring.create(request);
        if (createJob != null) {
            return createJob;
        } else {
            throw new XWikiException(String.format("Failed to schedule the create job for [%s]", entityReference),
                refactoring.getLastError());
        }
    }

    private String serializeJobId(List<String> jobId)
    {
        return StringUtils.join(jobId, "/");
    }

    private void convertSyntax(XWikiDocument doc, String targetSyntaxId, XWikiContext xcontext) throws XWikiException
    {
        // Convert the syntax without saving. The syntax conversion will be saved later along with the other changes.
        doc.convertSyntax(targetSyntaxId, xcontext);

        for (Locale locale : doc.getTranslationLocales(xcontext)) {
            // Skip the edited translation because we handle it separately.
            if (!Objects.equals(locale, doc.getLocale())) {
                XWikiDocument tdoc = doc.getTranslatedDocument(locale, xcontext);
                // Double check if the syntax has changed because each document translation can have a different syntax.
                if (!tdoc.getSyntax().toIdString().equals(targetSyntaxId)) {
                    // Convert the syntax and save the changes.
                    tdoc.convertSyntax(targetSyntaxId, xcontext);
                    xcontext.getWiki().saveDocument(tdoc,
                        String.format("Document converted from syntax %s to syntax %s", tdoc.getSyntax().toIdString(),
                            targetSyntaxId),
                        xcontext);
                }
            }
        }
    }
}

## 内容6
### 文件路径：
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.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.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.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.reference.WikiReference;
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.ResourceReference;
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.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.mandatory.RedirectClassDocumentInitializer;
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;

/**
 * <p>
 * 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.
 * </p>
 * <p>
 * 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.
 * </p>
 * <p>
 * Serving a request goes through the following phases:
 * </p>
 * <ul>
 * <li>Wrapping the request and response object in XWiki specific wrappers</li>
 * <li>Prepare the request {@link XWikiContext XWiki-specific context}</li>
 * <li>Initialize/retrieve the XWiki object corresponding to the requested wiki</li>
 * <li>Handle file uploads</li>
 * <li>Prepare the velocity context</li>
 * <li>Prepare the document objects corresponding to the requested URL</li>
 * <li>Send action pre-notifications to listeners</li>
 * <li>Run the overridden {@link #action(XWikiContext)}</li>
 * <li>If {@link #action(XWikiContext)} returns true, run the overridden {@link #render(XWikiContext)}</li>
 * <li>If {@link #render(XWikiContext)} returned a string (template name), render the template with that name</li>
 * <li>Send action post-notifications to listeners</li>
 * </ul>
 * <p>
 * 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.
 * </p>
 */
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;

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

    /**
     * Indicate if the XWiki.RedirectClass is handled by the action (see handleRedirectObject()).
     */
    protected boolean handleRedirectObject = false;

    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;
    }

    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
     */
    @Unstable
    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("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;
        ObservationManager om = Utils.getComponent(ObservationManager.class);
        Execution execution = Utils.getComponent(Execution.class);
        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 && om != null && execution != null) {
                actionProgress = new DefaultJobProgress(context.getURL().toExternalForm());
                om.addListener(new WrappedThreadEventListener(actionProgress));

                // Register the action progress in the context
                ExecutionContext econtext = 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());
                    om.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 = false;
                if (handleRedirectObject) {
                    hasRedirect = handleRedirectObject(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) {
                    if (doc.isNew() && "view".equals(context.getAction())
                        && !"recyclebin".equals(context.getRequest().get("viewer"))
                        && !"children".equals(context.getRequest().get("viewer"))
                        && !"siblings".equals(context.getRequest().get("viewer"))) {
                        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 {
                        om.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);

                om.removeListener(actionProgress.getName());
            }

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

    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;
    }

    /**
     * Redirect the user to an other location if the document holds an XWiki.RedirectClass instance (used when a
     * document is moved).
     *
     * @param context the XWiki context
     * @return either or not a redirection have been sent
     * @throws XWikiException if error occurs
     * @since 8.0RC1
     * @since 7.4.2
     */
    protected boolean handleRedirectObject(XWikiContext context) throws XWikiException
    {
        WikiReference wikiReference = context.getWikiReference();

        // Look if the document has a redirect object
        XWikiDocument doc = context.getDoc();
        BaseObject redirectObj = doc.getXObject(RedirectClassDocumentInitializer.REFERENCE);
        if (redirectObj == null) {
            return false;
        }

        // Get the location
        String location = redirectObj.getStringValue("location");
        if (StringUtils.isBlank(location)) {
            return false;
        }

        // Resolve the location to get a reference
        DocumentReferenceResolver<String> resolver = Utils.getComponent(DocumentReferenceResolver.TYPE_STRING);
        EntityReference locationReference = resolver.resolve(location, wikiReference);

        // Get the type of the current target
        ResourceReference resourceReference = Utils.getComponent(ResourceReferenceManager.class).getResourceReference();
        EntityResourceReference entityResource = (EntityResourceReference) resourceReference;
        EntityReference entityReference = entityResource.getEntityReference();

        // If the entity is inside a document, compute the new entity with the new document part.
        if (entityReference.getType().ordinal() > EntityType.DOCUMENT.ordinal()) {
            EntityReference parentDocument = entityReference.extractReference(EntityType.DOCUMENT);
            locationReference = entityReference.replaceParent(parentDocument, locationReference);
        }

        // Get the URL corresponding to the location
        // Note: the anchor part is lost in the process, because it is not sent to the server
        // (see: http://stackoverflow.com/a/4276491)
        String url = context.getWiki().getURL(locationReference, context.getAction(),
            context.getRequest().getQueryString(), null, context);

        // Send the redirection
        try {
            context.getResponse().sendRedirect(url);
        } catch (IOException e) {
            throw new XWikiException("Failed to redirect.", e);
        }

        return true;
    }

    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;
    }

    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):
     * <ul>
     * <li>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)</li>
     * <li>if A.B exists then continue</li>
     * <li>if A.B doesn't exist then forward to A.B.WebHome</li>
     * </ul>
     * 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);
        } 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);
    }
}