org.apache.click.Context.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.click.Context.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.click;

import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.click.service.FileUploadService;
import org.apache.click.service.LogService;
import org.apache.click.service.MessagesMapService;
import org.apache.click.service.TemplateService;
import org.apache.click.util.ClickUtils;
import org.apache.click.util.FlashAttribute;
import org.apache.commons.fileupload.FileItem;

/**
 * Provides the HTTP request context information for pages and controls.
 * A new Context object is created for each Page request. The request Context
 * object can be obtained from the thread local variable via the
 * {@link Context#getThreadLocalContext()} method.
 */
public class Context {

    // -------------------------------------------------------------- Constants

    /** The user's session Locale key: &nbsp; <tt>locale</tt>. */
    public static final String LOCALE = "locale";

    /**
     * The attribute key used for storing any error that occurred while
     * Context is created.
     */
    static final String CONTEXT_FATAL_ERROR = "_context_fatal_error";

    /** The thread local context stack. */
    private static final ThreadLocal<ContextStack> THREAD_LOCAL_CONTEXT_STACK = new ThreadLocal<ContextStack>();

    // ----------------------------------------------------- Instance Variables

    /** The servlet context. */
    protected final ServletContext context;

    /** The servlet config. */
    protected final ServletConfig config;

    /** The HTTP method is POST flag. */
    protected final boolean isPost;

    /** The servlet response. */
    protected final HttpServletResponse response;

    /** The click services interface. */
    final ClickServlet clickServlet;

    /** The servlet request. */
    final HttpServletRequest request;

    // ----------------------------------------------------------- Constructors

    /**
     * Create a new request context.
     *
     * @param context the servlet context
     * @param config the servlet config
     * @param request the servlet request
     * @param response the servlet response
     * @param isPost the servlet request is a POST
     * @param clickServlet the click servlet instance
     */
    public Context(ServletContext context, ServletConfig config, HttpServletRequest request,
            HttpServletResponse response, boolean isPost, ClickServlet clickServlet) {

        this.clickServlet = clickServlet;
        this.context = context;
        this.config = config;
        this.isPost = isPost;

        ContextStack contextStack = getContextStack();

        if (contextStack.size() == 0) {

            // CLK-312. Apply request.setCharacterEncoding before wrapping
            // request in ClickRequestWrapper
            String charset = clickServlet.getConfigService().getCharset();
            if (charset != null) {

                try {
                    request.setCharacterEncoding(charset);

                } catch (UnsupportedEncodingException ex) {
                    String msg = "The character encoding " + charset + " is invalid.";
                    LogService logService = clickServlet.getConfigService().getLogService();
                    logService.warn(msg, ex);
                }
            }

            FileUploadService fus = clickServlet.getConfigService().getFileUploadService();

            this.request = new ClickRequestWrapper(request, fus);

        } else {
            this.request = request;
        }
        this.response = response;
    }

    /**
     * Package private constructor to support Mock objects.
     *
     * @param request the servlet request
     * @param response the servlet response
     */
    Context(HttpServletRequest request, HttpServletResponse response) {
        // This method should be removed once the deprecated MockContext
        // constructor is removed
        this.request = request;
        this.response = response;
        this.clickServlet = null;
        this.context = null;
        this.config = null;
        this.isPost = "POST".equalsIgnoreCase(request.getMethod());
    }

    // --------------------------------------------------------- Public Methods

    /**
     * Return the thread local request context instance.
     *
     * @return the thread local request context instance.
     * @throws RuntimeException if a Context is not available on the thread.
     */
    public static Context getThreadLocalContext() {
        return getContextStack().peek();
    }

    /**
     * Returns true if a Context instance is available on the current thread,
     * false otherwise. Unlike {@link #getThreadLocalContext()} this method
     * can safely be used and will not throw an exception if Context is not
     * available on the current thread.
     * <p/>
     * This method is very useful inside a {@link Control} constructor which
     * might need access to the Context. As Controls could potentially be
     * instantiated during Click startup (in order to deploy their resources),
     * this check can be used to determine whether Context is available or not.
     * <p/>
     * For example:
     *
     * <pre class="prettyprint">
     * public MyControl extends AbstractControl {
     *     public MyControl(String name) {
     *         if (Context.hasThreadLocalContext()) {
     *             // Context is available, meaning a user initiated a web
     *             // request
     *             Context context = getContext();
     *             String state = (String) context.getSessionAttribute(name);
     *             setValue(state);
     *         } else {
     *             // No Context is available, meaning this is most probably
     *             // the application startup and deployment phase.
     *         }
     *     }
     * }
     * </pre>
     *
     * @return true if a Context instance is available on the current thread,
     * false otherwise
     */
    public static boolean hasThreadLocalContext() {
        ContextStack contextStack = THREAD_LOCAL_CONTEXT_STACK.get();
        if (contextStack == null) {
            return false;
        }
        return !contextStack.isEmpty();
    }

