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.context.request;
018:
019: import java.io.Serializable;
020: import java.util.HashMap;
021: import java.util.Iterator;
022: import java.util.Map;
023:
024: import javax.servlet.http.HttpServletRequest;
025: import javax.servlet.http.HttpSession;
026: import javax.servlet.http.HttpSessionBindingEvent;
027: import javax.servlet.http.HttpSessionBindingListener;
028:
029: import org.apache.commons.logging.Log;
030: import org.apache.commons.logging.LogFactory;
031:
032: import org.springframework.util.Assert;
033: import org.springframework.util.ClassUtils;
034: import org.springframework.web.util.WebUtils;
035:
036: /**
037: * Servlet-based implementation of the {@link RequestAttributes} interface.
038: *
039: * <p>Accesses objects from servlet request and HTTP session scope,
040: * with no distinction between "session" and "global session".
041: *
042: * @author Juergen Hoeller
043: * @since 2.0
044: * @see javax.servlet.ServletRequest#getAttribute
045: * @see javax.servlet.http.HttpSession#getAttribute
046: */
047: public class ServletRequestAttributes extends AbstractRequestAttributes {
048:
049: /**
050: * Constant identifying the {@link String} prefixed to the name of a
051: * destruction callback when it is stored in a {@link HttpSession}.
052: */
053: public static final String DESTRUCTION_CALLBACK_NAME_PREFIX = ServletRequestAttributes.class
054: .getName()
055: + ".DESTRUCTION_CALLBACK.";
056:
057: // We'll create a lot of these objects, so we don't want a new logger every time.
058: private static final Log logger = LogFactory
059: .getLog(ServletRequestAttributes.class);
060:
061: // Determine whether Servlet 2.3's HttpSessionBindingListener interface is available.
062: private final static boolean bindingListenerAvailable = ClassUtils
063: .isPresent("javax.servlet.http.HttpSessionBindingListener",
064: ServletRequestAttributes.class.getClassLoader());
065:
066: private final HttpServletRequest request;
067:
068: private HttpSession session;
069:
070: private final Map sessionAttributesToUpdate = new HashMap();
071:
072: /**
073: * Create a new ServletRequestAttributes instance for the given request.
074: * @param request current HTTP request
075: */
076: public ServletRequestAttributes(HttpServletRequest request) {
077: Assert.notNull(request, "Request must not be null");
078: this .request = request;
079: // Fetch existing session reference early, to have it available even
080: // after request completion (for example, in a custom child thread).
081: this .session = request.getSession(false);
082: }
083:
084: /**
085: * Exposes the native {@link HttpServletRequest} that we're wrapping.
086: */
087: public final HttpServletRequest getRequest() {
088: return this .request;
089: }
090:
091: /**
092: * Exposes the {@link HttpSession} that we're wrapping.
093: * @param allowCreate whether to allow creation of a new session if none exists yet
094: */
095: protected final HttpSession getSession(boolean allowCreate) {
096: try {
097: this .session = this .request.getSession(allowCreate);
098: return this .session;
099: } catch (IllegalStateException ex) {
100: // Couldn't access session... let's check why.
101: if (this .session == null) {
102: // No matter what happened - we cannot offer a session.
103: throw ex;
104: }
105: // We have a fallback session reference...
106: // Let's see whether it is appropriate to return it.
107: if (allowCreate) {
108: boolean canAskForExistingSession = false;
109: try {
110: this .session = this .request.getSession(false);
111: canAskForExistingSession = true;
112: } catch (IllegalStateException ex2) {
113: }
114: if (canAskForExistingSession) {
115: // Could ask for existing session, hence the IllegalStateException
116: // came from trying to create a new session too late -> rethrow.
117: throw ex;
118: }
119: }
120: // Else: Could not even ask for existing session, hence we assume that
121: // the request has been completed and the session is accessed later on
122: // (for example, in a custom child thread).
123: return this .session;
124: }
125: }
126:
127: public Object getAttribute(String name, int scope) {
128: if (scope == SCOPE_REQUEST) {
129: return this .request.getAttribute(name);
130: } else {
131: HttpSession session = getSession(false);
132: if (session != null) {
133: Object value = session.getAttribute(name);
134: if (value != null) {
135: this .sessionAttributesToUpdate.put(name, value);
136: }
137: return value;
138: } else {
139: return null;
140: }
141: }
142: }
143:
144: public void setAttribute(String name, Object value, int scope) {
145: if (scope == SCOPE_REQUEST) {
146: this .request.setAttribute(name, value);
147: } else {
148: HttpSession session = getSession(true);
149: session.setAttribute(name, value);
150: this .sessionAttributesToUpdate.remove(name);
151: }
152: }
153:
154: public void removeAttribute(String name, int scope) {
155: if (scope == SCOPE_REQUEST) {
156: this .request.removeAttribute(name);
157: removeRequestDestructionCallback(name);
158: } else {
159: HttpSession session = getSession(false);
160: if (session != null) {
161: session.removeAttribute(name);
162: this .sessionAttributesToUpdate.remove(name);
163: // Remove any registered destruction callback as well.
164: session
165: .removeAttribute(DESTRUCTION_CALLBACK_NAME_PREFIX
166: + name);
167: }
168: }
169: }
170:
171: public void registerDestructionCallback(String name,
172: Runnable callback, int scope) {
173: if (scope == SCOPE_REQUEST) {
174: registerRequestDestructionCallback(name, callback);
175: } else {
176: registerSessionDestructionCallback(name, callback);
177: }
178: }
179:
180: public String getSessionId() {
181: return getSession(true).getId();
182: }
183:
184: public Object getSessionMutex() {
185: return WebUtils.getSessionMutex(getSession(true));
186: }
187:
188: /**
189: * Update all accessed session attributes through <code>session.setAttribute</code>
190: * calls, explicitly indicating to the container that they might have been modified.
191: */
192: protected void updateAccessedSessionAttributes() {
193: HttpSession session = getSession(false);
194: if (session != null) {
195: for (Iterator it = this .sessionAttributesToUpdate
196: .entrySet().iterator(); it.hasNext();) {
197: Map.Entry entry = (Map.Entry) it.next();
198: String name = (String) entry.getKey();
199: Object newValue = entry.getValue();
200: Object oldValue = session.getAttribute(name);
201: if (oldValue == newValue) {
202: session.setAttribute(name, newValue);
203: }
204: }
205: }
206: this .sessionAttributesToUpdate.clear();
207: }
208:
209: /**
210: * Register the given callback as to be executed after session termination.
211: * @param name the name of the attribute to register the callback for
212: * @param callback the callback to be executed for destruction
213: */
214: private void registerSessionDestructionCallback(String name,
215: Runnable callback) {
216: if (bindingListenerAvailable) {
217: HttpSession session = getSession(true);
218: session.setAttribute(DESTRUCTION_CALLBACK_NAME_PREFIX
219: + name, new DestructionCallbackBindingListener(
220: callback));
221: } else {
222: if (logger.isWarnEnabled()) {
223: logger
224: .warn("Could not register destruction callback ["
225: + callback
226: + "] for attribute '"
227: + name
228: + "' in session scope because Servlet 2.3 API is not available");
229: }
230: }
231: }
232:
233: /**
234: * Adapter that implements the Servlet 2.3 HttpSessionBindingListener
235: * interface, wrapping a request destruction callback.
236: */
237: private static class DestructionCallbackBindingListener implements
238: HttpSessionBindingListener, Serializable {
239:
240: private final Runnable destructionCallback;
241:
242: public DestructionCallbackBindingListener(
243: Runnable destructionCallback) {
244: this .destructionCallback = destructionCallback;
245: }
246:
247: public void valueBound(HttpSessionBindingEvent event) {
248: }
249:
250: public void valueUnbound(HttpSessionBindingEvent event) {
251: this.destructionCallback.run();
252: }
253: }
254:
255: }
|