KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > jcorporate > expresso > core > controller > Transition


1 /* ====================================================================
2  * The Jcorporate Apache Style Software License, Version 1.2 05-07-2002
3  *
4  * Copyright (c) 1995-2002 Jcorporate Ltd. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  * notice, this list of conditions and the following disclaimer.
12  *
13  * 2. Redistributions in binary form must reproduce the above copyright
14  * notice, this list of conditions and the following disclaimer in
15  * the documentation and/or other materials provided with the
16  * distribution.
17  *
18  * 3. The end-user documentation included with the redistribution,
19  * if any, must include the following acknowledgment:
20  * "This product includes software developed by Jcorporate Ltd.
21  * (http://www.jcorporate.com/)."
22  * Alternately, this acknowledgment may appear in the software itself,
23  * if and wherever such third-party acknowledgments normally appear.
24  *
25  * 4. "Jcorporate" and product names such as "Expresso" must
26  * not be used to endorse or promote products derived from this
27  * software without prior written permission. For written permission,
28  * please contact info@jcorporate.com.
29  *
30  * 5. Products derived from this software may not be called "Expresso",
31  * or other Jcorporate product names; nor may "Expresso" or other
32  * Jcorporate product names appear in their name, without prior
33  * written permission of Jcorporate Ltd.
34  *
35  * 6. No product derived from this software may compete in the same
36  * market space, i.e. framework, without prior written permission
37  * of Jcorporate Ltd. For written permission, please contact
38  * partners@jcorporate.com.
39  *
40  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
41  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
42  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
43  * DISCLAIMED. IN NO EVENT SHALL JCORPORATE LTD OR ITS CONTRIBUTORS
44  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
45  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
46  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
47  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
48  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
49  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
50  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
51  * SUCH DAMAGE.
52  * ====================================================================
53  *
54  * This software consists of voluntary contributions made by many
55  * individuals on behalf of the Jcorporate Ltd. Contributions back
56  * to the project(s) are encouraged when you make modifications.
57  * Please send them to support@jcorporate.com. For more information
58  * on Jcorporate Ltd. and its products, please see
59  * <http://www.jcorporate.com/>.
60  *
61  * Portions of this software are based upon other open source
62  * products and are subject to their respective licenses.
63  */

64
65 package com.jcorporate.expresso.core.controller;
66
67 import com.jcorporate.expresso.core.ExpressoConstants;
68 import com.jcorporate.expresso.core.misc.ConfigManager;
69 import com.jcorporate.expresso.core.misc.StringDOMParser;
70 import com.jcorporate.expresso.core.misc.StringUtil;
71 import com.jcorporate.expresso.core.misc.URLUTF8Encoder;
72 import com.jcorporate.expresso.kernel.util.FastStringBuffer;
73 import org.apache.log4j.Logger;
74 import org.apache.struts.config.ActionConfig;
75 import org.w3c.dom.Document JavaDoc;
76 import org.w3c.dom.NamedNodeMap JavaDoc;
77 import org.w3c.dom.Node JavaDoc;
78 import org.w3c.dom.NodeList JavaDoc;
79
80 import javax.servlet.RequestDispatcher JavaDoc;
81 import javax.servlet.http.HttpServletRequest JavaDoc;
82 import javax.servlet.http.HttpServletResponse JavaDoc;
83 import java.io.IOException JavaDoc;
84 import java.util.Enumeration JavaDoc;
85 import java.util.Hashtable JavaDoc;
86 import java.util.Map JavaDoc;
87 import java.util.Vector JavaDoc;
88
89
90 /**
91  * <p>An Transition is a choice that the user can make that initiates
92  * either another sequence in this same controller or some new
93  * controller. A transition is one of the three types of objects that
94  * a controller produces when it enters a new state, the others
95  * being Input objects and Output objects.</p>
96  * <p>Another use of a Transition object is for internal transitioning between
97  * various controllers and their states. Typical example is as follows: <br/>
98  * <code>
99  * <pre>
100  * Transition t = new Transition();
101  * t.setControllerObject(com.myapp.MyController.class);
102  * t.setState("State2");
103  * t.addParameter("SampleParam","This is a parameter value");
104  * return t.transition();
105  * </pre>
106  * </code>
107  * </p>
108  * <p/>
109  * <h4>Recognized Attributes:</h4>
110  * <p>The following types are recognized by the expresso framework and
111  * automatically rendered: You may add your own types or ignore them
112  * if you are doing your own page rendering.</p>
113  * <p/>
114  * <p>header: Renders the transition in the jc-header class style</p>
115  * <p/>
116  * <p>button: Renders the transition as a button.
117  * <p/>
118  * <p><i>default behavior:</i> Renders as a clickable button</p>
119  */