    /**
     * Returns the servlet request.
     *
     * @return HttpServletRequest
     */
    public HttpServletRequest getRequest() {
        return request;
    }

    /**
     * Returns the servlet response.
     *
     * @return HttpServletResponse
     */
    public HttpServletResponse getResponse() {
        return response;
    }

    /**
     * Returns the servlet config.
     *
     * @return ServletConfig
     */
    public ServletConfig getServletConfig() {
        return config;
    }

    /**
     * Returns the servlet context.
     *
     * @return ServletContext
     */
    public ServletContext getServletContext() {
        return context;
    }

    /**
     * Return the user's HttpSession, creating one if necessary.
     *
     * @return the user's HttpSession, creating one if necessary.
     */
    public HttpSession getSession() {
        return request.getSession();
    }

    /**
     * Return the page resource path from the request. For example:
     * <pre class="codeHtml">
     * <span class="blue">http://www.mycorp.com/banking/secure/login.htm</span>  ->  <span class="red">/secure/login.htm</span> </pre>
     *
     * @return the page resource path from the request
     */
    public String getResourcePath() {
        return ClickUtils.getResourcePath(request);
    }

    /**
     * Return true if the request has been forwarded. A forwarded request
     * will contain a {@link ClickServlet#CLICK_FORWARD} request attribute.
     *
     * @return true if the request has been forwarded
     */
    public boolean isForward() {
        return (request.getAttribute(ClickServlet.CLICK_FORWARD) != null);
    }

    /**
     * Return true if the HTTP request method is "POST".
     *
     * @return true if the HTTP request method is "POST"
     */
    public boolean isPost() {
        return isPost;
    }

    /**
     * Return true if the HTTP request method is "GET".
     *
     * @return true if the HTTP request method is "GET"
     */
    public boolean isGet() {
        return !isPost && getRequest().getMethod().equalsIgnoreCase("GET");
    }

    /**
     * Return true is this is an Ajax request, false otherwise.
     * <p/>
     * An Ajax request is identified by the presence of the request <tt>header</tt>
     * or request <tt>parameter</tt>: "<tt>X-Requested-With</tt>".
     * "<tt>X-Requested-With</tt>" is the de-facto standard identifier used by
     * Ajax libraries.
     * <p/>
     * <b>Note:</b> incoming requests that contains a request <tt>parameter</tt>
     * "<tt>X-Requested-With</tt>" will result in this method returning true, even
     * though the request itself was not initiated through a <tt>XmlHttpRequest</tt>
     * object. This allows one to programmatically enable Ajax requests. A common
     * use case for this feature is when uploading files through an IFrame element.
     * By specifying "<tt>X-Requested-With</tt>" as a request parameter the IFrame
     * request will be handled like a normal Ajax request.
     *
     * @return true if this is an Ajax request, false otherwise
     */
    public boolean isAjaxRequest() {
        return ClickUtils.isAjaxRequest(getRequest());
    }

    /**
     * Return true if the request contains the named attribute.
     *
     * @param name the name of the request attribute
     * @return true if the request contains the named attribute
     */
    public boolean hasRequestAttribute(String name) {
        return (getRequestAttribute(name) != null);
    }

    /**
     * Return the named request attribute, or null if not defined.
     *
     * @param name the name of the request attribute
     * @return the named request attribute, or null if not defined
     */
    public Object getRequestAttribute(String name) {
        return request.getAttribute(name);
    }

    /**
     * This method will set the named object in the HTTP request.
     *
     * @param name the storage name for the object in the request
     * @param value the object to store in the request
     */
    public void setRequestAttribute(String name, Object value) {
        request.setAttribute(name, value);
    }

    /**
     * Return true if the request contains the named parameter.
     *
     * @param name the name of the request parameter
     * @return true if the request contains the named parameter
     */
    public boolean hasRequestParameter(String name) {
        if (name == null) {
            throw new IllegalArgumentException(
                    "hasRequestParameter was called" + " with null name argument. This is often caused when a"
                            + " Control binds to a request parameter, but its name was not" + " set.");
        }
        return (getRequestParameter(name) != null);
    }

