001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.web.util;
018:
019: import java.io.File;
020: import java.io.FileNotFoundException;
021: import java.util.Enumeration;
022: import java.util.Iterator;
023: import java.util.Map;
024: import java.util.TreeMap;
025:
026: import javax.servlet.ServletContext;
027: import javax.servlet.ServletRequest;
028: import javax.servlet.http.Cookie;
029: import javax.servlet.http.HttpServletRequest;
030: import javax.servlet.http.HttpSession;
031:
032: import org.springframework.util.Assert;
033: import org.springframework.util.StringUtils;
034:
035: /**
036: * Miscellaneous utilities for web applications.
037: * Used by various framework classes.
038: *
039: * @author Rod Johnson
040: * @author Juergen Hoeller
041: */
042: public abstract class WebUtils {
043:
044: /**
045: * Standard Servlet 2.3+ spec request attributes for include URI and paths.
046: * <p>If included via a RequestDispatcher, the current resource will see the
047: * originating request. Its own URI and paths are exposed as request attributes.
048: */
049: public static final String INCLUDE_REQUEST_URI_ATTRIBUTE = "javax.servlet.include.request_uri";
050: public static final String INCLUDE_CONTEXT_PATH_ATTRIBUTE = "javax.servlet.include.context_path";
051: public static final String INCLUDE_SERVLET_PATH_ATTRIBUTE = "javax.servlet.include.servlet_path";
052: public static final String INCLUDE_PATH_INFO_ATTRIBUTE = "javax.servlet.include.path_info";
053: public static final String INCLUDE_QUERY_STRING_ATTRIBUTE = "javax.servlet.include.query_string";
054:
055: /**
056: * Standard Servlet 2.4+ spec request attributes for forward URI and paths.
057: * <p>If forwarded to via a RequestDispatcher, the current resource will see its
058: * own URI and paths. The originating URI and paths are exposed as request attributes.
059: */
060: public static final String FORWARD_REQUEST_URI_ATTRIBUTE = "javax.servlet.forward.request_uri";
061: public static final String FORWARD_CONTEXT_PATH_ATTRIBUTE = "javax.servlet.forward.context_path";
062: public static final String FORWARD_SERVLET_PATH_ATTRIBUTE = "javax.servlet.forward.servlet_path";
063: public static final String FORWARD_PATH_INFO_ATTRIBUTE = "javax.servlet.forward.path_info";
064: public static final String FORWARD_QUERY_STRING_ATTRIBUTE = "javax.servlet.forward.query_string";
065:
066: /**
067: * Prefix of the charset clause in a content type String: ";charset="
068: */
069: public static final String CONTENT_TYPE_CHARSET_PREFIX = ";charset=";
070:
071: /**
072: * Default character encoding to use when <code>request.getCharacterEncoding</code>
073: * returns <code>null</code>, according to the Servlet spec.
074: * @see ServletRequest#getCharacterEncoding
075: */
076: public static final String DEFAULT_CHARACTER_ENCODING = "ISO-8859-1";
077:
078: /**
079: * Standard Servlet spec context attribute that specifies a temporary
080: * directory for the current web application, of type <code>java.io.File</code>.
081: */
082: public static final String TEMP_DIR_CONTEXT_ATTRIBUTE = "javax.servlet.context.tempdir";
083:
084: /**
085: * HTML escape parameter at the servlet context level
086: * (i.e. a context-param in <code>web.xml</code>): "defaultHtmlEscape".
087: */
088: public static final String HTML_ESCAPE_CONTEXT_PARAM = "defaultHtmlEscape";
089:
090: /**
091: * Web app root key parameter at the servlet context level
092: * (i.e. a context-param in <code>web.xml</code>): "webAppRootKey".
093: */
094: public static final String WEB_APP_ROOT_KEY_PARAM = "webAppRootKey";
095:
096: /** Default web app root key: "webapp.root" */
097: public static final String DEFAULT_WEB_APP_ROOT_KEY = "webapp.root";
098:
099: /** Name suffixes in case of image buttons */
100: public static final String[] SUBMIT_IMAGE_SUFFIXES = { ".x", ".y" };
101:
102: /** Key for the mutex session attribute */
103: public static final String SESSION_MUTEX_ATTRIBUTE = WebUtils.class
104: .getName()
105: + ".MUTEX";
106:
107: /**
108: * Set a system property to the web application root directory.
109: * The key of the system property can be defined with the "webAppRootKey"
110: * context-param in <code>web.xml</code>. Default is "webapp.root".
111: * <p>Can be used for tools that support substition with <code>System.getProperty</code>
112: * values, like Log4J's "${key}" syntax within log file locations.
113: * @param servletContext the servlet context of the web application
114: * @throws IllegalStateException if the system property is already set,
115: * or if the WAR file is not expanded
116: * @see #WEB_APP_ROOT_KEY_PARAM
117: * @see #DEFAULT_WEB_APP_ROOT_KEY
118: * @see WebAppRootListener
119: * @see Log4jWebConfigurer
120: */
121: public static void setWebAppRootSystemProperty(
122: ServletContext servletContext) throws IllegalStateException {
123: Assert.notNull(servletContext,
124: "ServletContext must not be null");
125: String root = servletContext.getRealPath("/");
126: if (root == null) {
127: throw new IllegalStateException(
128: "Cannot set web app root system property when WAR file is not expanded");
129: }
130: String param = servletContext
131: .getInitParameter(WEB_APP_ROOT_KEY_PARAM);
132: String key = (param != null ? param : DEFAULT_WEB_APP_ROOT_KEY);
133: String oldValue = System.getProperty(key);
134: if (oldValue != null && !StringUtils.pathEquals(oldValue, root)) {
135: throw new IllegalStateException(
136: "Web app root system property already set to different value: '"
137: + key
138: + "' = ["
139: + oldValue
140: + "] instead of ["
141: + root
142: + "] - "
143: + "Choose unique values for the 'webAppRootKey' context-param in your web.xml files!");
144: }
145: System.setProperty(key, root);
146: servletContext.log("Set web app root system property: '" + key
147: + "' = [" + root + "]");
148: }
149:
150: /**
151: * Remove the system property that points to the web app root directory.
152: * To be called on shutdown of the web application.
153: * @param servletContext the servlet context of the web application
154: * @see #setWebAppRootSystemProperty
155: */
156: public static void removeWebAppRootSystemProperty(
157: ServletContext servletContext) {
158: Assert.notNull(servletContext,
159: "ServletContext must not be null");
160: String param = servletContext
161: .getInitParameter(WEB_APP_ROOT_KEY_PARAM);
162: String key = (param != null ? param : DEFAULT_WEB_APP_ROOT_KEY);
163: System.getProperties().remove(key);
164: }
165:
166: /**
167: * Return whether default HTML escaping is enabled for the web application,
168: * i.e. the value of the "defaultHtmlEscape" context-param in <code>web.xml</code>
169: * (if any).
170: * @param servletContext the servlet context of the web application
171: * @return whether default HTML escaping is enabled (default is false)
172: */
173: public static boolean isDefaultHtmlEscape(
174: ServletContext servletContext) {
175: Assert.notNull(servletContext,
176: "ServletContext must not be null");
177: String param = servletContext
178: .getInitParameter(HTML_ESCAPE_CONTEXT_PARAM);
179: return Boolean.valueOf(param).booleanValue();
180: }
181:
182: /**
183: * Return the temporary directory for the current web application,
184: * as provided by the servlet container.
185: * @param servletContext the servlet context of the web application
186: * @return the File representing the temporary directory
187: */
188: public static File getTempDir(ServletContext servletContext) {
189: Assert.notNull(servletContext,
190: "ServletContext must not be null");
191: return (File) servletContext
192: .getAttribute(TEMP_DIR_CONTEXT_ATTRIBUTE);
193: }
194:
195: /**
196: * Return the real path of the given path within the web application,
197: * as provided by the servlet container.
198: * <p>Prepends a slash if the path does not already start with a slash,
199: * and throws a FileNotFoundException if the path cannot be resolved to
200: * a resource (in contrast to ServletContext's <code>getRealPath</code>,
201: * which returns null).
202: * @param servletContext the servlet context of the web application
203: * @param path the path within the web application
204: * @return the corresponding real path
205: * @throws FileNotFoundException if the path cannot be resolved to a resource
206: * @see javax.servlet.ServletContext#getRealPath
207: */
208: public static String getRealPath(ServletContext servletContext,
209: String path) throws FileNotFoundException {
210: Assert.notNull(servletContext,
211: "ServletContext must not be null");
212: // Interpret location as relative to the web application root directory.
213: if (!path.startsWith("/")) {
214: path = "/" + path;
215: }
216: String realPath = servletContext.getRealPath(path);
217: if (realPath == null) {
218: throw new FileNotFoundException("ServletContext resource ["
219: + path
220: + "] cannot be resolved to absolute file path - "
221: + "web application archive not expanded?");
222: }
223: return realPath;
224: }
225:
226: /**
227: * Determine the session id of the given request, if any.
228: * @param request current HTTP request
229: * @return the session id, or <code>null</code> if none
230: */
231: public static String getSessionId(HttpServletRequest request) {
232: Assert.notNull(request, "Request must not be null");
233: HttpSession session = request.getSession(false);
234: return (session != null ? session.getId() : null);
235: }
236:
237: /**
238: * Check the given request for a session attribute of the given name.
239: * Returns null if there is no session or if the session has no such attribute.
240: * Does not create a new session if none has existed before!
241: * @param request current HTTP request
242: * @param name the name of the session attribute
243: * @return the value of the session attribute, or <code>null</code> if not found
244: */
245: public static Object getSessionAttribute(
246: HttpServletRequest request, String name) {
247: Assert.notNull(request, "Request must not be null");
248: HttpSession session = request.getSession(false);
249: return (session != null ? session.getAttribute(name) : null);
250: }
251:
252: /**
253: * Check the given request for a session attribute of the given name.
254: * Throws an exception if there is no session or if the session has no such
255: * attribute. Does not create a new session if none has existed before!
256: * @param request current HTTP request
257: * @param name the name of the session attribute
258: * @return the value of the session attribute, or <code>null</code> if not found
259: * @throws IllegalStateException if the session attribute could not be found
260: */
261: public static Object getRequiredSessionAttribute(
262: HttpServletRequest request, String name)
263: throws IllegalStateException {
264:
265: Object attr = getSessionAttribute(request, name);
266: if (attr == null) {
267: throw new IllegalStateException("No session attribute '"
268: + name + "' found");
269: }
270: return attr;
271: }
272:
273: /**
274: * Set the session attribute with the given name to the given value.
275: * Removes the session attribute if value is null, if a session existed at all.
276: * Does not create a new session if not necessary!
277: * @param request current HTTP request
278: * @param name the name of the session attribute
279: * @param value the value of the session attribute
280: */
281: public static void setSessionAttribute(HttpServletRequest request,
282: String name, Object value) {
283: Assert.notNull(request, "Request must not be null");
284: if (value != null) {
285: request.getSession().setAttribute(name, value);
286: } else {
287: HttpSession session = request.getSession(false);
288: if (session != null) {
289: session.removeAttribute(name);
290: }
291: }
292: }
293:
294: /**
295: * Get the specified session attribute, creating and setting a new attribute if
296: * no existing found. The given class needs to have a public no-arg constructor.
297: * Useful for on-demand state objects in a web tier, like shopping carts.
298: * @param session current HTTP session
299: * @param name the name of the session attribute
300: * @param clazz the class to instantiate for a new attribute
301: * @return the value of the session attribute, newly created if not found
302: * @throws IllegalArgumentException if the session attribute could not be instantiated
303: */
304: public static Object getOrCreateSessionAttribute(
305: HttpSession session, String name, Class clazz)
306: throws IllegalArgumentException {
307:
308: Assert.notNull(session, "Session must not be null");
309: Object sessionObject = session.getAttribute(name);
310: if (sessionObject == null) {
311: try {
312: sessionObject = clazz.newInstance();
313: } catch (InstantiationException ex) {
314: throw new IllegalArgumentException(
315: "Could not instantiate class ["
316: + clazz.getName()
317: + "] for session attribute '" + name
318: + "': " + ex.getMessage());
319: } catch (IllegalAccessException ex) {
320: throw new IllegalArgumentException(
321: "Could not access default constructor of class ["
322: + clazz.getName()
323: + "] for session attribute '" + name
324: + "': " + ex.getMessage());
325: }
326: session.setAttribute(name, sessionObject);
327: }
328: return sessionObject;
329: }
330:
331: /**
332: * Return the best available mutex for the given session:
333: * that is, an object to synchronize on for the given session.
334: * <p>Returns the session mutex attribute if available; usually,
335: * this means that the HttpSessionMutexListener needs to be defined
336: * in <code>web.xml</code>. Falls back to the HttpSession itself
337: * if no mutex attribute found.
338: * <p>The session mutex is guaranteed to be the same object during
339: * the entire lifetime of the session, available under the key defined
340: * by the <code>SESSION_MUTEX_ATTRIBUTE</code> constant. It serves as a
341: * safe reference to synchronize on for locking on the current session.
342: * <p>In many cases, the HttpSession reference itself is a safe mutex
343: * as well, since it will always be the same object reference for the
344: * same active logical session. However, this is not guaranteed across
345: * different servlet containers; the only 100% safe way is a session mutex.
346: * @param session the HttpSession to find a mutex for
347: * @return the mutex object (never <code>null</code>)
348: * @see #SESSION_MUTEX_ATTRIBUTE
349: * @see HttpSessionMutexListener
350: */
351: public static Object getSessionMutex(HttpSession session) {
352: Assert.notNull(session, "Session must not be null");
353: Object mutex = session.getAttribute(SESSION_MUTEX_ATTRIBUTE);
354: if (mutex == null) {
355: mutex = session;
356: }
357: return mutex;
358: }
359:
360: /**
361: * Determine whether the given request is an include request,
362: * that is, not a top-level HTTP request coming in from the outside.
363: * <p>Checks the presence of the "javax.servlet.include.request_uri"
364: * request attribute. Could check any request attribute that is only
365: * present in an include request.
366: * @param request current servlet request
367: * @return whether the given request is an include request
368: */
369: public static boolean isIncludeRequest(ServletRequest request) {
370: return (request.getAttribute(INCLUDE_REQUEST_URI_ATTRIBUTE) != null);
371: }
372:
373: /**
374: * Expose the current request URI and paths as {@link javax.servlet.http.HttpServletRequest}
375: * attributes under the keys defined in the Servlet 2.4 specification,
376: * for containers that implement 2.3 or an earlier version of the Servlet API:
377: * <code>javax.servlet.forward.request_uri</code>,
378: * <code>javax.servlet.forward.context_path</code>,
379: * <code>javax.servlet.forward.servlet_path</code>,
380: * <code>javax.servlet.forward.path_info</code>,
381: * <code>javax.servlet.forward.query_string</code>.
382: * <p>Does not override values if already present, to not cause conflicts
383: * with the attributes exposed by Servlet 2.4+ containers themselves.
384: * @param request current servlet request
385: */
386: public static void exposeForwardRequestAttributes(
387: HttpServletRequest request) {
388: if (request.getAttribute(FORWARD_REQUEST_URI_ATTRIBUTE) == null) {
389: request.setAttribute(FORWARD_REQUEST_URI_ATTRIBUTE, request
390: .getRequestURI());
391: }
392: if (request.getAttribute(FORWARD_CONTEXT_PATH_ATTRIBUTE) == null) {
393: request.setAttribute(FORWARD_CONTEXT_PATH_ATTRIBUTE,
394: request.getContextPath());
395: }
396: if (request.getAttribute(FORWARD_SERVLET_PATH_ATTRIBUTE) == null) {
397: request.setAttribute(FORWARD_SERVLET_PATH_ATTRIBUTE,
398: request.getServletPath());
399: }
400: if (request.getAttribute(FORWARD_PATH_INFO_ATTRIBUTE) == null) {
401: request.setAttribute(FORWARD_PATH_INFO_ATTRIBUTE, request
402: .getPathInfo());
403: }
404: if (request.getAttribute(FORWARD_QUERY_STRING_ATTRIBUTE) == null) {
405: request.setAttribute(FORWARD_QUERY_STRING_ATTRIBUTE,
406: request.getQueryString());
407: }
408: }
409:
410: /**
411: * Expose the given Map as request attributes, using the keys as attribute names
412: * and the values as corresponding attribute values. Keys need to be Strings.
413: * @param request current HTTP request
414: * @param attributes the attributes Map
415: * @throws IllegalArgumentException if an invalid key is found in the Map
416: */
417: public static void exposeRequestAttributes(ServletRequest request,
418: Map attributes) throws IllegalArgumentException {
419:
420: Assert.notNull(request, "Request must not be null");
421: Iterator it = attributes.entrySet().iterator();
422: while (it.hasNext()) {
423: Map.Entry entry = (Map.Entry) it.next();
424: if (!(entry.getKey() instanceof String)) {
425: throw new IllegalArgumentException(
426: "Invalid key ["
427: + entry.getKey()
428: + "] in attributes Map - only Strings allowed as attribute keys");
429: }
430: request.setAttribute((String) entry.getKey(), entry
431: .getValue());
432: }
433: }
434:
435: /**
436: * Retrieve the first cookie with the given name. Note that multiple
437: * cookies can have the same name but different paths or domains.
438: * @param request current servlet request
439: * @param name cookie name
440: * @return the first cookie with the given name, or <code>null</code> if none is found
441: */
442: public static Cookie getCookie(HttpServletRequest request,
443: String name) {
444: Assert.notNull(request, "Request must not be null");
445: Cookie cookies[] = request.getCookies();
446: if (cookies != null) {
447: for (int i = 0; i < cookies.length; i++) {
448: if (name.equals(cookies[i].getName())) {
449: return cookies[i];
450: }
451: }
452: }
453: return null;
454: }
455:
456: /**
457: * Check if a specific input type="submit" parameter was sent in the request,
458: * either via a button (directly with name) or via an image (name + ".x" or
459: * name + ".y").
460: * @param request current HTTP request
461: * @param name name of the parameter
462: * @return if the parameter was sent
463: * @see #SUBMIT_IMAGE_SUFFIXES
464: */
465: public static boolean hasSubmitParameter(ServletRequest request,
466: String name) {
467: Assert.notNull(request, "Request must not be null");
468: if (request.getParameter(name) != null) {
469: return true;
470: }
471: for (int i = 0; i < SUBMIT_IMAGE_SUFFIXES.length; i++) {
472: String suffix = SUBMIT_IMAGE_SUFFIXES[i];
473: if (request.getParameter(name + suffix) != null) {
474: return true;
475: }
476: }
477: return false;
478: }
479:
480: /**
481: * Return a map containing all parameters with the given prefix.
482: * Maps single values to String and multiple values to String array.
483: * <p>For example, with a prefix of "spring_", "spring_param1" and
484: * "spring_param2" result in a Map with "param1" and "param2" as keys.
485: * <p>Similar to Servlet 2.3's <code>ServletRequest.getParameterMap</code>,
486: * but more flexible and compatible with Servlet 2.2.
487: * @param request HTTP request in which to look for parameters
488: * @param prefix the beginning of parameter names
489: * (if this is null or the empty string, all parameters will match)
490: * @return map containing request parameters <b>without the prefix</b>,
491: * containing either a String or a String array as values
492: * @see javax.servlet.ServletRequest#getParameterNames
493: * @see javax.servlet.ServletRequest#getParameterValues
494: * @see javax.servlet.ServletRequest#getParameterMap
495: */
496: public static Map getParametersStartingWith(ServletRequest request,
497: String prefix) {
498: Assert.notNull(request, "Request must not be null");
499: Enumeration paramNames = request.getParameterNames();
500: Map params = new TreeMap();
501: if (prefix == null) {
502: prefix = "";
503: }
504: while (paramNames != null && paramNames.hasMoreElements()) {
505: String paramName = (String) paramNames.nextElement();
506: if ("".equals(prefix) || paramName.startsWith(prefix)) {
507: String unprefixed = paramName
508: .substring(prefix.length());
509: String[] values = request.getParameterValues(paramName);
510: if (values == null || values.length == 0) {
511: // Do nothing, no values found at all.
512: } else if (values.length > 1) {
513: params.put(unprefixed, values);
514: } else {
515: params.put(unprefixed, values[0]);
516: }
517: }
518: }
519: return params;
520: }
521:
522: /**
523: * Extract the URL filename from the given request URL path.
524: * Correctly resolves nested paths such as "/products/view.html" as well.
525: * @param urlPath the request URL path (e.g. "/index.html")
526: * @return the extracted URI filename (e.g. "index")
527: */
528: public static String extractFilenameFromUrlPath(String urlPath) {
529: int begin = urlPath.lastIndexOf('/') + 1;
530: int end = urlPath.indexOf(';');
531: if (end == -1) {
532: end = urlPath.indexOf('?');
533: if (end == -1) {
534: end = urlPath.length();
535: }
536: }
537: String filename = urlPath.substring(begin, end);
538: int dotIndex = filename.lastIndexOf('.');
539: if (dotIndex != -1) {
540: filename = filename.substring(0, dotIndex);
541: }
542: return filename;
543: }
544:
545: }
|