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;
020:
021: import org.apache.beehive.netui.util.logging.Logger;
022: import org.apache.beehive.netui.util.internal.InternalStringBuilder;
023: import org.apache.beehive.netui.pageflow.scoping.ScopedServletUtils;
024: import org.apache.beehive.netui.pageflow.internal.InternalConstants;
025: import org.apache.beehive.netui.pageflow.internal.InternalUtils;
026: import org.apache.beehive.netui.pageflow.interceptor.action.InterceptorForward;
027: import org.apache.beehive.netui.pageflow.interceptor.action.ActionInterceptor;
028: import org.apache.beehive.netui.pageflow.interceptor.action.ActionInterceptorContext;
029: import org.apache.beehive.netui.pageflow.handler.Handlers;
030: import org.apache.beehive.netui.pageflow.handler.StorageHandler;
031: import org.apache.struts.config.ModuleConfig;
032:
033: import javax.servlet.http.HttpSessionBindingListener;
034: import javax.servlet.http.HttpSessionBindingEvent;
035: import javax.servlet.http.HttpServletRequest;
036: import javax.servlet.http.HttpSession;
037: import javax.servlet.ServletContext;
038: import java.util.Stack;
039: import java.io.Serializable;
040:
041: /**
042: * <p>
043: * Stack for keeping track of a series of nested page flows. When a nested page flow is entered,
044: * the previous page flow is pushed onto this stack, which is kept in the user session.
045: * </p>
046: * <p>
047: * This Stack implements the {@link HttpSessionBindingListener} which will receive a callback when
048: * this is removed from the {@link HttpSession}. At this time, any PageFlowController instances stored
049: * on the stack will be destroyed using the {@link PageFlowManagedObject#destroy(javax.servlet.http.HttpSession)}
050: * lifecycle method.
051: * </p>
052: */
053: public class PageFlowStack implements HttpSessionBindingListener,
054: Serializable {
055: private static final Logger _log = Logger
056: .getInstance(PageFlowStack.class);
057: private static final String JPF_STACK_ATTR = InternalConstants.ATTR_PREFIX
058: + "nestingStack";
059:
060: private Stack _stack = new Stack();
061: private transient ServletContext _servletContext;
062:
063: /**
064: * Wrapper that contains a pushed page flow and information related to it.
065: */
066: public static class PushedPageFlow implements Serializable {
067: private PageFlowController _pageFlow;
068: private ActionInterceptor _interceptor;
069: private InterceptorForward _interceptedForward;
070: private String _interceptedActionName;
071:
072: public PushedPageFlow(PageFlowController pageFlow) {
073: _pageFlow = pageFlow;
074: }
075:
076: public PushedPageFlow(PageFlowController pageFlow,
077: ActionInterceptor interceptor,
078: InterceptorForward interceptedFwd,
079: String interceptedActionName) {
080: this (pageFlow);
081: _interceptor = interceptor;
082: _interceptedForward = interceptedFwd;
083: _interceptedActionName = interceptedActionName;
084: }
085:
086: public PageFlowController getPageFlow() {
087: return _pageFlow;
088: }
089:
090: public ActionInterceptor getInterceptor() {
091: return _interceptor;
092: }
093:
094: public InterceptorForward getInterceptedForward() {
095: return _interceptedForward;
096: }
097:
098: public String getInterceptedActionName() {
099: return _interceptedActionName;
100: }
101:
102: public String toString() {
103: return _pageFlow.getURI();
104: }
105: }
106:
107: /**
108: * Get the stack of nested page flows for the current user session. Create and store an empty
109: * stack if none exists.
110: *
111: * @param request the current HttpServletRequest.
112: * @param servletContext the current ServletContext.
113: * @return the stack of nested page flows {@link PushedPageFlow}s) for the current user session.
114: */
115: public static PageFlowStack get(HttpServletRequest request,
116: ServletContext servletContext) {
117: return get(request, servletContext, true);
118: }
119:
120: /**
121: * Get the stack of nested page flows for the current user session. Create and store an empty
122: * stack if none exists.
123: * @deprecated Use {@link #get(HttpServletRequest, ServletContext)} instead.
124: *
125: * @param request the current HttpServletRequest.
126: * @return the stack of nested page flows {@link PushedPageFlow}s) for the current user session.
127: */
128: public static PageFlowStack get(HttpServletRequest request) {
129: return get(request, InternalUtils.getServletContext(request));
130: }
131:
132: /**
133: * Get the stack of nested page flows for the current user session. Create and store an empty
134: * stack if none exists.
135: *
136: * @param request the current HttpServletRequest.
137: * @param servletContext the current ServletContext.
138: * @return a {@link PageFlowStack} of nested page flows ({@link PageFlowController}s) for the current user session.
139: */
140: public static PageFlowStack get(HttpServletRequest request,
141: ServletContext servletContext, boolean createIfNotExist) {
142: StorageHandler sh = Handlers.get(servletContext)
143: .getStorageHandler();
144: HttpServletRequest unwrappedRequest = PageFlowUtils
145: .unwrapMultipart(request);
146: RequestContext rc = new RequestContext(unwrappedRequest, null);
147: String attrName = ScopedServletUtils.getScopedSessionAttrName(
148: JPF_STACK_ATTR, unwrappedRequest);
149: PageFlowStack jpfStack = (PageFlowStack) sh.getAttribute(rc,
150: attrName);
151:
152: if (jpfStack == null && createIfNotExist) {
153: jpfStack = new PageFlowStack(servletContext);
154: jpfStack.save(request);
155: } else if (jpfStack != null) {
156: jpfStack.setServletContext(servletContext);
157: }
158:
159: return jpfStack;
160: }
161:
162: /**
163: * Get the stack of nested page flows for the current user session. Create and store an empty
164: * stack if none exists.
165: * @deprecated Use {@link #get(HttpServletRequest, ServletContext, boolean)} instead.
166: *
167: * @param request the current HttpServletRequest
168: * @return a {@link PageFlowStack} of nested page flows ({@link PageFlowController}s) for the current user session.
169: */
170: public static PageFlowStack get(HttpServletRequest request,
171: boolean createIfNotExist) {
172: return get(request, InternalUtils.getServletContext(request),
173: createIfNotExist);
174: }
175:
176: /**
177: * Destroy the stack of {@link PageFlowController}s that have invoked nested page flows.
178: *
179: * @param request the current HttpServletRequest.
180: */
181: public void destroy(HttpServletRequest request) {
182: StorageHandler sh = Handlers.get(getServletContext())
183: .getStorageHandler();
184: HttpServletRequest unwrappedRequest = PageFlowUtils
185: .unwrapMultipart(request);
186: RequestContext rc = new RequestContext(unwrappedRequest, null);
187: String attrName = ScopedServletUtils.getScopedSessionAttrName(
188: JPF_STACK_ATTR, unwrappedRequest);
189:
190: sh.removeAttribute(rc, attrName);
191: }
192:
193: /**
194: * Pop page flows from the nesting stack until one of the given type is found.
195: *
196: * @return the last popped page flow if one of the given type was found, or <code>null</code>
197: * if none was found.
198: */
199: PageFlowController popUntil(HttpServletRequest request,
200: Class stopAt, boolean onlyIfPresent) {
201: if (onlyIfPresent && lastIndexOf(stopAt) == -1) {
202: return null;
203: }
204:
205: while (!isEmpty()) {
206: PageFlowController popped = pop(request).getPageFlow();
207:
208: if (popped.getClass().equals(stopAt)) {
209: //
210: // If we've popped everything from the stack, remove the stack attribute from the session.
211: //
212: if (isEmpty())
213: destroy(request);
214: return popped;
215: } else {
216: //
217: // We're discarding the popped page flow. Invoke its destroy() callback, unless it's longLived.
218: //
219: if (!popped.isLongLived())
220: popped.destroy(request.getSession(false));
221: }
222: }
223:
224: destroy(request); // we're empty -- remove the attribute from the session.
225: return null;
226: }
227:
228: private int lastIndexOf(Class target) {
229: for (int i = _stack.size() - 1; i >= 0; --i) {
230: if (((PushedPageFlow) _stack.elementAt(i)).getPageFlow()
231: .getClass().equals(target)) {
232: return i;
233: }
234: }
235:
236: return -1;
237: }
238:
239: void ensureFailover(HttpServletRequest request,
240: ServletContext servletContext) {
241: StorageHandler sh = Handlers.get(servletContext)
242: .getStorageHandler();
243: HttpServletRequest unwrappedRequest = PageFlowUtils
244: .unwrapMultipart(request);
245: RequestContext rc = new RequestContext(unwrappedRequest, null);
246: String attrName = ScopedServletUtils.getScopedSessionAttrName(
247: JPF_STACK_ATTR, unwrappedRequest);
248:
249: sh.ensureFailover(rc, attrName, this );
250: }
251:
252: void save(HttpServletRequest request) {
253: StorageHandler sh = Handlers.get(getServletContext())
254: .getStorageHandler();
255: HttpServletRequest unwrappedRequest = PageFlowUtils
256: .unwrapMultipart(request);
257: RequestContext rc = new RequestContext(unwrappedRequest, null);
258: String attrName = ScopedServletUtils.getScopedSessionAttrName(
259: JPF_STACK_ATTR, unwrappedRequest);
260:
261: sh.setAttribute(rc, attrName, this );
262: }
263:
264: private PageFlowStack(ServletContext servletContext) {
265: _servletContext = servletContext;
266: }
267:
268: /**
269: * Push a page flow onto the stack of nested page flows in the session.
270: *
271: * @param pageFlow the page flow to push.
272: * @param request the current HttpServletRequest.
273: */
274: public void push(PageFlowController pageFlow,
275: HttpServletRequest request) {
276: ActionInterceptorContext interceptorContext = ActionInterceptorContext
277: .getActiveContext(request, true);
278: if (interceptorContext != null) {
279: ActionInterceptor interceptor = interceptorContext
280: .getOverridingActionInterceptor();
281: InterceptorForward originalForward = interceptorContext
282: .getOriginalForward();
283: String actionName = interceptorContext.getActionName();
284: _stack.push(new PushedPageFlow(pageFlow, interceptor,
285: originalForward, actionName));
286: } else {
287: _stack.push(new PushedPageFlow(pageFlow));
288: }
289:
290: // Tell the page flow that it is on the nesting stack.
291: pageFlow.setIsOnNestingStack(true);
292:
293: // To ensure that this attribute is replicated for session failover.
294: ensureFailover(request, getServletContext());
295: }
296:
297: /**
298: * Pop the most recently-pushed page flow from the stack of nested page flows in the session.
299: *
300: * @param request the current HttpServletRequest.
301: * @return a {@link PushedPageFlow} that represents the popped page flow.
302: */
303: public PushedPageFlow pop(HttpServletRequest request) {
304: PushedPageFlow ppf = (PushedPageFlow) _stack.pop();
305: PageFlowController pfc = ppf.getPageFlow();
306: pfc.setIsOnNestingStack(false);
307:
308: if (request != null) // may be null if we're called from valueUnbound()
309: {
310: ServletContext servletContext = getServletContext();
311:
312: // Reinitialize the page flow, in case it's lost its transient state.
313: pfc.reinitialize(request, null, servletContext);
314: ensureFailover(request, servletContext); // to ensure that this attribute is replicated for session failover
315: }
316:
317: return ppf;
318: }
319:
320: /**
321: * Get the most recently-pushed page flow from the stack of nested page flows in the session.
322: *
323: * @return a {@link PushedPageFlow} that represents the page flow at the top of the stack.
324: */
325: public PushedPageFlow peek() {
326: return (PushedPageFlow) _stack.peek();
327: }
328:
329: /**
330: * Tell whether the stack of nested page flows is empty.
331: *
332: * @return <code>true</code> if there are no nested page flows on the stack.
333: */
334: public boolean isEmpty() {
335: return _stack.isEmpty();
336: }
337:
338: /**
339: * Get the size of the stack of nested page flows.
340: *
341: * @return the number of page flows that are currently (hidden away) on the stack.
342: */
343: public int size() {
344: return _stack.size();
345: }
346:
347: /**
348: * Callback for {@link HttpSessionBindingListener} -- should not be invoked directly.
349: */
350: public void valueBound(HttpSessionBindingEvent event) {
351: }
352:
353: /**
354: * Callback for {@link HttpSessionBindingListener} -- should not be invoked directly.
355: */
356: public void valueUnbound(HttpSessionBindingEvent event) {
357: if (_log.isDebugEnabled()) {
358: _log
359: .debug("The page flow stack is being unbound from the session.");
360: }
361:
362: while (!isEmpty()) {
363: PageFlowController jpf = pop(null).getPageFlow();
364:
365: // Note that this page flow may have been serialized/deserialized, which will cause its transient info
366: // to be lost. Rehydrate it.
367: HttpSession session = event.getSession();
368: if (session != null)
369: jpf.reinitialize(null, null, session
370: .getServletContext());
371:
372: if (!jpf.isLongLived())
373: jpf.destroy(event.getSession());
374: }
375: }
376:
377: /**
378: * Get a stack of PageFlowControllers, not of PushedPageFlows.
379: */
380: Stack getLegacyStack() {
381: Stack ret = new Stack();
382:
383: for (int i = 0; i < _stack.size(); ++i) {
384: ret.push(((PushedPageFlow) _stack.get(i)).getPageFlow());
385: }
386:
387: return ret;
388: }
389:
390: private ServletContext getServletContext() {
391: return _servletContext;
392: }
393:
394: private void setServletContext(ServletContext servletContext) {
395: _servletContext = servletContext;
396: }
397:
398: /**
399: * Internal (to our framework) method for seeing whether a given action exists in a page flow that is somewhere in
400: * the stack. If so, the page flow's ModuleConfig is returned.
401: */
402: ModuleConfig findActionInStack(String actionPath) {
403: for (int i = _stack.size() - 1; i >= 0; --i) {
404: ModuleConfig moduleConfig = ((PushedPageFlow) _stack
405: .elementAt(i)).getPageFlow().getModuleConfig();
406:
407: if (moduleConfig.findActionConfig(actionPath) != null) {
408: return moduleConfig;
409: }
410: }
411:
412: return null;
413: }
414:
415: public String toString() {
416: if (_stack.isEmpty()) {
417: return "[empty]";
418: }
419:
420: InternalStringBuilder sb = new InternalStringBuilder(_stack
421: .get(0).toString());
422: for (int i = 1; i < _stack.size(); ++i) {
423: sb.append(" -> ").append(_stack.get(i).toString());
424: }
425: return sb.toString();
426: }
427: }
|