    /**
     * Return the named request parameter. This method supports
     * <tt>"multipart/form-data"</tt> POST requests and should be used in
     * preference to the <tt>HttpServletRequest</tt> method
     * <tt>getParameter()</tt>.
     *
     * @see org.apache.click.control.Form#onProcess()
     * @see #isMultipartRequest()
     * @see #getFileItemMap()
     *
     * @param name the name of the request parameter
     * @return the value of the request parameter.
     */
    public String getRequestParameter(String name) {
        if (name == null) {
            throw new IllegalArgumentException(
                    "getRequestParameter was called" + " with null name argument. This is often caused when a"
                            + " Control binds to a request parameter, but its name was not" + " set.");
        }
        return request.getParameter(name);
    }

    /**
     * Returns an array of String objects containing all of the values the given
     * request parameter has, or null if the parameter does not exist.
     *
     * @param name a <tt>String</tt> containing the name of the parameter whose
     *     value is requested
     * @return an array of <tt>String</tt> objects containing the parameter's values
     */
    public String[] getRequestParameterValues(String name) {
        if (name == null) {
            throw new IllegalArgumentException(
                    "getRequestParameter was called" + " with null name argument. This is often caused when a"
                            + " Control binds to a request parameter, but its name was not" + " set.");
        }
        return request.getParameterValues(name);
    }

    /**
     * Return the named session attribute, or null if not defined.
     * <p/>
     * If the session is not defined this method will return null, and a
     * session will not be created.
     * <p/>
     * This method supports {@link FlashAttribute} which when accessed are then
     * removed from the session.
     *
     * @param name the name of the session attribute
     * @return the named session attribute, or null if not defined
     */
    public Object getSessionAttribute(String name) {
        if (hasSession()) {
            Object object = getSession().getAttribute(name);

            if (object instanceof FlashAttribute) {
                FlashAttribute flashObject = (FlashAttribute) object;
                object = flashObject.getValue();
                removeSessionAttribute(name);
            }

            return object;
        } else {
            return null;
        }
    }

    /**
     * This method will set the named object in the HttpSession.
     * <p/>
     * This method will create a session if one does not already exist.
     *
     * @param name the storage name for the object in the session
     * @param value the object to store in the session
     */
    public void setSessionAttribute(String name, Object value) {
        getSession().setAttribute(name, value);
    }

    /**
     * Remove the named attribute from the session. If the session does not
     * exist or the name is null, this method does nothing.
     *
     * @param name of the attribute to remove from the session
     */
    public void removeSessionAttribute(String name) {
        if (hasSession() && name != null) {
            getSession().removeAttribute(name);
        }
    }

    /**
     * Return true if there is a session and it contains the named attribute.
     *
     * @param name the name of the attribute
     * @return true if the session contains the named attribute
     */
    public boolean hasSessionAttribute(String name) {
        return (getSessionAttribute(name) != null);
    }

    /**
     * Return true if a HttpSession exists, or false otherwise.
     *
     * @return true if a HttpSession exists, or false otherwise
     */
    public boolean hasSession() {
        return (request.getSession(false) != null);
    }

    /**
     * This method will set the named object as a flash HttpSession object.
     * <p/>
     * The flash object will exist in the session until it is accessed once,
     * and then removed. Flash objects are typically used to display a message
     * once.
     *
     * @param name the storage name for the object in the session
     * @param value the object to store in the session
     */
    public void setFlashAttribute(String name, Object value) {
        getSession().setAttribute(name, new FlashAttribute(value));
    }

    /**
     * Return the cookie for the given name or null if not found.
     *
     * @param name the name of the cookie
     * @return the cookie for the given name or null if not found
     */
    public Cookie getCookie(String name) {
        return ClickUtils.getCookie(getRequest(), name);
    }

    /**
     * Return the cookie value for the given name or null if not found.
     *
     * @param name the name of the cookie
     * @return the cookie value for the given name or null if not found
     */
    public String getCookieValue(String name) {
        return ClickUtils.getCookieValue(getRequest(), name);
    }

