0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: *
0017: * $Header:$
0018: */
0019: package org.apache.beehive.netui.pageflow;
0020:
0021: import java.lang.reflect.Field;
0022: import java.util.Map;
0023: import javax.servlet.http.HttpServletRequest;
0024: import javax.servlet.http.HttpServletResponse;
0025: import javax.servlet.http.HttpSessionBindingEvent;
0026: import javax.servlet.ServletContext;
0027:
0028: import org.apache.struts.action.ActionForm;
0029: import org.apache.struts.action.ActionMapping;
0030: import org.apache.struts.action.ActionForward;
0031: import org.apache.struts.config.ModuleConfig;
0032: import org.apache.struts.config.ControllerConfig;
0033: import org.apache.beehive.controls.api.context.ControlContainerContext;
0034: import org.apache.beehive.netui.pageflow.config.PageFlowControllerConfig;
0035: import org.apache.beehive.netui.pageflow.config.PageFlowExceptionConfig;
0036: import org.apache.beehive.netui.pageflow.internal.CachedPageFlowInfo;
0037: import org.apache.beehive.netui.pageflow.internal.InternalUtils;
0038: import org.apache.beehive.netui.pageflow.internal.InternalConstants;
0039: import org.apache.beehive.netui.pageflow.internal.CachedSharedFlowRefInfo;
0040: import org.apache.beehive.netui.pageflow.internal.ViewRenderer;
0041: import org.apache.beehive.netui.pageflow.internal.PageFlowRequestWrapper;
0042: import org.apache.beehive.netui.pageflow.scoping.ScopedServletUtils;
0043: import org.apache.beehive.netui.pageflow.handler.StorageHandler;
0044: import org.apache.beehive.netui.pageflow.handler.Handlers;
0045: import org.apache.beehive.netui.util.internal.DiscoveryUtils;
0046: import org.apache.beehive.netui.util.internal.cache.ClassLevelCache;
0047: import org.apache.beehive.netui.util.internal.FileUtils;
0048: import org.apache.beehive.netui.util.internal.InternalStringBuilder;
0049: import org.apache.beehive.netui.util.logging.Logger;
0050:
0051: /**
0052: * <p>
0053: * Base class for controller logic, exception handlers, and state associated with a particular web directory path.
0054: * The class is configured through the
0055: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Controller @Jpf.Controller} annotation.
0056: * </p>
0057: *
0058: * <p>
0059: * When a page flow request (the page flow URI itself, or any ".do" or page URI in the directory path), arrives, an
0060: * instance of the associated PageFlowController class is set as the <i>current page flow</i>, and remains stored in the
0061: * session until a different one becomes active ("long lived" page flows stay in the session indefinitely;
0062: * see {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Controller#longLived longLived}
0063: * on {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Controller @Jpf.Controller}).
0064: * </p>
0065: *
0066: * <p>
0067: * The page flow class handles <i>actions</i> that are most commonly raised by user interaction with pages. The actions
0068: * are handled by <i>action methods</i> or <i>action annotations</i> that determine the next URI to be displayed, after
0069: * optionally performing arbitrary logic.
0070: * </p>
0071: *
0072: * <p>
0073: * If the PageFlowController is a "nested page flow"
0074: * ({@link org.apache.beehive.netui.pageflow.annotations.Jpf.Controller#nested nested} is set to <code>true</code>
0075: * on {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Controller @Jpf.Controller}), then this
0076: * is a reusable, modular flow that can be "nested" during other flows. It has entry points (actions with optional form
0077: * bean arguments), and exit points ({@link org.apache.beehive.netui.pageflow.annotations.Jpf.Forward @Jpf.Forward},
0078: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.SimpleAction @Jpf.SimpleAction}, or
0079: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.ConditionalForward @Jpf.ConditionalForward} annotations
0080: * that have <code>returnAction</code> attributes).
0081: * </p>
0082: *
0083: * <p>
0084: * The page flow class also handles exceptions thrown by actions or during page execution
0085: * (see {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Controller#catches catches} on
0086: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Controller @Jpf.Controller}). Unhandled exceptions are
0087: * handled in order by declared {@link SharedFlowController}s
0088: * (see {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Controller#sharedFlowRefs sharedFlowRefs} on
0089: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Controller @Jpf.Controller}).
0090: * </p>
0091: *
0092: * <p>
0093: * Properties in the current page flow instance can be accessed from JSP 2.0-style expressions like this one:
0094: * <code>${pageFlow.someProperty}</code>.
0095: * </p>
0096: *
0097: * <p>
0098: * There may only be one page flow in any package.
0099: * </p>
0100: *
0101: * @see SharedFlowController
0102: */
0103: public abstract class PageFlowController extends FlowController
0104: implements InternalConstants {
0105: /**
0106: * A 'return-to="page"' forward brings the user back to the previous page. This object
0107: * stores information about the current state of affairs, such as the origin URI and
0108: * its ActionForm.
0109: */
0110: private PreviousPageInfo _previousPageInfo = null;
0111: private PreviousPageInfo _currentPageInfo = null;
0112:
0113: /**
0114: * A 'return-to="action"' forward reruns the previous action. This object stores the previous
0115: * action URI and its ActionForm.
0116: */
0117: private PreviousActionInfo _previousActionInfo;
0118:
0119: private boolean _isOnNestingStack = false;
0120: private ViewRenderer _returnActionViewRenderer = null;
0121:
0122: private static final String REMOVING_PAGEFLOW_ATTR = InternalConstants.ATTR_PREFIX
0123: + "removingPageFlow";
0124: private static final String SAVED_PREVIOUS_PAGE_INFO_ATTR = InternalConstants.ATTR_PREFIX
0125: + "savedPrevPageInfo";
0126: private static final String CACHED_INFO_KEY = "cachedInfo";
0127: private static final Logger _log = Logger
0128: .getInstance(PageFlowController.class);
0129:
0130: /**
0131: * The bean context associated with this request
0132: */
0133: ControlContainerContext _beanContext;
0134:
0135: /**
0136: * Default constructor.
0137: */
0138: protected PageFlowController() {
0139: }
0140:
0141: /**
0142: * Get the Struts module path for this page flow.
0143: *
0144: * @return a String that is the Struts module path for this controller, and which is also
0145: * the directory path from the web application root to this PageFlowController
0146: * (not including the action filename).
0147: */
0148: public String getModulePath() {
0149: return getCachedInfo().getModulePath();
0150: }
0151:
0152: /**
0153: * Get the URI for addressing this PageFlowController.
0154: *
0155: * @return a String that is the URI which will execute the begin action on this
0156: * PageFlowController.
0157: */
0158: public String getURI() {
0159: return getCachedInfo().getURI();
0160: }
0161:
0162: /**
0163: * Tell whether this PageFlowController can be "nested", i.e., if it can be invoked from another page
0164: * flow with the intention of returning to the original one. Page flows are declared to be nested by specifying
0165: * <code>{@link org.apache.beehive.netui.pageflow.annotations.Jpf.Controller#nested nested}=true</code> on the
0166: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Controller @Jpf.Controller} annotation.
0167: *
0168: * @return <code>true</code> if this PageFlowController can be nested.
0169: */
0170: protected boolean isNestable() {
0171: return InternalUtils.isNestable(getModuleConfig());
0172: }
0173:
0174: /**
0175: * Tell whether this is a "long lived" page flow. Once it is invoked, a long lived page flow is never
0176: * removed from the session unless {@link #remove} is called. Navigating to another page flow hides
0177: * the current long lived controller, but does not remove it.
0178: */
0179: protected boolean isLongLived() {
0180: return InternalUtils.isLongLived(getModuleConfig());
0181: }
0182:
0183: /**
0184: * Remove this instance from the session. When inside a page flow action, {@link #remove} may be called instead.
0185: * This method is synchronized in order to maintain single threaded semantics for the Page Flow's
0186: * {@link #onDestroy(javax.servlet.http.HttpSession)} lifecycle method.
0187: */
0188: public synchronized void removeFromSession(
0189: HttpServletRequest request) {
0190: // This request attribute is used in persistInSession to prevent re-saving of this instance.
0191: request.setAttribute(REMOVING_PAGEFLOW_ATTR, this );
0192:
0193: if (isLongLived()) {
0194: PageFlowUtils.removeLongLivedPageFlow(getModulePath(),
0195: request);
0196: } else {
0197: InternalUtils.removeCurrentPageFlow(request,
0198: getServletContext());
0199: }
0200: }
0201:
0202: /**
0203: * Tell whether this is a PageFlowController.
0204: *
0205: * @return <code>true</code>.
0206: */
0207: public boolean isPageFlow() {
0208: return true;
0209: }
0210:
0211: /**
0212: * Store this object in the user session, in the appropriate place. Used by the framework; normally should not be
0213: * called directly.
0214: */
0215: public void persistInSession(HttpServletRequest request,
0216: HttpServletResponse response) {
0217: PageFlowController currentPageFlow = PageFlowUtils
0218: .getCurrentPageFlow(request, getServletContext());
0219:
0220: //
0221: // This code implicitly destroys the current page flow. In order to prevent multiple threads
0222: // from executing inside the JPF at once, synchronize on it in order to complete the "destroy"
0223: // atomically.
0224: //
0225: if (currentPageFlow != null
0226: && !currentPageFlow.isOnNestingStack()) {
0227: synchronized (currentPageFlow) {
0228: InternalUtils.setCurrentPageFlow(this , request,
0229: getServletContext());
0230: }
0231: }
0232: //
0233: // Here, there is no previous page flow to syncyronize upon before destruction
0234: //
0235: else {
0236: InternalUtils.setCurrentPageFlow(this , request,
0237: getServletContext());
0238: }
0239: }
0240:
0241: /**
0242: * <p>
0243: * Ensure that any changes to this object will be replicated in a cluster (for failover), even if the
0244: * replication scheme uses a change-detection algorithm that relies on
0245: * {@link javax.servlet.http.HttpSession#setAttribute(String, Object)} to be aware of changes. This method is
0246: * used by the framework and should not be called directly in most cases.
0247: * </p>
0248: * <p>
0249: * Note, this method ultimately causes the Page Flow to be persisted in the {@link StorageHandler} for this web application.
0250: * </p>
0251: *
0252: * @param request the current {@link HttpServletRequest}
0253: */
0254: public void ensureFailover(HttpServletRequest request) {
0255: //
0256: // remove() puts the pageflow instance into a request attribute. Make sure not to re-save this
0257: // instance if it's being removed. Also, if the session is null (after having been invalidated
0258: // by the user), don't recreate it.
0259: //
0260: if (request.getAttribute(REMOVING_PAGEFLOW_ATTR) != this
0261: && request.getSession(false) != null) {
0262: StorageHandler sh = Handlers.get(getServletContext())
0263: .getStorageHandler();
0264: HttpServletRequest unwrappedRequest = PageFlowUtils
0265: .unwrapMultipart(request);
0266: RequestContext rc = new RequestContext(unwrappedRequest,
0267: null);
0268:
0269: //
0270: // If this is a long-lived page flow, there are two attributes to deal with, and ensure that
0271: // both failover correctly.
0272: //
0273: if (isLongLived()) {
0274: String longLivedAttrName = InternalUtils
0275: .getLongLivedFlowAttr(getModulePath());
0276: longLivedAttrName = ScopedServletUtils
0277: .getScopedSessionAttrName(longLivedAttrName,
0278: unwrappedRequest);
0279: String currentLongLivedAttrName = ScopedServletUtils
0280: .getScopedSessionAttrName(
0281: CURRENT_LONGLIVED_ATTR,
0282: unwrappedRequest);
0283: sh.ensureFailover(rc, longLivedAttrName, this );
0284: sh.ensureFailover(rc, currentLongLivedAttrName,
0285: getModulePath());
0286: }
0287: //
0288: // This Page Flow is not long lived, so just the Page Flow itself needs to be added to the session.
0289: //
0290: else {
0291: String attrName = ScopedServletUtils
0292: .getScopedSessionAttrName(CURRENT_JPF_ATTR,
0293: unwrappedRequest);
0294: sh.ensureFailover(rc, attrName, this );
0295: }
0296: }
0297: }
0298:
0299: /**
0300: * @exclude
0301: */
0302: protected ActionForward internalExecute(ActionMapping mapping,
0303: ActionForm form, HttpServletRequest request,
0304: HttpServletResponse response) throws Exception {
0305: initializeSharedFlowFields(request);
0306: return super .internalExecute(mapping, form, request, response);
0307: }
0308:
0309: private void initializeSharedFlowFields(HttpServletRequest request) {
0310: //
0311: // Initialize the shared flow fields.
0312: //
0313: CachedSharedFlowRefInfo.SharedFlowFieldInfo[] sharedFlowMemberFields = getCachedInfo()
0314: .getSharedFlowMemberFields();
0315:
0316: if (sharedFlowMemberFields != null) {
0317: for (int i = 0; i < sharedFlowMemberFields.length; i++) {
0318: CachedSharedFlowRefInfo.SharedFlowFieldInfo fi = sharedFlowMemberFields[i];
0319: Field field = fi.field;
0320:
0321: if (fieldIsUninitialized(field)) {
0322: Map/*< String, SharedFlowController >*/sharedFlows = PageFlowUtils
0323: .getSharedFlows(request);
0324: String name = fi.sharedFlowName;
0325: SharedFlowController sf = name != null ? (SharedFlowController) sharedFlows
0326: .get(name)
0327: : PageFlowUtils.getGlobalApp(request);
0328:
0329: if (sf != null) {
0330: initializeField(field, sf);
0331: } else {
0332: _log
0333: .error("Could not find shared flow with name \""
0334: + fi.sharedFlowName
0335: + "\" to initialize field "
0336: + field.getName()
0337: + " in "
0338: + getClass().getName());
0339: }
0340: }
0341: }
0342: }
0343: }
0344:
0345: /**
0346: * Get the a map of shared flow name to shared flow instance.
0347: * @return a Map of shared flow name (string) to shared flow instance ({@link SharedFlowController}).
0348: */
0349: protected Map/*< String, SharedFlowController >*/getSharedFlows() {
0350: return PageFlowUtils.getSharedFlows(getRequest());
0351: }
0352:
0353: /**
0354: * This is a non-property public accessor that will return the property value <code>sharedFlows</code>.
0355: * This is a non-property method because properties are exposed to databinding and that would expose
0356: * internal data structures to attack.
0357: * @return a Map of shared flow name (string) to shared flow instance ({@link SharedFlowController}).
0358: */
0359: public final Map theSharedFlows() {
0360: return getSharedFlows();
0361: }
0362:
0363: /**
0364: * Get a shared flow, based on its name as defined in this page flow's
0365: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Controller#sharedFlowRefs sharedFlowRefs}
0366: * attribute on {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Controller @Jpf.Controller}.
0367: * To retrieve any shared flow based on its class name, use {@link PageFlowUtils#getSharedFlow}.
0368: *
0369: * @param sharedFlowName the name of the shared flow, as in this page flows's
0370: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Controller#sharedFlowRefs sharedFlowRefs}
0371: * attribute on the {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Controller @Jpf.Controller}
0372: * annotation.
0373: * @return the {@link SharedFlowController} with the given name.
0374: */
0375: public SharedFlowController getSharedFlow(String sharedFlowName) {
0376: return (SharedFlowController) PageFlowUtils.getSharedFlows(
0377: getRequest()).get(sharedFlowName);
0378: }
0379:
0380: /**
0381: * Remove a shared flow from the session, based on its name as defined in this page flow's
0382: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Controller#sharedFlowRefs sharedFlowRefs}
0383: * attribute on {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Controller @Jpf.Controller}.
0384: * To remove any shared flow based on its class name, use {@link PageFlowUtils#removeSharedFlow}.
0385: *
0386: * @param sharedFlowName the name of the shared flow, as in this page flows's
0387: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Controller#sharedFlowRefs sharedFlowRefs}
0388: * attribute on the {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Controller @Jpf.Controller}
0389: * annotation.
0390: */
0391: public void removeSharedFlow(String sharedFlowName) {
0392: SharedFlowController sf = getSharedFlow(sharedFlowName);
0393: if (sf != null)
0394: sf.removeFromSession(getRequest());
0395: }
0396:
0397: /**
0398: * This is a framework method for initializing a newly-created page flow, and should not normally be called
0399: * directly.
0400: */
0401: public final synchronized void create(HttpServletRequest request,
0402: HttpServletResponse response, ServletContext servletContext) {
0403: reinitialize(request, response, servletContext);
0404: initializeSharedFlowFields(request);
0405:
0406: if (isNestable()) {
0407: // Initialize a ViewRenderer for exiting the nested page flow. This is used (currently) as part of popup
0408: // window support -- when exiting a popup nested page flow, a special view renderer writes out javascript
0409: // that maps output values to the original window and closes the popup window.
0410: String vrClassName = request
0411: .getParameter(InternalConstants.RETURN_ACTION_VIEW_RENDERER_PARAM);
0412:
0413: if (vrClassName != null) {
0414: ViewRenderer vr = (ViewRenderer) DiscoveryUtils
0415: .newImplementorInstance(vrClassName,
0416: ViewRenderer.class);
0417:
0418: if (vr != null) {
0419: vr.init(request);
0420: PageFlowController nestingPageFlow = PageFlowUtils
0421: .getCurrentPageFlow(request, servletContext);
0422: nestingPageFlow.setReturnActionViewRenderer(vr);
0423: }
0424: }
0425: }
0426:
0427: super .create(request, response, servletContext);
0428: }
0429:
0430: /**
0431: * Get the "resource taxonomy": a period-separated list that starts with the current
0432: * web application name, continues through all of this PageFlowController's parent directories,
0433: * and ends with this PageFlowController's class name.
0434: */
0435: protected String getTaxonomy() {
0436: assert getRequest() != null : "this method can only be called during execute()";
0437: String contextPath = getRequest().getContextPath();
0438: assert contextPath.startsWith("/") : contextPath;
0439: return contextPath.substring(1) + '.' + getClass().getName();
0440: }
0441:
0442: /**
0443: * Get the submitted form bean from the most recent action execution in this PageFlowController.
0444: * <p>
0445: * <i>Note: if the current page flow does not contain a
0446: * </i>{@link org.apache.beehive.netui.pageflow.annotations.Jpf.Forward @Jpf.Forward}<i> or a
0447: * </i>{@link org.apache.beehive.netui.pageflow.annotations.Jpf.SimpleAction @Jpf.SimpleAction}<i> with
0448: * </i><code>navigateTo={@link org.apache.beehive.netui.pageflow.annotations.Jpf.NavigateTo#previousAction Jpf.NavigateTo.previousAction}</code><i>,
0449: * then this method will always return </i><code>null</code><i> by default. To enable it in this
0450: * situation, add the following method to the page flow:</i><br>
0451: * <blockquote>
0452: * <code>
0453: * protected boolean alwaysTrackPreviousAction()<br>
0454: * {<br>
0455: * return true;<br>
0456: * }<br>
0457: * </code>
0458: * </blockquote>
0459: *
0460: * @deprecated This method may return an <code>ActionForm</code> wrapper when the form bean type does not extend
0461: * <code>ActionForm</code>. Use {@link #getPreviousFormBean} instead.
0462: * @return the ActionForm instance from the most recent action execution, or <code>null</code>
0463: * if there was no form bean submitted.
0464: * @see #getPreviousPageInfo
0465: * @see #getCurrentPageInfo
0466: * @see #getPreviousActionInfo
0467: * @see #getPreviousActionURI
0468: * @see #getPreviousForwardPath
0469: * @see #getCurrentForwardPath
0470: */
0471: protected ActionForm getPreviousForm() {
0472: checkPreviousActionInfoDisabled();
0473: return _previousActionInfo != null ? _previousActionInfo
0474: .getForm() : null;
0475: }
0476:
0477: /**
0478: * Get the submitted form bean from the most recent action execution in this PageFlowController.
0479: * <p>
0480: * <i>Note: if the current page flow does not contain a
0481: * </i>{@link org.apache.beehive.netui.pageflow.annotations.Jpf.Forward @Jpf.Forward}<i> or a
0482: * </i>{@link org.apache.beehive.netui.pageflow.annotations.Jpf.SimpleAction @Jpf.SimpleAction}<i> with
0483: * </i><code>navigateTo={@link org.apache.beehive.netui.pageflow.annotations.Jpf.NavigateTo#previousAction Jpf.NavigateTo.previousAction}</code><i>,
0484: * then this method will always return </i><code>null</code><i> by default. To enable it in this
0485: * situation, add the following method to the page flow:</i><br>
0486: * <blockquote>
0487: * <code>
0488: * protected boolean alwaysTrackPreviousAction()<br>
0489: * {<br>
0490: * return true;<br>
0491: * }<br>
0492: * </code>
0493: * </blockquote>
0494: *
0495: * @return the form bean instance from the most recent action execution, or <code>null</code>
0496: * if there was no form bean submitted.
0497: * @see #getPreviousPageInfo
0498: * @see #getCurrentPageInfo
0499: * @see #getPreviousActionInfo
0500: * @see #getPreviousActionURI
0501: * @see #getPreviousForwardPath
0502: * @see #getCurrentForwardPath
0503: */
0504: protected Object getPreviousFormBean() {
0505: checkPreviousActionInfoDisabled();
0506: return _previousActionInfo != null ? InternalUtils
0507: .unwrapFormBean(_previousActionInfo.getForm()) : null;
0508: }
0509:
0510: /**
0511: * Get the URI for the most recent action in this PageFlowController.
0512: * <p>
0513: * <i>Note: if the current page flow does not use a
0514: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Forward @Jpf.Forward},
0515: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.SimpleAction @Jpf.SimpleAction}, or
0516: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.ConditionalForward @Jpf.ConditionalForward}
0517: * with <code>navigateTo={@link org.apache.beehive.netui.pageflow.annotations.Jpf.NavigateTo#previousAction previousAction}</code>,
0518: * then this method will always return </i><code>null</code><i> by default. To enable it in this situation, add the
0519: * following method to the page flow:</i><br>
0520: * <blockquote>
0521: * <code>
0522: * protected boolean alwaysTrackPreviousAction()<br>
0523: * {<br>
0524: * return true;<br>
0525: * }<br>
0526: * </code>
0527: * </blockquote>
0528: *
0529: * @return a String that is the most recent URI.
0530: * @see #getPreviousPageInfo
0531: * @see #getCurrentPageInfo
0532: * @see #getPreviousActionInfo
0533: * @see #getPreviousFormBean
0534: * @see #getPreviousForwardPath
0535: * @see #getCurrentForwardPath
0536: */
0537: protected String getPreviousActionURI() {
0538: checkPreviousActionInfoDisabled();
0539: return _previousActionInfo != null ? _previousActionInfo
0540: .getActionURI() : null;
0541: }
0542:
0543: /**
0544: * Get the webapp-relative URI for the most recent page (in this page flow) shown to the user.
0545: * <p>
0546: * <i>Note: if the current page flow does not use a
0547: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Forward @Jpf.Forward},
0548: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.SimpleAction @Jpf.SimpleAction}, or
0549: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.ConditionalForward @Jpf.ConditionalForward}
0550: * with <code>navigateTo={@link org.apache.beehive.netui.pageflow.annotations.Jpf.NavigateTo#currentPage currentPage}</code>
0551: * or <code>navigateTo={@link org.apache.beehive.netui.pageflow.annotations.Jpf.NavigateTo#previousPage previousPage}</code>,
0552: * then this method will always return </i><code>null</code><i> by default. To enable it in this situation, add the
0553: * following method to the page flow:</i><br>
0554: * <blockquote>
0555: * <code>
0556: * protected boolean alwaysTrackPreviousPage()<br>
0557: * {<br>
0558: * return true;<br>
0559: * }<br>
0560: * </code>
0561: * </blockquote>
0562: *
0563: * @return a String that is the URI path for the most recent page shown to the user.
0564: * @see #getPreviousPageInfo
0565: * @see #getCurrentPageInfo
0566: * @see #getPreviousActionInfo
0567: * @see #getPreviousActionURI
0568: * @see #getPreviousFormBean
0569: * @see #getPreviousForwardPath
0570: */
0571: public String getCurrentForwardPath() {
0572: PreviousPageInfo curPageInfo = getCurrentPageInfo();
0573: String path = null;
0574:
0575: if (curPageInfo != null) {
0576: ActionForward curForward = curPageInfo.getForward();
0577: if (curForward != null) {
0578: if (curForward.getContextRelative()) {
0579: path = curForward.getPath();
0580: } else {
0581: path = getModulePath() + curForward.getPath();
0582: }
0583: }
0584: }
0585: return path;
0586: }
0587:
0588: /**
0589: * Get the webapp-relative URI for the previous page (in this page flow) shown to the user.
0590: * The previous page is the one shown before the most recent page.
0591: * <p>
0592: * <i>Note: if the current page flow does not use a
0593: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Forward @Jpf.Forward},
0594: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.SimpleAction @Jpf.SimpleAction}, or
0595: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.ConditionalForward @Jpf.ConditionalForward}
0596: * with <code>navigateTo={@link org.apache.beehive.netui.pageflow.annotations.Jpf.NavigateTo#currentPage currentPage}</code>
0597: * or <code>navigateTo={@link org.apache.beehive.netui.pageflow.annotations.Jpf.NavigateTo#previousPage previousPage}</code>,
0598: * then this method will always return </i><code>null</code><i> by default. To enable it in this situation, add the
0599: * following method to the page flow:</i><br>
0600: * <blockquote>
0601: * <code>
0602: * protected boolean alwaysTrackPreviousPage()<br>
0603: * {<br>
0604: * return true;<br>
0605: * }<br>
0606: * </code>
0607: * </blockquote>
0608: *
0609: * @return a String that is the URI path for the previous page shown to the user.
0610: * @see #getPreviousPageInfo
0611: * @see #getCurrentPageInfo
0612: * @see #getPreviousActionInfo
0613: * @see #getPreviousActionURI
0614: * @see #getPreviousFormBean
0615: * @see #getCurrentForwardPath
0616: */
0617: protected String getPreviousForwardPath() {
0618: PreviousPageInfo prevPageInfo = getPreviousPageInfo();
0619:
0620: if (prevPageInfo != null) {
0621: ActionForward prevForward = prevPageInfo.getForward();
0622: return prevForward != null ? prevForward.getPath() : null;
0623: } else {
0624: return null;
0625: }
0626: }
0627:
0628: /**
0629: * Get a legacy PreviousPageInfo.
0630: * @deprecated This method will be removed without replacement in the next release.
0631: */
0632: public final PreviousPageInfo getPreviousPageInfoLegacy(
0633: PageFlowController curJpf, HttpServletRequest request) {
0634: if (PageFlowRequestWrapper.get(request)
0635: .isReturningFromNesting()) {
0636: return getCurrentPageInfo();
0637: } else {
0638: return getPreviousPageInfo();
0639: }
0640: }
0641:
0642: /**
0643: * Get information about the most recent page (in this page flow) shown to the user.
0644: * <p>
0645: * <i>Note: if the current page flow does not use a
0646: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Forward @Jpf.Forward},
0647: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.SimpleAction @Jpf.SimpleAction}, or
0648: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.ConditionalForward @Jpf.ConditionalForward}
0649: * with <code>navigateTo={@link org.apache.beehive.netui.pageflow.annotations.Jpf.NavigateTo#currentPage currentPage}</code>
0650: * or <code>navigateTo={@link org.apache.beehive.netui.pageflow.annotations.Jpf.NavigateTo#previousPage previousPage}</code>,
0651: * then this method will always return </i><code>null</code><i> by default. To enable it in this situation, add the
0652: * following method to the page flow:</i><br>
0653: * <blockquote>
0654: * <code>
0655: * protected boolean alwaysTrackPreviousPage()<br>
0656: * {<br>
0657: * return true;<br>
0658: * }<br>
0659: * </code>
0660: * </blockquote>
0661: *
0662: * @return a PreviousPageInfo with information about the most recent page shown to the user.
0663: * @see #getPreviousPageInfo
0664: * @see #getPreviousActionInfo
0665: * @see #getPreviousActionURI
0666: * @see #getPreviousFormBean
0667: * @see #getPreviousForwardPath
0668: * @see #getCurrentForwardPath
0669: */
0670: protected final PreviousPageInfo getCurrentPageInfo() {
0671: checkPreviousPageInfoDisabled();
0672:
0673: if (_currentPageInfo != null) {
0674: // Allows it to reconstruct transient members after session failover
0675: _currentPageInfo.reinitialize(this );
0676: }
0677:
0678: return _currentPageInfo;
0679: }
0680:
0681: /**
0682: * This is a non-property public accessor that will return the property value <code>currrentPageInfo</code>.
0683: * This is a non-property method because properties are exposed to databinding and that would expose
0684: * internal data structures to attack.
0685: * @return a PreviousPageInfo with information about the most recent page shown to the user.
0686: */
0687: public final PreviousPageInfo theCurrentPageInfo() {
0688: return getCurrentPageInfo();
0689: }
0690:
0691: /**
0692: * Get information about the previous page (in this page flow) shown to the user. The previous
0693: * page is the one shown before the most recent page.
0694: * <p>
0695: * <i>Note: if the current page flow does not use a
0696: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Forward @Jpf.Forward},
0697: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.SimpleAction @Jpf.SimpleAction}, or
0698: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.ConditionalForward @Jpf.ConditionalForward}
0699: * with <code>navigateTo={@link org.apache.beehive.netui.pageflow.annotations.Jpf.NavigateTo#currentPage currentPage}</code>
0700: * or <code>navigateTo={@link org.apache.beehive.netui.pageflow.annotations.Jpf.NavigateTo#previousPage previousPage}</code>,
0701: * then this method will always return </i><code>null</code><i> by default. To enable it in this situation, add the
0702: * following method to the page flow:</i><br>
0703: * </blockquote>
0704: *
0705: * @return a PreviousPageInfo with information about the previous page shown to the user.
0706: * @see #getCurrentPageInfo
0707: * @see #getPreviousActionInfo
0708: * @see #getPreviousActionURI
0709: * @see #getPreviousFormBean
0710: * @see #getPreviousForwardPath
0711: * @see #getCurrentForwardPath
0712: */
0713: protected final PreviousPageInfo getPreviousPageInfo() {
0714: checkPreviousPageInfoDisabled();
0715:
0716: PreviousPageInfo ret = _previousPageInfo != null ? _previousPageInfo
0717: : _currentPageInfo;
0718:
0719: if (ret != null) {
0720: ret.reinitialize(this ); // Allows it to reconstruct transient members after session failover
0721: }
0722:
0723: return ret;
0724: }
0725:
0726: /**
0727: * This is a non-property public accessor that will return the property value <code>previousPageInfo</code>.
0728: * This is a non-property method because properties are exposed to databinding and that would expose
0729: * internal data structures to attack.
0730: * @return a PreviousPageInfo with information about the previous page shown to the user.
0731: */
0732: public final PreviousPageInfo thePreviousPageInfo() {
0733: return getPreviousPageInfo();
0734: }
0735:
0736: /**
0737: * Get information about the most recent action run in this page flow.
0738: * <p>
0739: * <i>Note: if the current page flow does not use a
0740: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Forward @Jpf.Forward},
0741: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.SimpleAction @Jpf.SimpleAction}, or
0742: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.ConditionalForward @Jpf.ConditionalForward}
0743: * with <code>navigateTo={@link org.apache.beehive.netui.pageflow.annotations.Jpf.NavigateTo#previousAction previousAction}</code>,
0744: * then this method will always return </i><code>null</code><i> by default. To enable it in this situation, add the
0745: * following method to the page flow:</i><br>
0746: * <blockquote>
0747: * <code>
0748: * protected boolean alwaysTrackPreviousAction()<br>
0749: * {<br>
0750: * return true;<br>
0751: * }<br>
0752: * </code>
0753: * </blockquote>
0754: *
0755: * @return a PreviousActionInfo with information about the most recent action run in this page flow.
0756: * @see #getPreviousPageInfo
0757: * @see #getCurrentPageInfo
0758: * @see #getPreviousActionURI
0759: * @see #getPreviousFormBean
0760: * @see #getPreviousForwardPath
0761: * @see #getCurrentForwardPath
0762: */
0763: protected final PreviousActionInfo getPreviousActionInfo() {
0764: checkPreviousActionInfoDisabled();
0765: return _previousActionInfo;
0766: }
0767:
0768: /**
0769: * This is a non-property public accessor that will return the property value <code>previousActionInfo</code>.
0770: * This is a non-property method because properties are exposed to databinding and that would expose
0771: * internal data structures to attack.
0772: * @return a PreviousActionInfo with information about the most recent action run in this page flow.
0773: */
0774: public final PreviousActionInfo thePreviousActionInfo() {
0775: return getPreviousActionInfo();
0776: }
0777:
0778: private void checkPreviousActionInfoDisabled() {
0779: if (isPreviousActionInfoDisabled()) {
0780: throw new IllegalStateException(
0781: "Previous action information has been disabled in this page flow. Override alwaysTrackPreviousAction() to enable it.");
0782: }
0783: }
0784:
0785: private void checkPreviousPageInfoDisabled() {
0786: if (isPreviousPageInfoDisabled()) {
0787: throw new IllegalStateException(
0788: "Previous page information has been disabled in this page flow. Override alwaysTrackPreviousPage() to enable it.");
0789: }
0790: }
0791:
0792: /**
0793: * Get the display name of this page flow.
0794: * @return the display name (the URI) of this page flow.
0795: */
0796: public String getDisplayName() {
0797: return getURI();
0798: }
0799:
0800: public boolean isPreviousActionInfoDisabled() {
0801: if (alwaysTrackPreviousAction())
0802: return false;
0803:
0804: ModuleConfig mc = getModuleConfig();
0805: ControllerConfig cc = mc.getControllerConfig();
0806: return cc instanceof PageFlowControllerConfig
0807: && ((PageFlowControllerConfig) cc)
0808: .isReturnToActionDisabled();
0809: }
0810:
0811: public boolean isPreviousPageInfoDisabled() {
0812: if (alwaysTrackPreviousPage())
0813: return false;
0814:
0815: ModuleConfig mc = getModuleConfig();
0816: ControllerConfig cc = mc.getControllerConfig();
0817: return cc instanceof PageFlowControllerConfig
0818: && ((PageFlowControllerConfig) cc)
0819: .isReturnToPageDisabled();
0820: }
0821:
0822: /**
0823: * Called from {@link FlowController#execute}.
0824: */
0825: void savePreviousActionInfo(ActionForm form,
0826: HttpServletRequest request, ActionMapping mapping,
0827: ServletContext servletContext) {
0828: //
0829: // If previous-action is disabled (unused in this pageflow), just return.
0830: //
0831: if (isPreviousActionInfoDisabled())
0832: return;
0833: String actionURI = InternalUtils.getDecodedServletPath(request);
0834: _previousActionInfo = new PreviousActionInfo(form, actionURI,
0835: request.getQueryString());
0836: }
0837:
0838: /**
0839: * Store information about recent pages displayed. This is a framework-invoked method that should not normally be
0840: * called directly.
0841: */
0842: public void savePreviousPageInfo(ActionForward forward,
0843: ActionForm form, ActionMapping mapping,
0844: HttpServletRequest request, ServletContext servletContext,
0845: boolean isSpecialForward) {
0846: if (forward != null) {
0847: //
0848: // If previous-page is disabled (unused in this pageflow), or if we've already saved prevous-page info in
0849: // this request (for example, forward to foo.faces which forwards to foo.jsp), just return.
0850: //
0851: if (request.getAttribute(SAVED_PREVIOUS_PAGE_INFO_ATTR) != null
0852: || isPreviousPageInfoDisabled())
0853: return;
0854:
0855: String path = forward.getPath();
0856: int queryPos = path.indexOf('?');
0857: if (queryPos != -1)
0858: path = path.substring(0, queryPos);
0859:
0860: //
0861: // If a form bean was generated in this request, add it to the most recent PreviousPageInfo, so when we
0862: // go back to that page, the *updated* field values are restored (i.e., we don't revert to the values of
0863: // the form that was passed into the page originally).
0864: //
0865: if (form != null && _currentPageInfo != null) {
0866: ActionForm oldForm = _currentPageInfo.getForm();
0867: if (oldForm == null
0868: || oldForm.getClass().equals(form.getClass())) {
0869: _currentPageInfo.setForm(form);
0870: _currentPageInfo.setMapping(mapping);
0871: }
0872: }
0873:
0874: //
0875: // Only keep track of *pages* forwarded to -- not actions or pageflows.
0876: //
0877: if (!FileUtils.osSensitiveEndsWith(path, ACTION_EXTENSION)) {
0878: //
0879: // Only save previous-page info if the page is within this pageflow.
0880: //
0881: if (isLocalFile(forward)) // || PageFlowUtils.osSensitiveEndsWith( path, JPF_EXTENSION ) )
0882: {
0883: _previousPageInfo = _currentPageInfo;
0884: _currentPageInfo = new PreviousPageInfo(forward,
0885: form, mapping, request.getQueryString());
0886: request.setAttribute(SAVED_PREVIOUS_PAGE_INFO_ATTR,
0887: Boolean.TRUE);
0888: }
0889: }
0890: }
0891: }
0892:
0893: private boolean isLocalFile(ActionForward forward) {
0894: String path = forward.getPath();
0895:
0896: if (!forward.getContextRelative()) {
0897: return path.indexOf('/', 1) == -1; // all paths in Struts start with '/'
0898: } else {
0899: String modulePath = getModulePath();
0900:
0901: if (!path.startsWith(modulePath)) {
0902: return false;
0903: } else {
0904: return path.indexOf('/', modulePath.length() + 1) == -1;
0905: }
0906: }
0907: }
0908:
0909: private boolean isOnNestingStack() {
0910: return _isOnNestingStack;
0911: }
0912:
0913: /**
0914: * Callback when this object is removed from the user session. Causes {@link #onDestroy} to be called. This is a
0915: * framework-invoked method that should not be called directly.
0916: */
0917: public void valueUnbound(HttpSessionBindingEvent event) {
0918: //
0919: // Unless this pageflow has been pushed onto the nesting stack, do the onDestroy() callback.
0920: //
0921: if (!_isOnNestingStack) {
0922: super .valueUnbound(event);
0923: }
0924: }
0925:
0926: void setIsOnNestingStack(boolean isOnNestingStack) {
0927: _isOnNestingStack = isOnNestingStack;
0928: }
0929:
0930: ActionForward forwardTo(ActionForward fwd, ActionMapping mapping,
0931: PageFlowExceptionConfig exceptionConfig, String actionName,
0932: ModuleConfig altModuleConfig, ActionForm form,
0933: HttpServletRequest request, HttpServletResponse response,
0934: ServletContext servletContext) {
0935: ActionForward super Fwd = super .forwardTo(fwd, mapping,
0936: exceptionConfig, actionName, altModuleConfig, form,
0937: request, response, servletContext);
0938:
0939: //
0940: // Special case: the *only* way for a nested pageflow to nest itself is for it
0941: // to forward to itself as a .jpf. Simply executing an action in the .jpf isn't
0942: // enough, obviously, since it's impossible to tell whether it should be executed
0943: // in the current pageflow or a new nested one.
0944: //
0945: if (super Fwd != null
0946: && InternalUtils.isNestable(getModuleConfig())) {
0947: boolean selfNesting = false;
0948:
0949: if (super Fwd.getContextRelative()) {
0950: if (super Fwd.getPath().equals(getURI())) {
0951: selfNesting = true;
0952: }
0953: } else {
0954: if (super Fwd.getPath().equals(getStrutsLocalURI())) {
0955: selfNesting = true;
0956: }
0957: }
0958:
0959: if (selfNesting) {
0960: if (_log.isDebugEnabled()) {
0961: _log.debug("Self-nesting page flow " + getURI());
0962: }
0963:
0964: try {
0965: // This will cause the right pageflow stack stuff to happen.
0966: RequestContext rc = new RequestContext(request,
0967: response);
0968: FlowControllerFactory.get(getServletContext())
0969: .createPageFlow(rc, getClass());
0970: } catch (IllegalAccessException e) {
0971: // This should never happen -- if we successfully created this page flow once, we can do it again.
0972: assert false : e;
0973: _log.error(e);
0974: } catch (InstantiationException e) {
0975: _log.error(
0976: "Could not create PageFlowController instance of type "
0977: + getClass().getName(), e);
0978: }
0979: }
0980: }
0981:
0982: return super Fwd;
0983: }
0984:
0985: private String getStrutsLocalURI() {
0986: String className = getClass().getName();
0987: int lastDot = className.lastIndexOf('.');
0988: InternalStringBuilder ret = new InternalStringBuilder("/");
0989: return ret.append(className.substring(lastDot + 1)).append(
0990: PAGEFLOW_EXTENSION).toString();
0991: }
0992:
0993: private CachedPageFlowInfo getCachedInfo() {
0994: ClassLevelCache cache = ClassLevelCache.getCache(getClass());
0995: CachedPageFlowInfo info = (CachedPageFlowInfo) cache
0996: .getCacheObject(CACHED_INFO_KEY);
0997:
0998: if (info == null) {
0999: info = new CachedPageFlowInfo(getClass(),
1000: getServletContext());
1001: cache.setCacheObject(CACHED_INFO_KEY, info);
1002: }
1003:
1004: return info;
1005: }
1006:
1007: final void beforePage() {
1008: //
1009: // We may need to save the previous page info if the page was called directly (not forwarded through an action)
1010: // and we do not yet have the forward path in the page flow or it is a different path.
1011: //
1012: if (!isPreviousPageInfoDisabled()) {
1013: HttpServletRequest request = getRequest();
1014: String relativeUri = InternalUtils
1015: .getDecodedServletPath(request);
1016:
1017: String path = getCurrentForwardPath();
1018: if (path == null || !path.equals(relativeUri)) {
1019: ActionForward actionForward = new ActionForward(
1020: relativeUri);
1021: actionForward.setContextRelative(true);
1022: actionForward.setRedirect(false);
1023: savePreviousPageInfo(actionForward, null, null,
1024: request, getServletContext(), false);
1025: }
1026: }
1027: }
1028:
1029: /**
1030: * @exclude
1031: */
1032: public ActionForward exitNesting(HttpServletRequest request,
1033: HttpServletResponse response, ActionMapping mapping,
1034: ActionForm form) {
1035: // Get any return-action view renderer from the original ("nesting") page flow. If there is one, we'll
1036: // use it later to render the view.
1037: PageFlowController nestingPageFlow = PageFlowUtils
1038: .getNestingPageFlow(request, getServletContext());
1039: ViewRenderer returnActionViewRenderer = nestingPageFlow
1040: .getReturnActionViewRenderer();
1041:
1042: if (returnActionViewRenderer != null) {
1043: PageFlowRequestWrapper.get(request).setViewRenderer(
1044: returnActionViewRenderer);
1045: nestingPageFlow.setReturnActionViewRenderer(null); // we don't need it anymore
1046: }
1047:
1048: PerRequestState prevState = setPerRequestState(new PerRequestState(
1049: request, response, mapping));
1050:
1051: try {
1052: onExitNesting();
1053: } catch (Throwable th) {
1054: try {
1055: return handleException(th, mapping, form, request,
1056: response);
1057: } catch (Exception e) {
1058: _log
1059: .error(
1060: "Exception thrown while handling exception.",
1061: e);
1062: }
1063: } finally {
1064: setPerRequestState(prevState);
1065: }
1066:
1067: return null;
1068: }
1069:
1070: /**
1071: * Callback that is invoked when this controller instance is exiting nesting through a return action.
1072: * {@link FlowController#getRequest}, {@link FlowController#getResponse}, {@link FlowController#getSession}
1073: * may all be used during this method.
1074: */
1075: protected void onExitNesting() throws Exception {
1076: }
1077:
1078: private ViewRenderer getReturnActionViewRenderer() {
1079: return _returnActionViewRenderer;
1080: }
1081:
1082: private void setReturnActionViewRenderer(
1083: ViewRenderer returnActionViewRenderer) {
1084: _returnActionViewRenderer = returnActionViewRenderer;
1085: }
1086: }
|