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 org.apache.struts.action.ActionForward;
0022: import org.apache.struts.action.ActionMapping;
0023: import org.apache.struts.action.ActionForm;
0024: import org.apache.struts.config.ModuleConfig;
0025:
0026: import javax.servlet.http.HttpServletRequest;
0027: import javax.servlet.ServletContext;
0028: import javax.servlet.ServletRequest;
0029: import java.util.List;
0030: import java.util.ArrayList;
0031: import java.util.Map;
0032: import java.util.HashMap;
0033: import java.lang.reflect.Field;
0034: import java.lang.reflect.Modifier;
0035: import java.net.URI;
0036: import java.net.URL;
0037:
0038: import org.apache.beehive.netui.pageflow.config.PageFlowActionForward;
0039: import org.apache.beehive.netui.pageflow.internal.InternalUtils;
0040: import org.apache.beehive.netui.pageflow.internal.AdapterManager;
0041: import org.apache.beehive.netui.pageflow.internal.PageFlowRequestWrapper;
0042: import org.apache.beehive.netui.pageflow.handler.ReloadableClassHandler;
0043: import org.apache.beehive.netui.pageflow.handler.Handlers;
0044: import org.apache.beehive.netui.util.internal.FileUtils;
0045: import org.apache.beehive.netui.util.internal.InternalStringBuilder;
0046: import org.apache.beehive.netui.util.logging.Logger;
0047:
0048: /**
0049: * An object of this type is returned from an action methods in a {@link PageFlowController} to
0050: * determine the next URI to be displayed. It is constructed on the name of a forward defined
0051: * by the @{@link org.apache.beehive.netui.pageflow.annotations.Jpf.Forward @Jpf.Forward} annotation, and resolves to the URI
0052: * specified in that forward.
0053: */
0054: public class Forward extends ActionForward {
0055: private static final ActionForm[] EMPTY_ACTION_FORM_ARRAY = new ActionForm[0];
0056:
0057: public static final int RETURN_TO_NONE = 0;
0058: public static final int RETURN_TO_CURRENT_PAGE = 1;
0059: public static final int RETURN_TO_PREVIOUS_PAGE = 2;
0060: public static final int RETURN_TO_PREVIOUS_ACTION = 3;
0061:
0062: /**
0063: * @deprecated Use {@link #RETURN_TO_CURRENT_PAGE} or {@link #RETURN_TO_PREVIOUS_PAGE} instead.
0064: */
0065: public static final int RETURN_TO_PAGE = -1;
0066:
0067: /**
0068: * @deprecated Use {@link #RETURN_TO_PREVIOUS_ACTION} instead.
0069: */
0070: public static final int RETURN_TO_ACTION = -2;
0071:
0072: private static final Logger _log = Logger
0073: .getInstance(Forward.class);
0074:
0075: private static final String RETURN_TO_CURRENT_PAGE_STR = "currentPage";
0076: private static final String RETURN_TO_PREVIOUS_PAGE_STR = "previousPage";
0077: private static final String RETURN_TO_PAGE_LEGACY_STR = "page";
0078: private static final String RETURN_TO_PREVIOUS_ACTION_STR = "previousAction";
0079: private static final String RETURN_TO_ACTION_LEGACY_STR = "action";
0080:
0081: private static final Map/*< String, Class >*/PRIMITIVE_TYPES = new HashMap/*< String, Class >*/();
0082:
0083: static {
0084: PRIMITIVE_TYPES.put("boolean", boolean.class);
0085: PRIMITIVE_TYPES.put("byte", byte.class);
0086: PRIMITIVE_TYPES.put("char", char.class);
0087: PRIMITIVE_TYPES.put("double", double.class);
0088: PRIMITIVE_TYPES.put("float", float.class);
0089: PRIMITIVE_TYPES.put("int", int.class);
0090: PRIMITIVE_TYPES.put("long", long.class);
0091: PRIMITIVE_TYPES.put("short", short.class);
0092: }
0093:
0094: private List _outputForms;
0095:
0096: private boolean _isNestedReturn = false;
0097: private boolean _init = false;
0098: private transient ActionMapping _mapping = null; // will be reinitialized as necessary by PreviousPageInfo
0099: private transient FlowController _flowController = null; // will be reinitialized as necessary by PreviousPageInfo
0100: private transient ServletContext _servletContext = null; // will be reinitialized as necessary by PreviousPageInfo
0101: private String _mappingPath;
0102: private InternalStringBuilder _queryString;
0103: private boolean _explicitPath = false;
0104: private String _outputFormBeanType = null;
0105: private Map _actionOutputs = null;
0106: private int _returnToType;
0107: private boolean _redirectSpecifiedOnAnnotation = false;
0108: private boolean _redirectSetThroughMethod = false;
0109: private boolean _restoreQueryString = false;
0110: private boolean _externalRedirect = false;
0111: private boolean _outsidePageFlowDirectory = false;
0112: private String _relativeTo;
0113:
0114: /**
0115: * An alternate ModuleConfig from which to resolve forwards if they are not resolved
0116: * from the stored ActionMapping (and its stored ModuleConfig).
0117: */
0118: private ModuleConfig _altModuleConfig;
0119:
0120: /**
0121: * Construct based on an initializer Forward. This is a framework-invoked constructor that should not normally
0122: * be called directly.
0123: */
0124: protected Forward(Forward init) {
0125: _outputForms = init._outputForms;
0126: _init = init._init;
0127: _mapping = init._mapping;
0128: _mappingPath = init._mappingPath;
0129: _queryString = init._queryString;
0130: _explicitPath = init._explicitPath;
0131: _flowController = init._flowController;
0132: _servletContext = init._servletContext;
0133: _outputFormBeanType = init._outputFormBeanType;
0134: _actionOutputs = init._actionOutputs;
0135: _returnToType = init._returnToType;
0136: _restoreQueryString = init._restoreQueryString;
0137: _externalRedirect = init._externalRedirect;
0138: _redirectSpecifiedOnAnnotation = init._redirectSpecifiedOnAnnotation;
0139: _redirectSetThroughMethod = init._redirectSetThroughMethod;
0140: }
0141:
0142: /**
0143: * Construct based on a given request. This is a framework-invoked constructor that should not normally
0144: * be called directly.
0145: */
0146: protected Forward(HttpServletRequest request) {
0147: setPath(InternalUtils.getDecodedServletPath(request));
0148: setContextRelative(true);
0149: _explicitPath = true;
0150: }
0151:
0152: /**
0153: * Constructor which accepts the name of a forward defined by the
0154: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Forward @Jpf.Forward}
0155: * annotation. The values returned from {@link #getPath}, {@link #getRedirect} and
0156: * {@link #contextRelative} are resolved from this forward.
0157: *
0158: * @param forwardName the name of the forward
0159: * ({@link org.apache.beehive.netui.pageflow.annotations.Jpf.Forward @Jpf.Forward}) to resolve.
0160: */
0161: public Forward(String forwardName) {
0162: setName(forwardName);
0163: }
0164:
0165: /**
0166: * Constructor which accepts the name of a forward defined by the
0167: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Forward @Jpf.Forward}
0168: * annotation. The values returned from {@link #getPath}, {@link #getRedirect} and
0169: * {@link #contextRelative} are resolved from this forward. Also accepts a form bean
0170: * to make available in the request (or user session, as appropriate).
0171: *
0172: * @param forwardName the name of the forward
0173: * ({@link org.apache.beehive.netui.pageflow.annotations.Jpf.Forward @Jpf.Forward}) to resolve.
0174: * @param outputFormBean a form bean instance to make available in the request (or user session, as appropriate).
0175: * See {@link #addOutputForm} for details about how this manifests itself.
0176: */
0177: public Forward(String forwardName, Object outputFormBean) {
0178: this (forwardName);
0179:
0180: if (outputFormBean != null) {
0181: addOutputForm(outputFormBean);
0182: }
0183: }
0184:
0185: /**
0186: * Constructor which accepts the name of a forward defined by the
0187: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Forward @Jpf.Forward}
0188: * annotation. The values returned from {@link #getPath}, {@link #getRedirect} and
0189: * {@link #contextRelative} are resolved from this forward. Also accepts a named action output
0190: * to make available in the request, through {@link PageFlowUtils#getActionOutput}..
0191: *
0192: * @param forwardName the name of the forward
0193: * ({@link org.apache.beehive.netui.pageflow.annotations.Jpf.Forward @Jpf.Forward}) to resolve.
0194: * @param actionOutputName the name of a action output to make available in the request.
0195: * @param actionOutputValue the action output object to make available in the request.
0196: */
0197: public Forward(String forwardName, String actionOutputName,
0198: Object actionOutputValue) {
0199: this (forwardName);
0200: addActionOutput(actionOutputName, actionOutputValue);
0201: }
0202:
0203: /**
0204: * Constructs a Forward that returns the given URI for {@link #getPath}. By default
0205: * the Forward will cause server forward (not a browser redirect); to change this, use
0206: * {@link #setRedirect}.
0207: *
0208: * @param uri the URI to return for {@link #getPath}.
0209: */
0210: public Forward(URI uri) {
0211: setPath(uri.toString());
0212: setContextRelative(uri.getPath().startsWith("/"));
0213: if (uri.isAbsolute())
0214: super .setRedirect(true);
0215: _explicitPath = true;
0216: }
0217:
0218: /**
0219: * Constructs a Forward that returns the given URI for {@link #getPath}.
0220: *
0221: * @param uri the URI to return for {@link #getPath}.
0222: * @param doRedirect set to <code>true</code> if this Forward should cause a browser redirect;
0223: * <code>false</code> if it should cause a server forward.
0224: */
0225: public Forward(URI uri, boolean doRedirect) {
0226: if (!doRedirect && uri.isAbsolute()) {
0227: throw new IllegalStateException(
0228: "Redirect value cannot be set to false for an absolute URI.");
0229: }
0230:
0231: setPath(uri.toString());
0232: setRedirect(doRedirect);
0233: setContextRelative(uri.getPath().startsWith("/"));
0234: _explicitPath = true;
0235: }
0236:
0237: /**
0238: * Construct based on the given <strong>webapp-relative</strong> path. This is a framework-invoked constructor that
0239: * should not normally be called directly.
0240: */
0241: protected Forward(String path, boolean doRedirect) {
0242: setPath(path);
0243: setContextRelative(true);
0244: setRedirect(doRedirect);
0245: _explicitPath = true;
0246: }
0247:
0248: /**
0249: * Constructs a Forward that returns the given URL for {@link #getPath}. Because the URL path
0250: * is absolute by nature, this Forward will cause a browser redirect.
0251: *
0252: * @param url the URL to return for {@link #getPath}.
0253: */
0254: public Forward(URL url) {
0255: setPath(url.toString());
0256: super .setRedirect(true);
0257: _explicitPath = true;
0258: }
0259:
0260: /**
0261: * Internal. Initialize from an existing Struts ActionForward.
0262: */
0263: protected Forward(ActionForward initFwd,
0264: ServletContext servletContext) {
0265: _servletContext = servletContext;
0266: setName(initFwd.getName());
0267: initFrom(initFwd);
0268: }
0269:
0270: /**
0271: * Set whether the URI resolved by this Forward should be redirected to.
0272: *
0273: * @param doRedirect if <code>true</code>, the controller will send a browser redirect to
0274: * the URI for this Forward; otherwise, it will do a server forward to
0275: * the URI.
0276: */
0277: public void setRedirect(boolean doRedirect) {
0278: super .setRedirect(doRedirect);
0279: _redirectSetThroughMethod = true;
0280: }
0281:
0282: /**
0283: * Tell whether the URI resolved by this Forward should be redirected to.
0284: *
0285: * @return <code>true</code> if the controller will send a browser redirect to the URI for
0286: * this Forward; <code>false</code> if it will do a server forward to the URI.
0287: */
0288: public boolean isRedirect() {
0289: return super .getRedirect();
0290: }
0291:
0292: /**
0293: * Add a form bean that will be made available in the request (or user session, as
0294: * appropriate) if this Forward is returned by an action method in a {@link PageFlowController}.
0295: * Specifically, each form bean is stored as a request attribute with a name determined by
0296: * {@link PageFlowUtils#getFormBeanName}.
0297: *
0298: * @param formBean the form bean instance to add.
0299: */
0300: public final void addOutputForm(Object formBean) {
0301: assert formBean != null : "The output form bean may not me null.";
0302:
0303: if (formBean == null)
0304: throw new IllegalArgumentException(
0305: "The output form bean may not be null.");
0306: if (_outputForms == null)
0307: _outputForms = new ArrayList();
0308:
0309: //
0310: // Throw an exception if this is a redirect, and if there was an output form added. Output forms are carried
0311: // in the request, and will be lost on redirects.
0312: //
0313: if (_init && getRedirect()) {
0314: String actionPath = _mappingPath != null ? _mappingPath
0315: : "";
0316: String descrip = getName() != null ? getName() : getPath();
0317: PageFlowException ex = new IllegalRedirectOutputFormException(
0318: descrip, actionPath, _flowController, formBean
0319: .getClass().getName());
0320: InternalUtils.throwPageFlowException(ex);
0321: }
0322:
0323: _outputForms.add(InternalUtils.wrapFormBean(formBean));
0324: }
0325:
0326: /**
0327: * Get all form-beans attached to this forward through {@link #addOutputForm} or {@link #Forward(String, Object)}.
0328: *
0329: * @return an array of ActionForm instances that are attached to this forward.
0330: */
0331: public final ActionForm[] getOutputForms() {
0332: if (_outputForms == null)
0333: return EMPTY_ACTION_FORM_ARRAY;
0334: return (ActionForm[]) _outputForms
0335: .toArray(EMPTY_ACTION_FORM_ARRAY);
0336: }
0337:
0338: /**
0339: * Get the first output form bean that was added to this Forward.
0340: */
0341: public ActionForm getFirstOutputForm(HttpServletRequest request) {
0342: if (_outputForms == null || _outputForms.size() == 0) {
0343: if (_outputFormBeanType != null) {
0344: try {
0345: if (_log.isDebugEnabled()) {
0346: _log.debug("Creating form bean of type "
0347: + _outputFormBeanType);
0348: }
0349:
0350: ServletContext servletContext = InternalUtils
0351: .getServletContext(request);
0352: ReloadableClassHandler rch = Handlers.get(
0353: servletContext).getReloadableClassHandler();
0354: Object formBean = rch
0355: .newInstance(_outputFormBeanType);
0356: ActionForm wrappedFormBean = InternalUtils
0357: .wrapFormBean(formBean);
0358: addOutputForm(wrappedFormBean);
0359: return wrappedFormBean;
0360: } catch (Exception e) {
0361: _log.error(
0362: "Could not create form bean instance of "
0363: + _outputFormBeanType, e);
0364: }
0365: }
0366:
0367: return null;
0368: } else {
0369: return (ActionForm) _outputForms.get(0);
0370: }
0371: }
0372:
0373: /**
0374: * Tell whether {@link #getPath} will be successful, i.e., whether one of the following two
0375: * conditions is met:
0376: * <ul>
0377: * <li>the name around which this object was constructed resolves to a path defined
0378: * by a {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Forward @Jpf.Forward}, or</li>
0379: * <li>this object was constructed around an explicit path, by
0380: * {@link #Forward(URI)} or {@link #Forward(URL)}.</li>
0381: * </ul>
0382: *
0383: * @return <code>true</code> if this forward does resolve to a URI path.
0384: */
0385: public boolean doesResolve() {
0386: if (_explicitPath) {
0387: return true;
0388: }
0389:
0390: assert _mapping != null || _altModuleConfig != null : "PageFlow.Forward.doesResolve() called outside of request";
0391: return findForward(getName()) != null;
0392: }
0393:
0394: /**
0395: * Resolves the forward with the given name, from the stored ActionMapping if possible, or
0396: * from the stored alternate ModuleConfig as a last resort.
0397: *
0398: * @param forwardName the name of the forward to resolve.
0399: * @return the resolved ActionForward, or <code>null</code> if none is found.
0400: */
0401: protected ActionForward findForward(String forwardName) {
0402: ActionForward fwd = _mapping != null ? _mapping
0403: .findForward(forwardName) : null;
0404:
0405: if (fwd != null) {
0406: return fwd;
0407: } else if (_altModuleConfig != null) {
0408: return (ActionForward) _altModuleConfig
0409: .findForwardConfig(forwardName);
0410: }
0411:
0412: return null;
0413: }
0414:
0415: /**
0416: * Set an alternate ModuleConfig from which to resolve forwards if they are not resolved
0417: * from the stored ActionMapping (and its stored ModuleConfig).
0418: */
0419: public void setAlternateModuleConfig(ModuleConfig mc) {
0420: _altModuleConfig = mc;
0421: }
0422:
0423: private void init() {
0424: if (!_init) {
0425: if (_mappingPath == null && _altModuleConfig == null) {
0426: throw new IllegalStateException(
0427: "Forward is not initialized. Use initialize().");
0428: }
0429:
0430: ActionForward fwd = findForward(getName());
0431:
0432: if (fwd == null) {
0433: PageFlowException ex = new UnresolvableForwardException(
0434: getName(), _mappingPath, _flowController);
0435: InternalUtils.throwPageFlowException(ex);
0436: }
0437:
0438: initFrom(fwd);
0439:
0440: //
0441: // Throw an exception if this is a redirect, and if there was an output form or an action output added.
0442: // Output forms and action outputs are carried in the request, and will be lost on redirects.
0443: //
0444: if (getRedirect()) {
0445: if (_actionOutputs != null && !_actionOutputs.isEmpty()) {
0446: PageFlowException ex = new IllegalActionOutputException(
0447: getName(), _mappingPath, _flowController,
0448: (String) _actionOutputs.keySet().iterator()
0449: .next());
0450: InternalUtils.throwPageFlowException(ex);
0451: }
0452:
0453: if (_outputForms != null && !_outputForms.isEmpty()) {
0454: PageFlowException ex = new IllegalRedirectOutputFormException(
0455: getName(), _mappingPath, _flowController,
0456: _outputForms.get(0).getClass().getName());
0457: InternalUtils.throwPageFlowException(ex);
0458: }
0459: }
0460: }
0461: }
0462:
0463: private void initFrom(ActionForward fwd) {
0464: setContextRelative(fwd.getContextRelative());
0465:
0466: //
0467: // Add query params to the path.
0468: //
0469: path = fwd.getPath();
0470: if (_queryString != null)
0471: path += _queryString.toString();
0472:
0473: if (fwd instanceof PageFlowActionForward) {
0474: PageFlowActionForward fc = (PageFlowActionForward) fwd;
0475: _isNestedReturn = fc.isNestedReturn();
0476: _outputFormBeanType = fc.getReturnFormType();
0477: _redirectSpecifiedOnAnnotation = fc
0478: .hasExplicitRedirectValue();
0479: _restoreQueryString = fc.isRestoreQueryString();
0480: _externalRedirect = fc.isExternalRedirect();
0481: _relativeTo = fc.getRelativeTo();
0482:
0483: Class returnFormClass = null;
0484:
0485: if (_outputFormBeanType != null) {
0486: try {
0487: ReloadableClassHandler rch = Handlers.get(
0488: _servletContext)
0489: .getReloadableClassHandler();
0490: returnFormClass = rch
0491: .loadClass(_outputFormBeanType);
0492: } catch (ClassNotFoundException e) {
0493: // This should never happen -- the JPF compiler ensures that it's a valid class.
0494: assert false : e;
0495: }
0496: }
0497:
0498: if (fc.isReturnToPage() || fc.isReturnToAction()) {
0499: String fwdPath = fc.getPath();
0500:
0501: if (fwdPath.equals(RETURN_TO_PREVIOUS_PAGE_STR)) {
0502: _returnToType = RETURN_TO_PREVIOUS_PAGE;
0503: } else if (fwdPath.equals(RETURN_TO_CURRENT_PAGE_STR)) {
0504: _returnToType = RETURN_TO_CURRENT_PAGE;
0505: } else if (fwdPath.equals(RETURN_TO_PAGE_LEGACY_STR)) {
0506: _returnToType = RETURN_TO_PAGE; // legacy
0507: } else if (fwdPath
0508: .equals(RETURN_TO_PREVIOUS_ACTION_STR)) {
0509: _returnToType = RETURN_TO_PREVIOUS_ACTION;
0510: } else if (fwdPath.equals(RETURN_TO_ACTION_LEGACY_STR)) {
0511: _returnToType = RETURN_TO_ACTION; // legacy
0512: } else {
0513: assert false : "invalid return-to type for forward "
0514: + fc.getName() + ": " + fwdPath;
0515: _returnToType = RETURN_TO_CURRENT_PAGE;
0516: }
0517: }
0518:
0519: String retFormMember = fc.getReturnFormMember();
0520:
0521: if (retFormMember != null) {
0522: try {
0523: assert _flowController != null; // should be set in initialize()
0524: Field field = _flowController.getClass()
0525: .getDeclaredField(retFormMember);
0526: returnFormClass = field.getType();
0527: if (!Modifier.isPublic(field.getModifiers()))
0528: field.setAccessible(true);
0529: Object form = field.get(_flowController);
0530:
0531: if (form != null) {
0532: if (_log.isDebugEnabled()) {
0533: _log.debug("using member " + retFormMember
0534: + " for Forward " + getName());
0535: }
0536:
0537: addOutputForm(form);
0538: } else {
0539: if (_log.isInfoEnabled()) {
0540: _log.info("returnFormMember "
0541: + retFormMember + " was null.");
0542: }
0543: }
0544: } catch (NoSuchFieldException e) {
0545: assert false : "could not find field "
0546: + retFormMember; // compiler should catch this
0547: } catch (IllegalAccessException e) {
0548: assert false; // should not get here -- field is accessible.
0549: }
0550: }
0551:
0552: //
0553: // Make sure that if there's currently an output form, that it confirms to the return-form-type.
0554: //
0555: if (returnFormClass != null && _outputForms != null
0556: && _outputForms.size() > 0) {
0557: Object outputForm = InternalUtils
0558: .unwrapFormBean((ActionForm) _outputForms
0559: .get(0));
0560:
0561: if (!returnFormClass.isInstance(outputForm)) {
0562: PageFlowException ex = new IllegalOutputFormTypeException(
0563: getName(), _mappingPath, _flowController,
0564: outputForm.getClass().getName(),
0565: returnFormClass.getName());
0566: InternalUtils.throwPageFlowException(ex);
0567: }
0568: }
0569:
0570: checkActionOutputs(fc);
0571: }
0572:
0573: if (!_redirectSetThroughMethod)
0574: setRedirect(fwd.getRedirect());
0575:
0576: _init = true;
0577: }
0578:
0579: /**
0580: * Make sure required action outputs are present, and are of the right type (only make the latter check when not
0581: * in production mode
0582: */
0583: private void checkActionOutputs(PageFlowActionForward fc) {
0584: PageFlowActionForward.ActionOutput[] actionOutputs = fc
0585: .getActionOutputs();
0586: boolean doExpensiveChecks = _actionOutputs != null
0587: && !AdapterManager.getServletContainerAdapter(
0588: _servletContext).isInProductionMode();
0589:
0590: for (int i = 0; i < actionOutputs.length; ++i) {
0591: PageFlowActionForward.ActionOutput actionOutput = actionOutputs[i];
0592:
0593: if (!actionOutput.getNullable()) {
0594: String actionOutputName = actionOutput.getName();
0595: if (_actionOutputs == null
0596: || !_actionOutputs
0597: .containsKey(actionOutputName)) {
0598: PageFlowException ex = new MissingActionOutputException(
0599: _mappingPath, _flowController, actionOutput
0600: .getName(), getName());
0601: InternalUtils.throwPageFlowException(ex);
0602: } else if (_actionOutputs.get(actionOutputName) == null) {
0603: PageFlowException ex = new NullActionOutputException(
0604: _mappingPath, _flowController, actionOutput
0605: .getName(), getName());
0606: InternalUtils.throwPageFlowException(ex);
0607: }
0608: }
0609:
0610: //
0611: // If we're *not* in production mode, do some (expensive) checks to ensure that the types for the
0612: // action outputs match their declared types.
0613: //
0614: if (doExpensiveChecks) {
0615: Object actualActionOutput = _actionOutputs
0616: .get(actionOutput.getName());
0617:
0618: if (actualActionOutput != null) {
0619: String expectedTypeName = actionOutput.getType();
0620: int expectedArrayDims = 0;
0621:
0622: while (expectedTypeName.endsWith("[]")) {
0623: ++expectedArrayDims;
0624: expectedTypeName = expectedTypeName.substring(
0625: 0, expectedTypeName.length() - 2);
0626: }
0627:
0628: Class expectedType = (Class) PRIMITIVE_TYPES
0629: .get(expectedTypeName);
0630:
0631: if (expectedType == null) {
0632: try {
0633: ReloadableClassHandler rch = Handlers.get(
0634: _servletContext)
0635: .getReloadableClassHandler();
0636: expectedType = rch
0637: .loadClass(expectedTypeName);
0638: } catch (ClassNotFoundException e) {
0639: _log
0640: .error("Could not load expected action output type "
0641: + expectedTypeName
0642: + " for action output '"
0643: + actionOutput.getName()
0644: + "' on forward '"
0645: + fc.getName()
0646: + "'; skipping type check.");
0647: continue;
0648: }
0649: }
0650:
0651: Class actualType = actualActionOutput.getClass();
0652: int actualArrayDims = 0;
0653: InternalStringBuilder arraySuffix = new InternalStringBuilder();
0654:
0655: while (actualType.isArray()
0656: && actualArrayDims <= expectedArrayDims) {
0657: ++actualArrayDims;
0658: arraySuffix.append("[]");
0659: actualType = actualType.getComponentType();
0660: }
0661:
0662: if (actualArrayDims != expectedArrayDims
0663: || !expectedType
0664: .isAssignableFrom(actualType)) {
0665: PageFlowException ex = new MismatchedActionOutputException(
0666: _mappingPath, _flowController,
0667: actionOutput.getName(), getName(),
0668: expectedTypeName, actualType.getName()
0669: + arraySuffix);
0670: InternalUtils.throwPageFlowException(ex);
0671: }
0672: }
0673: }
0674: }
0675: }
0676:
0677: /**
0678: * Set the current ActionMapping and associated FlowController. Normally, this method is called
0679: * by the framework, but you can use it to initialize the Forward object in order to call {@link #getPath}.
0680: *
0681: * @deprecated Use {@link #initialize(ActionMapping, FlowController, ServletRequest)} instead.
0682: * @param mapping the current ActionMapping; this can be obtained from {@link FlowController#getMapping}.
0683: * @param flowController the object in which to look for referenced return-form members.
0684: */
0685: public void initialize(ActionMapping mapping,
0686: FlowController flowController) {
0687: _mapping = mapping;
0688: _mappingPath = mapping != null ? mapping.getPath() : null;
0689: _flowController = flowController;
0690: _servletContext = flowController.getServletContext();
0691: }
0692:
0693: /**
0694: * Set the current ActionMapping and associated FlowController. Normally, this method is called
0695: * by the framework, but you can use it to initialize the Forward object in order to call {@link #getPath}.
0696: *
0697: * @param mapping the current ActionMapping; this can be obtained from {@link FlowController#getMapping}.
0698: * @param flowController the object in which to look for referenced return-form members.
0699: */
0700: public void initialize(ActionMapping mapping,
0701: FlowController flowController, ServletRequest request) {
0702: _mapping = mapping;
0703: _mappingPath = mapping != null ? mapping.getPath() : null;
0704: _flowController = flowController;
0705: _servletContext = flowController.getServletContext();
0706: }
0707:
0708: /**
0709: * Set the path to be returned by {@link #getPath}. This overrides any path or forward name
0710: * set in a constructor.
0711: *
0712: * @param contextRelativePath the path to be returned by {@link #getPath}.
0713: */
0714: public void setPath(String contextRelativePath) {
0715: path = contextRelativePath;
0716: _init = true;
0717: }
0718:
0719: boolean isExplicitPath() {
0720: return _explicitPath;
0721: }
0722:
0723: /**
0724: * Tell whether this Forward was configured explicitly (through
0725: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Forward#redirect @Jpf.Forward(redirect=...)},
0726: * {@link #setRedirect}, or {@link #Forward(URI, boolean)}) to perform a redirect. Otherwise, a redirect is
0727: * implied by a URI that does not resolve to the current server.
0728: */
0729: public boolean hasExplicitRedirectValue() {
0730: return _redirectSetThroughMethod
0731: || _redirectSpecifiedOnAnnotation;
0732: }
0733:
0734: /**
0735: * Get the URI path associated with this object. Resolve it from the name of a forward
0736: * ({@link org.apache.beehive.netui.pageflow.annotations.Jpf.Forward @Jpf.Forward}) if necessary.
0737: *
0738: * @return a String that is the URI path.
0739: * @see #Forward(String)
0740: * @see #Forward(String, Object)
0741: * @see #Forward(URI)
0742: * @see #Forward(URL)
0743: * @see #setPath
0744: */
0745: public String getPath() {
0746: init();
0747: return super .getPath();
0748: }
0749:
0750: /**
0751: * Tell whether returning this forward from an action method will cause a return from
0752: * a nested {@link PageFlowController}.
0753: *
0754: * @return <code>true</code> if this forward will cause a return from nesting.
0755: */
0756: public boolean isNestedReturn() {
0757: init();
0758: return _isNestedReturn;
0759: }
0760:
0761: /**
0762: * Tell whether returning this forward from an action method will cause a previous page
0763: * to be displayed.
0764: *
0765: * @return <code>true</code> if returning this forward from an action method will cause
0766: * a previous page to be displayed.
0767: */
0768: public boolean isReturnToPage() {
0769: init();
0770: return _returnToType == RETURN_TO_PREVIOUS_PAGE
0771: || _returnToType == RETURN_TO_CURRENT_PAGE
0772: || _returnToType == RETURN_TO_PAGE;
0773: }
0774:
0775: /**
0776: * Tell whether returning this forward from an action method will cause the previous action
0777: * to be re-run.
0778: *
0779: * @return <code>true</code> if returning this forward from an action method will cause the
0780: * previous action to be re-run, i.e., whether the URI returned by {@link #getPath} will end
0781: * in "<i>previous-action-name</i>.do".
0782: */
0783: public boolean isReturnToAction() {
0784: init();
0785: return _returnToType == RETURN_TO_PREVIOUS_ACTION
0786: || _returnToType == RETURN_TO_ACTION;
0787: }
0788:
0789: /**
0790: * Tell whether this is a redirect to a URI outside of the current web application.
0791: */
0792: public boolean isExternalRedirect() {
0793: return _externalRedirect;
0794: }
0795:
0796: /**
0797: * Specify that this is a redirect to a URI outside of the current web application.
0798: */
0799: public void setExternalRedirect(boolean externalRedirect) {
0800: _externalRedirect = externalRedirect;
0801: if (externalRedirect)
0802: setRedirect(true);
0803: }
0804:
0805: /**
0806: * Tell whether this Forward will restore the original query string on the page restored when a
0807: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Forward @Jpf.Forward},
0808: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.SimpleAction @Jpf.SimpleAction},
0809: * or {@link org.apache.beehive.netui.pageflow.annotations.Jpf.ConditionalForward @Jpf.ConditionalForward}
0810: * with <code>navigateTo={@link org.apache.beehive.netui.pageflow.annotations.Jpf.NavigateTo#previousAction Jpf.NavigateTo.previousAction}
0811: * </code> is used.
0812: */
0813: public boolean doesRestoreQueryString() {
0814: init();
0815: return _restoreQueryString;
0816: }
0817:
0818: /**
0819: * Set whether this Forward will restore the original query string query string on the page restored when a
0820: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.Forward @Jpf.Forward},
0821: * {@link org.apache.beehive.netui.pageflow.annotations.Jpf.SimpleAction @Jpf.SimpleAction},
0822: * or {@link org.apache.beehive.netui.pageflow.annotations.Jpf.ConditionalForward @Jpf.ConditionalForward}
0823: * with <code>navigateTo={@link org.apache.beehive.netui.pageflow.annotations.Jpf.NavigateTo#previousAction Jpf.NavigateTo.previousAction}
0824: * </code> is used.
0825: */
0826: public void setRestoreQueryString(boolean doesRestoreQueryString) {
0827: _restoreQueryString = doesRestoreQueryString;
0828: }
0829:
0830: /**
0831: * Tell whether the URI returned by {@link #getPath} is for a page flow.
0832: *
0833: * @return <code>true</code> if the URI returned by {@link #getPath} is for a page flow, i.e.,
0834: * if it ends in ".jpf".
0835: */
0836: public boolean forwardsToPageFlow() {
0837: return FileUtils.osSensitiveEndsWith(getPath(),
0838: PageFlowConstants.PAGEFLOW_EXTENSION);
0839: }
0840:
0841: /**
0842: * Set the query string that will be appended to the URI returned by {@link #getPath}.
0843: *
0844: * @param queryString the query string that will be appended to the URI. If this string does not
0845: * start with <code>'?'</code>, then this character will be prepended; if the string is
0846: * <code>null</code>, the query string will be removed.
0847: */
0848: public void setQueryString(String queryString) {
0849: if (queryString == null || queryString.length() == 0) {
0850: _queryString = null;
0851: } else if (queryString.charAt(0) == '?') {
0852: _queryString = new InternalStringBuilder(queryString);
0853: } else {
0854: _queryString = new InternalStringBuilder("?")
0855: .append(queryString);
0856: }
0857: }
0858:
0859: /**
0860: * Get the query string that will be appended to the URI returned by {@link #getPath}.
0861: *
0862: * @return the query string that will be appended to the URI, or <code>null</code> if there
0863: * is no query string.
0864: */
0865: public String getQueryString() {
0866: return _queryString != null ? _queryString.toString() : null;
0867: }
0868:
0869: /**
0870: * Add a query parameter to the URI returned by {@link #getPath}.
0871: *
0872: * @param paramName the name of the query parameter.
0873: * @param value the value of the query parameter, or <code>null</code> if there is no value.
0874: */
0875: public void addQueryParam(String paramName, String value) {
0876: if (_queryString == null) {
0877: _queryString = new InternalStringBuilder("?");
0878: } else {
0879: _queryString.append('&');
0880: }
0881:
0882: _queryString.append(paramName);
0883:
0884: if (value != null) {
0885: _queryString.append('=').append(value);
0886: }
0887: }
0888:
0889: /**
0890: * Add a query parameter with no value to the URI returned by {@link #getPath}.
0891: *
0892: * @param paramName the name of the query parameter.
0893: */
0894: public final void addQueryParam(String paramName) {
0895: addQueryParam(paramName, null);
0896: }
0897:
0898: /**
0899: * Adds an action output that will be made available in the request, through {@link PageFlowUtils#getActionOutput}.
0900: *
0901: * @deprecated Use {@link #addActionOutput} instead.
0902: * @param paramName the name of the action output.
0903: * @param value the action output value.
0904: */
0905: public void addPageInput(String paramName, Object value) {
0906: addActionOutput(paramName, value);
0907: }
0908:
0909: /**
0910: * Adds an action output that will be made available in the request, through {@link PageFlowUtils#getActionOutput}.
0911: *
0912: * @param paramName the name of the action output.
0913: * @param value the action output value.
0914: */
0915: public void addActionOutput(String paramName, Object value) {
0916: if (paramName == null || paramName.length() == 0) {
0917: throw new IllegalArgumentException(
0918: "An action output name may not be null or empty.");
0919: }
0920:
0921: if (_actionOutputs == null) {
0922: _actionOutputs = new HashMap();
0923: }
0924:
0925: //
0926: // Throw an exception if this is a redirect, and if there was an action output. Action outputs are carried
0927: // in the request, and will be lost on redirects.
0928: //
0929: if (_init && getRedirect()) {
0930: String actionPath = _mappingPath != null ? _mappingPath
0931: : "";
0932: String descrip = getName() != null ? getName() : getPath();
0933: PageFlowException ex = new IllegalActionOutputException(
0934: descrip, actionPath, _flowController, paramName);
0935: InternalUtils.throwPageFlowException(ex);
0936: }
0937:
0938: _actionOutputs.put(paramName, value);
0939: }
0940:
0941: /**
0942: * Get all action outputs that have been set on this Forward.
0943: *
0944: * @deprecated Use {@link #getActionOutputs} instead.
0945: * @return a Map of name/value pairs representing all action outputs.
0946: * @see #addActionOutput
0947: */
0948: public Map getPageInputs() {
0949: return getActionOutputs();
0950: }
0951:
0952: /**
0953: * Get all action outputs that have been set on this Forward.
0954: *
0955: * @return a Map of name/value pairs representing all action outputs.
0956: * @see #addActionOutput
0957: */
0958: public Map getActionOutputs() {
0959: return _actionOutputs;
0960: }
0961:
0962: /**
0963: * Get the type of return, if this is a <code>return-to</code> type.
0964: *
0965: * @return one of the following values: {@link #RETURN_TO_CURRENT_PAGE}, {@link #RETURN_TO_PREVIOUS_PAGE},
0966: * {@link #RETURN_TO_PAGE}, {@link #RETURN_TO_PREVIOUS_ACTION}, {@link #RETURN_TO_ACTION}, or
0967: * {@link #RETURN_TO_NONE} if this Forward is not a <code>return-to</code> type.
0968: * @see #isReturnToAction
0969: * @see #isReturnToPage
0970: */
0971: public int getReturnToType() {
0972: return _returnToType;
0973: }
0974:
0975: /**
0976: * Get the type of return as a String, if this is a <code>return-to</code> type.
0977: *
0978: * @return one of the following values: <code>currentPage</code>, <code>previousPage</code>, <code>page</code>,
0979: * (deprecated), <code>previousAction</code>, <code>action</code> (deprecated), or <code>null</code>
0980: * if this is not a <code>return-to</code> type.
0981: * @see #isReturnToAction
0982: * @see #isReturnToPage
0983: */
0984: public String getReturnToTypeAsString() {
0985: switch (_returnToType) {
0986: case RETURN_TO_CURRENT_PAGE:
0987: return RETURN_TO_CURRENT_PAGE_STR;
0988:
0989: case RETURN_TO_PREVIOUS_PAGE:
0990: return RETURN_TO_PREVIOUS_PAGE_STR;
0991:
0992: case RETURN_TO_PAGE:
0993: return RETURN_TO_PAGE_LEGACY_STR;
0994:
0995: case RETURN_TO_PREVIOUS_ACTION:
0996: return RETURN_TO_PREVIOUS_ACTION_STR;
0997:
0998: case RETURN_TO_ACTION:
0999: return RETURN_TO_ACTION_LEGACY_STR;
1000: }
1001:
1002: return null;
1003: }
1004:
1005: void reinitialize(FlowController fc) {
1006: _flowController = fc;
1007: _servletContext = fc.getServletContext();
1008:
1009: if (_mapping == null && _mappingPath != null) {
1010: ModuleConfig mc = fc.getModuleConfig();
1011: assert mc != null : "no ModuleConfig found for "
1012: + fc.getClass().getName();
1013: _mapping = (ActionMapping) mc
1014: .findActionConfig(_mappingPath);
1015: }
1016: }
1017:
1018: /**
1019: * Tell whether this Forward is relative to a particular directory path
1020: * (as is the case when inheriting local paths from base classes). If
1021: * this is true, it implies the forward is an inherited global forward,
1022: * with a "relativeTo" path given in a <set-property>
1023: * in the forward config.
1024: *
1025: * <p>
1026: * This is a framework-invoked method that should not normally be called
1027: * directly.
1028: * </p>
1029: */
1030: public boolean hasRelativeToPath() {
1031: return _relativeTo != null;
1032: }
1033:
1034: /**
1035: * If this is a local path, change it so it's relative to the given path prefix, and remember that we did it in
1036: * a flag (_outsidePageFlowDirectory). This is a framework-invoked method that should not normally be called
1037: * directly.
1038: */
1039: public void initializeRelativePath(ServletRequest request,
1040: String relativeTo) {
1041: if (relativeTo == null) {
1042: relativeTo = _relativeTo;
1043: }
1044:
1045: if (relativeTo == null)
1046: return;
1047:
1048: assert !relativeTo.endsWith("/") : relativeTo;
1049: String path = getPath();
1050:
1051: // If this is a local (relative) path, prefix it with the given 'relativeTo' path, and save a flag.
1052: // We save this flag in a local variable because the Forward object may be saved/restored for use again.
1053: if (!getContextRelative() && !FileUtils.isAbsoluteURI(path)) {
1054: assert path.startsWith("/") : path;
1055: setPath(relativeTo + path);
1056: setContextRelative(true);
1057: _outsidePageFlowDirectory = true;
1058: }
1059:
1060: // Store a flag in the request that will prevent another page flow from being initialized implicitly because
1061: // we're hitting a path within it. We want to stay in the context of the current page flow.
1062: if (_outsidePageFlowDirectory) {
1063: PageFlowRequestWrapper.get(request).setStayInCurrentModule(
1064: true);
1065: }
1066: }
1067: }
|