    /**
     * Sets the given cookie value in the servlet response with the path "/".
     * <p/>
     * @see ClickUtils#setCookie(HttpServletRequest, HttpServletResponse, String, String, int, String)
     *
     * @param name the cookie name
     * @param value the cookie value
     * @param maxAge the maximum age of the cookie in seconds. A negative
     * value will expire the cookie at the end of the session, while 0 will delete
     * the cookie.
     * @return the Cookie object created and set in the response
     */
    public Cookie setCookie(String name, String value, int maxAge) {
        return ClickUtils.setCookie(getRequest(), getResponse(), name, value, maxAge, "/");
    }

    /**
     * Invalidate the specified cookie and delete it from the response object.
     * Deletes only cookies mapped against the root "/" path.
     *
     * @see ClickUtils#invalidateCookie(HttpServletRequest, HttpServletResponse, String)
     *
     * @param name the name of the cookie you want to delete.
     */
    public void invalidateCookie(String name) {
        ClickUtils.invalidateCookie(getRequest(), getResponse(), name);
    }

    /**
     * Return a new Page instance for the given path.
     * <p/>
     * This method can be used to create a target page for the
     * {@link Page#setForward(Page)}, for example:
     *
     * <pre class="codeJava">
     * UserEdit userEdit = (UserEdit) getContext().createPage(<span class="st">"/user-edit.htm"</span>);
     * userEdit.setUser(user);
     *
     * setForward(userEdit); </pre>
     *
     * The given page path must start with a <tt>'/'</tt>.
     *
     * @param path the Page path as configured in the click.xml file
     * @return a new Page object
     * @throws IllegalArgumentException if the Page is not found
     */
    @SuppressWarnings("unchecked")
    public <T extends Page> T createPage(String path) {
        if (path == null || path.length() == 0) {
            throw new IllegalArgumentException("page path cannot be null or empty");
        }

        if (path.charAt(0) != '/') {
            throw new IllegalArgumentException("page path must start with a '/'");
        }
        return (T) clickServlet.createPage(path, request);
    }

    /**
     * Return a new Page instance for the given class.
     * <p/>
     * This method can be used to create a target page for the
     * {@link Page#setForward(Page)}, for example:
     *
     * <pre class="codeJava">
     * UserEdit userEdit = (UserEdit) getContext().createPage(UserEdit.<span class="kw">class</span>);
     * userEdit.setUser(user);
     *
     * setForward(userEdit); </pre>
     *
     * @param pageClass the Page class as configured in the click.xml file
     * @return a new Page object
     * @throws IllegalArgumentException if the Page is not found, or is not
     * configured with a unique path
     */
    public <T extends Page> T createPage(Class<T> pageClass) {
        return clickServlet.createPage(pageClass, request);
    }

    /**
     * Return the path for the given page Class.
     *
     * @param pageClass the class of the Page to lookup the path for
     * @return the path for the given page Class
     * @throws IllegalArgumentException if the Page Class is not configured
     * with a unique path
     */
    public String getPagePath(Class<? extends Page> pageClass) {
        return clickServlet.getConfigService().getPagePath(pageClass);
    }

    /**
     * Return the page <tt>Class</tt> for the given path.
     *
     * @param path the page path
     * @return the page class for the given path
     * @throws IllegalArgumentException if the Page Class for the path is not
     * found
     */
    public Class<? extends Page> getPageClass(String path) {
        return clickServlet.getConfigService().getPageClass(path);
    }

    /**
     * Return the Click application mode value: &nbsp;
     * <tt>["production", "profile", "development", "debug", "trace"]</tt>.
     *
     * @return the application mode value
     */
    public String getApplicationMode() {
        return clickServlet.getConfigService().getApplicationMode();
    }

    /**
     * Return the Click application charset or ISO-8859-1 if not is defined.
     * <p/>
     * The charset is defined in click.xml through the charset attribute
     * on the click-app element.
     *
     * <pre class="codeConfig">
     * &lt;?xml version="1.0" encoding="UTF-8" standalone="yes"?&gt;
     * &lt;click-app <span class="blue">charset</span>="<span class="red">UTF-8</span>"&gt;
     *    ..
     * &lt;/click-app&gt; </pre>
     *
     * @return the application charset or ISO-8859-1 if not defined
     */
    public String getCharset() {
        String charset = clickServlet.getConfigService().getCharset();
        if (charset == null) {
            charset = "ISO-8859-1";
        }
        return charset;
    }

