0001: /* ====================================================================
0002: * The Jcorporate Apache Style Software License, Version 1.2 05-07-2002
0003: *
0004: * Copyright (c) 1995-2002 Jcorporate Ltd. All rights reserved.
0005: *
0006: * Redistribution and use in source and binary forms, with or without
0007: * modification, are permitted provided that the following conditions
0008: * are met:
0009: *
0010: * 1. Redistributions of source code must retain the above copyright
0011: * notice, this list of conditions and the following disclaimer.
0012: *
0013: * 2. Redistributions in binary form must reproduce the above copyright
0014: * notice, this list of conditions and the following disclaimer in
0015: * the documentation and/or other materials provided with the
0016: * distribution.
0017: *
0018: * 3. The end-user documentation included with the redistribution,
0019: * if any, must include the following acknowledgment:
0020: * "This product includes software developed by Jcorporate Ltd.
0021: * (http://www.jcorporate.com/)."
0022: * Alternately, this acknowledgment may appear in the software itself,
0023: * if and wherever such third-party acknowledgments normally appear.
0024: *
0025: * 4. "Jcorporate" and product names such as "Expresso" must
0026: * not be used to endorse or promote products derived from this
0027: * software without prior written permission. For written permission,
0028: * please contact info@jcorporate.com.
0029: *
0030: * 5. Products derived from this software may not be called "Expresso",
0031: * or other Jcorporate product names; nor may "Expresso" or other
0032: * Jcorporate product names appear in their name, without prior
0033: * written permission of Jcorporate Ltd.
0034: *
0035: * 6. No product derived from this software may compete in the same
0036: * market space, i.e. framework, without prior written permission
0037: * of Jcorporate Ltd. For written permission, please contact
0038: * partners@jcorporate.com.
0039: *
0040: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0041: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0042: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0043: * DISCLAIMED. IN NO EVENT SHALL JCORPORATE LTD OR ITS CONTRIBUTORS
0044: * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0045: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
0046: * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0047: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0048: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0049: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0050: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0051: * SUCH DAMAGE.
0052: * ====================================================================
0053: *
0054: * This software consists of voluntary contributions made by many
0055: * individuals on behalf of the Jcorporate Ltd. Contributions back
0056: * to the project(s) are encouraged when you make modifications.
0057: * Please send them to support@jcorporate.com. For more information
0058: * on Jcorporate Ltd. and its products, please see
0059: * <http://www.jcorporate.com/>.
0060: *
0061: * Portions of this software are based upon other open source
0062: * products and are subject to their respective licenses.
0063: */
0064:
0065: package com.jcorporate.expresso.core.controller;
0066:
0067: import com.jcorporate.expresso.core.ExpressoConstants;
0068: import com.jcorporate.expresso.core.misc.ConfigManager;
0069: import com.jcorporate.expresso.core.misc.StringDOMParser;
0070: import com.jcorporate.expresso.core.misc.StringUtil;
0071: import com.jcorporate.expresso.core.misc.URLUTF8Encoder;
0072: import com.jcorporate.expresso.kernel.util.FastStringBuffer;
0073: import org.apache.log4j.Logger;
0074: import org.apache.struts.config.ActionConfig;
0075: import org.w3c.dom.Document;
0076: import org.w3c.dom.NamedNodeMap;
0077: import org.w3c.dom.Node;
0078: import org.w3c.dom.NodeList;
0079:
0080: import javax.servlet.RequestDispatcher;
0081: import javax.servlet.http.HttpServletRequest;
0082: import javax.servlet.http.HttpServletResponse;
0083: import java.io.IOException;
0084: import java.util.Enumeration;
0085: import java.util.Hashtable;
0086: import java.util.Map;
0087: import java.util.Vector;
0088:
0089: /**
0090: * <p>An Transition is a choice that the user can make that initiates
0091: * either another sequence in this same controller or some new
0092: * controller. A transition is one of the three types of objects that
0093: * a controller produces when it enters a new state, the others
0094: * being Input objects and Output objects.</p>
0095: * <p>Another use of a Transition object is for internal transitioning between
0096: * various controllers and their states. Typical example is as follows: <br/>
0097: * <code>
0098: * <pre>
0099: * Transition t = new Transition();
0100: * t.setControllerObject(com.myapp.MyController.class);
0101: * t.setState("State2");
0102: * t.addParameter("SampleParam","This is a parameter value");
0103: * return t.transition();
0104: * </pre>
0105: * </code>
0106: * </p>
0107: * <p/>
0108: * <h4>Recognized Attributes:</h4>
0109: * <p>The following types are recognized by the expresso framework and
0110: * automatically rendered: You may add your own types or ignore them
0111: * if you are doing your own page rendering.</p>
0112: * <p/>
0113: * <p>header: Renders the transition in the jc-header class style</p>
0114: * <p/>
0115: * <p>button: Renders the transition as a button.
0116: * <p/>
0117: * <p><i>default behavior:</i> Renders as a clickable button</p>
0118: */
0119: public class Transition extends ControllerElement implements Cloneable,
0120: java.io.Serializable {
0121:
0122: private static Logger log = Logger.getLogger(Transition.class);
0123:
0124: /**
0125: * name of the controller object that created this transition
0126: */
0127: private String ownerObject = null;
0128:
0129: /**
0130: * The name of a controller object that handles this action
0131: */
0132: private String controllerObject = null;
0133:
0134: /**
0135: * The parameters to the controller object
0136: */
0137: private Hashtable params = new Hashtable(1);
0138: private String myState = null;
0139: private boolean returnToSender = false;
0140:
0141: /**
0142: * This is used to store a constructed parameter string to save on
0143: * re-constructing multiple times.
0144: */
0145: private transient String cacheParamStringSansController = null;
0146:
0147: /**
0148: * This is used to store a constructed parameter string to save on
0149: * re-constructing multiple times.
0150: */
0151: private transient String cacheParamStringWithController = null;
0152:
0153: /**
0154: * Used in place of ControllerResponse for URL encoding
0155: */
0156: private transient HttpServletResponse servletResponse = null;
0157:
0158: /**
0159: * Default Constructor. Normally you don't use this.
0160: */
0161: public Transition() {
0162: }
0163:
0164: /**
0165: * Convenience method to transition to another state in this same controller.
0166: *
0167: * @param newState the new name of the state.
0168: * @param myController An instantiated Controller object. Use a <code>ControllerFactory</code>
0169: * to instantiate the controller if you must use this constructor.
0170: */
0171: public Transition(String newState, Controller myController) {
0172:
0173: //
0174: //Precalculate the class name because it can take significant CPU time.
0175: //
0176: String className = myController.getClass().getName();
0177: myState = newState;
0178: setControllerObject(className);
0179: setOwnerController(className);
0180: setName(newState);
0181:
0182: //
0183: //Performance: get the hashmap because that way we don't invoke a state
0184: //copy for each object.
0185: //
0186: Hashtable allstates = myController.getStates();
0187: State theState = null;
0188: if (allstates != null) {
0189: theState = (State) allstates.get(newState);
0190: }
0191:
0192: if (theState == null) {
0193: throw new IllegalArgumentException("No such state as '"
0194: + newState + "' in controller '" + className + "'");
0195: }
0196:
0197: setLabel(theState.getDescription());
0198: addParam(Controller.STATE_PARAM_KEY, newState);
0199: } /* Transition(String, Controller) */
0200:
0201: /**
0202: * Convenience constructor to create an action with a label
0203: * and a controller already set.
0204: *
0205: * @param newLabel Label for the new action
0206: * @param newObject The name of the object this action referred to
0207: */
0208: public Transition(String newLabel, String newObject) {
0209: super ();
0210: setName(StringUtil.replace(newLabel, " ", ""));
0211: setLabel(newLabel);
0212: setControllerObject(newObject);
0213: } /* Transition(String, String) */
0214:
0215: /**
0216: * Convenience constructor to create an action with a label
0217: * and a controller already set.
0218: *
0219: * @param newName Name of this Transition object
0220: * @param newLabel Label for the new action
0221: * @param newObject The name of the Controller object this action referred to
0222: */
0223: public Transition(String newName, String newLabel, String newObject) {
0224: super ();
0225: setName(newName);
0226: setLabel(newLabel);
0227: setControllerObject(newObject);
0228: } /* Transition(String, String, String) */
0229:
0230: /**
0231: * Convenience method to allow for one line of code to construct a transition.
0232: *
0233: * @param name The name of the transition
0234: * @param label The label to use for the transition
0235: * @param controllerClass The <code>Class</code> of the controller to use
0236: * @param controllerState The name of the controller's state.
0237: */
0238: public Transition(String name, String label, Class controllerClass,
0239: String controllerState) {
0240: super ();
0241: setName(name);
0242: setLabel(label);
0243: setControllerObject(controllerClass);
0244: setState(controllerState);
0245: }
0246:
0247: /**
0248: * Convenience method to allow for one line of code to construct a transition.
0249: * The (internal) name of the transition will be the state name.
0250: *
0251: * @param label The label to use for the transition
0252: * @param controllerClass The <code>Class</code> of the controller to use
0253: * @param controllerState The name of the controller's state.
0254: */
0255: public Transition(String label, Class controllerClass,
0256: String controllerState) {
0257: super ();
0258: setName(controllerState);
0259: setLabel(label);
0260: setControllerObject(controllerClass);
0261: setState(controllerState);
0262: }
0263:
0264: /**
0265: * Adds a parameter to a transition. These parameters are meant to
0266: * be eventually consumed by the target controller via the request.getParameter()
0267: * method.
0268: *
0269: * @param paramCode The code name of the parameter
0270: * @param paramValue The value for the paramter
0271: */
0272: public synchronized void addParam(String paramCode,
0273: String paramValue) {
0274: clearCache();
0275: if (paramCode.equals(Controller.STATE_PARAM_KEY)) {
0276: setState(StringUtil.notNull(paramValue));
0277:
0278: return;
0279: }
0280: if (paramCode.equals(Controller.CONTROLLER_PARAM_KEY)) {
0281: setControllerObject(StringUtil.notNull(paramValue));
0282:
0283: return;
0284: }
0285:
0286: params.put(paramCode, StringUtil.notNull(paramValue));
0287: } /* addParam(String, String) */
0288:
0289: private synchronized void clearCache() {
0290: cacheParamStringWithController = null;
0291: cacheParamStringSansController = null;
0292: }
0293:
0294: /**
0295: * Sets the target state to transition to.
0296: *
0297: * @param newState java.lang.String
0298: */
0299: public synchronized void setState(String newState) {
0300: clearCache();
0301: myState = newState;
0302: }
0303:
0304: /**
0305: * Retrieve the currently set state
0306: *
0307: * @return java.lang.String
0308: */
0309: public String getState() {
0310: if (myState == null) {
0311: return getParam(Controller.STATE_PARAM_KEY);
0312: }
0313:
0314: return myState;
0315: }
0316:
0317: /**
0318: * Returns a copy of itself
0319: *
0320: * @return a cloned and instantiated <code>Transition</code> object.
0321: * @throws CloneNotSupportedException as required by the method
0322: * signature.
0323: */
0324: public Object clone() throws CloneNotSupportedException {
0325: Transition t;
0326:
0327: synchronized (this ) {
0328: t = (Transition) super .clone();
0329: t.params = (Hashtable) params.clone();
0330: t.controllerObject = controllerObject;
0331: t.myState = myState;
0332: t.returnToSender = returnToSender;
0333: }
0334:
0335: return t;
0336: }
0337:
0338: /**
0339: * Call this method when the state/controller being transitioned to should return control back
0340: * to the calling state once it has 'completed' successfully. Th 'completion' point depends on
0341: * whether the transition is to an external (new controller) or internal state. For external
0342: * transitions, the completion point is once the Final state has run successfully. For internal
0343: * transitions, the completion point is once the called state has run successfully.
0344: * <p/>
0345: * When this method is called with a non-null response parameter, this indicates the currently
0346: * running state should be the return address. If this method is called with a null response
0347: * parameter then this indicates that the return address should be determined at transition
0348: * execution time. This is useful/necessary when a transition is instantiated outside the scope
0349: * of a state execution. For example, when the controllerSecurityTransition value is set in
0350: * the controller constructor, the return state is not known/active.
0351: *
0352: * @param response the ControllerResponse object
0353: * @throws ControllerException upon error
0354: */
0355: public void enableReturnToSender(ControllerResponse response)
0356: throws ControllerException {
0357: returnToSender = true;
0358: String returnToSender = null;
0359: if (response != null) {
0360: FastStringBuffer fsb = FastStringBuffer.getInstance();
0361: try {
0362: Transition t = response.getCurrentState()
0363: .getReturnToSender();
0364: returnToSender = t.toXML(fsb).toString();
0365: } finally {
0366: fsb.release();
0367: fsb = null;
0368: }
0369:
0370: if (isExternalTransition(response.getControllerClass())) {
0371: if (getParam(Controller.CTL_SUCC_TRAN) == null) { //don't overwrite if already set while in run()
0372: addParam(Controller.CTL_SUCC_TRAN, returnToSender);
0373: }
0374: } else {
0375: if (getParam(Controller.STATE_SUCC_TRAN) == null) { //don't overwrite if already set while in run()
0376: addParam(Controller.STATE_SUCC_TRAN, returnToSender);
0377: }
0378: }
0379: }
0380: }
0381:
0382: /**
0383: * Returns True if the destination for this transition is a different
0384: * controller from the one currently active.
0385: *
0386: * @param runningController the name of the controller we're currently in.
0387: * Usually you would
0388: * use <code>this.getClass().getName()</code> within your own controller as
0389: * a parameter.
0390: * @return true if this is a transition to a another controller.
0391: */
0392: public boolean isExternalTransition(String runningController) {
0393: if (controllerObject == null
0394: || controllerObject.equals(runningController)) {
0395: return false;
0396: } else {
0397: return true;
0398: }
0399: }
0400:
0401: /**
0402: * Return the name of the controller object that this Transition
0403: * referred to. If this is null, then it refers to the same controller
0404: * object (e.g. intra-controller transition)
0405: *
0406: * @return The class name of the controller object this action
0407: * refers to.
0408: */
0409: public String getControllerObject() {
0410: if (controllerObject == null) {
0411: return getParam(Controller.CONTROLLER_PARAM_KEY);
0412: }
0413:
0414: return controllerObject;
0415: } /* getControllerObject() */
0416:
0417: /**
0418: * Sets the controller that created this transition. If controllerObject
0419: * equals owner controller, then no controller= parameter is generated.
0420: *
0421: * @return the owner controller
0422: */
0423: public String getOwnerController() {
0424: return this .ownerObject;
0425: }
0426:
0427: /**
0428: * Return the value for a specific parameter for this transition object.
0429: *
0430: * @param paramCode The code (name) of the parameter
0431: * @return The value of the parameter as a string
0432: */
0433: public String getParam(String paramCode) {
0434: return (String) params.get(paramCode);
0435: } /* getParam(String) */
0436:
0437: /**
0438: * Return the hashtable of parameters for this transition object.
0439: * These parameters are to be handed to the new controller
0440: * when the action is called.
0441: *
0442: * @return A hashtable of name/value pairs for the parameters
0443: */
0444: public Hashtable getParams() {
0445: return params;
0446: } /* getParams() */
0447:
0448: /**
0449: * @param includeControllerParameter whether to include controller param or not.
0450: * @return parameter string which includes all params added to trans, as well
0451: * as state param. controller param added optionally
0452: */
0453: public synchronized String getParamString(
0454: boolean includeControllerParameter) {
0455: if (includeControllerParameter
0456: && cacheParamStringWithController == null) {
0457: FastStringBuffer paramString = FastStringBuffer
0458: .getInstance();
0459: try {
0460: if (controllerObject != null) {
0461: paramString.append("controller=");
0462: paramString.append(controllerObject);
0463: }
0464:
0465: addNonControllerParams(paramString);
0466:
0467: cacheParamStringWithController = paramString.toString();
0468: } finally {
0469: paramString.release();
0470: paramString = null;
0471: }
0472:
0473: }
0474:
0475: if (!includeControllerParameter
0476: && cacheParamStringSansController == null) {
0477: FastStringBuffer paramString = FastStringBuffer
0478: .getInstance();
0479: try {
0480: addNonControllerParams(paramString);
0481:
0482: cacheParamStringSansController = paramString.toString();
0483: } finally {
0484: paramString.release();
0485: paramString = null;
0486: }
0487:
0488: }
0489:
0490: if (includeControllerParameter) {
0491: return cacheParamStringWithController;
0492: } else {
0493: return cacheParamStringSansController;
0494: }
0495:
0496: }
0497:
0498: private void addNonControllerParams(FastStringBuffer paramString) {
0499: if (this .params != null) {
0500: if (!this .params.isEmpty()) {
0501: String oneKey = null;
0502:
0503: for (Enumeration e = this .params.keys(); e
0504: .hasMoreElements();) {
0505: if (paramString.length() != 0) {
0506: paramString.append("&");
0507: }
0508: oneKey = (String) e.nextElement();
0509:
0510: //Encode user's Transition parameters otherwise is ueer's parameters has '&' then
0511: //it will mess up the addButtonParams() method when using Tokenizer.
0512: FastStringBuffer fsb = FastStringBuffer
0513: .getInstance();
0514: try {
0515: fsb.append(oneKey);
0516: fsb.append("=");
0517: fsb.append(URLUTF8Encoder
0518: .encode((String) this .params
0519: .get(oneKey)));
0520: paramString.append(fsb.toString());
0521: } finally {
0522: fsb.release();
0523: fsb = null;
0524: }
0525:
0526: }
0527: }
0528: }
0529:
0530: String stateString = StringUtil.notNull(getState());
0531: if (stateString.length() != 0) {
0532: if (paramString.length() != 0) {
0533: paramString.append("&");
0534: }
0535: paramString.append("state=");
0536: paramString.append(stateString);
0537: }
0538: }
0539:
0540: /**
0541: * Return a string of the current params This is NOT URL encoded string.
0542: * Use either getUrl() OR java.net.URLEncoder() to do this job.
0543: *
0544: * @return java.lang.String
0545: */
0546: public String getParamString() {
0547:
0548: return getParamString(true);
0549:
0550: } /* getParamString() */
0551:
0552: /**
0553: * This method invokes a new controller by dispatching to it rather than
0554: * calling it directly. This is required when transitioning to external
0555: * states so that Struts can setup the controller form.
0556: * <p/>
0557: * The currently active controller request is passed to the new state to
0558: * simulate a direct call to the state. The request is then picked up
0559: * by the controller's perform method. Any exceptions raised by the
0560: * target state will filter back here to be passed on. This approach
0561: * was needed in order to simulate a direct call the the state while at
0562: * the same time allowing Struts to setup the controller form.
0563: *
0564: * @param request the <code>ControllerRequest</code> Object
0565: * @return an instantiated <code>ControllerResponse</code> object
0566: */
0567: protected ControllerResponse newStateDispatch(
0568: ControllerRequest request) throws ControllerException,
0569: NonHandleableException {
0570: ServletControllerRequest servletRequest = (ServletControllerRequest) request;
0571: HttpServletRequest httpRequest = (HttpServletRequest) servletRequest
0572: .getServletRequest();
0573: HttpServletResponse httpResponse = (HttpServletResponse) servletRequest
0574: .getServletResponse();
0575: //Blank state required to avoid picking up an old state param while really we want
0576: //to transition to the initial/default state (ie state is not specified)
0577: // ActionMapping mm = ConfigManager.getMapping(controllerObject, "");
0578: ActionConfig ac = ConfigManager.getActionConfig("",
0579: controllerObject, "");
0580:
0581: if (ac == null) {
0582: throw new ControllerException(
0583: "Cannot transition to controller: "
0584: + controllerObject
0585: + " controller not defined in Struts configuration");
0586: }
0587:
0588: String apath = ac.getPath();
0589: FastStringBuffer newURL = FastStringBuffer.getInstance();
0590: RequestDispatcher dispatcher = null;
0591: try {
0592: newURL.append(apath);
0593: newURL.append(".do");
0594: newURL.append("?controller=" + getControllerObject());
0595:
0596: if (getState() != null) {
0597: newURL.append("&state=" + getState());
0598: }
0599:
0600: httpRequest.setAttribute(
0601: ExpressoConstants.CONTROLLER_REQUEST_KEY, request); //Push our request onto the 'queue'
0602:
0603: String urlValue = newURL.toString();
0604: dispatcher = httpRequest.getRequestDispatcher(urlValue);
0605:
0606: if (dispatcher == null) {
0607: throw new ControllerException(
0608: "Request dispatcher was null - cannot include URL '"
0609: + urlValue + "'");
0610: }
0611: } finally {
0612: newURL.release();
0613: newURL = null;
0614: }
0615: try {
0616: dispatcher.include(httpRequest, httpResponse);
0617: } catch (Exception e) {
0618: throw new ControllerException(e);
0619: }
0620:
0621: //Pop our request & response off the 'queue'
0622: Exception newStateException = (Exception) httpRequest
0623: .getAttribute(ExpressoConstants.NEWSTATE_EXCEPTION_KEY);
0624:
0625: if (newStateException != null) { //bubble up any exception
0626: if (newStateException instanceof ControllerException) {
0627: throw (ControllerException) newStateException;
0628: } else {
0629: if (newStateException instanceof NonHandleableException) {
0630: throw (NonHandleableException) newStateException;
0631: } else {
0632: throw (RuntimeException) newStateException;
0633: }
0634: }
0635: }
0636:
0637: request = (ControllerRequest) httpRequest
0638: .getAttribute(ExpressoConstants.CONTROLLER_REQUEST_KEY);
0639: httpRequest
0640: .removeAttribute(ExpressoConstants.CONTROLLER_REQUEST_KEY);
0641:
0642: ControllerResponse response = (ControllerResponse) httpRequest
0643: .getAttribute(ExpressoConstants.CONTROLLER_RESPONSE_KEY);
0644: httpRequest
0645: .removeAttribute(ExpressoConstants.CONTROLLER_RESPONSE_KEY);
0646:
0647: return response;
0648: }
0649:
0650: /**
0651: * Returns True if the destination for this transition is the same as the
0652: * currently active state. Avoid infinite loop.
0653: *
0654: * @param runningState the current state
0655: * @param runningController the current controller
0656: * @return true if we're recusing to the same state and controller as we're
0657: * already in.
0658: */
0659: public boolean isRecursiveTransition(String runningState,
0660: String runningController) {
0661: String targetController = StringUtil.notNull(controllerObject);
0662: String targetState = StringUtil.notNull(myState);
0663:
0664: if (targetController.equals(runningController)
0665: && targetState.equals(runningState)) {
0666: return true;
0667: }
0668:
0669: return false;
0670: }
0671:
0672: /**
0673: * Return the return-to-sender flag.
0674: *
0675: * @return <code>boolean</code>
0676: */
0677: public boolean isReturnToSenderEnabled() {
0678: return returnToSender;
0679: }
0680:
0681: /**
0682: * Returns a hidden form field string that is safe in either the GET or POST
0683: * case.
0684: * <p/>
0685: * Creation date: (1/10/01 11:24:00 AM)
0686: * author: Adam Rossi, PlatinumSolutions
0687: *
0688: * @return java.lang.String
0689: */
0690: public String getHTMLParamString() {
0691: String paramString = this .getParamString();
0692: paramString = URLUTF8Encoder.encode(paramString);
0693:
0694: //
0695: //Guess at a memory allocation to reduce re-allocation/copy
0696: //Alloc Adjust was guessed at based upon watching string lengths and
0697: //guessing a reasonable maximum size that would fit most applciations.
0698: //
0699:
0700: FastStringBuffer sb = FastStringBuffer.getInstance();
0701: String returnValue = null;
0702: try {
0703: sb.append("<input type=\"HIDDEN\" name=\"");
0704: sb.append(this .getName());
0705: sb.append("_params");
0706: sb.append("\" value=\"");
0707: sb.append(paramString);
0708: sb.append("\">");
0709: sb.append("<input type=\"HIDDEN\" name=\"");
0710: sb.append(this .getName());
0711: sb.append("_encoding");
0712: sb.append("\" value=\"u\">");
0713: returnValue = sb.toString();
0714: } finally {
0715: sb.release();
0716: sb = null;
0717: }
0718: return returnValue;
0719: } /* getHTMLParamStriong() */
0720:
0721: /**
0722: * Set the Controller that this action referrs to
0723: *
0724: * @param newObject Name of the Controller object that this Transition refers to
0725: */
0726: public synchronized void setControllerObject(String newObject) {
0727: clearCache();
0728: controllerObject = newObject;
0729: } /* setControllerObject(String) */
0730:
0731: /**
0732: * Mor Typesafe way of setting the controller object. Any typos will be caught
0733: * at compile time. Example usage:
0734: * <code><pre>
0735: * Transition t = new Transition();
0736: * t.setControllerObject(com.jcorporate.expresso.services.Status.class);
0737: * </pre></code>
0738: *
0739: * @param c the class of the controller object to add.
0740: */
0741: public synchronized void setControllerObject(Class c) {
0742: clearCache();
0743: if (c != null) {
0744: setControllerObject(c.getName());
0745: } else {
0746: setControllerObject((String) null);
0747: }
0748: }
0749:
0750: /**
0751: * Sets the controller that created this transition. If controllerObject
0752: * equals owner controller, then no controller= parameter is generated.
0753: *
0754: * @param newController the classname of the new controller
0755: */
0756: public synchronized void setOwnerController(String newController) {
0757: clearCache();
0758: ownerObject = newController;
0759: }
0760:
0761: /**
0762: * Set this transition's parameters to the passed in collection.
0763: *
0764: * @param newParams the new parameters in bulk
0765: */
0766: public synchronized void setParams(Hashtable newParams) {
0767: clearCache();
0768: params = new Hashtable(newParams);
0769: }
0770:
0771: /**
0772: * This method will take the request parameters that were passed to this state
0773: * and will copy them into this transition's parameters. These parameters
0774: * will then be used when this state is reinvoked (return-to-sender) in order
0775: * to re-establish the parameter context.
0776: *
0777: * @param newReturnToSenderRequest The <code>ControllerRequest</code> object
0778: */
0779: public synchronized void setReturnToSenderParms(
0780: ControllerRequest newReturnToSenderRequest) {
0781: clearCache();
0782: String oneParamName = null;
0783: Object oneParamValue = null;
0784: Hashtable params = newReturnToSenderRequest.getParameters();
0785: Hashtable newParams = new Hashtable();
0786:
0787: //Copy all parameters except hidden parameters xxx_params and xxx_encoding.
0788: //The xxx_params have already been unencoded and untokenized into separate parameters.
0789: //Passing the xxx_params caused problems when the transition was encoded again and sent to HTML.
0790: //When the double-encoded parameter comes back with the next user request it is never unencoded.
0791: //This ends up causing problems with the fromXML/toXML methods in Transition.
0792: for (Enumeration e = params.keys(); e.hasMoreElements();) {
0793: oneParamName = (String) e.nextElement();
0794:
0795: if (!oneParamName.endsWith("_params")
0796: && !oneParamName.endsWith("_encoding")
0797: && !oneParamName.equals(Controller.STATE_PARAM_KEY)
0798: && !oneParamName
0799: .equals(Controller.CONTROLLER_PARAM_KEY)) {
0800: oneParamValue = params.get(oneParamName);
0801: newParams.put(oneParamName, oneParamValue);
0802: }
0803: }
0804:
0805: setParams(newParams);
0806: }
0807:
0808: /**
0809: * Convert the object to an xml fragment.
0810: *
0811: * @param stream an instantiated FastStringBuffer to which we append to.
0812: * @return a FastStringBuffer object
0813: */
0814: public FastStringBuffer toXML(FastStringBuffer stream) {
0815: stream.append("<transition");
0816:
0817: if (this .getName() != null && this .getName().length() > 0) {
0818: stream.append(" name=\"");
0819: stream.append(StringUtil.xmlEscape(getName()));
0820: stream.append("\"");
0821: }
0822:
0823: String controllerName = this .getControllerObject();
0824:
0825: if (controllerName != null && controllerName.length() > 0) {
0826: stream.append(" controller=\"");
0827: stream.append(StringUtil.xmlEscape(controllerName));
0828: stream.append("\"");
0829: }
0830:
0831: String stateName = this .getState();
0832:
0833: if (stateName != null && stateName.length() > 0) {
0834: stream.append(" state=\"");
0835: stream.append(StringUtil.xmlEscape(stateName));
0836: stream.append("\"");
0837: }
0838:
0839: stream.append(">\n");
0840:
0841: Hashtable params = this .getParams();
0842: String oneKey = null;
0843:
0844: if (!params.isEmpty()) {
0845: stream.append("\t<transition-parameters>\n");
0846:
0847: for (Enumeration ap = params.keys(); ap.hasMoreElements();) {
0848: oneKey = (String) ap.nextElement();
0849: stream.append("\t\t<transition-param name=\"");
0850: stream.append(StringUtil.xmlEscape(oneKey));
0851: stream.append("\" value=\"");
0852: stream.append(StringUtil.xmlEscape((String) params
0853: .get(oneKey)));
0854: stream.append("\"/>\n");
0855: }
0856:
0857: stream.append("\t</transition-parameters>\n");
0858: }
0859:
0860: stream = super .toXML(stream);
0861: stream.append("</transition>\n");
0862:
0863: return stream;
0864: }
0865:
0866: /**
0867: * Return a Transition based upon the String based xml fragment
0868: *
0869: * @param newTransition an xml fragment
0870: * @return an instantiated Transition object
0871: * @throws ControllerException upon error
0872: */
0873: public static Transition fromXML(String newTransition)
0874: throws ControllerException {
0875: StringDOMParser parser = new StringDOMParser();
0876: Document d = parser.parseString(newTransition);
0877:
0878: if (d == null) {
0879: throw new ControllerException(
0880: "Transition returned mal-formed XML document");
0881: } else {
0882: parser.dumpDOM(d);
0883: }
0884:
0885: return (Transition) fromXML(d);
0886: }
0887:
0888: /**
0889: * Return a controller element based upon the xml fragment
0890: *
0891: * @param n a DOM Node object
0892: * @return an instantiated ControllerElement
0893: * @throws ControllerException upon error
0894: */
0895: public static ControllerElement fromXML(Node n)
0896: throws ControllerException {
0897:
0898: //If we're at the root node, then it'll be doc instead of input.
0899: if (n.getNodeName().equals("#document")) {
0900: return fromXML(n.getChildNodes().item(0));
0901: }
0902: if (!n.getNodeName().equals("transition")) {
0903: return null;
0904: }
0905:
0906: Transition t = new Transition();
0907:
0908: //Get node attributes
0909: NamedNodeMap transitionAttributes = n.getAttributes();
0910: Node attributeNode = transitionAttributes.getNamedItem("name");
0911:
0912: if (attributeNode != null) {
0913: String value = attributeNode.getNodeValue();
0914:
0915: if (value != null) {
0916: t.setName(value);
0917: }
0918: }
0919:
0920: attributeNode = transitionAttributes
0921: .getNamedItem(Controller.CONTROLLER_PARAM_KEY);
0922:
0923: if (attributeNode != null) {
0924: String value = attributeNode.getNodeValue();
0925:
0926: if (value != null) {
0927: t.setControllerObject(value);
0928: }
0929: }
0930:
0931: NodeList nl = n.getChildNodes();
0932:
0933: for (int i = 0; i < nl.getLength(); i++) {
0934: Node oneChild = nl.item(i);
0935: String nodeName = oneChild.getNodeName();
0936:
0937: if (nodeName.equals("transition-parameters")) {
0938: NodeList parameters = oneChild.getChildNodes();
0939:
0940: for (int j = 0; j < parameters.getLength(); j++) {
0941: if (parameters.item(j).getNodeName().equals(
0942: "transition-param")) {
0943: NamedNodeMap paramAttributes = parameters.item(
0944: j).getAttributes();
0945: Node paramAttribute = paramAttributes
0946: .getNamedItem("name");
0947: String name = null;
0948: String value = null;
0949:
0950: if (paramAttribute != null) {
0951: name = paramAttribute.getNodeValue();
0952: }
0953:
0954: paramAttribute = paramAttributes
0955: .getNamedItem("value");
0956:
0957: if (paramAttribute != null) {
0958: value = paramAttribute.getNodeValue();
0959: }
0960: if (name != null && value != null) {
0961: t.addParam(name, value);
0962: }
0963: }
0964: }
0965: } else if (nodeName.equals("controller-element")) {
0966: t = (Transition) ControllerElement.fromXML(oneChild, t);
0967: }
0968: }
0969:
0970: return t;
0971: }
0972:
0973: /**
0974: * Internal use for retrieving the URL that this transition points to.
0975: *
0976: * @param resolveControllerReference should the controller be resolved to
0977: * a mapping, or should it just be the request path with a controller equals
0978: * parameter. True if you want the URL mapped to a .do mapping.
0979: * @return java.lang.String
0980: * @throws ControllerException if there is an error or there is no controller
0981: * response object available to help resoolve the reference.
0982: */
0983: public String getTheUrl(boolean resolveControllerReference)
0984: throws ControllerException {
0985: String returnValue = null;
0986: FastStringBuffer fsb = FastStringBuffer.getInstance();
0987: try {
0988: String paramString = "";
0989:
0990: if (resolveControllerReference
0991: && this .getControllerObject() != null
0992: && this .getControllerObject().length() > 0) {
0993:
0994: // try mapping first
0995: try {
0996: fsb.append(getMapping());
0997: } catch (Exception e) {
0998: // try response
0999: ControllerResponse myResponse = getControllerResponse();
1000:
1001: if (myResponse == null) {
1002: throw new ControllerException(
1003: "No controller object, nor controller response object available - cannot build URL");
1004: }
1005:
1006: fsb.clear();
1007: fsb.append(myResponse.getRequestPath());
1008: }
1009:
1010: paramString = getParamString(false);
1011:
1012: } else {
1013:
1014: // try response first
1015: ControllerResponse myResponse = getControllerResponse();
1016:
1017: if (myResponse == null) {
1018: try {
1019: fsb.append(getMapping());
1020: } catch (Exception e) {
1021: throw new ControllerException(
1022: "No controller param nor ControllerResponse available - cannot build URL");
1023: }
1024: } else {
1025: fsb.append(myResponse.getRequestPath());
1026: }
1027:
1028: paramString = getParamString(true);
1029: }
1030:
1031: if (paramString != null && paramString.length() > 0) {
1032: fsb.append("?");
1033: fsb.append(paramString);
1034: }
1035:
1036: returnValue = fsb.toString();
1037: if (log.isDebugEnabled()) {
1038: log.debug("transition redirects to: " + returnValue);
1039: }
1040: } finally {
1041: fsb.release();
1042: fsb = null;
1043: }
1044: return returnValue;
1045: }
1046:
1047: /**
1048: * Returns a URL reference for this transition. The URL does NOT include
1049: * the context path.
1050: *
1051: * @return java.lang.String
1052: * @throws ControllerException upon error
1053: */
1054: public String getUrl() throws ControllerException {
1055: return getTheUrl(true);
1056: }
1057:
1058: /**
1059: * Similar to getURL but also includes the context path. Useful for working
1060: * with JSTL cout expressions inside an <a> link.
1061: * <p>If the ControllerResponse has been set and running in a servlet environment,
1062: * then this function also encodes the resulting URL with suitable session id's
1063: * if necessary too</p>
1064: * This URL is optimized, so it includes a Controller.CONTROLLER_PARAM_KEY param only if
1065: * the destination controller is different than the controller of the ControllerResponse (if
1066: * the response is known).
1067: *
1068: * @return java.lang.String
1069: * @throws ControllerException upon error.
1070: * @see #getTheUrl
1071: */
1072: public String getFullUrl() throws ControllerException {
1073: HttpServletResponse servResponse = this .getServletResponse();
1074: if (servResponse != null) {
1075: return servResponse.encodeURL(ConfigManager
1076: .getContextPath()
1077: + getTheUrl(true));
1078: } else {
1079: return ConfigManager.getContextPath() + getTheUrl(true);
1080: }
1081: }
1082:
1083: /**
1084: * This function returns the mapping of the Struts action (including the
1085: * .do part) but without a context prepended to the mapping.
1086: *
1087: * @return java.lang.String
1088: */
1089: public String getMapping() throws ControllerException {
1090: ActionConfig actionConfig = ConfigManager.getActionConfig("",
1091: this .getControllerObject(), this .getState());
1092: if (actionConfig == null) {
1093: throw new ControllerException(
1094: "Unable to locate action mapping for: "
1095: + this .getControllerObject()
1096: + " and state " + this .getState());
1097: }
1098: return actionConfig.getPath() + ".do";
1099: }
1100:
1101: /**
1102: * Run this transition - e.g. transition to the new state of the
1103: * specified controller object immediately, setting the specified
1104: * response to the response of this new controller/state, discarding
1105: * any previous response
1106: *
1107: * @param req The ControllerRequest object handed to you by the framework
1108: * @param res the ControllerResponse object handed to you by the framework
1109: * @return the ControllerResponse Object from the called controller
1110: * @throws ControllerException upon error
1111: * @throws NonHandleableException upon fatal error
1112: */
1113: public ControllerResponse transition(ControllerRequest req,
1114: ControllerResponse res) throws ControllerException,
1115: NonHandleableException {
1116: return transition(req, res, true);
1117: }
1118:
1119: /**
1120: * Transition to a new controller and state by issuing a <code>Redirect</code>
1121: * request to the browser. This can only be used in a Servlet environment,
1122: * and is mainly useful when you want the URL for the browser to change after
1123: * a request, so for example, after making an online purchase, you display the
1124: * invoice, but if the user hit's refresh, you don't want the processing
1125: * to occur again.
1126: *
1127: * @param request The ControllerRequest object handed to you by the framework
1128: * @param response the ControllerResponse object handed to you by the framework
1129: */
1130: public void redirectTransition(ControllerRequest request,
1131: ControllerResponse response) throws ControllerException {
1132: try {
1133:
1134: if (isRecursiveTransition(response.getCurrentState()
1135: .getName(), response.getControllerClass())) {
1136: throw new ControllerException(
1137: "State cannot transition to itself.");
1138: }
1139:
1140: if (this .getControllerObject() == null) {
1141: throw new ControllerException(
1142: "Transition.redirectTransition(): "
1143: + " controller object parameter must be set before calling this function");
1144: }
1145:
1146: if (!(request instanceof ServletControllerRequest)) {
1147: log
1148: .error("Cannot redirect transition in a non-servlet environment. "
1149: + "Transitioning normally instead");
1150: try {
1151: this .transition(request, response);
1152: } catch (NonHandleableException ex) {
1153: log
1154: .error(
1155: "Non Handleable Exception during transition",
1156: ex);
1157: throw new ControllerException(ex);
1158: }
1159: return;
1160: }
1161:
1162: ServletControllerRequest scr = (ServletControllerRequest) request;
1163: HttpServletResponse hserv = (HttpServletResponse) scr
1164: .getServletResponse();
1165: this .setControllerResponse(response);
1166:
1167: // ActionMapping mapping = ConfigManager.getMapping(this.getControllerObject(),
1168: // this.getState());
1169: ActionConfig actionConfig = ConfigManager.getActionConfig(
1170: "", this .getControllerObject(), this .getState());
1171: response.setRequestPath(actionConfig.getPath());
1172:
1173: hserv.sendRedirect(((HttpServletRequest) scr
1174: .getServletRequest()).getContextPath()
1175: + this .getTheUrl(true));
1176: //WE have to set custom response to true, otherwise the framework
1177: //will attempt to send more html once the redirect is done, which
1178: //will cause exceptions
1179: response.setCustomResponse(true);
1180: } catch (IOException ex) {
1181: log
1182: .error(
1183: "IO Error sending HTTP Redirect. Connection was broken.",
1184: ex);
1185: }
1186: }
1187:
1188: /**
1189: * Run this transition - e.g. transition to the new state of the
1190: * specified controller object immediately, setting the specified
1191: * response to the response of this new controller/state, discarding
1192: * any previous response (if "clear" is specified)
1193: * <p/>
1194: * NB: all parameters in the original request are discarded, except
1195: * those that are explicit params added to this Transition.
1196: * Therefore, if you have a param X in the original state,
1197: * and you need it in the upcoming
1198: * state, use addParam() to preserve it, OR use the ControllerResponse.setFormCache()
1199: * to save them before the transition(),
1200: *
1201: * @param req The ControllerRequest object handed to you by the framework
1202: * @param res the ControllerResponse object handed to you by the framework
1203: * @param clear True clears the response object before adding the outputs
1204: * from the called controller?
1205: * @return the ControllerResponse Object from the called controller
1206: * @throws ControllerException upon error
1207: * @throws NonHandleableException upon fatal error
1208: * @see ControllerResponse#setFormCache
1209: * <p/>
1210: * and response.getFormCache(paramName) to retrieve them
1211: * @see ControllerResponse#getFormCache(String)
1212: */
1213: public ControllerResponse transition(ControllerRequest req,
1214: ControllerResponse res, boolean clear)
1215: throws ControllerException, NonHandleableException {
1216: if (isRecursiveTransition(res.getCurrentState().getName(), res
1217: .getControllerClass())) {
1218: throw new ControllerException(
1219: "State cannot transition to itself.");
1220: }
1221:
1222: boolean externalTransition = isExternalTransition(res
1223: .getControllerClass());
1224: //Clear out parameters - Avoids junk building up (can cause infinite loops)
1225: req.setParameters(null);
1226:
1227: //Pass the Transition object that will be executed by the called state when it needs to return.
1228: if (returnToSender) {
1229: enableReturnToSender(res);
1230: }
1231:
1232: String oneParamKey = null;
1233: String oneParamValue = null;
1234:
1235: for (Enumeration ep = params.keys(); ep.hasMoreElements();) {
1236: oneParamKey = (String) ep.nextElement();
1237: oneParamValue = (String) params.get(oneParamKey);
1238:
1239: if (oneParamValue != null) {
1240: req.setParameter(oneParamKey, oneParamValue);
1241: }
1242: }
1243:
1244: ControllerRequest newRequest = (ControllerRequest) req.clone();
1245: newRequest.setParameters(this .params);
1246: newRequest.setParameter(Controller.STATE_PARAM_KEY, StringUtil
1247: .notNull(this .getState()));
1248: newRequest.setParameter(Controller.CONTROLLER_PARAM_KEY, this
1249: .getControllerObject());
1250:
1251: ControllerResponse newResponse = null;
1252:
1253: if ((externalTransition)
1254: && (req instanceof ServletControllerRequest)) {
1255: newResponse = newStateDispatch(req);
1256: } else {
1257: Controller c = null;
1258:
1259: if (this .getControllerObject() == null) {
1260: c = ConfigManager.getControllerFactory().getController(
1261: newRequest);
1262: } else {
1263: c = ConfigManager.getControllerFactory().getController(
1264: this .getControllerObject());
1265: }
1266:
1267: newResponse = c.newState(getState(), newRequest);
1268: }
1269: if (clear) {
1270: res.clearOutputCache();
1271: res.clearInputCache();
1272: res.clearTransitionCache();
1273: res.clearBlockCache();
1274: res.clearAttributes();
1275: }
1276:
1277: res.setDBName(newResponse.getDBName());
1278: res.setCustomResponse(newResponse.isCustomResponse());
1279: res.setCurrentState(newResponse.getCurrentState());
1280: res.setStyle(newResponse.getStyle());
1281: res.setControllerClass(newResponse.getControllerClass());
1282: res.setSchemaStack(newResponse.getSchemaStack());
1283: res.setTitle(newResponse.getTitleKey());
1284:
1285: Block oneBlock = null;
1286: Vector newBlocks = newResponse.getBlocks();
1287:
1288: if (newBlocks != null) {
1289: for (Enumeration eb = newBlocks.elements(); eb
1290: .hasMoreElements();) {
1291: oneBlock = (Block) eb.nextElement();
1292: oneBlock.setControllerResponse(res);
1293: res.addBlock(oneBlock);
1294: }
1295: }
1296:
1297: Output oneOutput = null;
1298: Vector newOutputs = newResponse.getOutputs();
1299:
1300: if (newOutputs != null) {
1301: for (Enumeration eo = newOutputs.elements(); eo
1302: .hasMoreElements();) {
1303: oneOutput = (Output) eo.nextElement();
1304: oneOutput.setControllerResponse(res);
1305: res.addOutput(oneOutput);
1306: }
1307: }
1308:
1309: Input oneInput = null;
1310: Vector newInputs = newResponse.getInputs();
1311:
1312: if (newInputs != null) {
1313: for (Enumeration ei = newInputs.elements(); ei
1314: .hasMoreElements();) {
1315: oneInput = (Input) ei.nextElement();
1316: oneInput.setControllerResponse(res);
1317: res.addInput(oneInput);
1318: }
1319: }
1320:
1321: Transition oneTransition = null;
1322: Vector newTransitions = newResponse.getTransitions();
1323:
1324: if (newTransitions != null) {
1325: for (Enumeration et = newTransitions.elements(); et
1326: .hasMoreElements();) {
1327: oneTransition = (Transition) et.nextElement();
1328: oneTransition.setControllerResponse(res);
1329: res.addTransition(oneTransition);
1330: }
1331: }
1332: if (StringUtil.notNull(res.getControllerClass()).length() == 0) {
1333: res.setControllerClass(getControllerObject());
1334: }
1335:
1336: Map attributes = newResponse.getAttributes();
1337: if (attributes != null) {
1338: res.setAttributes(attributes);
1339: }
1340:
1341: return res;
1342: }
1343:
1344: /**
1345: * Gets an underlying ServletResponse if it has been set either through
1346: * setting the controller response or manually
1347: *
1348: * @return HttpServletResponse or NULL if not being used.
1349: */
1350: public HttpServletResponse getServletResponse() {
1351: return servletResponse;
1352: }
1353:
1354: /**
1355: * Low level, sets the servlet response. Normally you won't use this function
1356: * except under specialty situations where you are using a Transition more
1357: * as a URL generator
1358: *
1359: * @param servletResponse a servlet response object for encoding URLs
1360: */
1361: public void setServletResponse(HttpServletResponse servletResponse) {
1362: this .servletResponse = servletResponse;
1363: }
1364:
1365: /**
1366: * Override of the normal setControllerResponse so that the HttpServletResponse
1367: * is also set for this particular transition.
1368: *
1369: * @param newResponse the controllerResponse to set.
1370: */
1371: public synchronized void setControllerResponse(
1372: ControllerResponse newResponse) {
1373: super .setControllerResponse(newResponse);
1374: if (newResponse == null) {
1375: log.warn("Null 'newReponse'");
1376: return;
1377: }
1378: if (newResponse.getRequest() != null
1379: && (newResponse.getRequest() instanceof ServletControllerRequest)) {
1380:
1381: this
1382: .setServletResponse((HttpServletResponse) ((ServletControllerRequest) newResponse
1383: .getRequest()).getServletResponse());
1384: }
1385: }
1386: } /* Transition */
|