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.StringUtils;
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: private final HttpServletRequest request;
062:
063: private HttpSession session;
064:
065: private final Map sessionAttributesToUpdate = new HashMap();
066:
067: /**
068: * Create a new ServletRequestAttributes instance for the given request.
069: * @param request current HTTP request
070: */
071: public ServletRequestAttributes(HttpServletRequest request) {
072: Assert.notNull(request, "Request must not be null");
073: this .request = request;
074: // Fetch existing session reference early, to have it available even
075: // after request completion (for example, in a custom child thread).
076: this .session = request.getSession(false);
077: }
078:
079: /**
080: * Exposes the native {@link HttpServletRequest} that we're wrapping.
081: */
082: public final HttpServletRequest getRequest() {
083: return this .request;
084: }
085:
086: /**
087: * Exposes the {@link HttpSession} that we're wrapping.
088: * @param allowCreate whether to allow creation of a new session if none exists yet
089: */
090: protected final HttpSession getSession(boolean allowCreate) {
091: try {
092: this .session = this .request.getSession(allowCreate);
093: return this .session;
094: } catch (IllegalStateException ex) {
095: // Couldn't access session... let's check why.
096: if (this .session == null) {
097: // No matter what happened - we cannot offer a session.
098: throw ex;
099: }
100: // We have a fallback session reference...
101: // Let's see whether it is appropriate to return it.
102: if (allowCreate) {
103: boolean canAskForExistingSession = false;
104: try {
105: this .session = this .request.getSession(false);
106: canAskForExistingSession = true;
107: } catch (IllegalStateException ex2) {
108: }
109: if (canAskForExistingSession) {
110: // Could ask for existing session, hence the IllegalStateException
111: // came from trying to create a new session too late -> rethrow.
112: throw ex;
113: }
114: }
115: // Else: Could not even ask for existing session, hence we assume that
116: // the request has been completed and the session is accessed later on
117: // (for example, in a custom child thread).
118: return this .session;
119: }
120: }
121:
122: public Object getAttribute(String name, int scope) {
123: if (scope == SCOPE_REQUEST) {
124: return this .request.getAttribute(name);
125: } else {
126: HttpSession session = getSession(false);
127: if (session != null) {
128: Object value = session.getAttribute(name);
129: if (value != null) {
130: this .sessionAttributesToUpdate.put(name, value);
131: }
132: return value;
133: } else {
134: return null;
135: }
136: }
137: }
138:
139: public void setAttribute(String name, Object value, int scope) {
140: if (scope == SCOPE_REQUEST) {
141: this .request.setAttribute(name, value);
142: } else {
143: HttpSession session = getSession(true);
144: session.setAttribute(name, value);
145: this .sessionAttributesToUpdate.remove(name);
146: }
147: }
148:
149: public void removeAttribute(String name, int scope) {
150: if (scope == SCOPE_REQUEST) {
151: this .request.removeAttribute(name);
152: removeRequestDestructionCallback(name);
153: } else {
154: HttpSession session = getSession(false);
155: if (session != null) {
156: session.removeAttribute(name);
157: this .sessionAttributesToUpdate.remove(name);
158: // Remove any registered destruction callback as well.
159: session
160: .removeAttribute(DESTRUCTION_CALLBACK_NAME_PREFIX
161: + name);
162: }
163: }
164: }
165:
166: public String[] getAttributeNames(int scope) {
167: if (scope == SCOPE_REQUEST) {
168: return StringUtils.toStringArray(this .request
169: .getAttributeNames());
170: } else {
171: HttpSession session = getSession(false);
172: if (session != null) {
173: return StringUtils.toStringArray(session
174: .getAttributeNames());
175: } else {
176: return new String[0];
177: }
178: }
179: }
180:
181: public void registerDestructionCallback(String name,
182: Runnable callback, int scope) {
183: if (scope == SCOPE_REQUEST) {
184: registerRequestDestructionCallback(name, callback);
185: } else {
186: registerSessionDestructionCallback(name, callback);
187: }
188: }
189:
190: public String getSessionId() {
191: return getSession(true).getId();
192: }
193:
194: public Object getSessionMutex() {
195: return WebUtils.getSessionMutex(getSession(true));
196: }
197:
198: /**
199: * Update all accessed session attributes through <code>session.setAttribute</code>
200: * calls, explicitly indicating to the container that they might have been modified.
201: */
202: protected void updateAccessedSessionAttributes() {
203: HttpSession session = getSession(false);
204: if (session != null) {
205: for (Iterator it = this .sessionAttributesToUpdate
206: .entrySet().iterator(); it.hasNext();) {
207: Map.Entry entry = (Map.Entry) it.next();
208: String name = (String) entry.getKey();
209: Object newValue = entry.getValue();
210: Object oldValue = session.getAttribute(name);
211: if (oldValue == newValue) {
212: session.setAttribute(name, newValue);
213: }
214: }
215: }
216: this .sessionAttributesToUpdate.clear();
217: }
218:
219: /**
220: * Register the given callback as to be executed after session termination.
221: * @param name the name of the attribute to register the callback for
222: * @param callback the callback to be executed for destruction
223: */
224: private void registerSessionDestructionCallback(String name,
225: Runnable callback) {
226: HttpSession session = getSession(true);
227: session.setAttribute(DESTRUCTION_CALLBACK_NAME_PREFIX + name,
228: new DestructionCallbackBindingListener(callback));
229: }
230:
231: /**
232: * Adapter that implements the Servlet 2.3 HttpSessionBindingListener
233: * interface, wrapping a request destruction callback.
234: */
235: private static class DestructionCallbackBindingListener implements
236: HttpSessionBindingListener, Serializable {
237:
238: private final Runnable destructionCallback;
239:
240: public DestructionCallbackBindingListener(
241: Runnable destructionCallback) {
242: this .destructionCallback = destructionCallback;
243: }
244:
245: public void valueBound(HttpSessionBindingEvent event) {
246: }
247:
248: public void valueUnbound(HttpSessionBindingEvent event) {
249: this.destructionCallback.run();
250: }
251: }
252:
253: }
|