    /**
     * Return a new messages map for the given baseClass (a page or control)
     * and the given global resource bundle name.
     *
     * @param baseClass the target class
     * @param globalResource the global resource bundle name
     * @return a new messages map with the messages for the target.
     */
    public Map<String, String> createMessagesMap(Class<?> baseClass, String globalResource) {
        MessagesMapService messagesMapService = clickServlet.getConfigService().getMessagesMapService();

        return messagesMapService.createMessagesMap(baseClass, globalResource, getLocale());
    }

    /**
     * Returns a map of <tt>FileItem arrays</tt> keyed on request parameter
     * name for "multipart" POST requests (file uploads). Thus each map entry
     * will consist of one or more <tt>FileItem</tt> objects.
     *
     * @return map of <tt>FileItem arrays</tt> keyed on request parameter name
     * for "multipart" POST requests
     */
    public Map<String, FileItem[]> getFileItemMap() {
        return findClickRequestWrapper(request).getFileItemMap();
    }

    /**
     * Returns the value of a request parameter as a FileItem, for
     * "multipart" POST requests (file uploads), or null if the parameter
     * is not found.
     * <p/>
     * If there were multivalued parameters in the request (ie two or more
     * file upload fields with the same name), the first fileItem
     * in the array is returned.
     *
     * @param name the name of the parameter of the fileItem to retrieve
     *
     * @return the fileItem for the specified name
     */
    public FileItem getFileItem(String name) {
        Object value = findClickRequestWrapper(request).getFileItemMap().get(name);

        if (value != null) {
            if (value instanceof FileItem[]) {
                FileItem[] array = (FileItem[]) value;
                if (array.length >= 1) {
                    return array[0];
                }
            } else if (value instanceof FileItem) {
                return (FileItem) value;
            }
        }
        return null;
    }

    /**
     * Return the users Locale.
     * <p/>
     * If the users Locale is stored in their session this will be returned.
     * Else if the click-app configuration defines a default Locale this
     * value will be returned, otherwise the request's Locale will be returned.
     * <p/>
     * To override the default request Locale set the users Locale using the
     * {@link #setLocale(Locale)} method.
     * <p/>
     * Pages and Controls obtain the users Locale using this method.
     *
     * @return the users Locale in the session, or if null the request Locale
     */
    public Locale getLocale() {
        Locale locale = (Locale) getSessionAttribute(LOCALE);

        if (locale == null) {

            if (clickServlet.getConfigService().getLocale() != null) {
                locale = clickServlet.getConfigService().getLocale();

            } else {
                locale = getRequest().getLocale();
            }
        }

        return locale;
    }

    /**
     * This method stores the given Locale in the users session. If the given
     * Locale is null, the "locale" attribute will be removed from the session.
     * <p/>
     * The Locale object is stored in the session using the {@link #LOCALE}
     * key.
     *
     * @param locale the Locale to store in the users session using the key
     * "locale"
     */
    public void setLocale(Locale locale) {
        if (locale == null && hasSession()) {
            getSession().removeAttribute(LOCALE);
        } else {
            setSessionAttribute(LOCALE, locale);
        }
    }

    /**
     * Return true if the request is a multi-part content type POST request.
     *
     * @return true if the request is a multi-part content type POST request
     */
    public boolean isMultipartRequest() {
        return ClickUtils.isMultipartRequest(request);
    }

    /**
     * Return a rendered Velocity template and model for the given
     * class and model data.
     * <p/>
     * This method will merge the class <tt>.htm</tt> Velocity template and
     * model data using the applications Velocity Engine.
     * <p/>
     * An example of the class template resolution is provided below:
     * <pre class="codeConfig">
     * <span class="cm">// Full class name</span>
     * com.mycorp.control.CustomTextField
     *
     * <span class="cm">// Template path name</span>
     * /com/mycorp/control/CustomTextField.htm </pre>
     *
     * Example method usage:
     * <pre class="codeJava">
     * <span class="kw">public String</span> toString() {
     *     Map model = getModel();
     *     <span class="kw">return</span> getContext().renderTemplate(getClass(), model);
     * } </pre>
     *
     * @param templateClass the class to resolve the template for
     * @param model the model data to merge with the template
     * @return rendered Velocity template merged with the model data
     * @throws RuntimeException if an error occurs
     */
    @SuppressWarnings("unchecked")
    public String renderTemplate(Class templateClass, Map<String, ?> model) {

        if (templateClass == null) {
            String msg = "Null templateClass parameter";
            throw new IllegalArgumentException(msg);
        }

        String templatePath = templateClass.getName();
        templatePath = '/' + templatePath.replace('.', '/') + ".htm";

        return renderTemplate(templatePath, model);
    }

