001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: * $Header:$
018: */
019: package org.apache.beehive.netui.pageflow.internal;
020:
021: import org.apache.beehive.netui.pageflow.handler.StorageHandler;
022: import org.apache.beehive.netui.pageflow.RequestContext;
023: import org.apache.beehive.netui.pageflow.ServletContainerAdapter;
024: import org.apache.beehive.netui.pageflow.PageFlowController;
025: import org.apache.beehive.netui.pageflow.scoping.ScopedServletUtils;
026: import org.apache.beehive.netui.util.logging.Logger;
027: import org.apache.beehive.netui.util.internal.ServletUtils;
028:
029: import javax.servlet.ServletContext;
030: import javax.servlet.ServletRequest;
031: import javax.servlet.http.HttpServletRequest;
032: import javax.servlet.http.HttpSession;
033: import javax.servlet.http.HttpSessionBindingListener;
034: import javax.servlet.http.HttpSessionBindingEvent;
035: import java.util.HashSet;
036: import java.util.HashMap;
037: import java.util.Iterator;
038: import java.util.Map;
039: import java.util.Enumeration;
040: import java.util.ArrayList;
041: import java.util.Collections;
042:
043: /**
044: * This alternate session storage handler does not write any attribute into the session until the very end of a chain
045: * of forwarded requests (i.e., not even at the end of an inner forwarded request). This allows it to handle multiple
046: * concurrent forwarded requests, each of which is modifying the same data, in a more reasonable way. Basically,
047: * each request works in its own snapshot of the session, and the last one to commit is the one whose snapshot wins.
048: * This is a better alternative than allowing them to interfere with each other in the middle of the request chain.
049: */
050: public class DeferredSessionStorageHandler extends DefaultHandler
051: implements StorageHandler {
052: private static final Logger _log = Logger
053: .getInstance(DeferredSessionStorageHandler.class);
054:
055: private static final String CHANGELIST_ATTR = InternalConstants.ATTR_PREFIX
056: + "changedAttrs";
057: private static final String FAILOVER_MAP_ATTR = InternalConstants.ATTR_PREFIX
058: + "failoverAttrs";
059:
060: private static ThreadLocal _isCommittingChanges = new ThreadLocal() {
061: public Object initialValue() {
062: return Boolean.FALSE;
063: }
064: };
065:
066: public DeferredSessionStorageHandler(ServletContext servletContext) {
067: init(null, null, servletContext);
068: }
069:
070: private static final class SessionBindingEvent extends
071: HttpSessionBindingEvent {
072: public SessionBindingEvent(HttpSession httpSession,
073: String attrName) {
074: super (httpSession, attrName);
075: }
076:
077: public SessionBindingEvent(HttpSession httpSession,
078: String attrName, Object attrVal) {
079: super (httpSession, attrName, attrVal);
080: }
081: }
082:
083: public void setAttribute(RequestContext context, String attrName,
084: Object value) {
085: if (_log.isTraceEnabled()) {
086: _log.trace("setAttribute: " + attrName + "=" + value);
087: }
088:
089: HttpServletRequest request = ScopedServletUtils
090: .getOuterRequest((HttpServletRequest) context
091: .getRequest());
092: Object currentValue = request.getAttribute(attrName);
093:
094: //
095: // Note that we unconditionally get/create the session. We want to make sure that the session exists when
096: // we set an attribute, since we will *not* create it in applyChanges, to prevent recreation of an
097: // intentionally-invalidated session.
098: //
099: HttpSession session = request.getSession();
100:
101: //
102: // Emulate a setAttribute on the session: if the value is an HttpSessionBindingListener, invoke its
103: // valueUnbound(). Note that we don't currently care about calling valueBound().
104: //
105: if (currentValue != null && currentValue != value
106: && currentValue instanceof HttpSessionBindingListener) {
107: HttpSessionBindingEvent event = new SessionBindingEvent(
108: session, attrName, currentValue);
109: ((HttpSessionBindingListener) currentValue)
110: .valueUnbound(event);
111: }
112:
113: request.setAttribute(attrName, value);
114: getChangedAttributesList(request, true, false).add(attrName);
115:
116: //
117: // For attributes that have changed and are now tracked in the changed attribute list, be sure to remove
118: // them from the list of failover attributes.
119: //
120: HashMap failoverAttrs = getFailoverAttributesMap(request,
121: false, false);
122: if (failoverAttrs != null)
123: failoverAttrs.remove(attrName);
124: }
125:
126: public void removeAttribute(RequestContext context, String attrName) {
127: if (_log.isTraceEnabled()) {
128: _log.trace("removeAttribute: " + attrName);
129: }
130:
131: HttpServletRequest request = ScopedServletUtils
132: .getOuterRequest((HttpServletRequest) context
133: .getRequest());
134: Object currentValue = request.getAttribute(attrName);
135:
136: //
137: // Emulate a removeAttribute on the session: if the value is an HttpSessionBindingListener, invoke its
138: // valueUnbound().
139: //
140: if (currentValue != null
141: && currentValue instanceof HttpSessionBindingListener) {
142: HttpSessionBindingEvent event = new SessionBindingEvent(
143: request.getSession(), attrName, currentValue);
144: ((HttpSessionBindingListener) currentValue)
145: .valueUnbound(event);
146: }
147:
148: request.removeAttribute(attrName);
149: getChangedAttributesList(request, true, false).add(attrName);
150:
151: HashMap failoverAttrs = getFailoverAttributesMap(request,
152: false, false);
153: if (failoverAttrs != null)
154: failoverAttrs.remove(attrName);
155: }
156:
157: public Object getAttribute(RequestContext context,
158: String attributeName) {
159: if (_log.isTraceEnabled()) {
160: _log.trace("getAttribute: " + attributeName);
161: }
162:
163: HttpServletRequest request = ScopedServletUtils
164: .getOuterRequest((HttpServletRequest) context
165: .getRequest());
166: Object val = request.getAttribute(attributeName);
167: if (val != null)
168: return val;
169:
170: // If the attribute isn't present in the request and is in the list of changed attrs, then it was removed.
171: // Don't fall back to the session attribute in that case.
172: HashSet changedAttrs = getChangedAttributesList(request, false,
173: false);
174: if (changedAttrs != null
175: && changedAttrs.contains(attributeName))
176: return null;
177:
178: // Get the attribute out of the session, and put it into the request. Until applyChanges is called, this is
179: // the value we'll use.
180: HttpSession session = request.getSession(false);
181: if (session != null) {
182: val = session.getAttribute(attributeName);
183: if (val != null)
184: request.setAttribute(attributeName, val);
185: }
186:
187: return val;
188: }
189:
190: public void applyChanges(RequestContext context) {
191: HttpServletRequest request = ScopedServletUtils
192: .getOuterRequest((HttpServletRequest) context
193: .getRequest());
194: assert request != null;
195: HttpSession session = request.getSession(false);
196:
197: //
198: // If the session doesn't exist, we don't want to recreate it. It has most likely been intentionally
199: // invalidated, since we did ensure its existance in getAttribute.
200: //
201: if (session == null)
202: return;
203:
204: if (_log.isDebugEnabled())
205: _log.debug("Applying changes for request "
206: + request.getRequestURI() + ". Identity: "
207: + System.identityHashCode(request));
208:
209: Object sessionMutex = ServletUtils.getSessionMutex(session,
210: ServletUtils.SESSION_MUTEX_ATTRIBUTE);
211:
212: //
213: // Synchronize on the session mutex in order to make the operation of applying changes from a request into
214: // the session atomic. Atomicity is needed in order to ensure that the attributes set in the session via
215: // "session.setAttribute(...)" are the same ones present when the "ensureFailover" call is made against the
216: // ServletContainerAdapter.
217: //
218: synchronized (sessionMutex) {
219:
220: HashSet changedAttrs = getChangedAttributesList(request,
221: false, true);
222: if (changedAttrs != null) {
223: //
224: // Ensure that the Page Flow in the request is run through its destroy lifecycle correctly
225: //
226: for (Iterator i = changedAttrs.iterator(); i.hasNext();) {
227: String attrName = (String) i.next();
228: Object val = request.getAttribute(attrName);
229:
230: // Write it to the session, but only if the current value isn't already this value.
231: Object currSessValue = session
232: .getAttribute(attrName);
233:
234: //
235: // Here, the value in the session is different than the value about to be persisted to the
236: // session. In some cases, this requires a PageFlowManagedObject lifecycle method to be
237: // invoked on that object.
238: //
239: if (currSessValue != null
240: && val != currSessValue
241: && val instanceof PageFlowController
242: && val instanceof HttpSessionBindingListener) {
243: //
244: // In order to maintain the single-threaded semantics of PFMO destruction, it's necessary
245: // to synchronize on PFMO objects here.
246: //
247: HttpSessionBindingEvent event = new SessionBindingEvent(
248: session, attrName, currSessValue);
249: synchronized (currSessValue) {
250: ((HttpSessionBindingListener) currSessValue)
251: .valueUnbound(event);
252: }
253: }
254: }
255:
256: //
257: // Go through each changed attribute, and either write it to the session or remove it from the session,
258: // depending on whether or not it exists in the request.
259: //
260: for (Iterator i = changedAttrs.iterator(); i.hasNext();) {
261: String attrName = (String) i.next();
262: Object val = request.getAttribute(attrName);
263:
264: if (val != null) {
265: // Write it to the session, but only if the current value isn't already this value.
266: Object currentValue = session
267: .getAttribute(attrName);
268:
269: if (currentValue != val) {
270: if (_log.isTraceEnabled())
271: _log
272: .trace("Committing attribute "
273: + attrName
274: + " to the session.");
275:
276: // This ThreadLocal value allows others (e.g., an HttpSessionBindingListener like
277: // PageFlowManagedObject) that we're in the middle of committing changes to the session.
278: _isCommittingChanges.set(Boolean.TRUE);
279:
280: try {
281: session.setAttribute(attrName, val);
282: } finally {
283: _isCommittingChanges.set(Boolean.FALSE);
284: }
285:
286: request.removeAttribute(attrName);
287: }
288: } else {
289: if (_log.isTraceEnabled())
290: _log.trace("Removing attribute " + attrName
291: + " from the session.");
292:
293: //
294: // This ThreadLocal value allows others (e.g., an HttpSessionBindingListener like
295: // PageFlowManagedObject) to query whether the Framework is in the middle of committing
296: // changes to the session
297: //
298: _isCommittingChanges.set(Boolean.TRUE);
299:
300: try {
301: session.removeAttribute(attrName);
302: } finally {
303: _isCommittingChanges.set(Boolean.FALSE);
304: }
305: }
306: }
307: }
308:
309: //
310: // Now, run the ensureFailover step to make sure that clusterable containers do the right thing
311: // with attributes that need to be serialized for failover.
312: //
313: HashMap failoverAttrs = getFailoverAttributesMap(request,
314: false, true);
315: if (failoverAttrs != null) {
316: ServletContainerAdapter sa = AdapterManager
317: .getServletContainerAdapter(getServletContext());
318:
319: for (Iterator i = failoverAttrs.entrySet().iterator(); i
320: .hasNext();) {
321: Map.Entry entry = (Map.Entry) i.next();
322: if (_log.isTraceEnabled())
323: _log.trace("Ensure failover for attribute "
324: + entry.getKey());
325:
326: // This ThreadLocal value allows others (e.g., an HttpSessionBindingListener like
327: // PageFlowManagedObject) that we're in the middle of committing changes to the session.
328: _isCommittingChanges.set(Boolean.TRUE);
329: try {
330: sa.ensureFailover((String) entry.getKey(),
331: entry.getValue(), request);
332: } finally {
333: _isCommittingChanges.set(Boolean.FALSE);
334: }
335: }
336: }
337: } // release lock on the HttpSession
338:
339: if (_log.isDebugEnabled())
340: _log.debug("Completed applying changes for request "
341: + request.getRequestURI() + ". Identity: "
342: + System.identityHashCode(request));
343: }
344:
345: public void dropChanges(RequestContext context) {
346: HttpServletRequest request = ScopedServletUtils
347: .getOuterRequest((HttpServletRequest) context
348: .getRequest());
349: if (_log.isDebugEnabled()) {
350: _log.debug("Dropping changes for request "
351: + request.getRequestURI());
352: }
353: getChangedAttributesList(request, false, true);
354: getFailoverAttributesMap(request, false, true);
355: }
356:
357: public Enumeration getAttributeNames(RequestContext context) {
358: HttpServletRequest request = ScopedServletUtils
359: .getOuterRequest((HttpServletRequest) context
360: .getRequest());
361: HttpSession session = request.getSession(false);
362: ArrayList attrNames = new ArrayList();
363:
364: // Start with the attribute names that are in the session.
365: if (session != null) {
366: for (Enumeration e = session.getAttributeNames(); e
367: .hasMoreElements();) {
368: attrNames.add(e.nextElement());
369: }
370: }
371:
372: // Add or remove as necessary, based on the list of changed attributes.
373: HashSet changedAttrs = getChangedAttributesList(request, false,
374: false);
375: if (changedAttrs != null) {
376: for (Iterator i = changedAttrs.iterator(); i.hasNext();) {
377: String attrName = (String) i.next();
378:
379: if (request.getAttribute(attrName) != null) {
380: attrNames.add(attrName);
381: } else {
382: // If the attribute isn't present in the request and is in the session, then it's scheduled for
383: // removal.
384: attrNames.remove(attrName);
385: }
386: }
387: }
388:
389: return Collections.enumeration(attrNames);
390: }
391:
392: public void ensureFailover(RequestContext context,
393: String attributeName, Object value) {
394: HttpServletRequest request = ScopedServletUtils
395: .getOuterRequest((HttpServletRequest) context
396: .getRequest());
397: getFailoverAttributesMap(request, true, false).put(
398: attributeName, value);
399: }
400:
401: public boolean allowBindingEvent(Object event) {
402: return !((Boolean) _isCommittingChanges.get()).booleanValue();
403: }
404:
405: private static HashSet getChangedAttributesList(
406: ServletRequest request, boolean create, boolean remove) {
407: HashSet set = (HashSet) request.getAttribute(CHANGELIST_ATTR);
408:
409: //
410: // Create a new Set in which to store the changed attributes
411: //
412: if (set == null && create) {
413: set = new HashSet();
414: request.setAttribute(CHANGELIST_ATTR, set);
415: }
416:
417: //
418: // Remove the list of changed attributes from the request
419: //
420: if (set != null && remove)
421: request.removeAttribute(CHANGELIST_ATTR);
422:
423: return set;
424: }
425:
426: private static HashMap getFailoverAttributesMap(
427: ServletRequest request, boolean create, boolean remove) {
428: HashMap map = (HashMap) request.getAttribute(FAILOVER_MAP_ATTR);
429:
430: if (map == null && create) {
431: map = new HashMap();
432: request.setAttribute(FAILOVER_MAP_ATTR, map);
433: }
434:
435: if (map != null && remove)
436: request.removeAttribute(FAILOVER_MAP_ATTR);
437:
438: return map;
439: }
440: }
|