com.xpn.xwiki.web.Utils.java Source code

Java tutorial

Introduction

Here is the source code for com.xpn.xwiki.web.Utils.java

Source

/*
 * 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.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.struts.upload.MultipartRequestWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xwiki.component.manager.ComponentLookupException;
import org.xwiki.component.manager.ComponentManager;
import org.xwiki.context.Execution;
import org.xwiki.context.ExecutionContext;
import org.xwiki.xml.XMLUtils;

import com.xpn.xwiki.XWiki;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.plugin.fileupload.FileUploadPlugin;
import com.xpn.xwiki.util.Util;

public class Utils {
    protected static final Logger LOGGER = LoggerFactory.getLogger(Utils.class);

    /** A key that is used for placing a map of replaced (for protection) strings in the context. */
    private static final String PLACEHOLDERS_CONTEXT_KEY = Utils.class.getCanonicalName() + "_placeholders";

    /** Whether placeholders are enabled or not. */
    private static final String PLACEHOLDERS_ENABLED_CONTEXT_KEY = Utils.class.getCanonicalName()
            + "_placeholders_enabled";

    /**
     * The component manager used by {@link #getComponent(Class)} and {@link #getComponent(Class, String)}. It is useful
     * for any non component code that need to initialize/access components.
     */
    private static ComponentManager componentManager;

    /**
     * Generate the response by parsing a velocity template and printing the result to the {@link XWikiResponse
     * Response}. This is the main entry point to the View part of the XWiki MVC architecture.
     * 
     * @param template The name of the template to parse, without the {@code .vm} prefix. The template will be searched
     *            in the usual places: current XWikiSkins object, attachment of the current skin document, current skin
     *            folder, baseskin folder, /templates/ folder.
     * @param context the current context
     * @throws XWikiException when the response cannot be written to the client (for example when the client canceled
     *             the request, thus closing the socket)
     * @see XWiki#parseTemplate(String, XWikiContext)
     */
    public static void parseTemplate(String template, XWikiContext context) throws XWikiException {
        parseTemplate(template, true, context);
    }

    /**
     * Generate the response by parsing a velocity template and (optionally) printing the result to the
     * {@link XWikiResponse Response}.
     * 
     * @param template The name of the template to parse, without the {@code .vm} prefix. The template will be searched
     *            in the usual places: current XWikiSkins object, attachment of the current skin document, current skin
     *            folder, baseskin folder, /templates/ folder.
     * @param write Whether the generated response should be written to the client or not. If {@code false}, only the
     *            needed headers are generated, suitable for implementing a HEAD response.
     * @param context the current context
     * @throws XWikiException when the response cannot be written to the client (for example when the client canceled
     *             the request, thus closing the socket)
     * @see XWiki#parseTemplate(String, XWikiContext)
     */
    public static void parseTemplate(String template, boolean write, XWikiContext context) throws XWikiException {
        XWikiResponse response = context.getResponse();

        // Set content-type and encoding (this can be changed later by pages themselves)
        if (context.getResponse() instanceof XWikiPortletResponse) {
            response.setContentType("text/html");
        } else {
            response.setContentType("text/html; charset=" + context.getWiki().getEncoding());
        }

        String action = context.getAction();
        if ((!"download".equals(action)) && (!"skin".equals(action))) {
            if (context.getResponse() instanceof XWikiServletResponse) {
                // Add a last modified to tell when the page was last updated
                if (context.getWiki().getXWikiPreferenceAsLong("headers_lastmodified", 0, context) != 0) {
                    if (context.getDoc() != null) {
                        response.setDateHeader("Last-Modified", context.getDoc().getDate().getTime());
                    }
                }
                // Set a nocache to make sure the page is reloaded after an edit
                if (context.getWiki().getXWikiPreferenceAsLong("headers_nocache", 1, context) != 0) {
                    response.setHeader("Pragma", "no-cache");
                    response.setHeader("Cache-Control", "no-cache");
                }
                // Set an expires in one month
                long expires = context.getWiki().getXWikiPreferenceAsLong("headers_expires", -1, context);
                if (expires == -1) {
                    response.setDateHeader("Expires", -1);
                } else if (expires != 0) {
                    response.setDateHeader("Expires", (new Date()).getTime() + 30 * 24 * 3600 * 1000L);
                }
            }
        }

        if (("download".equals(action)) || ("skin".equals(action))) {
            // Set a nocache to make sure these files are not cached by proxies
            if (context.getWiki().getXWikiPreferenceAsLong("headers_nocache", 1, context) != 0) {
                response.setHeader("Cache-Control", "no-cache");
            }
        }

        context.getWiki().getPluginManager().beginParsing(context);
        // This class allows various components in the rendering chain to use placeholders for some fragile data. For
        // example, the URL generated for the image macro should not be further rendered, as it might get broken by wiki
        // filters. For this to work, keep a map of [used placeholders -> values] in the context, and replace them when
        // the content is fully rendered. The rendering code can use Utils.createPlaceholder.
        // Initialize the placeholder map
        enablePlaceholders(context);
        String content = "";
        try {
            content = context.getWiki().evaluateTemplate(template + ".vm", context);
            // Replace all placeholders with the protected values
            content = replacePlaceholders(content, context);
            disablePlaceholders(context);
            content = context.getWiki().getPluginManager().endParsing(content.trim(), context);
        } catch (IOException e) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("IOException while evaluating template [" + template + "] from /templates/", e);
            }

            // get Error template "This template does not exist
            try {
                content = context.getWiki().evaluateTemplate("templatedoesnotexist.vm", context);
                content = content.trim();
            } catch (IOException ex) {
                // Cannot write output, can't do anything else
            }
        }

        if (!context.isFinished()) {
            if (context.getResponse() instanceof XWikiServletResponse) {
                // Set the content length to the number of bytes, not the
                // string length, so as to handle multi-byte encodings
                try {
                    response.setContentLength(content.getBytes(context.getWiki().getEncoding()).length);
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
            }

            if (write) {
                try {
                    try {
                        response.getOutputStream().write(content.getBytes(context.getWiki().getEncoding()));
                    } catch (IllegalStateException ex) {
                        response.getWriter().write(content);
                    }
                } catch (IOException e) {
                    throw new XWikiException(XWikiException.MODULE_XWIKI_APP,
                            XWikiException.ERROR_XWIKI_APP_SEND_RESPONSE_EXCEPTION,
                            "Exception while sending response", e);
                }
            }
        }

        try {
            response.getOutputStream().flush();
        } catch (Throwable ex) {
            try {
                response.getWriter().flush();
            } catch (Throwable ex2) {
            }
        }
    }

    /**
     * Retrieve the URL to which the client should be redirected after the successful completion of the requested
     * action. This is taken from the {@code xredirect} parameter in the query string. If this parameter is not set, or
     * is set to an empty value, return the default redirect specified as the second argument.
     * 
     * @param request the current request
     * @param defaultRedirect the default value to use if no {@code xredirect} parameter is present
     * @return the destination URL, as specified in the {@code xredirect} parameter, or the specified default URL
     */
    public static String getRedirect(XWikiRequest request, String defaultRedirect) {
        String redirect = request.getParameter("xredirect");
        if (StringUtils.isBlank(redirect)) {
            redirect = defaultRedirect;
        }

        return redirect;
    }

    /**
     * Retrieve the URL to which the client should be redirected after the successful completion of the requested
     * action. This is taken from the {@code xredirect} parameter in the query string. If this parameter is not set, or
     * is set to an empty value, compose an URL back to the current document, using the specified action and query
     * string, and return it.
     * 
     * @param action the XWiki action to use for composing the default redirect URL ({@code view}, {@code edit}, etc)
     * @param queryString the query parameters to append to the fallback URL
     * @param context the current context
     * @return the destination URL, as specified in the {@code xredirect} parameter, or computed using the current
     *         document and the specified action and query string
     */
    public static String getRedirect(String action, String queryString, XWikiContext context) {
        return getRedirect(action, queryString, "xredirect");
    }

    /**
     * Retrieve the URL to which the client should be redirected after the successful completion of the requested
     * action. If any of the specified {@code redirectParameters} (in order) is present in the query string, it is
     * returned as the redirect destination. If none of the parameters is set, compose an URL back to the current
     * document using the specified action and query string, and return it.
     * 
     * @param action the XWiki action to use for composing the default redirect URL ({@code view}, {@code edit}, etc)
     * @param queryString the query parameters to append to the fallback URL
     * @param redirectParameters list of request parameters to look for as the redirect destination; each of the
     *            parameters is tried in the order they are passed, and the first one set to a non-empty value is
     *            returned, if any
     * @return the destination URL, as specified in one of the {@code redirectParameters}, or computed using the current
     *         document and the specified action and query string
     */
    public static String getRedirect(String action, String queryString, String... redirectParameters) {
        XWikiContext context = (XWikiContext) Utils.getComponent(Execution.class).getContext()
                .getProperty("xwikicontext");
        XWikiRequest request = context.getRequest();
        String redirect = null;
        for (String p : redirectParameters) {
            redirect = request.getParameter(p);
            if (StringUtils.isNotEmpty(redirect)) {
                break;
            }
        }
        if (StringUtils.isEmpty(redirect)) {
            redirect = context.getDoc().getURL(action, queryString, true, context);
        }
        return redirect;
    }

    /**
     * Retrieve the URL to which the client should be redirected after the successful completion of the requested
     * action. This is taken from the {@code xredirect} parameter in the query string. If this parameter is not set, or
     * is set to an empty value, compose an URL back to the current document, using the specified action, and return it.
     * 
     * @param action the XWiki action to use for composing the default redirect URL ({@code view}, {@code edit}, etc)
     * @param context the current context
     * @return the destination URL, as specified in the {@code xredirect} parameter, or computed using the current
     *         document and the specified action
     */
    public static String getRedirect(String action, XWikiContext context) {
        return getRedirect(action, null, context);
    }

    /**
     * Retrieve the name of the velocity template which should be used to generate the response. This is taken from the
     * {@code xpage} parameter in the query string. If this parameter is not set, or is set to an empty value, return
     * the provided default name.
     * 
     * @param request the current request
     * @param defaultpage the default value to use if no {@code xpage} parameter is set
     * @return the name of the requested template, as specified in the {@code xpage} parameter, or the specified default
     *         template
     */
    public static String getPage(XWikiRequest request, String defaultpage) {
        String page = request.getParameter("xpage");
        if (StringUtils.isEmpty(page)) {
            page = defaultpage;
        }

        return page;
    }

    /**
     * Get the name of an uploaded file, corresponding to the specified form field.
     * 
     * @param filelist the list of uploaded files, computed by the FileUpload plugin
     * @param name the name of the form field
     * @return the original name of the file, if the specified field name does correspond to an uploaded file, or
     *         {@code null} otherwise
     */
    public static String getFileName(List<FileItem> filelist, String name) {
        for (FileItem item : filelist) {
            if (name.equals(item.getFieldName())) {
                return item.getName();
            }
        }

        return null;
    }

    /**
     * Get the content of an uploaded file, corresponding to the specified form field.
     * 
     * @param filelist the list of uploaded files, computed by the FileUpload plugin
     * @param name the name of the form field
     * @return the content of the file, if the specified field name does correspond to an uploaded file, or {@code null}
     *         otherwise
     * @throws XWikiException if the file cannot be read due to an underlying I/O exception
     */
    public static byte[] getContent(List<FileItem> filelist, String name) throws XWikiException {
        for (FileItem item : filelist) {
            if (name.equals(item.getFieldName())) {
                byte[] data = new byte[(int) item.getSize()];
                InputStream fileis = null;
                try {
                    fileis = item.getInputStream();
                    fileis.read(data);
                } catch (IOException e) {
                    throw new XWikiException(XWikiException.MODULE_XWIKI_APP,
                            XWikiException.ERROR_XWIKI_APP_UPLOAD_FILE_EXCEPTION,
                            "Exception while reading uploaded parsed file", e);
                } finally {
                    IOUtils.closeQuietly(fileis);
                }

                return data;
            }
        }

        return null;
    }

    public static XWikiContext prepareContext(String action, XWikiRequest request, XWikiResponse response,
            XWikiEngineContext engine_context) throws XWikiException {
        XWikiContext context = new XWikiContext();
        String dbname = "xwiki";
        URL url = XWiki.getRequestURL(request);
        context.setURL(url);

        context.setEngineContext(engine_context);
        context.setRequest(request);
        context.setResponse(response);
        context.setAction(action);
        context.setDatabase(dbname);

        int mode = 0;
        if (request instanceof XWikiServletRequest) {
            mode = XWikiContext.MODE_SERVLET;
        } else if (request instanceof XWikiPortletRequest) {
            mode = XWikiContext.MODE_PORTLET;
        }
        context.setMode(mode);

        return context;
    }

    /**
     * Parse the request parameters from the specified String using the specified encoding. <strong>IMPLEMENTATION
     * NOTE</strong>: URL decoding is performed individually on the parsed name and value elements, rather than on the
     * entire query string ahead of time, to properly deal with the case where the name or value includes an encoded "="
     * or "&" character that would otherwise be interpreted as a delimiter.
     * <p>
     * Code borrowed from Apache Tomcat 5.0
     * </p>
     * 
     * @param data input string containing request parameters
     * @param encoding the encoding to use for transforming bytes into characters
     * @throws IllegalArgumentException if the data is malformed
     */
    public static Map<String, String[]> parseParameters(String data, String encoding)
            throws UnsupportedEncodingException {
        if (!StringUtils.isEmpty(data)) {
            // use the specified encoding to extract bytes out of the
            // given string so that the encoding is not lost. If an
            // encoding is not specified, let it use platform default
            byte[] bytes = null;
            try {
                if (encoding == null) {
                    bytes = data.getBytes();
                } else {
                    bytes = data.getBytes(encoding);
                }
            } catch (UnsupportedEncodingException uee) {
            }

            return parseParameters(bytes, encoding);
        }

        return Collections.emptyMap();
    }

    /**
     * Parse the request parameters from the specified byte array using the specified encoding. <strong>IMPLEMENTATION
     * NOTE</strong>: URL decoding is performed individually on the parsed name and value elements, rather than on the
     * entire query string ahead of time, to properly deal with the case where the name or value includes an encoded "="
     * or "&" character that would otherwise be interpreted as a delimiter.
     * <p>
     * NOTE: byte array data is modified by this method. Caller beware.
     * </p>
     * <p>
     * Code borrowed from Apache Tomcat 5.0
     * </p>
     * 
     * @param data input byte array containing request parameters
     * @param encoding Encoding to use for converting hex
     * @throws UnsupportedEncodingException if the data is malformed
     */
    public static Map<String, String[]> parseParameters(byte[] data, String encoding)
            throws UnsupportedEncodingException {
        Map<String, String[]> map = new HashMap<String, String[]>();

        if (data != null && data.length > 0) {
            int ix = 0;
            int ox = 0;
            String key = null;
            String value = null;
            while (ix < data.length) {
                byte c = data[ix++];
                switch ((char) c) {
                case '&':
                    value = new String(data, 0, ox, encoding);
                    if (key != null) {
                        putMapEntry(map, key, value);
                        key = null;
                    }
                    ox = 0;
                    break;
                case '=':
                    if (key == null) {
                        key = new String(data, 0, ox, encoding);
                        ox = 0;
                    } else {
                        data[ox++] = c;
                    }
                    break;
                case '+':
                    data[ox++] = (byte) ' ';
                    break;
                case '%':
                    data[ox++] = (byte) ((convertHexDigit(data[ix++]) << 4) + convertHexDigit(data[ix++]));
                    break;
                default:
                    data[ox++] = c;
                }
            }
            // The last value does not end in '&'. So save it now.
            if (key != null) {
                value = new String(data, 0, ox, encoding);
                putMapEntry(map, key, value);
            }
        }

        return map;
    }

    /**
     * Convert a byte character value to the corresponding hexidecimal digit value.
     * <p>
     * Code borrowed from Apache Tomcat 5.0
     * </p>
     * 
     * @param b the character value byte
     */
    private static byte convertHexDigit(byte b) {
        if ((b >= '0') && (b <= '9')) {
            return (byte) (b - '0');
        }
        if ((b >= 'a') && (b <= 'f')) {
            return (byte) (b - 'a' + 10);
        }
        if ((b >= 'A') && (b <= 'F')) {
            return (byte) (b - 'A' + 10);
        }

        return 0;
    }

    /**
     * Put name-value pair in map. When an entry for {@code name} already exist, add the new value to the array of
     * values.
     * <p>
     * Code borrowed from Apache Tomcat 5.0
     * </p>
     * 
     * @param map the map that is being constructed
     * @param name the name of the parameter
     * @param value the value of the parameter
     */
    private static void putMapEntry(Map<String, String[]> map, String name, String value) {
        String[] newValues = null;
        String[] oldValues = map.get(name);
        if (oldValues == null) {
            newValues = new String[] { value };
        } else {
            newValues = new String[oldValues.length + 1];
            System.arraycopy(oldValues, 0, newValues, 0, oldValues.length);
            newValues[oldValues.length] = value;
        }
        map.put(name, newValues);
    }

    /**
     * Escapes the XML special characters in a <code>String</code> using numerical XML entities.
     * 
     * @param value the text to escape, may be null
     * @return a new escaped <code>String</code>, <code>null</code> if null input
     * @deprecated starting with 2.7 use {@link XMLUtils#escape(Object) $services.xml.escape(content)}
     */
    @Deprecated
    public static String formEncode(String value) {
        return XMLUtils.escape(value);
    }

    public static String SQLFilter(String text) {
        try {
            return text.replaceAll("'", "''");
        } catch (Exception e) {
            return text;
        }
    }

    /**
     * @deprecated replaced by {@link com.xpn.xwiki.util.Util#encodeURI(String, XWikiContext)} since 1.3M2
     */
    @Deprecated
    public static String encode(String text, XWikiContext context) {
        return Util.encodeURI(text, context);
    }

    /**
     * @deprecated replaced by {@link com.xpn.xwiki.util.Util#decodeURI(String, XWikiContext)} since 1.3M2
     */
    @Deprecated
    public static String decode(String text, XWikiContext context) {
        return Util.decodeURI(text, context);
    }

    /**
     * Process a multi-part request, extracting all the uploaded files.
     * 
     * @param request the current request to process
     * @param context the current context
     * @return the instance of the {@link FileUploadPlugin} used to parse the uploaded files
     */
    public static FileUploadPlugin handleMultipart(HttpServletRequest request, XWikiContext context) {
        FileUploadPlugin fileupload = null;
        try {
            if (request instanceof MultipartRequestWrapper) {
                fileupload = new FileUploadPlugin("fileupload", "fileupload", context);
                fileupload.loadFileList(context);
                context.put("fileuploadplugin", fileupload);
                MultipartRequestWrapper mpreq = (MultipartRequestWrapper) request;
                List<FileItem> fileItems = fileupload.getFileItems(context);
                for (FileItem item : fileItems) {
                    if (item.isFormField()) {
                        String sName = item.getFieldName();
                        String sValue = item.getString(context.getWiki().getEncoding());
                        mpreq.setParameter(sName, sValue);
                    }
                }
            }
        } catch (Exception e) {
            if ((e instanceof XWikiException)
                    && (((XWikiException) e).getCode() == XWikiException.ERROR_XWIKI_APP_FILE_EXCEPTION_MAXSIZE)) {
                context.put("exception", e);
            } else {
                e.printStackTrace();
            }
        }
        return fileupload;
    }

    /**
     * @param componentManager the component manager used by {@link #getComponent(Class)} and
     *            {@link #getComponent(Class, String)}
     */
    public static void setComponentManager(ComponentManager componentManager) {
        Utils.componentManager = componentManager;
    }

    /**
     * @return the component manager used by {@link #getComponent(Class)} and {@link #getComponent(Class, String)}
     */
    public static ComponentManager getComponentManager() {
        return componentManager;
    }

    /**
     * Lookup a XWiki component by role and hint.
     * 
     * @param role the class (aka role) that the component implements
     * @param hint a value to differentiate different component implementations for the same role
     * @return the component's instance
     * @throws RuntimeException if the component cannot be found/initialized, or if the component manager is not
     *             initialized
     */
    public static <T> T getComponent(Class<T> role, String hint) {
        T component = null;
        if (componentManager != null) {
            try {
                component = componentManager.lookup(role, hint);
            } catch (ComponentLookupException e) {
                throw new RuntimeException(
                        "Failed to load component [" + role.getName() + "] for hint [" + hint + "]", e);
            }
        } else {
            throw new RuntimeException("Component manager has not been initialized before lookup for ["
                    + role.getName() + "] for hint [" + hint + "]");
        }

        return component;
    }

    /**
     * Lookup a XWiki component by role (uses the default hint).
     * 
     * @param role the class (aka role) that the component implements
     * @return the component's instance
     * @throws RuntimeException if the component cannot be found/initialized, or if the component manager is not
     *             initialized
     */
    public static <T> T getComponent(Class<T> role) {
        return getComponent(role, "default");
    }

    /**
     * @param <T> the component type
     * @param role the role for which to return implementing components
     * @return all components implementing the passed role
     * @throws RuntimeException if some of the components cannot be found/initialized, or if the component manager is
     *             not initialized
     * @since 2.0M3
     */
    public static <T> List<T> getComponentList(Class<T> role) {
        List<T> components;
        if (componentManager != null) {
            try {
                components = componentManager.lookupList(role);
            } catch (ComponentLookupException e) {
                throw new RuntimeException("Failed to load components with role [" + role.getName() + "]", e);
            }
        } else {
            throw new RuntimeException(
                    "Component manager has not been initialized before lookup for role [" + role.getName() + "]");
        }

        return components;
    }

    /**
     * Helper method for obtaining a valid xcontext from the execution context.
     * <p>
     * NOTE: Don't use this method to access the XWiki context in a component because
     * {@link #setComponentManager(ComponentManager)} is not called when running component unit tests. You have to take
     * the XWiki context yourself from the injected Execution when inside a component. This method should be used only
     * by non-component code.
     * 
     * @return the current context or {@code null} if the execution context is not yet initialized
     * @since 3.2M3
     */
    public static XWikiContext getContext() {
        ExecutionContext ec = getComponent(Execution.class).getContext();
        return (XWikiContext) ec.getProperty(XWikiContext.EXECUTIONCONTEXT_KEY);
    }

    /**
     * Check if placeholders are enabled in the current context.
     * 
     * @param context The current context.
     * @return <code>true</code> if placeholders can be used, <code>false</code> otherwise.
     */
    public static boolean arePlaceholdersEnabled(XWikiContext context) {
        Boolean enabled = (Boolean) context.get(PLACEHOLDERS_ENABLED_CONTEXT_KEY);

        return enabled != null && enabled;
    }

    /**
     * Enable placeholder support in the current request context.
     * 
     * @param context The current context.
     */
    public static void enablePlaceholders(XWikiContext context) {
        context.put(PLACEHOLDERS_CONTEXT_KEY, new HashMap<String, String>());
        context.put(PLACEHOLDERS_ENABLED_CONTEXT_KEY, new Boolean(true));
    }

    /**
     * Disable placeholder support in the current request context.
     * 
     * @param context The current context.
     */
    public static void disablePlaceholders(XWikiContext context) {
        context.remove(PLACEHOLDERS_CONTEXT_KEY);
        context.remove(PLACEHOLDERS_ENABLED_CONTEXT_KEY);
    }

    /**
     * Create a placeholder key for a string that should be protected from further processing. The value is stored in
     * the context, and the returned key can be used by the calling code as many times in the rendering result. At the
     * end of the rendering process all placeholder keys are replaced with the values they replace.
     * 
     * @param value The string to hide.
     * @param context The current context.
     * @return The key to be used instead of the value.
     */
    public static String createPlaceholder(String value, XWikiContext context) {
        if (!arePlaceholdersEnabled(context)) {
            return value;
        }
        @SuppressWarnings("unchecked")
        Map<String, String> renderingKeys = (Map<String, String>) context.get(PLACEHOLDERS_CONTEXT_KEY);
        String key;
        do {
            key = "KEY" + RandomStringUtils.randomAlphanumeric(10) + "KEY";
        } while (renderingKeys.containsKey(key));
        renderingKeys.put(key, value);

        return key;
    }

    /**
     * Insert back the replaced strings.
     * 
     * @param content The rendered content, with placeholders.
     * @param context The current context.
     * @return The content with all placeholders replaced with the real values.
     */
    public static String replacePlaceholders(String content, XWikiContext context) {
        if (!arePlaceholdersEnabled(context)) {
            return content;
        }

        String result = content;
        @SuppressWarnings("unchecked")
        Map<String, String> renderingKeys = (Map<String, String>) context.get(PLACEHOLDERS_CONTEXT_KEY);
        for (Entry<String, String> e : renderingKeys.entrySet()) {
            result = result.replace(e.getKey(), e.getValue());
        }

        return result;
    }

    /**
     * Verify if the current request is an AJAX request.
     * 
     * @param context the current request context
     * @return True if this is an AJAX request, false otherwise.
     * @since 2.4M2
     */
    public static Boolean isAjaxRequest(XWikiContext context) {
        return BooleanUtils.isTrue((Boolean) context.get("ajax"));
    }
}