    /**
     * Return a rendered Velocity template and model data.
     * <p/>
     * Example method usage:
     * <pre class="codeJava">
     * <span class="kw">public String</span> toString() {
     *     Map model = getModel();
     *     <span class="kw">return</span> getContext().renderTemplate(<span class="st">"/custom-table.htm"</span>, model);
     * } </pre>
     *
     * @param templatePath the path of the Velocity template to render
     * @param model the model data to merge with the template
     * @return rendered Velocity template merged with the model data
     * @throws RuntimeException if an error occurs
     */
    public String renderTemplate(String templatePath, Map<String, ?> model) {

        if (templatePath == null) {
            String msg = "Null templatePath parameter";
            throw new IllegalArgumentException(msg);
        }

        if (model == null) {
            String msg = "Null model parameter";
            throw new IllegalArgumentException(msg);
        }

        StringWriter stringWriter = new StringWriter(1024);

        TemplateService templateService = clickServlet.getConfigService().getTemplateService();

        try {
            templateService.renderTemplate(templatePath, model, stringWriter);

        } catch (Exception e) {
            String msg = "Error occurred rendering template: " + templatePath + "\n";
            clickServlet.getConfigService().getLogService().error(msg, e);

            throw new RuntimeException(e);
        }

        return stringWriter.toString();
    }

    // ------------------------------------------------ Package Private Methods

    /**
     * Return the stack data structure where Context's are stored.
     *
     * @return stack data structure where Context's are stored
     */
    static ContextStack getContextStack() {
        ContextStack contextStack = THREAD_LOCAL_CONTEXT_STACK.get();

        if (contextStack == null) {
            contextStack = new ContextStack(2);
            THREAD_LOCAL_CONTEXT_STACK.set(contextStack);
        }

        return contextStack;
    }

    /**
     * Adds the specified Context on top of the Context stack.
     *
     * @param context a context instance
     */
    static void pushThreadLocalContext(Context context) {
        getContextStack().push(context);
    }

    /**
     * Remove and return the context instance on top of the context stack.
     *
     * @return the context instance on top of the context stack
     */
    static Context popThreadLocalContext() {
        ContextStack contextStack = getContextStack();
        Context context = contextStack.pop();

        if (contextStack.isEmpty()) {
            THREAD_LOCAL_CONTEXT_STACK.remove();
        }

        return context;
    }

    /**
     * Find and return the ClickRequestWrapper that is wrapped by the specified
     * request.
     *
     * @param request the servlet request that wraps the ClickRequestWrapper
     * @return the wrapped servlet request
     */
    static ClickRequestWrapper findClickRequestWrapper(ServletRequest request) {

        while (!(request instanceof ClickRequestWrapper) && request instanceof HttpServletRequestWrapper
                && request != null) {
            request = ((HttpServletRequestWrapper) request).getRequest();
        }

        if (request instanceof ClickRequestWrapper) {
            return (ClickRequestWrapper) request;
        } else {
            throw new IllegalStateException("Click request is not present");
        }
    }

    // -------------------------------------------------- Inner Classes

    /**
     * Provides an unsynchronized Context Stack.
     */
    static final class ContextStack extends ArrayList<Context> {

        /** Serialization version indicator. */
        private static final long serialVersionUID = 1L;

        /**
         * Create a new ContextStack with the given initial capacity.
         *
         * @param initialCapacity specify initial capacity of this stack
         */
        private ContextStack(int initialCapacity) {
            super(initialCapacity);
        }

        /**
         * Pushes the Context onto the top of this stack.
         *
         * @param context the Context to push onto this stack
         * @return the Context pushed on this stack
         */
        private Context push(Context context) {
            add(context);

            return context;
        }

        /**
         * Removes and return the Context at the top of this stack.
         *
         * @return the Context at the top of this stack
         */
        private Context pop() {
            Context context = peek();

            remove(size() - 1);

            return context;
        }

        /**
         * Looks at the Context at the top of this stack without removing it.
         *
         * @return the Context at the top of this stack
         */
        private Context peek() {
            int length = size();

            if (length == 0) {
                String msg = "No Context available on ThreadLocal Context Stack";
                throw new RuntimeException(msg);
            }

            return get(length - 1);
        }
    }

}