| /* |
| * 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: <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: |
| * <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"> |
| * <?xml version="1.0" encoding="UTF-8" standalone="yes"?> |
| * <click-app <span class="blue">charset</span>="<span class="red">UTF-8</span>"> |
| * .. |
| * </click-app> </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); |
| } |
| } |
| |
| } |