120 public class Transition
121         extends ControllerElement
122         implements Cloneable JavaDoc,
123         java.io.Serializable JavaDoc {
124
125     private static Logger log = Logger.getLogger(Transition.class);
126
127     /**
128      * name of the controller object that created this transition
129      */

130     private String JavaDoc ownerObject = null;
131
132     /**
133      * The name of a controller object that handles this action
134      */

135     private String JavaDoc controllerObject = null;
136
137     /**
138      * The parameters to the controller object
139      */

140     private Hashtable JavaDoc params = new Hashtable JavaDoc(1);
141     private String JavaDoc myState = null;
142     private boolean returnToSender = false;
143
144     /**
145      * This is used to store a constructed parameter string to save on
146      * re-constructing multiple times.
147      */

148     private transient String JavaDoc cacheParamStringSansController = null;
149
150     /**
151      * This is used to store a constructed parameter string to save on
152      * re-constructing multiple times.
153      */

154     private transient String JavaDoc cacheParamStringWithController = null;
155
156
157     /**
158      * Used in place of ControllerResponse for URL encoding
159      */

160     private transient HttpServletResponse JavaDoc servletResponse = null;
161
162     /**
163      * Default Constructor. Normally you don't use this.
164      */

165     public Transition() {
166     }
167
168     /**
169      * Convenience method to transition to another state in this same controller.
170      *
171      * @param newState the new name of the state.
172      * @param myController An instantiated Controller object. Use a <code>ControllerFactory</code>
173      * to instantiate the controller if you must use this constructor.
174      */

175     public Transition(String JavaDoc newState, Controller myController) {
176
177         //
178
//Precalculate the class name because it can take significant CPU time.
179
//
180
String JavaDoc className = myController.getClass().getName();
181         myState = newState;
182         setControllerObject(className);
183         setOwnerController(className);
184         setName(newState);
185
186         //
187
//Performance: get the hashmap because that way we don't invoke a state
188
//copy for each object.
189
//
190
Hashtable JavaDoc allstates = myController.getStates();
191         State theState = null;
192         if (allstates != null) {
193             theState = (State) allstates.get(newState);
194         }
195
196         if (theState == null) {
197             throw new IllegalArgumentException JavaDoc("No such state as '" +
198                     newState + "' in controller '" +
199                     className +
200                     "'");
201         }
202
203         setLabel(theState.getDescription());
204         addParam(Controller.STATE_PARAM_KEY, newState);
205     } /* Transition(String, Controller) */
206
207     /**
208      * Convenience constructor to create an action with a label
209      * and a controller already set.
210      *
211      * @param newLabel Label for the new action
212      * @param newObject The name of the object this action referred to
213      */

214     public Transition(String JavaDoc newLabel, String JavaDoc newObject) {
215         super();
216         setName(StringUtil.replace(newLabel, " ", ""));
217         setLabel(newLabel);
218         setControllerObject(newObject);
219     } /* Transition(String, String) */
220
221     /**
222      * Convenience constructor to create an action with a label
223      * and a controller already set.
224      *
225      * @param newName Name of this Transition object
226      * @param newLabel Label for the new action
227      * @param newObject The name of the Controller object this action referred to
228      */

229     public Transition(String JavaDoc newName, String JavaDoc newLabel, String JavaDoc newObject) {
230         super();
231         setName(newName);
232         setLabel(newLabel);
233         setControllerObject(newObject);
234     } /* Transition(String, String, String) */
235
236     /**
237      * Convenience method to allow for one line of code to construct a transition.
238      *
239      * @param name The name of the transition
240      * @param label The label to use for the transition
241      * @param controllerClass The <code>Class</code> of the controller to use
242      * @param controllerState The name of the controller's state.
243      */

244     public Transition(String JavaDoc name,
245                       String JavaDoc label,
246                       Class JavaDoc controllerClass,
247                       String JavaDoc controllerState) {
248         super();
249         setName(name);
250         setLabel(label);
251         setControllerObject(controllerClass);
252         setState(controllerState);
253     }
254
255     /**
256      * Convenience method to allow for one line of code to construct a transition.
257      * The (internal) name of the transition will be the state name.
258      *
259      * @param label The label to use for the transition
260      * @param controllerClass The <code>Class</code> of the controller to use
261      * @param controllerState The name of the controller's state.
262      */

263     public Transition(String JavaDoc label,
264                       Class JavaDoc controllerClass,
265                       String JavaDoc controllerState) {
266         super();
267         setName(controllerState);
268         setLabel(label);
269         setControllerObject(controllerClass);
270         setState(controllerState);
271     }
272
273     /**
274      * Adds a parameter to a transition. These parameters are meant to
275      * be eventually consumed by the target controller via the request.getParameter()
276      * method.
277      *
278      * @param paramCode The code name of the parameter
279      * @param paramValue The value for the paramter
280      */

281     public synchronized void addParam(String JavaDoc paramCode, String JavaDoc paramValue) {
282         clearCache();
283         if (paramCode.equals(Controller.STATE_PARAM_KEY)) {
284             setState(StringUtil.notNull(paramValue));
285
286             return;
287         }
288         if (paramCode.equals(Controller.CONTROLLER_PARAM_KEY)) {
289             setControllerObject(StringUtil.notNull(paramValue));
290
291             return;
292         }
293
294         params.put(paramCode, StringUtil.notNull(paramValue));
295     } /* addParam(String, String) */
296
297     private synchronized void clearCache() {
298         cacheParamStringWithController = null;
299         cacheParamStringSansController = null;
300     }
301
302     /**
303      * Sets the target state to transition to.
304      *
305      * @param newState java.lang.String
306      */

307     public synchronized void setState(String JavaDoc newState) {
308         clearCache();
309         myState = newState;
310     }
311
312     /**
313      * Retrieve the currently set state
314      *
315      * @return java.lang.String
316      */

317     public String JavaDoc getState() {
318         if (myState == null) {
319             return getParam(Controller.STATE_PARAM_KEY);
320         }
321
322         return myState;
323     }
324
325     /**
326      * Returns a copy of itself
327      *
328      * @return a cloned and instantiated <code>Transition</code> object.
329      * @throws CloneNotSupportedException as required by the method
330      * signature.
331      */

332     public Object JavaDoc clone()
333             throws CloneNotSupportedException JavaDoc {
334         Transition t;
335
336         synchronized (this) {
337             t = (Transition) super.clone();
338             t.params = (Hashtable JavaDoc) params.clone();
339             t.controllerObject = controllerObject;
340             t.myState = myState;
341             t.returnToSender = returnToSender;
342         }
343
344         return t;
345     }
346
347     /**
348      * Call this method when the state/controller being transitioned to should return control back
349      * to the calling state once it has 'completed' successfully. Th 'completion' point depends on
350      * whether the transition is to an external (new controller) or internal state. For external
351      * transitions, the completion point is once the Final state has run successfully. For internal
352      * transitions, the completion point is once the called state has run successfully.
353      * <p/>
354      * When this method is called with a non-null response parameter, this indicates the currently
355      * running state should be the return address. If this method is called with a null response
356      * parameter then this indicates that the return address should be determined at transition
357      * execution time. This is useful/necessary when a transition is instantiated outside the scope
358      * of a state execution. For example, when the controllerSecurityTransition value is set in
359      * the controller constructor, the return state is not known/active.
360      *
361      * @param response the ControllerResponse object
362      * @throws ControllerException upon error
363      */

364     public void enableReturnToSender(ControllerResponse response)
365             throws ControllerException {
366         returnToSender = true;
367         String JavaDoc returnToSender = null;
368         if (response != null) {
369             FastStringBuffer fsb = FastStringBuffer.getInstance();
370             try {
371                 Transition t = response.getCurrentState().getReturnToSender();
372                 returnToSender = t.toXML(fsb).toString();
373             } finally {
374                 fsb.release();
375                 fsb = null;
376             }
377
378             if (isExternalTransition(response.getControllerClass())) {
379                 if (getParam(Controller.CTL_SUCC_TRAN) == null) { //don't overwrite if already set while in run()
380
addParam(Controller.CTL_SUCC_TRAN, returnToSender);
381                 }
382             } else {
383                 if (getParam(Controller.STATE_SUCC_TRAN) == null) { //don't overwrite if already set while in run()
384
addParam(Controller.STATE_SUCC_TRAN, returnToSender);
385                 }
386             }
387         }
388     }
389
390     /**
391      * Returns True if the destination for this transition is a different
392      * controller from the one currently active.
393      *
394      * @param runningController the name of the controller we're currently in.
395      * Usually you would
396      * use <code>this.getClass().getName()</code> within your own controller as
397      * a parameter.
398      * @return true if this is a transition to a another controller.
399      */

400     public boolean isExternalTransition(String JavaDoc runningController) {
401         if (controllerObject == null || controllerObject.equals(runningController)) {
402             return false;
403         } else {
404             return true;
405         }
406     }
407
408     /**
409      * Return the name of the controller object that this Transition
410      * referred to. If this is null, then it refers to the same controller
411      * object (e.g. intra-controller transition)
412      *
413      * @return The class name of the controller object this action
414      * refers to.
415      */

416     public String JavaDoc getControllerObject() {
417         if (controllerObject == null) {
418             return getParam(Controller.CONTROLLER_PARAM_KEY);
419         }
420
421         return controllerObject;
422     } /* getControllerObject() */
423
424     /**
425      * Sets the controller that created this transition. If controllerObject
426      * equals owner controller, then no controller= parameter is generated.
427      *
428      * @return the owner controller
429      */

430     public String JavaDoc getOwnerController() {
431         return this.ownerObject;
432     }
433
434     /**
435      * Return the value for a specific parameter for this transition object.
436      *
437      * @param paramCode The code (name) of the parameter
438      * @return The value of the parameter as a string
439      */

440     public String JavaDoc getParam(String JavaDoc paramCode) {
441         return (String JavaDoc) params.get(paramCode);
442     } /* getParam(String) */
443
444     /**
445      * Return the hashtable of parameters for this transition object.
446      * These parameters are to be handed to the new controller
447      * when the action is called.
448      *
449      * @return A hashtable of name/value pairs for the parameters
450      */

451     public Hashtable JavaDoc getParams() {
452         return params;
453     } /* getParams() */
454
455     /**
456      * @param includeControllerParameter whether to include controller param or not.
457      * @return parameter string which includes all params added to trans, as well
458      * as state param. controller param added optionally
459      */

460     public synchronized String JavaDoc getParamString(boolean includeControllerParameter) {
461         if (includeControllerParameter && cacheParamStringWithController == null) {
462             FastStringBuffer paramString = FastStringBuffer.getInstance();
463             try {
464                 if (controllerObject != null) {
465                     paramString.append("controller=");
466                     paramString.append(controllerObject);
467                 }
468
469                 addNonControllerParams(paramString);
470
471                 cacheParamStringWithController = paramString.toString();
472             } finally {
473                 paramString.release();
474                 paramString = null;
475             }
476
477         }
478
479         if (!includeControllerParameter && cacheParamStringSansController == null) {
480             FastStringBuffer paramString = FastStringBuffer.getInstance();
481             try {
482                 addNonControllerParams(paramString);
483
484                 cacheParamStringSansController = paramString.toString();
485             } finally {
486                 paramString.release();
487                 paramString = null;
488             }
489
490         }
491
492
493         if (includeControllerParameter) {
494             return cacheParamStringWithController;
495         } else {
496             return cacheParamStringSansController;
497         }
498
499     }
500
501     private void addNonControllerParams(FastStringBuffer paramString) {
502         if (this.params != null) {
503             if (!this.params.isEmpty()) {
504                 String JavaDoc oneKey = null;
505
506                 for (Enumeration JavaDoc e = this.params.keys(); e.hasMoreElements();) {
507                     if (paramString.length() != 0) {
508                         paramString.append("&");
509                     }
510                     oneKey = (String JavaDoc) e.nextElement();
511
512                     //Encode user's Transition parameters otherwise is ueer's parameters has '&' then
513
//it will mess up the addButtonParams() method when using Tokenizer.
514
FastStringBuffer fsb = FastStringBuffer.getInstance();
515                     try {
516                         fsb.append(oneKey);
517                         fsb.append("=");
518                         fsb.append(URLUTF8Encoder.encode((String JavaDoc) this.params.get(oneKey)));
519                         paramString.append(fsb.toString());
520                     } finally {
521                         fsb.release();
522                         fsb = null;
523                     }
524
525                 }
526             }
527         }
528
529         String JavaDoc stateString = StringUtil.notNull(getState());
530         if (stateString.length() != 0) {
531             if (paramString.length() != 0) {
532                 paramString.append("&");
533             }
534             paramString.append("state=");
535             paramString.append(stateString);
536         }
537     }
538
539     /**
540      * Return a string of the current params This is NOT URL encoded string.
541      * Use either getUrl() OR java.net.URLEncoder() to do this job.
542      *
543      * @return java.lang.String
544      */

545     public String JavaDoc getParamString() {
546
547         return getParamString(true);
548
549     } /* getParamString() */
550
551     /**
552      * This method invokes a new controller by dispatching to it rather than
553      * calling it directly. This is required when transitioning to external
554      * states so that Struts can setup the controller form.
555      * <p/>
556      * The currently active controller request is passed to the new state to
557      * simulate a direct call to the state. The request is then picked up
558      * by the controller's perform method. Any exceptions raised by the
559      * target state will filter back here to be passed on. This approach
560      * was needed in order to simulate a direct call the the state while at
561      * the same time allowing Struts to setup the controller form.
562      *
563      * @param request the <code>ControllerRequest</code> Object
564      * @return an instantiated <code>ControllerResponse</code> object
565      */

566     protected ControllerResponse newStateDispatch(ControllerRequest request)
567             throws ControllerException,
568             NonHandleableException {
569         ServletControllerRequest servletRequest = (ServletControllerRequest) request;
570         HttpServletRequest JavaDoc httpRequest = (HttpServletRequest JavaDoc) servletRequest.getServletRequest();
571         HttpServletResponse JavaDoc httpResponse = (HttpServletResponse JavaDoc) servletRequest.getServletResponse();
572         //Blank state required to avoid picking up an old state param while really we want
573
//to transition to the initial/default state (ie state is not specified)
574
// ActionMapping mm = ConfigManager.getMapping(controllerObject, "");
575
ActionConfig ac = ConfigManager.getActionConfig("", controllerObject, "");
576
577         if (ac == null) {
578             throw new ControllerException("Cannot transition to controller: " +
579                     controllerObject +
580                     " controller not defined in Struts configuration");
581         }
582
583         String JavaDoc apath = ac.getPath();
584         FastStringBuffer newURL = FastStringBuffer.getInstance();
585         RequestDispatcher JavaDoc dispatcher = null;
586         try {
587             newURL.append(apath);
588             newURL.append(".do");
589             newURL.append("?controller=" + getControllerObject());
590
591             if (getState() != null) {
592                 newURL.append("&state=" + getState());
593             }
594
595             httpRequest.setAttribute(ExpressoConstants.CONTROLLER_REQUEST_KEY, request); //Push our request onto the 'queue'
596

597             String JavaDoc urlValue = newURL.toString();
598             dispatcher = httpRequest.getRequestDispatcher(urlValue);
599
600             if (dispatcher == null) {
601                 throw new ControllerException("Request dispatcher was null - cannot include URL '" +
602                         urlValue + "'");
603             }
604         } finally {
605             newURL.release();
606             newURL = null;
607         }
608         try {
609             dispatcher.include(httpRequest, httpResponse);
610         } catch (Exception JavaDoc e) {
611             throw new ControllerException(e);
612         }
613
614         //Pop our request & response off the 'queue'
615
Exception JavaDoc newStateException = (Exception JavaDoc) httpRequest.getAttribute(ExpressoConstants.NEWSTATE_EXCEPTION_KEY);
616
617         if (newStateException != null) { //bubble up any exception
618
if (newStateException instanceof ControllerException) {
619                 throw (ControllerException) newStateException;
620             } else {
621                 if (newStateException instanceof NonHandleableException) {
622                     throw (NonHandleableException) newStateException;
623                 } else {
624                     throw (RuntimeException JavaDoc) newStateException;
625                 }
626             }
627         }
628
629         request = (ControllerRequest) httpRequest.getAttribute(ExpressoConstants.CONTROLLER_REQUEST_KEY);
630         httpRequest.removeAttribute(ExpressoConstants.CONTROLLER_REQUEST_KEY);
631
632         ControllerResponse response = (ControllerResponse) httpRequest.getAttribute(
633                 ExpressoConstants.CONTROLLER_RESPONSE_KEY);
634         httpRequest.removeAttribute(ExpressoConstants.CONTROLLER_RESPONSE_KEY);
635
636         return response;
637     }
638
639     /**
640      * Returns True if the destination for this transition is the same as the
641      * currently active state. Avoid infinite loop.
642      *
643      * @param runningState the current state
644      * @param runningController the current controller
645      * @return true if we're recusing to the same state and controller as we're
646      * already in.
647      */

648     public boolean isRecursiveTransition(String JavaDoc runningState,
649                                          String JavaDoc runningController) {
650         String JavaDoc targetController = StringUtil.notNull(controllerObject);
651         String JavaDoc targetState = StringUtil.notNull(myState);
652
653         if (targetController.equals(runningController) &&
654                 targetState.equals(runningState)) {
655             return true;
656         }
657
658         return false;
659     }
660
661     /**
662      * Return the return-to-sender flag.
663      *
664      * @return <code>boolean</code>
665      */

666     public boolean isReturnToSenderEnabled() {
667         return returnToSender;
668     }
669
670     /**
671      * Returns a hidden form field string that is safe in either the GET or POST
672      * case.
673      * <p/>
674      * Creation date: (1/10/01 11:24:00 AM)
675      * author: Adam Rossi, PlatinumSolutions
676      *
677      * @return java.lang.String
678      */

679     public String JavaDoc getHTMLParamString() {
680         String JavaDoc paramString = this.getParamString();
681         paramString = URLUTF8Encoder.encode(paramString);
682
683         //
684
//Guess at a memory allocation to reduce re-allocation/copy
685
//Alloc Adjust was guessed at based upon watching string lengths and
686
//guessing a reasonable maximum size that would fit most applciations.
687
//
688

689         FastStringBuffer sb = FastStringBuffer.getInstance();
690         String JavaDoc returnValue = null;
691         try {
692             sb.append("<input type=\"HIDDEN\" name=\"");
693             sb.append(this.getName());
694             sb.append("_params");
695             sb.append("\" value=\"");
696             sb.append(paramString);
697             sb.append("\">");
698             sb.append("<input type=\"HIDDEN\" name=\"");
699             sb.append(this.getName());
700             sb.append("_encoding");
701             sb.append("\" value=\"u\">");
702             returnValue = sb.toString();
703         } finally {
704             sb.release();
705             sb = null;
706         }
707         return returnValue;
708     } /* getHTMLParamStriong() */
709
710     /**
711      * Set the Controller that this action referrs to
712      *
713      * @param newObject Name of the Controller object that this Transition refers to
714      */

715     public synchronized void setControllerObject(String JavaDoc newObject) {
716         clearCache();
717         controllerObject = newObject;
718     } /* setControllerObject(String) */
719
720     /**
721      * Mor Typesafe way of setting the controller object. Any typos will be caught
722      * at compile time. Example usage:
723      * <code><pre>
724      * Transition t = new Transition();
725      * t.setControllerObject(com.jcorporate.expresso.services.Status.class);
726      * </pre></code>
727      *
728      * @param c the class of the controller object to add.
729      */

730     public synchronized void setControllerObject(Class JavaDoc c) {
731         clearCache();
732         if (c != null) {
733             setControllerObject(c.getName());
734         } else {
735             setControllerObject((String JavaDoc) null);
736         }
737     }
738
739     /**
740      * Sets the controller that created this transition. If controllerObject
741      * equals owner controller, then no controller= parameter is generated.
742      *
743      * @param newController the classname of the new controller
744      */

745     public synchronized void setOwnerController(String JavaDoc newController) {
746         clearCache();
747         ownerObject = newController;
748     }
749
750     /**
751      * Set this transition's parameters to the passed in collection.
752      *
753      * @param newParams the new parameters in bulk
754      */

755     public synchronized void setParams(Hashtable JavaDoc newParams) {
756         clearCache();
757         params = new Hashtable JavaDoc(newParams);
758     }
759
760     /**
761      * This method will take the request parameters that were passed to this state
762      * and will copy them into this transition's parameters. These parameters
763      * will then be used when this state is reinvoked (return-to-sender) in order
764      * to re-establish the parameter context.
765      *
766      * @param newReturnToSenderRequest The <code>ControllerRequest</code> object
767      */

768     public synchronized void setReturnToSenderParms(ControllerRequest newReturnToSenderRequest) {
769         clearCache();
770         String JavaDoc oneParamName = null;
771         Object JavaDoc oneParamValue = null;
772         Hashtable JavaDoc params = newReturnToSenderRequest.getParameters();
773         Hashtable JavaDoc newParams = new Hashtable JavaDoc();
774
775         //Copy all parameters except hidden parameters xxx_params and xxx_encoding.
776
//The xxx_params have already been unencoded and untokenized into separate parameters.
777
//Passing the xxx_params caused problems when the transition was encoded again and sent to HTML.
778
//When the double-encoded parameter comes back with the next user request it is never unencoded.
779
//This ends up causing problems with the fromXML/toXML methods in Transition.
780
for (Enumeration JavaDoc e = params.keys(); e.hasMoreElements();) {
781             oneParamName = (String JavaDoc) e.nextElement();
782
783             if (!oneParamName.endsWith("_params") &&
784                     !oneParamName.endsWith("_encoding") &&
785                     !oneParamName.equals(Controller.STATE_PARAM_KEY) &&
786                     !oneParamName.equals(Controller.CONTROLLER_PARAM_KEY)) {
787                 oneParamValue = params.get(oneParamName);
788                 newParams.put(oneParamName, oneParamValue);
789             }
790         }
791
792         setParams(newParams);
793     }
794
795     /**
796      * Convert the object to an xml fragment.
797      *
798      * @param stream an instantiated FastStringBuffer to which we append to.
799      * @return a FastStringBuffer object
800      */

801     public FastStringBuffer toXML(FastStringBuffer stream) {
802         stream.append("<transition");
803
804         if (this.getName() != null && this.getName().length() > 0) {
805             stream.append(" name=\"");
806             stream.append(StringUtil.xmlEscape(getName()));
807             stream.append("\"");
808         }
809
810         String JavaDoc controllerName = this.getControllerObject();
811
812         if (controllerName != null && controllerName.length() > 0) {
813             stream.append(" controller=\"");
814             stream.append(StringUtil.xmlEscape(controllerName));
815             stream.append("\"");
816         }
817
818         String JavaDoc stateName = this.getState();
819
820         if (stateName != null && stateName.length() > 0) {
821             stream.append(" state=\"");
822             stream.append(StringUtil.xmlEscape(stateName));
823             stream.append("\"");
824         }
825
826         stream.append(">\n");
827
828         Hashtable JavaDoc params = this.getParams();
829         String JavaDoc oneKey = null;
830
831         if (!params.isEmpty()) {
832             stream.append("\t<transition-parameters>\n");
833
834             for (Enumeration JavaDoc ap = params.keys(); ap.hasMoreElements();) {
835                 oneKey = (String JavaDoc) ap.nextElement();
836                 stream.append("\t\t<transition-param name=\"");
837                 stream.append(StringUtil.xmlEscape(oneKey));
838                 stream.append("\" value=\"");
839                 stream.append(StringUtil.xmlEscape((String JavaDoc) params.get(oneKey)));
840                 stream.append("\"/>\n");
841             }
842
843             stream.append("\t</transition-parameters>\n");
844         }
845
846         stream = super.toXML(stream);
847         stream.append("</transition>\n");
848
849         return stream;
850     }
851
852     /**
853      * Return a Transition based upon the String based xml fragment
854      *
855      * @param newTransition an xml fragment
856      * @return an instantiated Transition object
857      * @throws ControllerException upon error
858      */

859     public static Transition fromXML(String JavaDoc newTransition)
860             throws ControllerException {
861         StringDOMParser parser = new StringDOMParser();
862         Document JavaDoc d = parser.parseString(newTransition);
863
864         if (d == null) {
865             throw new ControllerException("Transition returned mal-formed XML document");
866         } else {
867             parser.dumpDOM(d);
868         }
869
870         return (Transition) fromXML(d);
871     }
872
873     /**
874      * Return a controller element based upon the xml fragment
875      *
876      * @param n a DOM Node object
877      * @return an instantiated ControllerElement
878      * @throws ControllerException upon error
879      */

880     public static ControllerElement fromXML(Node JavaDoc n)
881             throws ControllerException {
882
883         //If we're at the root node, then it'll be doc instead of input.
884
if (n.getNodeName().equals("#document")) {
885             return fromXML(n.getChildNodes().item(0));
886         }
887         if (!n.getNodeName().equals("transition")) {
888             return null;
889         }
890
891         Transition t = new Transition();
892
893         //Get node attributes
894
NamedNodeMap JavaDoc transitionAttributes = n.getAttributes();
895         Node JavaDoc attributeNode = transitionAttributes.getNamedItem("name");
896
897         if (attributeNode != null) {
898             String JavaDoc value = attributeNode.getNodeValue();
899
900             if (value != null) {
901                 t.setName(value);
902             }
903         }
904
905         attributeNode = transitionAttributes.getNamedItem(Controller.CONTROLLER_PARAM_KEY);
906
907         if (attributeNode != null) {
908             String JavaDoc value = attributeNode.getNodeValue();
909
910             if (value != null) {
911                 t.setControllerObject(value);
912             }
913         }
914
915         NodeList JavaDoc nl = n.getChildNodes();
916
917         for (int i = 0; i < nl.getLength(); i++) {
918             Node JavaDoc oneChild = nl.item(i);
919             String JavaDoc nodeName = oneChild.getNodeName();
920
921             if (nodeName.equals("transition-parameters")) {
922                 NodeList JavaDoc parameters = oneChild.getChildNodes();
923
924                 for (int j = 0; j < parameters.getLength(); j++) {
925                     if (parameters.item(j).getNodeName().equals("transition-param")) {
926                         NamedNodeMap JavaDoc paramAttributes = parameters.item(j).getAttributes();
927                         Node JavaDoc paramAttribute = paramAttributes.getNamedItem("name");
928                         String JavaDoc name = null;
929                         String JavaDoc value = null;
930
931                         if (paramAttribute != null) {
932                             name = paramAttribute.getNodeValue();
933                         }
934
935                         paramAttribute = paramAttributes.getNamedItem("value");
936
937                         if (paramAttribute != null) {
938                             value = paramAttribute.getNodeValue();
939                         }
940                         if (name != null && value != null) {
941                             t.addParam(name, value);
942                         }
943                     }
944                 }
945             } else if (nodeName.equals("controller-element")) {
946                 t = (Transition) ControllerElement.fromXML(oneChild, t);
947             }
948         }
949
950         return t;
951     }
952
953     /**
954      * Internal use for retrieving the URL that this transition points to.
955      *
956      * @param resolveControllerReference should the controller be resolved to
957      * a mapping, or should it just be the request path with a controller equals
958      * parameter. True if you want the URL mapped to a .do mapping.
959      * @return java.lang.String
960      * @throws ControllerException if there is an error or there is no controller
961      * response object available to help resoolve the reference.
962      */

963     public String JavaDoc getTheUrl(boolean resolveControllerReference) throws ControllerException {
964         String JavaDoc returnValue = null;
965         FastStringBuffer fsb = FastStringBuffer.getInstance();
966         try {
967             String JavaDoc paramString = "";
968
969             if (resolveControllerReference
970                     && this.getControllerObject() != null
971                     && this.getControllerObject().length() > 0) {
972
973                 // try mapping first
974
try {
975                     fsb.append(getMapping());
976                 } catch (Exception JavaDoc e) {
977                     // try response
978
ControllerResponse myResponse = getControllerResponse();
979
980                     if (myResponse == null) {
981                         throw new ControllerException(
982                                 "No controller object, nor controller response object available - cannot build URL");
983                     }
984
985                     fsb.clear();
986                     fsb.append(myResponse.getRequestPath());
987                 }
988
989                 paramString = getParamString(false);
990
991             } else {
992
993                 // try response first
994
ControllerResponse myResponse = getControllerResponse();
995
996                 if (myResponse == null) {
997                     try {
998                         fsb.append(getMapping());
999                     } catch (Exception JavaDoc e) {
1000                        throw new ControllerException(
1001                                "No controller param nor ControllerResponse available - cannot build URL");
1002                    }
1003                } else {
1004                    fsb.append(myResponse.getRequestPath());
1005                }
1006
1007                paramString = getParamString(true);
1008            }
1009
1010            if (paramString != null && paramString.length() > 0) {
1011                fsb.append("?");
1012                fsb.append(paramString);
1013            }
1014
1015            returnValue = fsb.toString();
1016            if (log.isDebugEnabled()) {
1017                log.debug("transition redirects to: " + returnValue);
1018            }
1019        } finally {
1020            fsb.release();
1021            fsb = null;
1022        }
1023        return returnValue;
1024    }
1025
1026    /**
1027     * Returns a URL reference for this transition. The URL does NOT include
1028     * the context path.
1029     *
1030     * @return java.lang.String
1031     * @throws ControllerException upon error
1032     */

1033    public String JavaDoc getUrl()
1034            throws ControllerException {
1035        return getTheUrl(true);
1036    }
1037
1038    /**
1039     * Similar to getURL but also includes the context path. Useful for working
1040     * with JSTL cout expressions inside an &lt;a&gt; link.
1041     * <p>If the ControllerResponse has been set and running in a servlet environment,
1042     * then this function also encodes the resulting URL with suitable session id's
1043     * if necessary too</p>
1044     * This URL is optimized, so it includes a Controller.CONTROLLER_PARAM_KEY param only if
1045     * the destination controller is different than the controller of the ControllerResponse (if
1046     * the response is known).
1047     *
1048     * @return java.lang.String
1049     * @throws ControllerException upon error.
1050     * @see #getTheUrl
1051     */

1052    public String JavaDoc getFullUrl() throws ControllerException {
1053        HttpServletResponse JavaDoc servResponse = this.getServletResponse();
1054        if (servResponse != null) {
1055            return servResponse.encodeURL(ConfigManager.getContextPath() + getTheUrl(true));
1056        } else {
1057            return ConfigManager.getContextPath() + getTheUrl(true);
1058        }
1059    }
1060
1061    /**
1062     * This function returns the mapping of the Struts action (including the
1063     * .do part) but without a context prepended to the mapping.
1064     *
1065     * @return java.lang.String
1066     */

1067    public String JavaDoc getMapping() throws ControllerException {
1068        ActionConfig actionConfig = ConfigManager.getActionConfig("",
1069                this.getControllerObject(), this.getState());
1070        if (actionConfig == null) {
1071            throw new ControllerException("Unable to locate action mapping for: "
1072                    + this.getControllerObject()
1073                    + " and state " + this.getState());
1074        }
1075        return actionConfig.getPath() + ".do";
1076    }
1077
1078
1079    /**
1080     * Run this transition - e.g. transition to the new state of the
1081     * specified controller object immediately, setting the specified
1082     * response to the response of this new controller/state, discarding
1083     * any previous response
1084     *
1085     * @param req The ControllerRequest object handed to you by the framework
1086     * @param res the ControllerResponse object handed to you by the framework
1087     * @return the ControllerResponse Object from the called controller
1088     * @throws ControllerException upon error
1089     * @throws NonHandleableException upon fatal error
1090     */

1091    public ControllerResponse transition(ControllerRequest req,
1092                                         ControllerResponse res)
1093            throws ControllerException,
1094            NonHandleableException {
1095        return transition(req, res, true);
1096    }
1097
1098    /**
1099     * Transition to a new controller and state by issuing a <code>Redirect</code>
1100     * request to the browser. This can only be used in a Servlet environment,
1101     * and is mainly useful when you want the URL for the browser to change after
1102     * a request, so for example, after making an online purchase, you display the
1103     * invoice, but if the user hit's refresh, you don't want the processing
1104     * to occur again.
1105     *
1106     * @param request The ControllerRequest object handed to you by the framework
1107     * @param response the ControllerResponse object handed to you by the framework
1108     */

1109    public void redirectTransition(ControllerRequest request,
1110                                   ControllerResponse response) throws ControllerException {
1111        try {
1112
1113            if (isRecursiveTransition(response.getCurrentState().getName(),
1114                    response.getControllerClass())) {
1115                throw new ControllerException("State cannot transition to itself.");
1116            }
1117
1118            if (this.getControllerObject() == null) {
1119                throw new ControllerException("Transition.redirectTransition(): "
1120                        + " controller object parameter must be set before calling this function");
1121            }
1122
1123            if (!(request instanceof ServletControllerRequest)) {
1124                log.error("Cannot redirect transition in a non-servlet environment. " +
1125                        "Transitioning normally instead");
1126                try {
1127                    this.transition(request, response);
1128                } catch (NonHandleableException ex) {
1129                    log.error("Non Handleable Exception during transition", ex);
1130                    throw new ControllerException(ex);
1131                }
1132                return;
1133            }
1134
1135            ServletControllerRequest scr = (ServletControllerRequest) request;
1136            HttpServletResponse JavaDoc hserv = (HttpServletResponse JavaDoc) scr.getServletResponse();
1137            this.setControllerResponse(response);
1138
1139
1140// ActionMapping mapping = ConfigManager.getMapping(this.getControllerObject(),
1141
// this.getState());
1142
ActionConfig actionConfig = ConfigManager.getActionConfig("",
1143                    this.getControllerObject(), this.getState());
1144            response.setRequestPath(actionConfig.getPath());
1145
1146            hserv.sendRedirect(((HttpServletRequest JavaDoc) scr.getServletRequest()).getContextPath() + this.getTheUrl(true));
1147            //WE have to set custom response to true, otherwise the framework
1148
//will attempt to send more html once the redirect is done, which
1149
//will cause exceptions
1150
response.setCustomResponse(true);
1151        } catch (IOException JavaDoc ex) {
1152            log.error("IO Error sending HTTP Redirect. Connection was broken.", ex);
1153        }
1154    }
1155
1156
1157    /**
1158     * Run this transition - e.g. transition to the new state of the
1159     * specified controller object immediately, setting the specified
1160     * response to the response of this new controller/state, discarding
1161     * any previous response (if "clear" is specified)
1162     * <p/>
1163     * NB: all parameters in the original request are discarded, except
1164     * those that are explicit params added to this Transition.
1165     * Therefore, if you have a param X in the original state,
1166     * and you need it in the upcoming
1167     * state, use addParam() to preserve it, OR use the ControllerResponse.setFormCache()
1168     * to save them before the transition(),
1169     *
1170     * @param req The ControllerRequest object handed to you by the framework
1171     * @param res the ControllerResponse object handed to you by the framework
1172     * @param clear True clears the response object before adding the outputs
1173     * from the called controller?
1174     * @return the ControllerResponse Object from the called controller
1175     * @throws ControllerException upon error
1176     * @throws NonHandleableException upon fatal error
1177     * @see ControllerResponse#setFormCache
1178     * <p/>
1179     * and response.getFormCache(paramName) to retrieve them
1180     * @see ControllerResponse#getFormCache(String)
1181     */

1182    public ControllerResponse transition(ControllerRequest req,
1183                                         ControllerResponse res, boolean clear)
1184            throws ControllerException,
1185            NonHandleableException {
1186        if (isRecursiveTransition(res.getCurrentState().getName(),
1187                res.getControllerClass())) {
1188            throw new ControllerException("State cannot transition to itself.");
1189        }
1190
1191        boolean externalTransition = isExternalTransition(res.getControllerClass());
1192        //Clear out parameters - Avoids junk building up (can cause infinite loops)
1193
req.setParameters(null);
1194
1195        //Pass the Transition object that will be executed by the called state when it needs to return.
1196
if (returnToSender) {
1197            enableReturnToSender(res);
1198        }
1199
1200        String JavaDoc oneParamKey = null;
1201        String JavaDoc oneParamValue = null;
1202
1203        for (Enumeration JavaDoc ep = params.keys(); ep.hasMoreElements();) {
1204            oneParamKey = (String JavaDoc) ep.nextElement();
1205            oneParamValue = (String JavaDoc) params.get(oneParamKey);
1206
1207            if (oneParamValue != null) {
1208                req.setParameter(oneParamKey, oneParamValue);
1209            }
1210        }
1211
1212        ControllerRequest newRequest = (ControllerRequest) req.clone();
1213        newRequest.setParameters(this.params);
1214        newRequest.setParameter(Controller.STATE_PARAM_KEY, StringUtil.notNull(this.getState()));
1215        newRequest.setParameter(Controller.CONTROLLER_PARAM_KEY, this.getControllerObject());
1216
1217        ControllerResponse newResponse = null;
1218
1219        if ((externalTransition) &&
1220                (req instanceof ServletControllerRequest)) {
1221            newResponse = newStateDispatch(req);
1222        } else {
1223            Controller c = null;
1224
1225            if (this.getControllerObject() == null) {
1226                c = ConfigManager.getControllerFactory().getController(newRequest);
1227            } else {
1228                c = ConfigManager.getControllerFactory().getController(this.getControllerObject());
1229            }
1230
1231            newResponse = c.newState(getState(), newRequest);
1232        }
1233        if (clear) {
1234            res.clearOutputCache();
1235            res.clearInputCache();
1236            res.clearTransitionCache();
1237            res.clearBlockCache();
1238            res.clearAttributes();
1239        }
1240
1241        res.setDBName(newResponse.getDBName());
1242        res.setCustomResponse(newResponse.isCustomResponse());
1243        res.setCurrentState(newResponse.getCurrentState());
1244        res.setStyle(newResponse.getStyle());
1245        res.setControllerClass(newResponse.getControllerClass());
1246        res.setSchemaStack(newResponse.getSchemaStack());
1247        res.setTitle(newResponse.getTitleKey());
1248
1249        Block oneBlock = null;
1250        Vector JavaDoc newBlocks = newResponse.getBlocks();
1251
1252        if (newBlocks != null) {
1253            for (Enumeration JavaDoc eb = newBlocks.elements(); eb.hasMoreElements();) {
1254                oneBlock = (Block) eb.nextElement();
1255                oneBlock.setControllerResponse(res);
1256                res.addBlock(oneBlock);
1257            }
1258        }
1259
1260        Output oneOutput = null;
1261        Vector JavaDoc newOutputs = newResponse.getOutputs();
1262
1263        if (newOutputs != null) {
1264            for (Enumeration JavaDoc eo = newOutputs.elements(); eo.hasMoreElements();) {
1265                oneOutput = (Output) eo.nextElement();
1266                oneOutput.setControllerResponse(res);
1267                res.addOutput(oneOutput);
1268            }
1269        }
1270
1271        Input oneInput = null;
1272        Vector JavaDoc newInputs = newResponse.getInputs();
1273
1274        if (newInputs != null) {
1275            for (Enumeration JavaDoc ei = newInputs.elements(); ei.hasMoreElements();) {
1276                oneInput = (Input) ei.nextElement();
1277                oneInput.setControllerResponse(res);
1278                res.addInput(oneInput);
1279            }
1280        }
1281
1282        Transition oneTransition = null;
1283        Vector JavaDoc newTransitions = newResponse.getTransitions();
1284
1285        if (newTransitions != null) {
1286            for (Enumeration JavaDoc et = newTransitions.elements();
1287                 et.hasMoreElements();) {
1288                oneTransition = (Transition) et.nextElement();
1289                oneTransition.setControllerResponse(res);
1290                res.addTransition(oneTransition);
1291            }
1292        }
1293        if (StringUtil.notNull(res.getControllerClass()).length() == 0) {
1294            res.setControllerClass(getControllerObject());
1295        }
1296
1297        Map JavaDoc attributes = newResponse.getAttributes();
1298        if (attributes != null) {
1299            res.setAttributes(attributes);
1300        }
1301
1302        return res;
1303    }
1304
1305    /**
1306     * Gets an underlying ServletResponse if it has been set either through
1307     * setting the controller response or manually
1308     *
1309     * @return HttpServletResponse or NULL if not being used.
1310     */

1311    public HttpServletResponse JavaDoc getServletResponse() {
1312        return servletResponse;
1313    }
1314
1315    /**
1316     * Low level, sets the servlet response. Normally you won't use this function
1317     * except under specialty situations where you are using a Transition more
1318     * as a URL generator
1319     *
1320     * @param servletResponse a servlet response object for encoding URLs
1321     */

1322    public void setServletResponse(HttpServletResponse JavaDoc servletResponse) {
1323        this.servletResponse = servletResponse;
1324    }
1325
1326    /**
1327     * Override of the normal setControllerResponse so that the HttpServletResponse
1328     * is also set for this particular transition.
1329     *
1330     * @param newResponse the controllerResponse to set.
1331     */

1332    public synchronized void setControllerResponse(ControllerResponse newResponse) {
1333        super.setControllerResponse(newResponse);
1334        if (newResponse == null) {
1335            log.warn("Null 'newReponse'");
1336            return;
1337        }
1338        if (newResponse.getRequest() != null && (newResponse.getRequest() instanceof ServletControllerRequest)) {
1339
1340            this.setServletResponse(
1341                    (HttpServletResponse JavaDoc) ((ServletControllerRequest) newResponse.getRequest()).getServletResponse());
1342        }
1343    }
1344} /* Transition */
1345
Popular Tags