KickJava   Java API By Example, From Geeks To Geeks.

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


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
68 import com.jcorporate.expresso.core.ExpressoConstants;
69 import com.jcorporate.expresso.core.ExpressoSchema;
70 import com.jcorporate.expresso.core.controller.session.PersistentSession;
71 import com.jcorporate.expresso.core.db.DBException;
72 import com.jcorporate.expresso.core.dbobj.Schema;
73 import com.jcorporate.expresso.core.dbobj.SchemaFactory;
74 import com.jcorporate.expresso.core.i18n.Messages;
75 import com.jcorporate.expresso.core.jsdkapi.GenericDispatcher;
76 import com.jcorporate.expresso.core.misc.ConfigManager;
77 import com.jcorporate.expresso.core.misc.ConfigurationException;
78 import com.jcorporate.expresso.core.misc.DateTime;
79 import com.jcorporate.expresso.core.misc.EventHandler;
80 import com.jcorporate.expresso.core.misc.SerializableString;
81 import com.jcorporate.expresso.core.misc.StringUtil;
82 import com.jcorporate.expresso.core.security.DelayThread;
83 import com.jcorporate.expresso.core.security.User;
84 import com.jcorporate.expresso.core.servlet.CheckLogin;
85 import com.jcorporate.expresso.kernel.util.ClassLocator;
86 import com.jcorporate.expresso.kernel.util.FastStringBuffer;
87 import com.jcorporate.expresso.services.dbobj.ControllerSecurity;
88 import com.jcorporate.expresso.services.dbobj.RegistrationDomain;
89 import com.jcorporate.expresso.services.dbobj.Setup;
90 import org.apache.commons.beanutils.BeanUtils;
91 import org.apache.log4j.Logger;
92 import org.apache.struts.Globals;
93 import org.apache.struts.action.Action;
94 import org.apache.struts.action.ActionForm;
95 import org.apache.struts.action.ActionForward;
96 import org.apache.struts.action.ActionMapping;
97 import org.apache.struts.config.ActionConfig;
98 import org.apache.struts.config.ForwardConfig;
99 import org.apache.struts.upload.MultipartRequestHandler;
100
101 import javax.servlet.ServletException JavaDoc;
102 import javax.servlet.http.HttpServletRequest JavaDoc;
103 import javax.servlet.http.HttpServletResponse JavaDoc;
104 import java.io.ByteArrayOutputStream JavaDoc;
105 import java.io.IOException JavaDoc;
106 import java.io.PrintStream JavaDoc;
107 import java.io.Serializable JavaDoc;
108 import java.lang.reflect.InvocationTargetException JavaDoc;
109 import java.lang.reflect.Method JavaDoc;
110 import java.util.ArrayList JavaDoc;
111 import java.util.Enumeration JavaDoc;
112 import java.util.Hashtable JavaDoc;
113 import java.util.Iterator JavaDoc;
114 import java.util.Locale JavaDoc;
115 import java.util.Stack JavaDoc;
116 import java.util.Vector JavaDoc;
117
118
119 /**
120  * A sequence of interaction with a user is defined as a &quot;Controller&quot;.
121  * <p/>
122  * An Expresso controller is best thought of as a finite state machine. The only
123  * real difference is that because of the stateless nature of the web, anybody
124  * can reach any state with using the &quot;state&quot; parameter in the URL.
125  * </p>
126  * <p/>
127  * Usually for a web deployment environment, you would derive your own controller
128  * from DBController, which adds a database-backed Security matrix to the
129  * controller's base capabilities. Controllers (and DBControllers) do not necessarily
130  * need to be run inside a servlet environment. If they never downcast their ControllerResponse
131  * object to ServletControllerResponse, the Controller can also be used in products
132  * such as Expresso Webservices, or
133  * </p>
134  * <p>See the <em>Expresso Developer's Guide</em> for more information on creating
135  * and using a Controller.</p>
136  * <p/>
137  * A Controller takes a series of parameters (optional) and then provides
138  * a series of Blocks, Inputs, Outputs, and Transitions to the client</p>
139  * <itemizedlist>
140  * <listitem>An Input specifies a piece of information we want FROM the client</listitem>
141  * <listitem>An Output is a piece of information we supply TO the client</listitem>
142  * <listitem>A Transition is an action the client can take from this point - e.g.
143  * further controllers that are followups to this controller</listitem>
144  * <listitem>A Block is a collection of Inputs, Outputs, Transitions or other Blocks</listitem>
145  * </itemizedlist>
146  *
147  * @see com.jcorporate.expresso.core.controller.DBController
148  * @see com.jcorporate.expresso.core.controller.SecureIfSetController
149  */

150 public abstract class Controller
151         extends Action
152         implements Serializable JavaDoc {
153     /**
154      * log for methods in this class alone
155      */

156     private static Logger log = Logger.getLogger(Controller.class);
157
158     /**
159      * if subclass calls getLogger(), this will create logger with
160      * subclass name
161      */

162     protected Logger mLog = null;
163
164     /**
165      * Controller response key
166      */

167     public final static String JavaDoc RESPONSE_KEY = ExpressoConstants.CONTROLLER_RESPONSE_KEY;
168     /**
169      * Controller original URL key
170      */

171     public final static String JavaDoc ORIGINAL_URL_KEY = ExpressoConstants.CONTROLLER_ORIGINAL_URL_KEY;
172     /**
173      * Controller request key
174      */

175     public final static String JavaDoc REQUEST_KEY = ExpressoConstants.CONTROLLER_REQUEST_KEY;
176
177     /**
178      * New state exception key
179      */

180     public final static String JavaDoc NEWSTATE_EXCEPTION_KEY = ExpressoConstants.NEWSTATE_EXCEPTION_KEY;
181
182
183     // *PP* I have not deprecated these at all Tue Jun 24 20:09:00 BST 2003
184
public final static String JavaDoc CTL_SUCC_TRAN = "controllerSuccessReturn";
185     public final static String JavaDoc CTL_SUCC_CTL = "controllerSuccessReturnController";
186     public final static String JavaDoc CTL_SUCC_STATE = "controllerSuccessReturnState";
187     public final static String JavaDoc STATE_SUCC_TRAN = "stateSuccessReturn";
188     public final static String JavaDoc STATE_SUCC_CTL = "stateSuccessReturnController";
189     public final static String JavaDoc STATE_SUCC_STATE = "stateSuccessReturnState";
190     public final static String JavaDoc STATE_ERR_TRAN = "stateErrorReturn";
191     public final static String JavaDoc STATE_ERR_CTL = "stateErrorReturnController";
192     public final static String JavaDoc STATE_ERR_STATE = "stateErrorReturnState";
193     public final static String JavaDoc RETURN_TO_SENDER_TRAN = "returnToSender";
194
195
196     /**
197      * key for putting state into parameter map
198      */

199     public static final String JavaDoc STATE_PARAM_KEY = "state";
200
201     /**
202      * key for putting controller into parameter map
203      */

204     public static final String JavaDoc CONTROLLER_PARAM_KEY = "controller";
205
206     /* The name of the state which is the "initial" state of this controller */
207     /* E.g. where the controller starts if no state parameter is specified */
208     private static Hashtable JavaDoc initialStates = new Hashtable JavaDoc();
209
210     /**
211      * Static map of classes to schema instances
212      */

213     private static Hashtable JavaDoc schemas = new Hashtable JavaDoc();
214
215
216     /**
217      * States is a hashtable of hashtables, e.g. the states hashtable itself
218      * is indexed by controller class name. Each entry against a classname is itself a hashtable
219      * with a list of the valid state objects for the given controller
220      */

221     private static Hashtable JavaDoc states = new Hashtable JavaDoc();
222
223
224     /**
225      * used to specify the classes of parameters for introspection;
226      * since these classes are constant across all controllers, we use a static
227      */

228     private static final Class JavaDoc[] newStateParams = {
229         java.lang.String JavaDoc.class,
230         com.jcorporate.expresso.core.controller.ControllerRequest.class
231     };
232
233
234     /**
235      * used for reflection into the state handlers
236      */

237     private static final Class JavaDoc[] stateHandlerParams = {
238         com.jcorporate.expresso.core.controller.ControllerRequest.class,
239         com.jcorporate.expresso.core.controller.ControllerResponse.class
240     };
241
242     /**
243      * used for reflection into the state handlers
244      */

245     private static final Class JavaDoc[] servletStateHandlerParams = {
246         com.jcorporate.expresso.core.controller.ServletControllerRequest.class,
247         com.jcorporate.expresso.core.controller.ControllerResponse.class
248     };
249
250
251     /**
252      * promptStates is only updated in the constructor. The instantiation of Controller by Struts is
253      * synchronized so we don't need to worry about threadsafety while updating this collection even
254      * though it is shared by many threads(singleton). However, Controller is also instantiated by Transition and
255      * Controller itself and unlike Struts these are not synchronized. Luckily, unlike Struts, these
256      * instances are not shared between threads. So we should be OK with no sychronized access and
257      * also with using a non-synchronized collection (ArrayList).
258      * The main reason this collection was not static like most of the other fields in this class
259      * is because of the way this collection is populated. Unlike the 'states' collection, this one
260      * appends new items to the collection rather than overwriting (hashing). So if this had been
261      * static, every time Transition/Controller instantiated a new instance of this class it would
262      * try to append the same states to the collection making the collection inaccurate (and big).
263      */

264     private ArrayList JavaDoc promptStates = new ArrayList JavaDoc(3);
265
266
267     /**
268      * handleStates is only updated in the constructor. The instantiation of Controller by Struts is
269      * synchronized so we don't need to worry about threadsafety while updating this collection even
270      * though it is shared by many threads(singleton). However, Controller is also instantiated by Transition and
271      * Controller itself and unlike Struts these are not synchronized. Luckily, unlike Struts, these
272      * instances are not shared between threads. So we should be OK with no sychronized access and
273      * also with using a non-synchronized collection (ArrayList).
274      * The main reason this collection was not static like most of the other fields in this class
275      * is because of the way this collection is populated. Unlike the 'states' collection, this one
276      * appends new items to the collection rather than overwriting (hashing). So if this had been
277      * static, every time Transition/Controller instantiated a new instance of this class it would
278      * try to append the same states to the collection making the collection inaccurate (and big).
279      */

280     private ArrayList JavaDoc handleStates = new ArrayList JavaDoc(3);
281
282     private State finalState;
283
284     private Transition controllerChainingTransition = null;
285
286     private Transition controllerSecurityTransition = null;
287
288
289     /**
290      * Default constructor
291      */

292     public Controller() {
293
294     } /* Controller() */
295
296
297     /**
298      * The constructor of the child object should call addFinalState to define
299      * the last state to be executed for this controller.
300      * Once the final state is added then no other states can be added via the
301      * addStatePairing method.
302      *
303      * @param newFinalState the new state to use as your final state.
304      * @throws NonHandleableException if you send it an improper final state.
305      */

306     protected void addFinalState(State newFinalState)
307             throws NonHandleableException {
308         if (promptStates.contains(newFinalState) ||
309                 handleStates.contains(newFinalState)) {
310             throw new NonHandleableException("Final state must not be the same as any of the paired states.");
311         }
312
313
314         finalState = newFinalState;
315         addState(newFinalState);
316
317
318         //Set the initial state to this final state only if it is the only state in this controller.
319
if (StringUtil.notNull(getInitialState()).equals("")) {
320             setInitialState(newFinalState.getName());
321         }
322     }
323
324     /**
325      * If nextState is a prompt state (as determined by the addStatePairing method)
326      * then add a 'next' and 'previous' transitions as required.
327      * A 'next' transition will refer to a prompt state's associated validate state.
328      * A 'previous' transition will refer to the prompt state that precedes the
329      * passed in state. The 'previous' state will not be added to the response if
330      * if the passed in state is the first one (iniial state) in this controller.
331      *
332      * @param nextState in this controller the new state add
333      * @param response the ControllerResponse that these transitions will be
334      * added to.
335      * @throws ControllerException upon error.
336      */

337     protected void addPromptTransitions(State nextState,
338                                         ControllerResponse response)
339             throws ControllerException {
340
341
342         //Tack on the wizards Prev/Next/Finish transitions to any promptStates.
343
if (isPromptState(nextState)) {
344             String JavaDoc previousPromptState = previousPromptState(nextState);
345
346
347             if (previousPromptState != null) {
348                 Transition previousTransition = new Transition();
349                 previousTransition.setControllerObject(getClass().getName());
350                 previousTransition.setState(previousPromptState);
351                 previousTransition.setName("previous");
352                 previousTransition.setLabel("Previous");
353                 response.addTransition(previousTransition);
354             }
355
356
357             //Add the 'Next' or 'Done' buttons ('Done' is replaced with the name of the final state)
358
Transition nextTransition = null;
359             String JavaDoc nextPromptState = nextPromptState(nextState);
360
361
362             if (nextPromptState != null) { //Would not exist if this is the last screen in wizard
363
String JavaDoc nextHandleState = nextHandleState(nextState);
364
365
366                 //Safer to use no-arg constructor, this prevents the transition's ownerObject from being set.
367
//Setting this can prevent the getParamString() method from returning a 'controller' param.
368
//This can be problematic in situations, eg - DefaultViewHandler uses the getRequestPath()
369
//of a request to set the form's 'action' attribute which could be a different controller
370
//from the state's controller. This would raise an exception because no controller parm passed.
371
nextTransition = new Transition();
372                 nextTransition.setControllerObject(getClass().getName());
373                 nextTransition.setState(nextHandleState);
374                 nextTransition.setName("next");
375                 nextTransition.setLabel("Next");
376                 nextTransition.addParam(STATE_SUCC_STATE, nextPromptState); //ie transition to next screen in wizard
377
nextTransition.addParam(STATE_SUCC_CTL,
378                         response.getControllerClass());
379             } else { //Final state will be last and must exist - It will auto rerun any handler states
380
State finalState = getFinalState();
381
382
383                 if (finalState == null) {
384                     throw new ControllerException("Final state must exist.");
385                 }
386
387
388                 nextTransition = new Transition();
389                 nextTransition.setControllerObject(getClass().getName());
390                 nextTransition.setState(finalState.getName());
391                 nextTransition.setName("next");
392                 nextTransition.setLabel(finalState.getDescription());
393             }
394
395
396             response.addTransition(nextTransition);
397         }
398     }
399
400     /**
401      * The constructor of the child object should call addState to define
402      * each of the states available in this method.
403      *
404      * @param newState The State object to be added to the list of states for this
405      * controller
406      */

407     protected void addState(State newState) {
408         try {
409             newState.setController(this);
410         } catch (ControllerException ce) {
411             log.error("Unable to set controller", ce);
412         }
413
414
415         Hashtable JavaDoc myStateList = (Hashtable JavaDoc) states.get(getClass().getName());
416
417
418         if (myStateList == null) {
419             myStateList = new Hashtable JavaDoc();
420             states.put(getClass().getName(), myStateList);
421         }
422
423
424         myStateList.put(newState.getName(), newState);
425     } /* addState(State) */
426
427
428     /**
429      * The constructor of the child object should call this method with a pairing of State objects.
430      * The sequence of calls to this method determine the runtime ordering of states.
431      *
432      * @param promptState The state that is part of the 'prompting' workflow
433      * @param handleState The state that is part of the 'processing' workflow
434      * @param stateFormClass The <code>ActionForm</code> name to use
435      * @throws NonHandleableException if there's an error in pairing. Usually
436      * caused by a java.lang.IllegalArumentException
437      */

438     protected void addStatePairing(State promptState, State handleState,
439                                    String JavaDoc stateFormClass)
440             throws NonHandleableException {
441         if (promptState == null || handleState == null) {
442             throw new NonHandleableException("Can't add null state in pairing.");
443         }
444         //This check helps ensure that initialState is set correctly (ie to 1st prompt state if it exists,
445
//otherwise it will be set to the lone state). That lone state will be the final state.
446
if (finalState != null) {
447             throw new NonHandleableException("Can't add states once final state has been added.");
448         }
449         if (promptStates.contains(promptState) ||
450                 promptStates.contains(handleState) ||
451                 handleStates.contains(promptState) ||
452                 handleStates.contains(handleState)) {
453             throw new NonHandleableException("Paired states must be unique.");
454         }
455
456
457         promptState.setStateFormClass(stateFormClass);
458         handleState.setStateFormClass(stateFormClass);
459         promptStates.add(promptState);
460         handleStates.add(handleState);
461         addState(promptState);
462         addState(handleState);
463
464
465         //Let's enter via the 1st prompt state in the controller if the value hasn't already been explicitly set.
466
if (StringUtil.notNull(getInitialState()).equals("")) {
467             setInitialState(promptState.getName());
468         }
469
470
471         //Set the default transition to return to prompt the user if any errors generated
472
//within the handler state. This setting can be overriden at state-runtime.
473
Transition errorTransition = new Transition();
474         errorTransition.setState(promptState.getName());
475         errorTransition.setControllerObject(getClass().getName());
476         handleState.setErrorTransition(errorTransition);
477     }
478
479     /**
480      * Return the Struts ActionForm associated with this controller as specified
481      * in the Struts config file. Struts would have already created and populated
482      * this form and put it either in the request or session scope.
483      *
484      * @param request The ControllerRequest object
485      * @return The appropriate <code>ActionForm</code> for this controller request
486      * @throws ControllerException if unable to find the Controller Form.
487      */

488     protected ActionForm findControllerForm(ControllerRequest request)
489             throws ControllerException {
490         ActionConfig config = ConfigManager.getActionConfig("", getClass().getName(), null);
491 // ActionMapping mapping = ConfigManager.getMapping(getClass().getName(),
492
// null);
493
if (config == null) {
494             return null;
495         }
496
497         String JavaDoc controllerFormAttribute = config.getAttribute();
498
499
500         if (controllerFormAttribute == null) { //no form associated with this controller
501
return null;
502         }
503
504
505         // Find the controller's form. It would have been created by Strut's form logic before getting here.
506
PersistentSession session = request.getSession();
507
508
509         if ("request".equals(config.getScope())) {
510
511
512             //The getAttribute() method appears to read attributes from the HTTPRequest scope!!
513
return (ActionForm) session.getAttribute(controllerFormAttribute);
514         } else {
515             return (ActionForm) session.getPersistentAttribute(controllerFormAttribute);
516         }
517     }
518
519     /**
520      * Return the transition that will be executed once this controller
521      * completes without errors. The controller 'completes' once the
522      * final state (as defined using addFinalState) returns without errors
523      * in the error collection.
524      *
525      * @return The Transition for this controller chaining setup.
526      */

527     protected Transition getControllerChainingTransition() {
528         return controllerChainingTransition;
529     }
530
531     /**
532      * Return the transition that will be executed if any state in this controller
533      * cannot be run because the user does not have sufficient authorization.
534      *
535      * @return The ControllerSecurity Transition
536      */

537     protected Transition getControllerSecurityTransition() {
538         return controllerSecurityTransition;
539     }
540
541     /**
542      * Return the final state for this controller. See addFinalState().
543      *
544      * @return the &quot;Final&quot; state for this workflow.
545      */

546     public State getFinalState() {
547         return finalState;
548     }
549
550     /**
551      * Get the initial state in a controller. This is the state to use if there
552      * is no <code>state</code> parameter specified in the requesting URL or
553      * execution container.
554      *
555      * @return The name of the State to use as default for this controller
556      */

557     public String JavaDoc getInitialState() {
558         return (String JavaDoc) initialStates.get(getClass().getName());
559     } /* getInitialState() */
560
561
562     /**
563      * Get the name of the schema object that this Controller belongs to
564      *
565      * @return The schema class name associated with this controller
566      */

567     protected final String JavaDoc getSchema() {
568         Stack JavaDoc mySchemaStack = (Stack JavaDoc) schemas.get(getClass().getName());
569
570
571         if (mySchemaStack == null) {
572             setSchema(ExpressoSchema.class.getName());
573             mySchemaStack = (Stack JavaDoc) schemas.get(getClass().getName());
574         }
575
576         String JavaDoc returnValue = (String JavaDoc) mySchemaStack.peek();
577         return returnValue;
578     } /* getSchema() */
579
580
581     /**
582      * Retrieve a full instance of the Schema object that is associated with
583      * this controller
584      *
585      * @return Schema instance.
586      */

587     protected Schema getSchemaInstance() {
588         return SchemaFactory.getInstance().getSchema(this.getSchema());
589     }
590
591
592     /**
593      * Retrieve a stack of all schemas in a 'derived sense'
594      *
595      * @return java.util.Stack with the schema hierarchy ending in ExpressoSchema
596      * at the bottom.
597      * @deprecated 7/04; v5.5. just name changed. see getSchemaStack
598      */

599     public synchronized Stack JavaDoc getSchemaHierarchy() {
600         return getSchemaStack();
601     }
602
603     /**
604      * Retrieve a stack of all schemas. hands out actual object instance; singleton for this controller.
605      *
606      * @return java.util.Stack with the schema hierarchy ending in ExpressoSchema at the bottom. never null.
607      */

608     public synchronized Stack JavaDoc getSchemaStack() {
609         Stack JavaDoc s = (Stack JavaDoc) schemas.get(getClass().getName());
610         if (s == null) {
611             s = new Stack JavaDoc();
612             schemas.put(this.getClass().getName(), s);
613         }
614
615         if (s.isEmpty()) {
616             s.push(com.jcorporate.expresso.core.ExpressoSchema.class.getName());
617         }
618
619         return s;
620     }
621
622     /**
623      * Return a specific state
624      *
625      * @param stateName The name of the state to retrieve
626      * @return the Instantiated State.
627      */

628     public final State getState(String JavaDoc stateName) {
629         Hashtable JavaDoc myStateList = (Hashtable JavaDoc) states.get(getClass().getName());
630
631
632         if (myStateList == null) {
633             return null;
634         }
635
636
637         State stateClass = (State) myStateList.get(stateName);
638
639
640         if (stateClass == null) {
641             return null;
642         }
643         try {
644             State newStateClass = (State) stateClass.clone();
645             newStateClass.setController(this);
646
647
648             return newStateClass;
649         } catch (CloneNotSupportedException JavaDoc cne) {
650             log.error("Unable to clone " + "State object '" + stateClass +
651                     "'", cne);
652         } catch (ControllerException ce) {
653             log.error("Unable to clone " + "State object '" + stateClass +
654                     "'", ce);
655         }
656
657
658         return null;
659     } /* getState(String) */
660
661
662     /**
663      * Return the hashtable of valid states for this controller
664      *
665      * @return a Hashtable of states for this controller
666      */

667     public final Hashtable JavaDoc getStates() {
668         Hashtable JavaDoc myStateList = (Hashtable JavaDoc) states.get(getClass().getName());
669
670         if (myStateList == null) {
671             return null;
672         }
673
674         return myStateList;
675     } /* getStates() */
676
677
678     /**
679      * Convenience version, uses default locale of default db context
680      * <p/>
681      * IMPORTANT: in general, if there's a ControllerResponse object available,
682      * use ControllerResponse.getString(String) instead, this is able
683      * to return the local language by considering the user's locale.
684      *
685      * @param stringCode The properties file string code to retrieve
686      * @return translated string, or if not found, the stringCode
687      */

688     protected String JavaDoc getString(String JavaDoc stringCode) {
689         return this.getString(stringCode, new Object JavaDoc[0]);
690     } /* getString(String) */
691
692
693     /**
694      * Convenience version, uses default locale of default db context
695      * <p/>
696      * IMPORTANT: In general, if there's a ControllerResponse object available,
697      * use ControllerResponse.getString(String) instead, this is able
698      * to return the local language by considering the user's locale.
699      *
700      * @param stringCode The properties file string code to retrieve
701      * @param args The Object array for i18n formatting.
702      * @return translated string, or if not found, the stringCode
703      */

704     protected String JavaDoc getString(String JavaDoc stringCode, Object JavaDoc[] args) {
705         String JavaDoc result = stringCode;
706         Stack JavaDoc s = (Stack JavaDoc) schemas.get(this.getClass().getName());
707
708         if (s == null || s.isEmpty()) {
709             result = Messages.getString(getSchema(), stringCode, args);
710         } else {
711             Locale JavaDoc theLocale = Messages.getDefaultLocale();
712             result = Messages.getString(s, theLocale, stringCode, args);
713         }
714
715         return result;
716     }
717
718
719     /**
720      * Return the title of this Controller
721      *
722      * @return java.lang.String The Title of the controller
723      */

724     public String JavaDoc getTitle() {
725         return ("No Title");
726     } /* getTitle() */
727
728
729     /**
730      * Handle an exception error that is not otherwise caught or handled
731      * by the Controller instance itself. Typically, these will be reserved
732      * for "system failure" type of situations, as anything that the user
733      * can reasonable be expected to correct should be reported in the ErrorCollection
734      * instead.
735      *
736      * @param req The HTTPServletRequest
737      * @param creq The ControllerRequest Object
738      * @param dbName The current data context
739      * @param userName The currently logged in Username
740      * @param theException The <code>java.lang.Throwable</code> object that
741      * was caught
742      * @throws ServletException upon fatal error for handling the exception
743      */

744     protected void handleException(HttpServletRequest JavaDoc req,
745                                    ControllerRequest creq, String JavaDoc dbName,
746                                    String JavaDoc userName, Throwable JavaDoc theException)
747             throws ServletException JavaDoc {
748         if (StringUtil.notNull(dbName).equals("")) {
749             dbName = "default";
750         }
751
752         if (creq == null || dbName == null || req == null || userName == null) {
753             log.error("Error handling exception", theException);
754             throw new ServletException JavaDoc("ControllerRequest Error", theException);
755         }
756
757         try {
758             ControllerResponse errorResponse = new ControllerResponse();
759             errorResponse.setRequest(creq);
760             errorResponse.setTitle(getTitle());
761             errorResponse.setSchemaStack((java.util.Stack JavaDoc) this.getSchemaStack().clone());
762             errorResponse.setControllerClass(getClass().getName());
763
764
765             /* Stash the stackTrace, if available, in an output */
766             boolean showStack = true;
767
768
769             try {
770                 if (ConfigManager.getContext(dbName) != null) {
771                     showStack = ConfigManager.getContext(dbName).showStackTrace();
772                 }
773             } catch (ConfigurationException ce) {
774                 throw new ServletException JavaDoc(ce);
775             }
776             if (showStack) {
777                 if (theException != null) {
778                     ByteArrayOutputStream JavaDoc bos = new ByteArrayOutputStream JavaDoc();
779                     theException.printStackTrace(new PrintStream JavaDoc(bos));
780                     errorResponse.addOutput(new Output("stackTrace",
781                             bos.toString()));
782                 } else {
783                     errorResponse.addOutput(new Output("stackTrace",
784                             "No stack trace was available"));
785                 }
786             } else {
787                 errorResponse.addOutput(new Output("stackTrace",
788                         "Stack trace display is not " + "enabled."));
789             }
790
791
792             errorResponse.addOutput(new Output("exceptionMessage",
793                     theException.getMessage()));
794
795
796             if (theException instanceof com.jcorporate.expresso.kernel.exception.ChainedException) {
797                 com.jcorporate.expresso.kernel.exception.ChainedException c = (com.jcorporate.expresso.kernel.exception.ChainedException) theException;
798                 Throwable JavaDoc nestedException = c.getNested();
799
800
801                 if (nestedException != null) {
802                     String JavaDoc nested = StringUtil.notNull(c.getNested().getMessage());
803
804
805                     if (theException.getMessage().indexOf(nested) == -1) {
806                         errorResponse.addOutput(new Output("nestedMessage",
807                                 nested));
808                     }
809                 }
810                 if (c.getErrorNumber() != 0) {
811                     errorResponse.addOutput(new Output("errorNumber",
812                             Integer.toString(c.getErrorNumber())));
813                 }
814             }
815
816
817             errorResponse.addOutput(new Output("requestedURL",
818                     StringUtil.notNull(req.getRequestURI())));
819             errorResponse.addOutput(new Output("queryString",
820                     StringUtil.notNull(req.getQueryString())));
821             errorResponse.addOutput(new Output("db", dbName));
822             errorResponse.addOutput(new Output("userName", userName));
823             errorResponse.addOutput(new Output("dbDescrip",
824                     ConfigManager.getContext(dbName).getDescription()));
825             errorResponse.addOutput(new Output("errorClass",
826                     theException.getClass().getName()));
827
828
829             if (theException instanceof SecurityException JavaDoc) {
830                 errorResponse.addOutput(new Output("errorType", "security"));
831                 addRequestedURLtoSession(req, creq);
832             } else if (theException instanceof DBException) {
833                 errorResponse.addOutput(new Output("errorType", "database"));
834
835
836                 DBException de = (DBException) theException;
837                 errorResponse.addOutput(new Output("dbMessage",
838                         de.getDBMessage()));
839             } else {
840                 errorResponse.addOutput(new Output("errorType", "other"));
841             }
842
843
844             errorResponse.addOutput(new Output("controllerClass",
845                     getClass().getName()));
846             req.setAttribute(ExpressoConstants.CONTROLLER_RESPONSE_KEY, errorResponse);
847
848
849             /* Now see if we also email the error anywhere */
850             boolean mailError = false;
851
852
853             try {
854                 String JavaDoc servletEventFlag = StringUtil.notNull(Setup.getValue(dbName, "ServletEvent"));
855
856
857                 if (StringUtil.toBoolean(servletEventFlag)) {
858                     mailError = true;
859                 } else if (servletEventFlag.equalsIgnoreCase("E")) {
860                     if (!(theException instanceof SecurityException JavaDoc)) {
861                         mailError = true;
862                     }
863                 }
864             } catch (Exception JavaDoc e) {
865                 log.error("Unable to email error correctly", e);
866             }
867             if (!mailError) {
868                 log.info("Sending of '" + theException.getClass().getName() +
869                         "' errors via email is not enabled in " +
870                         "db/context '" + dbName + "'");
871             } else {
872                 FastStringBuffer theMessage = FastStringBuffer.getInstance();
873                 try {
874                     theMessage.append("Error:\n");
875                     theMessage.append("\tA " +
876                             theException.getClass().getName() +
877                             " Error occurred at " +
878                             DateTime.getDateTimeForDB(dbName) +
879                             " in database/context '" + dbName +
880                             "' to user '" + userName + "'\n");
881                     theMessage.append("\tThe requested URL was '");
882                     theMessage.append(getRequestURL(req));
883                     theMessage.append("'\n");
884
885                     String JavaDoc errorMessage = theException.getMessage();
886
887                     if (errorMessage != null) {
888                         theMessage.append("\t");
889                         theMessage.append(errorMessage);
890                     } else {
891                         theMessage.append("No error message was available");
892                     }
893
894
895                     theMessage.append(errorResponse.getOutput("stackTrace").getContent());
896                     String JavaDoc finalMessage = theMessage.toString();
897                     log.error(finalMessage, theException);
898                     EventHandler.Event(dbName, "SYSERROR",
899                             finalMessage, false);
900                 } catch (Exception JavaDoc ee) {
901                     log.error("Unable to send error email", ee);
902                 } finally {
903                     theMessage.release();
904                     theMessage = null;
905                 }
906
907             } /* if we send an email */
908
909         } catch (NullPointerException JavaDoc npe) {
910             log.error("Null Pointer Exception while handling error", npe);
911             throw new ServletException JavaDoc(npe);
912         } catch (ControllerException ce) {
913             log.error("ControllerException while handling error", ce);
914             throw new ServletException JavaDoc(ce);
915         } catch (Exception JavaDoc ee) {
916             log.error("Exception while handling error", ee);
917             throw new ServletException JavaDoc(ee);
918         }
919     } /* handleException */
920
921
922     /* Stash the original URL that we asked for in the session,
923 * so that if we log in and want to try again, it's available
924 */

925     protected void addRequestedURLtoSession(HttpServletRequest JavaDoc req,
926                                             ControllerRequest creq)
927             throws ControllerException {
928         PersistentSession session = creq.getSession();
929
930         if (session != null) {
931             String JavaDoc origUrl = getRequestURL(req);
932             session.setPersistentAttribute(ExpressoConstants.CONTROLLER_ORIGINAL_URL_KEY,
933                     new SerializableString(origUrl));
934         }
935     }
936
937     /**
938      * recreate requested URL; never null, though could be empty string
939      */

940     protected String JavaDoc getRequestURL(HttpServletRequest JavaDoc req) {
941         String JavaDoc origUrl = StringUtil.notNull(req.getRequestURI());
942
943         if (req.getQueryString() != null) {
944             origUrl = origUrl + "?" + req.getQueryString();
945         }
946         return origUrl;
947     }
948
949     /**
950      * ???????????????????????
951      *
952      * @param req ?
953      * @param creq ?
954      * @param regDomain ?
955      */

956     protected void addRegDomainParamtoSession(HttpServletRequest JavaDoc req,
957                                               ControllerRequest creq,
958                                               String JavaDoc regDomain)
959             throws ControllerException {
960         PersistentSession session = creq.getSession();
961
962         if (session != null) {
963             session.setPersistentAttribute("regDomain",
964                     new SerializableString(regDomain));
965         }
966     }
967
968     /**
969      * Factory method to create an Controller from it's name
970      * Do not call this function directly! Use <code>ConfigManager.getControllerFactory()
971      * .getInstance(java.lang.String)</code> instead.
972      *
973      * @param className The classname of the controller to instantiated
974      * @return a fully instantiated Controller Object
975      * @throws ControllerException upon construction failure
976      */

977     public static synchronized Controller instantiate(String JavaDoc className)
978             throws ControllerException {
979         StringUtil.assertNotBlank(className,
980                 "Controller class name " +
981                 " may not be blank or null here");
982
983
984         try {
985
986             Class JavaDoc c = ClassLocator.loadClass(className);
987             return (Controller) c.newInstance();
988         } catch (ClassNotFoundException JavaDoc cn) {
989             throw new ControllerException("Controller object '" + className +
990                     "' not found", cn);
991         } catch (InstantiationException JavaDoc ie) {
992             throw new ControllerException("Controller object '" + className +
993                     "' cannot be instantiated", ie);
994         } catch (IllegalArgumentException JavaDoc e) {
995             throw new ControllerException("Controller object '" + className +
996                     "' cannot be instantiated (IllegalArgumentException",
997                     e);
998         } catch (IllegalAccessException JavaDoc iae) {
999             throw new ControllerException("llegal access loading " +
1000                    "Controller object '" + className +
1001                    "'", iae);
1002        }
1003    } /* instantiate(String) */
1004
1005
1006    /**
1007     * Return True if the passed in state was added to this controller
1008     * as a final state.
1009     *
1010     * @param newState the state name to use as the final state.
1011     * @return true if this is the final state.
1012     */

1013    protected boolean isFinalState(String JavaDoc newState) {
1014        if (finalState == null) {
1015            return false;
1016        }
1017
1018        return finalState.getName().equals(newState);
1019    }
1020
1021    /**
1022     * Return True if the passed in state was added to this controller
1023     * as a handle state.
1024     *
1025     * @param nextState the state name to check against
1026     * @return true if it is supposed to handle the state.
1027     */

1028    protected boolean isHandleState(State nextState) {
1029        return handleStates.contains(nextState);
1030    }
1031
1032    /**
1033     * Return True if the passed in state was added to this controller
1034     * as a prompt state.
1035     *
1036     * @param nextState the state name to check against
1037     * @return true if it is the prompt state.
1038     */

1039    protected boolean isPromptState(State nextState) {
1040        return promptStates.contains(nextState);
1041    }
1042
1043    /**
1044     * Return the state's form with data from the controller's form.
1045     * The controller's form is specified in the struts config file. The state
1046     * form is specified in the state's stateFormClass attribute.
1047     *
1048     * @param nextState The next state to use
1049     * @param controllerForm the ControllerForm as specified in the struts config file.
1050     * @return a fully instantiated StateForm
1051     * @throws ControllerException if there is an error instantiating the State Form
1052     */

1053    protected StateForm loadStateForm(State nextState,
1054                                      ActionForm controllerForm)
1055            throws ControllerException {
1056        StateForm stateForm = null;
1057        String JavaDoc stateFormClass = StringUtil.notNull(nextState.getStateFormClass());
1058
1059
1060        if (controllerForm == null) {
1061            if (!stateFormClass.equals("")) {
1062                throw new ControllerException("Controller needs ActionForm because it's state expects one.");
1063            }
1064
1065
1066            return (null);
1067        } else {
1068
1069
1070            //If the state's form is blank then use the controller's form - if it's of ControllerForm type
1071
if (stateFormClass.equals("")) {
1072                /** changed instanceof ControllerForm for instanceof DefaultForm *RD* Mon Jul 27 2004 */
1073                if (controllerForm instanceof DefaultForm) {
1074                    return (StateForm) controllerForm;
1075                } else {
1076                    return (null); //backwards compatible reqt
1077
}
1078            } else {
1079
1080
1081                //Populate a new instance of the state's form from the controller's form.
1082
try {
1083
1084                    Class JavaDoc clazz = ClassLocator.loadClass(nextState.getStateFormClass());
1085                    stateForm = (StateForm) clazz.newInstance();
1086
1087
1088                    try {
1089                        BeanUtils.populate(stateForm,
1090                                BeanUtils.describe(controllerForm));
1091                    } catch (Exception JavaDoc e) {
1092                        throw new ControllerException(e);
1093                    }
1094                } catch (Throwable JavaDoc t) {
1095                    throw new ControllerException(t);
1096                }
1097
1098
1099                return (stateForm);
1100            }
1101        }
1102    }
1103
1104    /**
1105     * This is the method where all of the real work of the controller
1106     * is done. The client calls this to indicate what state the
1107     * controller is transitioning into.
1108     * <p/>
1109     * This is normally what is called from the command line when driving a
1110     * Controller object. If running in a struts environment, this function call
1111     * is wrapped by execute() method which does the actual running, plus then
1112     * performs tasks such as finding the next forward etc.
1113     * </p>
1114     *
1115     * @param newState The new state to transition to
1116     * @param myRequest The calling controllerRequest object.
1117     * @return a newly instantiated ControllerResponse for this state.
1118     * @throws ControllerException on Error
1119     * @throws NonHandleableException if the error should not be handled
1120     * by an error controller
1121     */

1122    public ControllerResponse newState(String JavaDoc newState,
1123                                       ControllerRequest myRequest)
1124            throws ControllerException,
1125            NonHandleableException {
1126        newState = StringUtil.notNull(newState);
1127
1128        if (log.isDebugEnabled()) {
1129            log.debug("Transitioning to state '" + newState +
1130                    "' in controller '" + getClass().getName() + "'");
1131        }
1132        if (newState.length() == 0) {
1133            if (getInitialState() != null) {
1134                return newState(getInitialState(), myRequest);
1135            } else {
1136                throw new ControllerException("You must specify a " +
1137                        "non-blank state. No default initial state is specified.");
1138            }
1139        } /* if state blank */
1140
1141
1142        //Used to ensure the state we are executing "newState" is in fact the one used to lookup
1143
//the controller security matrix. This should not be needed but is added for 'comfort'.
1144
String JavaDoc entryState = newState;
1145
1146
1147        /* Is this a valid state for this controller? */
1148        State nextState = getState(newState);
1149
1150
1151        if (nextState == null) {
1152            throw new ControllerException("State '" + newState +
1153                    "' is unknown for Controller " +
1154                    getClass().getName());
1155        } /* if new state exists */
1156
1157
1158        /* Is the user permitted to access this state? */
1159        // todo what does newstate=="" mean?
1160
if (!newState.equals("")) {
1161            if (controllerSecurityTransition == null && !stateAllowed(newState, myRequest)) {
1162                DelayThread.delay();
1163                throw new SecurityException JavaDoc("State '" +
1164                        nextState.getDescription() +
1165                        "' is not allowed for user '" +
1166                        myRequest.getUser() + "' (" +
1167                        myRequest.getUid() + ")" +
1168                        " in controller '" + getTitle() +
1169                        "' (" + getClass().getName() +
1170                        ") in context '" +
1171                        myRequest.getDataContext() + "'");
1172            } /* user not allowed */
1173
1174
1175        } /* if state not empty */
1176
1177
1178        setupReturnToSender(nextState, myRequest);
1179
1180        /** added to help struts validator to find the state to go back to
1181         * if an error is found in the form *RD* Mon Jul 27 2004 */

1182        PersistentSession mySession = myRequest.getSession();
1183        mySession.removePersistentAttribute("previousState");
1184        mySession.setPersistentAttribute("previousState", newState);
1185        mySession.removePersistentAttribute("previousController");
1186        mySession.setPersistentAttribute("previousController", this.getClass().getName());
1187
1188
1189
1190        //We have reached the final state so rerun any handler states before continuing.
1191
//If a handler state had to transition to some external state then it would likely set
1192
//returnToSender=True. So breaking out of the loop below and returning works great
1193
//because external state will reinvoke this Final state when it is done successfully.
1194
//If a handler state had to transition to some internal state then it would likely set
1195
//returnToSender=False because it has returned the user into some earlier screen in the
1196
//'wizard' to correct any errors. The user would then continue forward through the all
1197
//these wizard screens until the final screen which would reinvoke this Final state once again.
1198
if (isFinalState(newState)) {
1199            //Get the xml version of the return to sender transition
1200
FastStringBuffer fsb = FastStringBuffer.getInstance();
1201            String JavaDoc returnToSender = null;
1202            try {
1203                Transition t = nextState.getReturnToSender();
1204                returnToSender = t.toXML(fsb).toString();
1205            } finally {
1206                fsb.release();
1207                fsb = null;
1208            }
1209
1210
1211            for (Iterator JavaDoc i = handleStates.iterator(); i.hasNext();) {
1212                State oneHandleState = (State) i.next();
1213
1214
1215                //Any return to sender transitions while in 'final mode' will return to the 'final' state.
1216
myRequest.setParameter(RETURN_TO_SENDER_TRAN, returnToSender);
1217
1218
1219                ControllerResponse statusResponse = newState(oneHandleState.getName(),
1220                        myRequest);
1221
1222
1223                if (!statusResponse.getCurrentState().getName().equals(oneHandleState.getName())) {
1224                    return statusResponse; //The handler didn't return cleanly so bail out.
1225
}
1226            } //End loop on all handler states
1227

1228
1229        }
1230
1231        // Use the Servlet controller response to help pure Struts developers
1232
// Peter Pilgrim Fri Nov 22 03:15:58 GMT 2002
1233
ServletControllerResponse myResponse = new ServletControllerResponse();
1234
1235        if (myRequest == null) {
1236            throw new NonHandleableException("Request cannot be null here");
1237        }
1238
1239
1240        myResponse.setRequest(myRequest);
1241        myResponse.setTitle(getTitle());
1242        myResponse.setCurrentState(nextState);
1243        myResponse.setSchemaStack((java.util.Stack JavaDoc) this.getSchemaStack().clone());
1244        myResponse.setControllerClass(getClass().getName());
1245
1246
1247        /* Do we have all of the required parameters for this state */
1248        String JavaDoc oneParamName = null;
1249        String JavaDoc oneParamValue = null;
1250
1251
1252        for (Enumeration JavaDoc pe = nextState.getParameters().elements();
1253             pe.hasMoreElements();) {
1254            oneParamName = (String JavaDoc) pe.nextElement();
1255            oneParamValue = StringUtil.notNull(myRequest.getParameter(oneParamName)).trim();
1256
1257
1258            if (oneParamValue.equals("")) {
1259                throw new ControllerException("Value for parameter '" +
1260                        oneParamName +
1261                        "' is required for state " +
1262                        nextState.getDescription() +
1263                        " (" + nextState.getName() +
1264                        ")");
1265            } /* if parameter is empty */
1266
1267
1268        } /* for each required parameter */
1269
1270
1271        processRequestTransitions(nextState, myRequest);
1272
1273
1274        /* Now execute the "run" method of the state */
1275
1276
1277        //Setup the controller and state forms
1278
ActionForm controllerForm = findControllerForm(myRequest);
1279        StateForm stateForm = loadStateForm(nextState, controllerForm);
1280// populateStateForm(stateForm, myRequest); //This will add other params (eg hidden fields)
1281

1282
1283        ControllerResponse cr = null;
1284
1285
1286        //Double-check that our newState was not accidentally changed since we started this method.
1287
if (!newState.equals(entryState)) {
1288            throw new ControllerException("newState was modified between method entry and security check!");
1289        }
1290
1291        /* Is the user permitted to access this state? */
1292        if (controllerSecurityTransition != null && !stateAllowed(newState, myRequest)) {
1293            //Make it thread-safe
1294
Transition securityTransition = null;
1295
1296
1297            try {
1298                securityTransition = (Transition) controllerSecurityTransition.clone();
1299            } catch (CloneNotSupportedException JavaDoc cne) {
1300                throw new ControllerException("Cannot clone the securityTransition class");
1301            }
1302
1303            //Could also add a 'security-related' message in the ErrorCollection and set the
1304
//nextState.setErrorTransition(securityTransition) to display on the other screen.
1305
nextState.setSuccessTransition(securityTransition);
1306            cr = myResponse;
1307        } else { /* user allowed */
1308            //Now execute the "perform" method of the state
1309
prePerform(nextState, myRequest, myResponse);
1310            nextState.perform(stateForm, myRequest, myResponse);
1311
1312            cr = nextState.getResponse();
1313        }
1314
1315        //Move the state form back into the controller form
1316
unloadStateForm(stateForm, controllerForm);
1317
1318
1319        if (processTransitions(myRequest, cr, nextState) != null) {
1320            return cr;
1321        }
1322
1323
1324        addPromptTransitions(nextState, cr);
1325
1326
1327        //Don't call the derived class if a derived State class has already
1328
//been provided.
1329
if (nextState.getClass().getName()
1330                .equals("com.jcorporate.expresso.core.controller.State")) {
1331            Class JavaDoc thisClass = this.getClass();
1332            Class JavaDoc tryClass = thisClass; //For walking up the object hierarchy
1333

1334
1335            try {
1336
1337
1338                boolean methodFound = false;
1339                Method JavaDoc m = null;
1340
1341
1342                //
1343
//New as of v4.1 - Attempts to walk up the class hierarchy
1344
//to find an applicable class to execute. This should allow
1345
//better templating capabilities for deriving controllers from one another.
1346
//
1347
while (!methodFound) {
1348                    try {
1349                        m = tryClass.getDeclaredMethod(nextState.getHandlerName(), stateHandlerParams);
1350                        methodFound = true;
1351                    } catch (NoSuchMethodException JavaDoc nsme) {
1352                        if (myRequest instanceof ServletControllerRequest) {
1353                            //As of 5.3: Now try a signature for ServletControllerRequest
1354
//instead.
1355
try {
1356                                m = tryClass.getDeclaredMethod(nextState.getHandlerName(),
1357                                        servletStateHandlerParams);
1358                                methodFound = true;
1359                            } catch (NoSuchMethodException JavaDoc ex) {
1360                                if (log.isDebugEnabled()) {
1361                                    log.debug("Didn't find ServletControllerRequest method");
1362                                }
1363                            }
1364                        }
1365
1366                        if (!methodFound) {
1367                            tryClass = tryClass.getSuperclass();
1368
1369                            if (tryClass == null) {
1370                                throw nsme;
1371                            }
1372                        }
1373                    }
1374                }
1375                if (m == null) {
1376                    throw new NonHandleableException("Null pointer when we should have a method object");
1377                }
1378
1379
1380                Object JavaDoc[] invokeParams = {myRequest, cr};
1381                m.setAccessible(true);
1382
1383
1384                ControllerResponse cr2 = (ControllerResponse) m.invoke(this,
1385                        invokeParams);
1386
1387
1388                if (cr2 != null) {
1389                    ErrorCollection eold = cr.getErrors();
1390                    cr = cr2;
1391                    cr.saveErrors(eold);
1392                }
1393
1394                postPerform(nextState, myRequest, myResponse);
1395            } catch (SecurityException JavaDoc se) {
1396                log.error("Unable to reflect to method: " +
1397                        this.getClass().getName() + "." +
1398                        nextState.getHandlerName() +
1399                        " setAccessible permissions need to be allowed from your security policy",
1400                        se);
1401                throw new NonHandleableException(se);
1402            } catch (IllegalAccessException JavaDoc illae) {
1403                log.error("Unable to reflect to method: " +
1404                        this.getClass().getName() + "." +
1405                        nextState.getHandlerName() +
1406                        "illegal access Exception", illae);
1407                throw new NonHandleableException(illae);
1408            } catch (IllegalArgumentException JavaDoc iae) {
1409                log.error("Illegal Argument Exception", iae);
1410                throw new NonHandleableException(iae);
1411            } catch (InvocationTargetException JavaDoc ite) {
1412                Throwable JavaDoc e = ite.getTargetException();
1413                log.error("Error in substate", e);
1414                e.printStackTrace();
1415
1416                if (e instanceof NonHandleableException) {
1417                    throw new NonHandleableException(e); //BUG BUG Ok to not create a new instance??
1418
} else if (e instanceof SecurityException JavaDoc) {
1419                    throw new SecurityException JavaDoc(e.getMessage());
1420                } else {
1421                    throw new ControllerException(e);
1422                }
1423            } catch (NoSuchMethodException JavaDoc nsme) {
1424                try {
1425
1426// test to see if this method exists in thisClass
1427
thisClass.getDeclaredMethod("newState",
1428                            newStateParams);
1429
1430// it does exist, since no exception thrown. give feedback
1431
if (log.isDebugEnabled()) {
1432                        log.debug("Method '" + nextState.getHandlerName() +
1433                                "' does not exist in controller class '" +
1434                                getClass().getName() +
1435                                "'. State must be handled by newState method.");
1436                    }
1437                } catch (NoSuchMethodException JavaDoc nsme2) {
1438
1439
1440                    // If the controller doesn't have a newState method, then
1441
// we have a problem....
1442
String JavaDoc errMsg = ("Method '" +
1443                            nextState.getHandlerName() +
1444                            "' does not exist in '" +
1445                            getClass().getName() +
1446                            "' and there is no 'newState' method to handle " +
1447                            "transitions. Unable to transition");
1448                    log.error(errMsg, nsme);
1449                    throw new ControllerException(errMsg, nsme);
1450                }
1451            }
1452        }
1453
1454        //This is the old error handling transition logic for backward compatability.
1455
ErrorCollection finalErrors = cr.getErrors();
1456        if (finalErrors != null) {
1457            if (finalErrors.getErrorCount() > 0) {
1458                String JavaDoc errorState = nextState.getErrorState();
1459                if (errorState != null) {
1460                    cr.setFormCache(); //Is this really needed?
1461
transition(errorState, myRequest, cr);
1462                    return cr;
1463                }
1464            }
1465        }
1466
1467        return cr;
1468    } /* newState(String) */
1469
1470
1471    /**
1472     * Template Method, allowing a subclass to do an action after
1473     * any state in this controller is performed.
1474     * For example, a common header for the web pages
1475     * could be created here, so long as the web pages in question,
1476     * with the common header, were all in the same controller which overrides
1477     * this method.
1478     *
1479     * @param nextState the state to be performed
1480     * @param request the request object
1481     * @param response the response object
1482     */

1483    protected void prePerform(State nextState,
1484                              ControllerRequest request,
1485                              ControllerResponse response) throws ControllerException {
1486    }
1487
1488
1489    /**
1490     * Template Method, allowing a subclass to into actions after the invocation of
1491     * any state in this controller.
1492     * For example, a common footer for web pages
1493     * could be created here, so long as the web pages in question,
1494     * with the common footer, were all in the same controller which overrides
1495     * this method. (Multiple controllers which need common pre/post actions
1496     * could share a common superclass.)
1497     *
1498     * @param nextState the state to be performed
1499     * @param request the request object
1500     * @param response the response object
1501     */

1502    protected void postPerform(State nextState,
1503                               ControllerRequest request,
1504                               ControllerResponse response) throws ControllerException {
1505    }
1506
1507    /**
1508     * Return the name of the handle state that is associated with the
1509     * passed in prompt state. Null will be returned if the passed in
1510     * state is not a prompt state.
1511     *
1512     * @param nextState the next state to check against
1513     * @return the name of the 'handle' state paired with the nextState parameter
1514     */

1515    protected String JavaDoc nextHandleState(State nextState) {
1516        String JavaDoc nextHandle = null;
1517        int i = promptStates.indexOf(nextState);
1518
1519
1520        if (i != -1) { //Valid prompt newState name passed in
1521
State handleState = (State) handleStates.get(i);
1522            nextHandle = handleState.getName();
1523        }
1524
1525
1526        return nextHandle;
1527    }
1528
1529    /**
1530     * Return the name of the prompt state that is 'next' to the passed
1531     * in prompt state in the sequence for this controller. If the
1532     * passed in state is the last prompt state in this controller then
1533     * null will be returned.
1534     *
1535     * @param nextState the next state to check against
1536     * @return the name of the 'prompt' state paired with the nextState parameter
1537     */

1538    protected String JavaDoc nextPromptState(State nextState) {
1539        String JavaDoc nextPrompt = null;
1540        int i = promptStates.indexOf(nextState);
1541        i++;
1542
1543
1544        if ((i > 0) && (i < promptStates.size())) { //Next prompt(s) exist
1545
State promptState = (State) promptStates.get(i);
1546            nextPrompt = promptState.getName();
1547        }
1548
1549
1550        return nextPrompt;
1551    }
1552
1553    /**
1554     * Used for logging time of requests.
1555     *
1556     * @param beginTimer The start execution time
1557     * @param request The HTTPServletRequest to instrument
1558     */

1559    protected void endTimer(long beginTimer, HttpServletRequest JavaDoc request) {
1560        if (log.isInfoEnabled()) {
1561            log.info("Request '" + getRequestURL(request) +
1562                    "', total time " +
1563                    (System.currentTimeMillis() - beginTimer) +
1564                    " milliseconds");
1565        }
1566    }
1567
1568    /**
1569     * Process the specified HTTP request, and create the corresponding HTTP
1570     * response (or forward to another web component that will create it).
1571     * Return an <code>ActionForward</code> instance describing where and how
1572     * control should be forwarded, or <code>null</code> if the response has
1573     * already been completed. Note, that if you wish to run a controller within
1574     * something other than a HTTP session, then call newState directly.
1575     *
1576     * @param mapping The ActionMapping used to select this instance
1577     * @param form The optional ActionForm bean for this request (if any)
1578     * @param request The HTTP request we are processing
1579     * @param response The HTTP response we are creating
1580     * @return The ActionForward or null
1581     * @throws IOException if an input/output error occurs
1582     * @throws ServletException if a servlet exception occurs
1583     */

1584    public ActionForward execute(ActionMapping mapping, ActionForm form,
1585                                 HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response)
1586            throws IOException JavaDoc, ServletException JavaDoc {
1587        long beginTimer = System.currentTimeMillis();
1588
1589        /** to avoid nullPointerExceptions we initialize here the error collection *RD* Mon Jul 27 2004 */
1590        ErrorCollection actionErrors = new ErrorCollection();
1591
1592        // Call newState directly if we were called from a dispatch in Transition.
1593
ControllerRequest queuedRequest = (ControllerRequest) request.getAttribute(
1594                ExpressoConstants.CONTROLLER_REQUEST_KEY);
1595
1596        // TODO: Please document what is the purpose of this QUEUED REQUEST code. *PP* Tue Jan 27 11:18:40 GMT 2004
1597
if (queuedRequest != null) {
1598            request.removeAttribute(ExpressoConstants.CONTROLLER_REQUEST_KEY);
1599            String JavaDoc queuedState = StringUtil.notNull(request.getParameter(STATE_PARAM_KEY));
1600
1601            // Only reset the controller form when the controller is
1602
// invoked! If state is specified then the form is not
1603
// reset (even if the state specified is the controller's
1604
// initial state).
1605
if (queuedState.equals("")) {
1606                //Reset the ControllerForm and populate from request
1607
if (form instanceof ControllerForm) {
1608                    ((ControllerForm) form).resetController();
1609                }
1610            }
1611
1612            // TODO: Why are we repopulating the bean? *PP* Tue Jan 27 11:19:15 GMT 2004
1613
/** I commented out the code and nothing seems broken. The form is already populated *RD* Mon Jul 27 2004 */
1614            /*try {
1615                BeanUtils.populate(form, queuedRequest.getParameters());
1616            } catch (Exception e) {
1617                throw new ServletException(e);
1618            }*/

1619
1620            ControllerResponse queuedResponse = null;
1621
1622            try {
1623                queuedRequest.setFormAttribute(mapping.getAttribute());
1624                /** test if this action must be automatically validated.
1625                 * If it's the case validate the form. *RD* Mon Jul 27 2004 */

1626                /** replaced
1627                 * queuedResponse = newState(queuedState, queuedRequest);
1628                 * with all this *RD* Mon Jul 27 2004 */

1629                if (mapping.getValidate()) {
1630                    actionErrors = (ErrorCollection) form.validate(mapping, request);
1631                }
1632                /** test if there are no validation errors *RD* Mon Jul 27 2004 */
1633                if (actionErrors.size() == 0) {
1634                    /**no errors: forward to the handle state *RD* Mon Jul 27 2004 */
1635                    queuedResponse = newState(queuedState, queuedRequest);
1636                } else {
1637                    /** errors: forward control to the prompt state *RD* Mon Jul 27 2004 */
1638                    /** retrieve the previous controller and states parameters from the session *RD* Mon Jul 27 2004 */
1639                    String JavaDoc previousController = request.getSession().getAttribute("previousController").toString();
1640                    String JavaDoc previousState = request.getSession().getAttribute("previousState").toString();
1641                    /** test if the prompt state belongs to this same controller *RD* Mon Jul 27 2004 */
1642                    if (previousController.equals(this.getClass().getName())) {
1643                        queuedResponse = newState(StringUtil.notNull(previousState),
1644                                queuedRequest);
1645                        queuedResponse.saveErrors(actionErrors);
1646                    } else {
1647                        /** if not transition to the new controller *RD* Mon Jul 27 2004 */
1648                        transition(StringUtil.notNull(previousState),Class.forName(StringUtil.notNull(previousController)),queuedRequest, new ControllerResponse());
1649                    }
1650
1651                }
1652              } catch (Exception JavaDoc e) {
1653                request.setAttribute(ExpressoConstants.NEWSTATE_EXCEPTION_KEY, e);
1654                //push back the exception
1655
return (null);
1656            }
1657
1658            request.setAttribute(ExpressoConstants.CONTROLLER_REQUEST_KEY, queuedRequest);
1659            request.setAttribute(ExpressoConstants.CONTROLLER_RESPONSE_KEY, queuedResponse);
1660
1661            return (null);
1662        }
1663
1664
1665        /* User for error handling */
1666        String JavaDoc dbName = null;
1667        String JavaDoc userName = null;
1668        ServletControllerRequest req = null;
1669
1670
1671        try {
1672            String JavaDoc requestDB = request.getParameter("db");
1673            // STRUTS 11
1674
log.debug("request.getParameter(\"db\") = `" + request.getParameter("db") + "'");
1675            CheckLogin.getInstance().checkLogin(request, requestDB);
1676
1677            /* Get any, if they exist, parameters from the ActionForm bean */
1678            boolean multiPart = false;
1679
1680            if (form != null) {
1681                // Call the Struts API to figure if we are dealing
1682
// with a multipart form bean, which are typical of
1683
// uploading file attachments.
1684
//
1685
// See Later: If the form is "session" scoped, we need
1686
// to reset this handler
1687
MultipartRequestHandler mp = form.getMultipartRequestHandler();
1688
1689                if (mp != null) {
1690                    // The form bean is multipart HTTP request so we
1691
// need to handle it as such. Certain parameters
1692
// in the `HttpServletRequest' may be multipart
1693
// file types.
1694
if (log.isDebugEnabled()) {
1695                        log.debug("Multipart request");
1696                    }
1697
1698                    multiPart = true;
1699                    req = ServletControllerRequest.parseParamsMultiPart(mp, mapping, form,
1700                            request, response, this);
1701                    // STRUTS 1.1
1702
req.setCallingServlet(this.getServlet());
1703                }
1704            }
1705            if (!multiPart) {
1706                // The form bean is NOT multipart HTTP request so we
1707
// process as normal.
1708
if (log.isDebugEnabled()) {
1709                    log.debug("Non-multipart request");
1710                }
1711                req = ServletControllerRequest.parseParams(mapping, form, request, response, this);
1712                // STRUTS 1.1
1713
req.setCallingServlet(this.getServlet());
1714            }
1715
1716
1717            // Check the session for an error collection.... if the error collection exists there,
1718
//
1719
// then we MOVE it to the request context This is used by
1720
// Controller when it needs to do a redirectRequest, but
1721
// cannot be kept in the session otherwise it will show up
1722
// as an error for every page which doesn't have an error
1723
// in the response.
1724
//
1725
// NOTE: Legacy Expresso Controllers, I think, stored error
1726
// collection in the session scope. *PP*
1727
ErrorCollection errors = (ErrorCollection) req.getSession().getAttribute(Globals.ERROR_KEY);
1728            if (errors == null) {
1729                // retrieve errors from persistent attributes, if any
1730
errors = (ErrorCollection) req.getSession().getPersistentAttribute(Globals.ERROR_KEY);
1731                // move to request
1732
if (errors != null) {
1733                    req.getSession().removePersistentAttribute(Globals.ERROR_KEY);
1734                    req.getSession().setAttribute(Globals.ERROR_KEY, errors);
1735                }
1736            }
1737
1738
1739            /*
1740         * If we were called as part of a transition request to
1741         * another controller, the parameters will contain a
1742         * 'controller' parameter with a value that indicates a
1743         * controller other than this one. If this happens, then
1744         * we do a lookup to find the ActionMapping for that
1745         * controller/state (or just the controller if there's no
1746         * mapping directly to the specified state) and forward to
1747         * that mapping
1748         */

1749            String JavaDoc controllerRequested = StringUtil.notNull(req.getParameter(ExpressoConstants.CONTROLLER_KEY));
1750
1751            // Find out if there is a request state from the
1752
// abstracted "ControllerRequest" parameters. Note this
1753
// different from the "HttpServletRequest"
1754
// parameters. By the time we get here. A "state" set by a
1755
// button (Transition) will have been picked up and
1756
// set. Likewise a "state" parameter from the
1757
// "HttpServletRequest", if any. So we are checking for
1758
// missing controller state definition here.
1759
String JavaDoc requestedState = StringUtil.notNull(req.getParameter(STATE_PARAM_KEY));
1760
1761            log.debug("requestedState = `" + requestedState + "'");
1762            if (!controllerRequested.equals("")) {
1763                if (!controllerRequested.equals(getClass().getName())) {
1764
1765                    // If we get here, then we have been asked to
1766
// transition to a state in a different
1767
// controller.
1768
ActionConfig mm = ConfigManager.getActionConfig("", controllerRequested, requestedState);
1769                    if (mm == null) {
1770                        throw new ServletException JavaDoc("The controller requested: "
1771                                + controllerRequested.toString()
1772                                + " and/or state within that controller: "
1773                                + requestedState.toString()
1774                                + " are not available in configuration mapping; if you expected this to be "
1775                                + "available, double-check the /config/*-config.xml files.");
1776                    }
1777
1778                    // Build up dispatch URL from all the required
1779
// parameters for the external controller and its
1780
// target state method
1781
FastStringBuffer newURL = FastStringBuffer.getInstance();
1782                    String JavaDoc urlString = null;
1783                    try {
1784                        newURL.append(mm.getPath());
1785                        newURL.append(".do");
1786                        newURL.append("?state=");
1787                        newURL.append(requestedState);
1788
1789
1790                        String JavaDoc oneParamName = null;
1791                        String JavaDoc oneParamValue = null;
1792
1793                        // TODO: Is this a bug or not core developers? *PP*
1794
// BUG BUG - Should parameter names and values be URLEncoded?
1795
for (Enumeration JavaDoc ep = req.getParameters().keys();
1796                             ep.hasMoreElements();) {
1797                            oneParamName = (String JavaDoc) ep.nextElement();
1798                            oneParamValue = req.getParameter(oneParamName);
1799                            newURL.append("&");
1800                            newURL.append(oneParamName);
1801                            newURL.append("=");
1802                            newURL.append(oneParamValue);
1803                        }
1804
1805                        if (log.isDebugEnabled()) {
1806                            log.debug("Request to controller '" +
1807                                    getClass().getName() +
1808                                    "' was for a transition " + "to class '" +
1809                                    controllerRequested +
1810                                    "', so forwarding to URL '" +
1811                                    newURL.toString() + "'");
1812                        }
1813                        if (log.isInfoEnabled()) {
1814                            endTimer(beginTimer, request);
1815                        }
1816
1817                    } finally {
1818                        urlString = newURL.toString();
1819                        newURL.release();
1820                        newURL = null;
1821                    }
1822
1823                    // Request dispatch forward to the target controller and state method
1824
GenericDispatcher.forward(request, response, urlString);
1825                    request.removeAttribute(ExpressoConstants.USED_ROUTING_KEY);
1826                    return null;
1827                }
1828            }
1829
1830
1831            // Add flag 'usedRouting' to request attribute to indicate
1832
// routing params have been consumed already. This will
1833
// prevent any other controllers that this controller may
1834
// dispatch to from reusing these parameters (ie the
1835
// controller and state parameters).
1836
request.setAttribute(ExpressoConstants.USED_ROUTING_KEY, "inuse");
1837
1838            //
1839
// If we are configured to use SSL, then we must possibly
1840
// issue a redirect to get to the right place.
1841
//
1842
if (ConfigManager.getContext(req.getDataContext()).isUseSSL()) {
1843
1844
1845                if (log.isDebugEnabled()) {
1846                    log.debug("Checking if we need to redirect to different protocol");
1847                }
1848                Hashtable JavaDoc myStateList = (Hashtable JavaDoc) states.get(getClass().getName());
1849                String JavaDoc stateName = req.getParameter(STATE_PARAM_KEY);
1850                if (stateName == null || stateName.length() == 0) {
1851                    stateName = this.getInitialState();
1852                }
1853
1854
1855                if (this.checkSsl(request, response,
1856                        ((State) myStateList.get(stateName)).isSecure())) {
1857                    return null;
1858                }
1859
1860
1861                if (log.isDebugEnabled()) {
1862                    log.debug("No more redirection necessary");
1863                }
1864            }
1865
1866            if (log.isDebugEnabled()) {
1867                log.debug("Controller '" + getClass().getName() +
1868                        "' to state '" +
1869                        StringUtil.notNull(req.getParameter(STATE_PARAM_KEY)) + "'");
1870            }
1871
1872            req.setFormAttribute(mapping.getAttribute());
1873
1874            // Only reset the controller form when the controller is
1875
// invoked! If state is specified then the form is not
1876
// reset (even if the state specified is the controller's
1877
// initial state). This avoids an unwanted reset when
1878
// user clicks on 'previous' in the 2nd screen of a
1879
// wizard.
1880
if (requestedState.equals("")) {
1881                //Reset the ControllerForm and populate from request
1882
if (form instanceof ControllerForm) {
1883                    ((ControllerForm) form).resetController();
1884                }
1885            }
1886
1887            // TODO: Why are we setting the form bean again here? What are doing here in this code? *PP* Tue Jan 27 11:35:22 GMT 2004
1888
/* BUG BUG
1889                    the test if form instanceof DefaultForm should be done before
1890                    BeanUtils.populate() otherwise the DefaultForm never gets populated
1891                    This also fixes Input.setDefaultValue(ControllerResponse) which wasn't working
1892             *RD* Mon Jul 27 2004
1893
1894            try {
1895                BeanUtils.populate(form, req.getParameters());
1896            } catch (Exception e) {
1897                if (form instanceof DefaultForm) {
1898                    try {
1899                        ((DefaultForm) form).setUsingHashtableParameters(req.getParameters());
1900                    } catch (Exception ex) {
1901                        ex.printStackTrace();
1902                        throw new ServletException(ex);
1903                    }
1904                } else {
1905                    log.error("Error populating form", e);
1906                    throw new ServletException(e);
1907                }
1908                         }*/

1909            /** corrected this *RD* Mon Jul 27 2004 */
1910            /** we populate the DefaultForm with the contents of the HTML form *RD* Mon Jul 27 2004 */
1911            if (form instanceof DefaultForm) {
1912                try {
1913                    ((DefaultForm) form).setUsingHashtableParameters(req.getParameters());
1914                } catch (Exception JavaDoc ex) {
1915                    ex.printStackTrace();
1916                    throw new ServletException JavaDoc(ex);
1917                }
1918            }
1919            /** I commented out the code and nothing seems broken. The form is already populated *RD* Mon Jul 27 2004 */
1920            /*else {
1921                     try {
1922                       BeanUtils.populate(form, req.getParameters());
1923                     }
1924                     catch (Exception e) {
1925                       log.error("Error populating form", e);
1926                       throw new ServletException(e);
1927                     }
1928            }*/

1929
1930
1931            // Remove the last controller response from the request
1932
// scope in preparation for invoking the new state method.
1933
request.removeAttribute(ExpressoConstants.CONTROLLER_RESPONSE_KEY);
1934            dbName = req.getDataContext();
1935            userName = req.getUser();
1936            if (log.isDebugEnabled()) {
1937                log.debug("Before newstate in '" + getClass().getName() + "'");
1938            }
1939
1940            // ============================================================
1941
// ============================================================
1942
// Call the target controller's new "state method" as required
1943
// ============================================================
1944
// ============================================================
1945
ControllerResponse res = null;
1946            try {
1947                /** test if this action must be automatically validated.
1948                 * If it's the case validate the form. *RD* Mon Jul 27 2004
1949                 * replaced
1950                 * res = newState(StringUtil.notNull(previousState), req);
1951                 * with all this *RD* Mon Jul 27 2004 */

1952
1953                if (mapping.getValidate()) {
1954                    actionErrors = (ErrorCollection) form.validate(mapping, request);
1955                }
1956                /** test if there are no validation errors *RD* Mon Jul 27 2004 */
1957                if (actionErrors.size() == 0) {
1958                    /** no errors: forward to the handle state *RD* Mon Jul 27 2004 */
1959                    res = newState(StringUtil.notNull(requestedState), req);
1960                } else {
1961                    /** errors: forward control to the prompt state *RD* Mon Jul 27 2004 */
1962                    /** etrieve the previous controller and states parameters from the session *RD* Mon Jul 27 2004 */
1963                    String JavaDoc previousController = request.getSession().getAttribute("previousController").toString();
1964                    String JavaDoc previousState = request.getSession().getAttribute("previousState").toString();
1965                    /** test if the prompt state belongs to this same controller *RD* Mon Jul 27 2004 */
1966                    if (previousController.equals(this.getClass().getName())) {
1967                        res = newState(StringUtil.notNull(previousState), req);
1968                        res.saveErrors(actionErrors);
1969                    } else {
1970                        /** if not transition to the new controller *RD* Mon Jul 27 2004 */
1971                        transition(StringUtil.notNull(previousState),Class.forName(StringUtil.notNull(previousController)),req, new ControllerResponse());
1972                    }
1973                }
1974
1975            } finally {
1976                if (form != null && "session".equals(mapping.getScope())) {
1977                    // Here is a fix for multipart action form beans that
1978
// are "session" scoped. We need to reset the
1979
// multipart request handler for these beans in order
1980
// to prevent infinite looping in the Controller's
1981
// transition mechanism.
1982
form.setMultipartRequestHandler(/*MultipartRequestHandler*/ null);
1983                }
1984            }
1985
1986            if (log.isDebugEnabled()) {
1987                log.debug("After newstate in '" + getClass().getName() +
1988                        "' controller is '" + res.getControllerClass() + "'");
1989            }
1990
1991            // Store the controller response now
1992
res.setRequestPath(mapping.getPath());
1993            request.setAttribute(ExpressoConstants.CONTROLLER_RESPONSE_KEY, res);
1994
1995            // Has the controller state method declared a custom
1996
// response? For example if the controller is sending down
1997
// a binary stream of a multimedia file stream its own
1998
// accord.
1999
if (res.isCustomResponse()) {
2000                // Custom response it is, then. End the timing and
2001
// return `null' to also flag this condition to the
2002
// Struts MVC framework
2003
if (log.isInfoEnabled()) {
2004                    endTimer(beginTimer, request);
2005                }
2006                request.removeAttribute(ExpressoConstants.USED_ROUTING_KEY);
2007                return null;
2008            }
2009
2010            ActionConfig finalConfig = mapping;
2011
2012            // Now get the mapping again if needed, this time for the controller
2013
// specified by the ControllerResponse.getControllerClass(), which
2014
// may or may not be the controller we started out with ...
2015
if (!res.getControllerClass().equals(mapping.getType())) {
2016                if (log.isDebugEnabled()) {
2017                    log.debug("Controller '" + getClass().getName() +
2018                            "' transitioned internally to controller '" +
2019                            res.getControllerClass() + "', state '" +
2020                            res.getCurrentState() +
2021                            "'. Getting alternate mapping...");
2022                }
2023
2024                // mapping = ConfigManager.getMapping(res.getControllerClass(),
2025
// res.getCurrentState().getName());
2026

2027                ActionConfig config = ConfigManager.getActionConfig("", res.getControllerClass(),
2028                        res.getCurrentState().getName());
2029
2030                if (config == null) {
2031                    throw new ControllerException("Unable to find ActionConfig for: "
2032                            + res.getControllerClass() + ":" + res.getCurrentState().getName());
2033                }
2034                finalConfig = config;
2035
2036                if (log.isDebugEnabled()) {
2037                    log.debug("Got mapping for '" + finalConfig.getPath() + "'");
2038                }
2039            } else {
2040                if (log.isDebugEnabled()) {
2041                    log.debug("Controller '" + getClass().getName() +
2042                            "' using type '" + finalConfig.getType() +
2043                            "', transitioning to state '" +
2044                            res.getCurrentState() +
2045                            "' within the same controller.");
2046                }
2047            }
2048
2049            // Retrieve the final action forward derived from the
2050
// controller state method
2051
ActionForward fwd = getActionForward(req, finalConfig, res);
2052
2053            if (log.isInfoEnabled()) {
2054                endTimer(beginTimer, request);
2055            }
2056
2057            // Remove the used routing key if it was set to avoid looping.
2058
request.removeAttribute(ExpressoConstants.USED_ROUTING_KEY);
2059            // Return the action forward as normal for Struts Action controller
2060
return fwd;
2061        } catch (NonHandleableException ne) {
2062            throw new ServletException JavaDoc(ne);
2063            /**
2064             * special case: when there is no login, redirect to login screen
2065             */

2066        } catch (SecurityException JavaDoc securExcep) {
2067            // Has this current user logged in?
2068
if (userName.equals(User.UNKNOWN_USER)) {
2069                // If not redirect user to login screen using the
2070
// global action forward
2071
ActionForward loginForward = mapping.findForward(ExpressoConstants.APPLICATION_LOGON_FORWARD);
2072                if (loginForward == null) {
2073                    // do try/catch to capture stack trace
2074
try {
2075                        throw new Exception JavaDoc(
2076                                "cannot find forward for 'logon'; one should be in <application>-config.xml");
2077                    } catch (Exception JavaDoc e) {
2078                        e.printStackTrace(System.err);
2079                        handleException(request, req, dbName, userName, e);
2080                        return (mapping.findForward(ExpressoConstants.ERROR_FORWARD));
2081                    }
2082                }
2083
2084                try {
2085                    addRequestedURLtoSession(request, req);
2086                } catch (Exception JavaDoc e) {
2087                    // do not abort here; we just had problem adding an
2088
// item to session; we will live without it.
2089
log.error("unexpected problem adding item to session: ", e);
2090                }
2091
2092                // Do not return an action forward since the URL will
2093
// say the wrong thing. Instead, do a redirect so
2094
// that browser will show the correct login URL
2095
redirectRequest(response, ConfigManager.getContextPath() + loginForward.getPath());
2096                return null;
2097            } else {
2098                // If we get here, then we have a recognised user with
2099
// a genuine login account.
2100

2101                // See if there is a regDomain out there assigned to
2102
try {
2103                    ControllerSecurity cs = new ControllerSecurity();
2104                    RegistrationDomain rd = new RegistrationDomain();
2105                    ControllerSecurity oneSecurityEntry = null;
2106                    String JavaDoc groupName = null;
2107                    cs.setDataContext(dbName);
2108
2109                    // Search for the Controller which extended from
2110
// this class (the calling controller)
2111
cs.setField("ControllerClass", getClass().getName());
2112                    for (Iterator JavaDoc cse = cs.searchAndRetrieveList().iterator(); cse.hasNext();) {
2113                        cs.clear();
2114                        oneSecurityEntry = (ControllerSecurity) cse.next();
2115                        groupName = oneSecurityEntry.getField("GroupName");
2116                        // Now see if there is a registration domain for that group
2117
rd.clear();
2118                        rd.setField("GroupName", groupName);
2119                        if (rd.find()) {
2120                            // Find the action forward on the registration mapping
2121
ActionForward registrationForward = mapping.findForward(ExpressoConstants.REGISTER_FORWARD);
2122
2123                            if (registrationForward == null) {
2124                                // do try/catch to capture stack trace
2125
try {
2126                                    throw new Exception JavaDoc(
2127                                            "cannot find forward for '" + ExpressoConstants.REGISTER_FORWARD + "'; One should be defined in <application>-config.xml");
2128                                } catch (Exception JavaDoc e) {
2129                                    e.printStackTrace(System.err);
2130                                    handleException(request, req, dbName, userName, e);
2131
2132                                    return (mapping.findForward(ExpressoConstants.ERROR_FORWARD));
2133                                }
2134                            }
2135
2136                            try {
2137                                addRegDomainParamtoSession(request, req, rd.getField("Name"));
2138
2139                            } catch (Exception JavaDoc e) {
2140                                // do not abort here; we just had problem adding an
2141
// item to session; we will live without it.
2142
log.error("unexpected problem adding item to session: ", e);
2143                            }
2144
2145
2146                            // Do not return an action forward since the URL will say the wrong
2147
// thing. Instead, do a redirect so that browser will
2148
// show the login URL
2149
try {
2150                                ErrorCollection ec = req.getErrorCollection();
2151                                if (ec == null) {
2152                                    ec = new ErrorCollection();
2153                                }
2154                                ec.addError(securExcep.getMessage());
2155                                req.getSession().setPersistentAttribute(Globals.ERROR_KEY, ec);
2156                            } catch (ControllerException ex) {
2157                                log.error("Unable to save error for a permission denied message", ex);
2158                            }
2159                            redirectRequest(response, ConfigManager.getContextPath() + registrationForward.getPath());
2160                            return null;
2161
2162                        } else {
2163                            log.warn(
2164                                    "Registration Domain for " + groupName + " and class " + getClass().getName() + " not found");
2165                        }
2166                    }
2167                } catch (DBException dbe) {
2168                    handleException(request, req, dbName, userName, dbe);
2169                }
2170
2171                // standard handling--user has logged in, but tried to
2172
// do something not allowed.
2173
System.err.println(securExcep.getMessage());
2174                handleException(request, req, dbName, userName, securExcep);
2175
2176                return (mapping.findForward(ExpressoConstants.ERROR_FORWARD));
2177            }
2178        } catch (NullPointerException JavaDoc npe) {
2179            npe.printStackTrace(System.err);
2180            log.error("Null Pointer Exception", npe);
2181            handleException(request, req, dbName, userName, npe);
2182            return (mapping.findForward("error"));
2183        } catch (Exception JavaDoc ee) {
2184            ee.printStackTrace(System.err);
2185            handleException(request, req, dbName, userName, ee);
2186            return (mapping.findForward("error"));
2187        }
2188    }
2189
2190    /**
2191     * Determine the forward appropriate for this controller/state,
2192     * either from parameters or from configuration files, or from
2193     * remapping based on past extensions like .xsl
2194     *
2195     * @param req the ServletControllerRequest object
2196     * @param mapping the ActionConfig that defines the mapping
2197     * @param res the ControllerResponse object that has been handed back to
2198     * us by Controller.newState()
2199     * @return an ActionForward instance.
2200     * @throws NonHandleableException upon fatal error
2201     * @throws ControllerException upon error
2202     */

2203    protected ActionForward getActionForward(ServletControllerRequest req,
2204                                             ActionConfig mapping,
2205                                             ControllerResponse res) throws NonHandleableException,
2206            ControllerException {
2207
2208        ForwardConfig fwd = null;
2209
2210        /* If we were overridden and told to go to a specific style */
2211        if (!StringUtil.notNull(req.getParameter("style")).equals("")) {
2212            String JavaDoc style = req.getParameter("style");
2213            fwd = mapping.findForwardConfig(style);
2214
2215
2216            if (fwd == null) {
2217                fwd = mapping.getModuleConfig().findForwardConfig(style);
2218                if (fwd == null) {
2219                    throw new NonHandleableException("Style '" +
2220                            req.getParameter("style") +
2221                            "' was not found for controller '" +
2222                            res.getControllerClass() +
2223                            "'");
2224                }
2225            }
2226        } else if (res instanceof ServletControllerResponse) {
2227            /* If the response is actually an servlet controller response
2228             * see if there was an action forward defined.
2229             */

2230            ServletControllerResponse resServ = (ServletControllerResponse) res;
2231            if (resServ.getActionForward() != null) {
2232                fwd = resServ.getActionForward();
2233            }
2234        }
2235
2236        /* If this controller returned a specific "style", then try to find the
2237         * ActionForward for this style.
2238         */

2239        if ((fwd == null) && (res.getStyle() != null)) {
2240            fwd = mapping.findForwardConfig(res.getStyle());
2241            if (fwd == null) {
2242                fwd = mapping.getModuleConfig().findForwardConfig(res.getStyle());
2243            }
2244        }
2245
2246        /* If we didn't specify a style, or the style was not found, then
2247         * use the name of the current state to determine the forward to use
2248         */

2249        if (fwd == null) {
2250            fwd = ConfigManager.findForwardConfig(mapping, res.getCurrentState().getName());
2251
2252            //Config Manager has it's own implementation because it seems that
2253
//mapping.findForwardConfig does NOT return the same results as
2254
//iterating through the forward array and returning the match :(
2255
// fwd = mapping.findForwardConfig(res.getCurrentState().getName());
2256
}
2257        /* If nothing yet (e.g. no error and no forward), then use default
2258    */

2259        if (fwd == null) {
2260            if (log.isDebugEnabled()) {
2261                log.debug("No forward for '" +
2262                        res.getCurrentState().getName() +
2263                        "' in controller '" + res.getControllerClass() +
2264                        "' using mapping for '" + mapping.getPath() +
2265                        "', which is for class '" + mapping.getType() +
2266                        "'. Using default view.");
2267            }
2268
2269
2270            fwd = mapping.getModuleConfig().findForwardConfig("default");
2271        }
2272
2273        fwd = remapFromExtension(fwd, mapping, req);
2274
2275        if (log.isDebugEnabled()) {
2276            if (fwd == null) {
2277                log.debug("Controller returning a null fwd object");
2278            } else {
2279                log.debug("Controller '" + res.getControllerClass() +
2280                        "', state '" + res.getCurrentState().getName() +
2281                        "' forwarding to view '" + fwd.getPath() + "'");
2282            }
2283        }
2284
2285        ActionForward forward = new ActionForward(fwd.getPath(), fwd.getPath()
2286                , fwd.getRedirect(), fwd.getContextRelative());
2287
2288        return forward;
2289    }
2290
2291    /**
2292     * if the extension in the current forward is ".xsl" or ".xslt",
2293     * remap the forward to "xml", and set a parameter for the xsl style sheet.
2294     * author: Larry Hamel, CodeGuild, Inc.
2295     *
2296     * @param fwd The ForwardConfig object
2297     * @param mapping the ActionConfig for the particular controller instance
2298     * @param req the ServletControllerRequest for the request.
2299     * @return ForwardConfig instance.
2300     * @throws ControllerException upon error
2301     */

2302    protected ForwardConfig remapFromExtension(ForwardConfig fwd,
2303                                               ActionConfig mapping,
2304                                               ServletControllerRequest req) throws ControllerException {
2305
2306        if (fwd != null) {
2307            String JavaDoc path = fwd.getPath();
2308            if (path != null) {
2309                int lastDot = path.lastIndexOf('.');
2310                if (lastDot != -1) {
2311                    String JavaDoc ext = path.substring(lastDot);
2312                    if (ext.equalsIgnoreCase(".xsl") || ext.equalsIgnoreCase(".xslt")) {
2313                        fwd = mapping.getModuleConfig().findForwardConfig("xml");
2314                        if (fwd == null) {
2315                            log.warn("cannot find action forward 'xml' which must be defined when using XSL forwards");
2316                        }
2317                        req.setParameter("xsl", path);
2318                    }
2319                }
2320            }
2321        }
2322        return fwd;
2323    }
2324
2325
2326    /**
2327     * redirect response so that URL changes in client browser
2328     *
2329     * @param response The Servlet Response
2330     * @param redirectURL The URL to redirect the browser to.
2331     * @throws IOException upon browser error.
2332     */

2333    protected void redirectRequest(HttpServletResponse JavaDoc response,
2334                                   String JavaDoc redirectURL)
2335            throws IOException JavaDoc {
2336
2337
2338        try {
2339            if (log.isDebugEnabled()) {
2340                log.debug("redirecting to: " + redirectURL);
2341            }
2342
2343
2344            response.sendRedirect(redirectURL);
2345        } catch (java.lang.IllegalStateException JavaDoc e) {
2346            log.error("Error performing redirect.", e);
2347        }
2348
2349
2350        // don't flush so that subclasses can make use of this too;
2351
// sendRedirect() just sets headers to 301
2352
}
2353
2354    /**
2355     * Populate the state form with any matching request parameters.
2356     *
2357     * @param stateForm The name of the state form to populate
2358     * @param request The source of the variables for the state form.
2359     * @throws ControllerException upon <code>BeanUtils.populate()</code> error
2360     */

2361    protected void populateStateForm(StateForm stateForm,
2362                                     ControllerRequest request)
2363            throws ControllerException {
2364        try {
2365            BeanUtils.populate(stateForm, request.getParameters());
2366        } catch (Exception JavaDoc e) {
2367            throw new ControllerException(e);
2368        }
2369    }
2370
2371    /**
2372     * Return the name of the prompt state that is 'previous' to the passed
2373     * in prompt state in the sequence for this controller. If the
2374     * passed in state is the first prompt state in this controller then
2375     * null will be returned.
2376     *
2377     * @param nextState The state to check against
2378     * @return the previous prompt state
2379     */

2380    protected String JavaDoc previousPromptState(State nextState) {
2381        String JavaDoc previousPrompt = null;
2382        int i = promptStates.indexOf(nextState);
2383
2384
2385        if (i > 0) { //Previous prompt(s) exist
2386
State previousState = (State) promptStates.get(i - 1);
2387            previousPrompt = previousState.getName();
2388        }
2389
2390
2391        return previousPrompt;
2392    }
2393
2394    /**
2395     * <p/>
2396     * This method picks up the following routing parameters from the current request
2397     * and builds transition objects from them before the state is invoked.
2398     * </p>
2399     * <p/>
2400     * 1. "Controller Success"
2401     * These parameters identify the state that should be run once this
2402     * controller completes successfully (ie the'final' state has completed without errors).
2403     * Any controller success parameters will cause this method to put a
2404     * serialized transition object specific to this controller in session scope.
2405     * </p>
2406     * <p/>
2407     * <p/>
2408     * 2. "State Success"
2409     * These parameters identify the state that should be run if this state
2410     * completes without errors.
2411     * Any state success parameters will cause this method to assign a
2412     * transition object to this currently running state.
2413     * </p>
2414     * <p/>
2415     * <p/>
2416     * 3. "State Error"
2417     * These parameters identify the state that should be run if this state
2418     * completes with errors.
2419     * Any state error parameters will cause this method to assign a
2420     * transition object to this currently running state.
2421     * </p>
2422     * <p/>
2423     * <b>Note</b>, the transition specified at this time can be overridden by
2424     * the state when it executes.
2425     *
2426     * @param nextState the next state to check process
2427     * @param request The controllerRequest object
2428     * @throws ControllerException upon error.
2429     */

2430    protected void processRequestTransitions(State nextState,
2431                                             ControllerRequest request)
2432            throws ControllerException {
2433        PersistentSession session = request.getSession();
2434        String JavaDoc sessionKey = CTL_SUCC_TRAN + getClass().getName();
2435
2436
2437        //process any CONTROLLER SUCCESS routing
2438
String JavaDoc controllerSuccessReturn = request.getParameter(CTL_SUCC_TRAN);
2439
2440
2441        if (controllerSuccessReturn != null) {
2442            request.removeParameter(CTL_SUCC_TRAN);
2443            session.setPersistentAttribute(sessionKey, controllerSuccessReturn);
2444        } else {
2445            String JavaDoc controllerSuccessReturnController = request.getParameter(CTL_SUCC_CTL);
2446
2447
2448            if (controllerSuccessReturnController != null) {
2449                String JavaDoc controllerSuccessReturnState = request.getParameter(CTL_SUCC_STATE);
2450                Transition t = new Transition();
2451                t.setState(controllerSuccessReturnState);
2452                t.setControllerObject(controllerSuccessReturnController);
2453                FastStringBuffer fsb = FastStringBuffer.getInstance();
2454                try {
2455                    controllerSuccessReturn = t.toXML(fsb).toString();
2456                } finally {
2457                    fsb.release();
2458                    fsb = null;
2459                }
2460                session.setPersistentAttribute(sessionKey,
2461                        controllerSuccessReturn);
2462                request.removeParameter(CTL_SUCC_CTL);
2463                request.removeParameter(CTL_SUCC_STATE);
2464            }
2465        }
2466
2467
2468        //process any STATE SUCCESS routing
2469
Transition successTransition = null;
2470        String JavaDoc successTran = request.getParameter(STATE_SUCC_TRAN);
2471
2472
2473        if (successTran != null) {
2474            request.removeParameter(STATE_SUCC_TRAN);
2475
2476
2477            try {
2478                successTransition = Transition.fromXML(successTran);
2479            } catch (Exception JavaDoc e) {
2480                throw new ControllerException(e);
2481            }
2482        }
2483
2484
2485        String JavaDoc stateSuccessReturnController = request.getParameter(STATE_SUCC_CTL);
2486
2487
2488        if (stateSuccessReturnController != null) {
2489            if (successTransition != null) {
2490                throw new ControllerException("State cannot handle stateSuccessReturn Transition object " +
2491                        "AND stateSuccessXXX parameters in the same call.");
2492            }
2493
2494
2495            String JavaDoc stateSuccessReturnState = request.getParameter(STATE_SUCC_STATE);
2496            successTransition = new Transition();
2497            successTransition.setState(stateSuccessReturnState);
2498            successTransition.setControllerObject(stateSuccessReturnController);
2499            request.removeParameter(STATE_SUCC_CTL);
2500            request.removeParameter(STATE_SUCC_STATE);
2501        }
2502        //Only set if not null. This allows design-time defaults if no runtime routing overrides it.
2503
if (successTransition != null) {
2504            nextState.setSuccessTransition(successTransition);
2505        }
2506
2507
2508        //process any STATE ERROR routing
2509
Transition errorTransition = null;
2510        String JavaDoc errorTran = request.getParameter(STATE_ERR_TRAN);
2511
2512
2513        if (errorTran != null) {
2514            request.removeParameter(STATE_ERR_TRAN);
2515
2516
2517            try {
2518                errorTransition = Transition.fromXML(errorTran);
2519            } catch (Exception JavaDoc e) {
2520                throw new ControllerException(e);
2521            }
2522        }
2523
2524
2525        String JavaDoc stateErrorReturnController = request.getParameter(STATE_ERR_CTL);
2526
2527
2528        if (stateErrorReturnController != null) {
2529            if (errorTransition != null) {
2530                throw new ControllerException("State cannot handle stateErrorReturn Transition object " +
2531                        "AND stateErrorXXX parameters in the same call.");
2532            }
2533
2534
2535            String JavaDoc stateErrorReturnState = request.getParameter(STATE_ERR_STATE);
2536            errorTransition = new Transition();
2537            errorTransition.setState(stateErrorReturnState);
2538            errorTransition.setControllerObject(stateErrorReturnController);
2539            request.removeParameter(STATE_ERR_CTL);
2540            request.removeParameter(STATE_ERR_STATE);
2541        }
2542        //Only set if not null. This allows design-time defaults if no runtime routing overrides it.
2543
if (errorTransition != null) {
2544            nextState.setErrorTransition(errorTransition);
2545        }
2546    }
2547
2548    /**
2549     * This method is the traffic cop that determines which transition to execute
2550     * after a state completes.
2551     * <p/>
2552     * 1. "State Error"
2553     * The state returned with errors in the error collection. The 'error transition'
2554     * associated with the state is executed. This transition could have been set
2555     * either in processRequestTransitions() prior to state execution or in the
2556     * state itself during execution.
2557     * </p>
2558     * <p/>
2559     * <p/>
2560     * 2. "State Success"
2561     * The state returned without errors in the error collection. The following
2562     * priority is followed:
2563     * - If the 'success transition' is not null for this state then it is executed.
2564     * - If the state that just completed is a final state then:
2565     * - If the 'chaining transition' is not null for this controller then it is executed.
2566     * - If the 'controller success transition' is not null for this controller then
2567     * it is executed.
2568     * </p>
2569     *
2570     * @param request The ControllerRequest Object
2571     * @param response The ControllerResponse Object
2572     * @param nextState the state to transition to.
2573     * @return An instantiated Transition Object
2574     * @throws NonHandleableException upon a fatal error.
2575     */

2576    protected Transition processTransitions(ControllerRequest request,
2577                                            ControllerResponse response,
2578                                            State nextState)
2579            throws ControllerException,
2580            NonHandleableException {
2581        Transition successTransition = nextState.getSuccessTransition();
2582        Transition errorTransition = nextState.getErrorTransition();
2583        Transition chainingTransition = null;
2584        PersistentSession session = request.getSession();
2585        String JavaDoc sessionKey = CTL_SUCC_TRAN + getClass().getName();
2586
2587
2588        //Determine where we will transition to!!
2589
Transition nextTransition = null;
2590
2591
2592        if (response.hasErrors()) {
2593            nextTransition = errorTransition; //could execute this on final state.
2594
} else {
2595            nextTransition = successTransition;
2596
2597            if ((nextTransition == null) && isFinalState(nextState.getName())) {
2598                if (getControllerChainingTransition() != null) {
2599
2600
2601                    //Make it thread-safe
2602
try {
2603                        chainingTransition = (Transition) getControllerChainingTransition().clone();
2604                    } catch (CloneNotSupportedException JavaDoc cne) {
2605                        throw new ControllerException("Cannot clone the chainingTransition class");
2606                    }
2607
2608
2609                    nextTransition = chainingTransition;
2610                } else {
2611                    Object JavaDoc successObject = null;
2612
2613
2614                    try {
2615                        successObject = session.getPersistentAttribute(sessionKey);
2616                    } //Can occur if user invalidated the session! (ugly)
2617
catch (NullPointerException JavaDoc npe) {
2618                        log.debug("Invalidated Session", npe);
2619                    }
2620                    if (successObject != null) {
2621                        String JavaDoc controllerSuccess = successObject.toString();
2622
2623
2624                        try {
2625                            nextTransition = Transition.fromXML(controllerSuccess);
2626                        } catch (Exception JavaDoc e) {
2627                            throw new ControllerException(e);
2628                        }
2629                    }
2630                }
2631            }
2632        }
2633        // Now setup context (if chaining) and then transition. Context includes any
2634
// parameters required for the destination controller as well as any return
2635
// address that the source controller delegates to the destination controller.
2636
if (nextTransition != null) {
2637            if (nextTransition == chainingTransition) {
2638
2639                String JavaDoc oneParamKey = "";
2640                String JavaDoc oneParamValue = "";
2641                Hashtable JavaDoc transitionParams = nextTransition.getParams();
2642                for (Enumeration JavaDoc ep = transitionParams.keys(); ep.hasMoreElements();) {
2643                    oneParamKey = (String JavaDoc) ep.nextElement();
2644                    oneParamValue = (String JavaDoc) transitionParams.get(oneParamKey);
2645
2646                    //Assign the 'required' parameters from the calling controller.
2647
if (oneParamValue.equals("")) {
2648                        oneParamValue = request.getParameter(oneParamKey);
2649                        if (oneParamValue == null) {
2650                            throw new ControllerException("Required chaining parameter missing" + oneParamKey);
2651                        } else { //Update the blank param with its value from the calling state's request
2652
nextTransition.addParam(oneParamKey, oneParamValue);
2653                        }
2654                    }
2655                }
2656
2657
2658                Object JavaDoc successObject = null;
2659                try {
2660                    successObject = session.getPersistentAttribute(sessionKey);
2661                } //Can occur if user invalidated the session! (ugly)
2662
catch (NullPointerException JavaDoc npe) {
2663                    log.debug("Invalidated Session", npe);
2664                }
2665                if (successObject != null) {
2666                    String JavaDoc controllerSuccessReturn = successObject.toString();
2667
2668
2669                    //pass the ctler's return address so the next controller will inherit it.
2670
nextTransition.addParam(CTL_SUCC_TRAN,
2671                            controllerSuccessReturn);
2672                }
2673            }
2674
2675
2676            nextTransition.transition(request, response);
2677
2678
2679            return nextTransition;
2680        }
2681
2682
2683        return null; //No transition was handled here.
2684
}
2685
2686    /**
2687     * Set the transition that will be executed when this controller completes
2688     * successfully. This will not likely be used as often as the runtime
2689     * 'controller success' routing parameters. However when used, it will
2690     * relay any 'controller success' parameters onto the next controller
2691     * so that the return path is not lost.
2692     *
2693     * @param newControllerChainingTransition
2694     * The new transition for when the
2695     * controller completes successfully.
2696     * @throws NonHandleableException upon incorrect parameters.
2697     */

2698    protected void setControllerChainingTransition(Transition newControllerChainingTransition)
2699            throws NonHandleableException {
2700        if (!newControllerChainingTransition.isExternalTransition(getClass().getName())) {
2701            throw new NonHandleableException("Chained Transition must go to another controller.");
2702        }
2703        if (newControllerChainingTransition.isReturnToSenderEnabled()) {
2704            throw new NonHandleableException("Chained Transition cannot return to sender.");
2705        }
2706
2707
2708        controllerChainingTransition = newControllerChainingTransition;
2709    }
2710
2711    /**
2712     * Set the transition that will be executed when a user attempts to run
2713     * a state he doesn't have authorization on. As always, this transition
2714     * can have return-to-sender enabled. This could allow a login screen
2715     * to be called and then control would automatically return to the
2716     * state whose authorization failed.
2717     *
2718     * @param newControllerSecurityTransition
2719     * To execute.
2720     */

2721    protected void setControllerSecurityTransition(Transition newControllerSecurityTransition) {
2722        controllerSecurityTransition = newControllerSecurityTransition;
2723    }
2724
2725    /**
2726     * Convenience method to be able to access the state
2727     * as a property from a JSP.
2728     *
2729     * @param newState The newState to set to.
2730     * @param params The ControllerRequest object
2731     * @return a newly instantiated ControllerResponse
2732     * @throws ControllerException upon error
2733     * @throws NonHandleableException upon fatal error
2734     */

2735    public ControllerResponse setCurrentState(String JavaDoc newState,
2736                                              ControllerRequest params)
2737            throws ControllerException,
2738            NonHandleableException {
2739        return newState(newState, params);
2740    } /* setCurrentState(String) */
2741
2742
2743    /**
2744     * Set what state to invoke if no state parameter is set.
2745     *
2746     * @param newInitialState The state to use as the initial state.
2747     */

2748    public void setInitialState(String JavaDoc newInitialState) {
2749        if (!StringUtil.notNull(newInitialState).equals("")) {
2750            if (getState(newInitialState) != null) {
2751                initialStates.put(getClass().getName(), newInitialState);
2752            } else {
2753                throw new IllegalArgumentException JavaDoc("Unable to set initial state " + " to '" +
2754                        newInitialState + "', this is not a valid " +
2755                        "state for this controller.");
2756            }
2757        }
2758    } /* setInitialState(String) */
2759
2760
2761    /**
2762     * Tell this Controller object what Schema it belongs to. This is used
2763     * when the Controller tries to use it's "getString(String, Object[])"
2764     * method to prepare internationalized messages - it passes the call
2765     * along to the appropriate schema which knows how to locate the
2766     * proper message file.
2767     *
2768     * @param schemaClass The classname of the Schema to associate with.
2769     */

2770    protected void setSchema(String JavaDoc schemaClass) {
2771        StringUtil.assertNotBlank(schemaClass,
2772                "Must specify a non-blank schema");
2773        Stack JavaDoc stack = (Stack JavaDoc) schemas.get(getClass().getName());
2774        if (stack == null) {
2775            stack = new Stack JavaDoc();
2776            //We have to push Expresso on because of items such as Must Login First
2777
stack.push(com.jcorporate.expresso.core.ExpressoSchema.class.getName());
2778        }
2779
2780        if (!stack.contains(schemaClass)) {
2781            stack.push(schemaClass);
2782        }
2783
2784        schemas.put(getClass().getName(), stack);
2785    } /* setSchema(String) */
2786
2787
2788    /**
2789     * Identical to setSchema(String) but provides a typesafe way of passing
2790     * parameters. Example: <br />
2791     * <code> setSchema(com.jcorporate.expresso.core.ExpressoSchema
2792     * .<b>class</b>);</code> <br />
2793     *
2794     * @param schemaClass The Class of the schema to set
2795     */

2796    protected void setSchema(Class JavaDoc schemaClass) {
2797        if (schemaClass == null) {
2798            throw new IllegalArgumentException JavaDoc("Parameter schemaClass must not be null");
2799        }
2800
2801
2802        setSchema(schemaClass.getName());
2803    }
2804
2805    /**
2806     * Allows for DBCreate to set up proper default
2807     * values for views, etc.
2808     *
2809     * @param dbName the name of the db to add these values to.
2810     * @throws DBException upon error
2811     */

2812    public void setupDefaultValues(String JavaDoc dbName)
2813            throws DBException {
2814    }
2815
2816
2817    /**
2818     * This method is called before a state executes. It will assign a
2819     * return-to-sender transition to the state. This transition will
2820     * include the parameters that this state is being called with.
2821     * <p/>
2822     * If this state causes a transition (with return-to-sender enabled) to
2823     * be executed, then this state's return-to-sender transition will be
2824     * serialized and passed to the next controller. This controller will
2825     * then use this transition (once the controller completes successfull)
2826     * to return to this state with the same parameters that this state was
2827     * initally called with.
2828     * <p/>
2829     * This method will also check for a request parameter that overrides the
2830     * return-to-sender transition that would normally be associated with this
2831     * state. This is useful when the final state reruns all of the controller's
2832     * handle states. In that case, any transitions should return to the final
2833     * state and not the handle state that caused the transition.
2834     *
2835     * @param nextState = The state that is about to be executed
2836     * @param request The parsed ControllerRequest object
2837     * @throws ControllerException upon error.
2838     */

2839    protected void setupReturnToSender(State nextState,
2840                                       ControllerRequest request)
2841            throws ControllerException {
2842        Transition returnToSender = null;
2843        String JavaDoc tran = request.getParameter(RETURN_TO_SENDER_TRAN);
2844
2845
2846        if (tran != null) {
2847            request.removeParameter(RETURN_TO_SENDER_TRAN);
2848
2849
2850            try {
2851                returnToSender = Transition.fromXML(tran);
2852            } catch (Exception JavaDoc e) {
2853                throw new ControllerException(e);
2854            }
2855        }
2856        //This is the transition that will be executed from another state/controller to return to
2857
//this state. So don't call enableReturnToSender() otherwise we will run into a little
2858
//ping pong action.
2859
if (returnToSender == null) { //don't override if passed in (eg final mode)
2860
returnToSender = new Transition();
2861            returnToSender.setState(nextState.getName());
2862            returnToSender.setControllerObject(getClass().getName()); //back to here
2863

2864
2865            //Cache the parameters so that when/if this Transition is executed by some other state, our
2866
//input parameters to this state will be available.
2867
returnToSender.setReturnToSenderParms(request);
2868        }
2869
2870
2871        nextState.setReturnToSender(returnToSender);
2872    }
2873
2874    /**
2875     * Is this state allowed for the current user? The generic controller
2876     * object can't determine this, but the DBController child of this
2877     * object checks the database to determine which users can access which
2878     * states.
2879     *
2880     * @param newState The name of the state being checked
2881     * @param params The ControllerRequestObject
2882     * @return True if the state is allowed, else false if it is not
2883     * @throws ControllerException upon error
2884     */

2885    public boolean stateAllowed(String JavaDoc newState, ControllerRequest params)
2886            throws ControllerException {
2887        return true;
2888    } /* stateAllowed(String) */
2889
2890
2891    protected void transition(String JavaDoc newState, ControllerRequest req,
2892                              ControllerResponse res)
2893            throws ControllerException, NonHandleableException {
2894        transition(newState, req, res, true);
2895    } /* transition(String, ControllerRequest, ControllerResponse) */
2896
2897    /**
2898     * convenience method for transition to other controller
2899     *
2900     * @param newState the new state to transition to
2901     * @param externalController the class of the external controller
2902     * @param req the ControllerRequest parameter that the state handler has
2903     * @param res the ControllerResponse object that was passed to your state method
2904     * @throws ControllerException upon error
2905     * @throws NonHandleableException upon fatal error
2906     */

2907    protected void transition(String JavaDoc newState, Class JavaDoc externalController, ControllerRequest req,
2908                              ControllerResponse res)
2909            throws ControllerException, NonHandleableException {
2910
2911        Transition trans = new Transition("", "", externalController, newState);
2912        trans.transition(req, res);
2913    }
2914
2915
2916    /**
2917     * Transition to an internal state. By internal state we mean that this method
2918     * is used for transitioning to another 'state' inside the same controller class
2919     * [inherited states not withstanding]. You cannot, however use this method
2920     * to transfer control between controllers
2921     *
2922     * @param newState the state to transition to
2923     * @param req The ControllerRequest object that has been given your state
2924     * handler
2925     * @param res The ControllerResponse object that has been given to your
2926     * state handler
2927     * @param clear Should existing ControllerElements (Input/Output/Block/Transition)
2928     * be removed from the resulting ControllerResponse (set to true if that is the
2929     * desired behavior)
2930     * @throws ControllerException upon error
2931     * @throws NonHandleableException upon fatal error
2932     */

2933    protected void transition(String JavaDoc newState, ControllerRequest req,
2934                              ControllerResponse res, boolean clear)
2935            throws ControllerException, NonHandleableException {
2936        if (log.isDebugEnabled()) {
2937            log.debug("Transitioning to state '" + newState +
2938                    "' in controller '" + getClass().getName() + "'");
2939        }
2940
2941
2942        ControllerResponse newResponse = newState(newState, req);
2943
2944
2945        if (clear) {
2946            res.clearOutputCache();
2947            res.clearInputCache();
2948            res.clearTransitionCache();
2949            res.clearBlockCache();
2950        }
2951
2952
2953        res.setDBName(newResponse.getDBName());
2954        res.setCustomResponse(newResponse.isCustomResponse());
2955        res.setCurrentState(newResponse.getCurrentState());
2956        res.setStyle(newResponse.getStyle());
2957        res.setTitle(newResponse.getTitle());
2958
2959        Block oneBlock = null;
2960        Vector JavaDoc newBlocks = newResponse.getBlocks();
2961
2962
2963        if (newBlocks != null) {
2964            for (Enumeration JavaDoc eb = newBlocks.elements(); eb.hasMoreElements();) {
2965                oneBlock = (Block) eb.nextElement();
2966                oneBlock.setControllerResponse(res);
2967                res.addBlock(oneBlock);
2968            }
2969        }
2970
2971
2972        Output oneOutput = null;
2973        Vector JavaDoc newOutputs = newResponse.getOutputs();
2974
2975
2976        if (newOutputs != null) {
2977            for (Enumeration JavaDoc eo = newOutputs.elements(); eo.hasMoreElements();) {
2978                oneOutput = (Output) eo.nextElement();
2979                oneOutput.setControllerResponse(res);
2980                res.addOutput(oneOutput);
2981            }
2982        }
2983
2984
2985        Vector JavaDoc newInputs = newResponse.getInputs();
2986        Input oneInput = null;
2987
2988
2989        if (newInputs != null) {
2990            for (Enumeration JavaDoc ei = newInputs.elements(); ei.hasMoreElements();) {
2991                oneInput = (Input) ei.nextElement();
2992                oneInput.setControllerResponse(res);
2993                res.addInput(oneInput);
2994            }
2995        }
2996
2997
2998        Vector JavaDoc newTransitions = newResponse.getTransitions();
2999
3000
3001        if (newTransitions != null) {
3002            Transition oneTransition = null;
3003
3004
3005            for (Enumeration JavaDoc et = newResponse.getTransitions().elements();
3006                 et.hasMoreElements();) {
3007                oneTransition = (Transition) et.nextElement();
3008                oneTransition.setControllerResponse(res);
3009                res.addTransition(oneTransition);
3010            }
3011        }
3012
3013
3014        res.setControllerClass(newResponse.getControllerClass());
3015    } /* transition(String, ControllerRequest, ControllerResponse) */
3016
3017
3018    /**
3019     * Move the state's form data back into the controller's form.
3020     * The controller's form is specified in the struts config file. The state's
3021     * form is specified in the state's stateFormClass attribute.
3022     *
3023     * @param stateForm The stateForm to unload
3024     * @param controllerForm The associated controllerForm
3025     * @throws ControllerException upon error transferring the data between
3026     * the forms.
3027     */

3028    protected void unloadStateForm(StateForm stateForm,
3029                                   ActionForm controllerForm)
3030            throws ControllerException {
3031        if (stateForm != controllerForm) {
3032            try { //stateForm could be null here.
3033
BeanUtils.populate(controllerForm,
3034                        BeanUtils.describe(stateForm));
3035            } catch (Exception JavaDoc e) {
3036                if (com.jcorporate.expresso.core.controller.DefaultForm.class.getName()
3037                        .equals(controllerForm.getClass().getName())) {
3038                    return;
3039                }
3040                throw new ControllerException(e);
3041            }
3042        }
3043    }
3044
3045
3046    /**
3047     * redirect response so that URL changes in client browser
3048     *
3049     * @param request The ControllerRquest object.
3050     * @param response The ControllerResponse object
3051     * @param redirectURL The URL to redirect the browser to.
3052     * @throws IOException upon redirect error.
3053     */

3054    public void redirectRequest(ControllerRequest request,
3055                                ControllerResponse response,
3056                                String JavaDoc redirectURL)
3057            throws IOException JavaDoc {
3058        response.setCustomResponse(true); // tell expresso not to forward to jsp after finishing controller method
3059
ServletControllerRequest sr = (ServletControllerRequest) request;
3060        HttpServletResponse JavaDoc httpResponse = (HttpServletResponse JavaDoc) sr.getServletResponse();
3061        this.redirectRequest(httpResponse, redirectURL);
3062    }
3063
3064
3065    /**
3066     * Checks to see if SSL should be toggled for this
3067     * action
3068     *
3069     * @param aRequest The current request object
3070     * @param aResponse The current response object
3071     * @param isSecure should the request be secure.
3072     * @return true if this state should be toggled for protocol
3073     */

3074    private boolean checkSsl(HttpServletRequest JavaDoc aRequest,
3075                             HttpServletResponse JavaDoc aResponse,
3076                             boolean isSecure) {
3077
3078
3079        String JavaDoc redirectString = null;
3080        com.jcorporate.expresso.core.misc.ConfigExpresso config =
3081                ConfigManager.getConfig();
3082        redirectString =
3083                SecureRequestUtils.getRedirectString(aRequest,
3084                        config.getHttpPort(),
3085                        config.getSslPort(),
3086                        isSecure);
3087
3088
3089        if (redirectString != null) {
3090            try {
3091                // Redirect the page to the desired URL
3092
this.redirectRequest(aResponse, redirectString);
3093                return true;
3094            } catch (java.io.IOException JavaDoc ioe) {
3095                log.warn("IOException in redirect: ", ioe);
3096// System.out.println("IOException in redirect" + ioe.getMessage());
3097
}
3098        }
3099
3100
3101        return false;
3102    }
3103
3104
3105    /**
3106     * Call from subclass to log into Category with subclass name
3107     * will create logger with subclass name as necessary.
3108     * @return org.apache.logj.Logger
3109     */

3110    public synchronized Logger getLogger() {
3111        if (mLog == null) {
3112            setupSubclassLog();
3113        }
3114
3115        return mLog;
3116    }
3117
3118    /**
3119     * setup a subclass logger separately from the base controller logger.
3120     * Is this right? *PP*
3121     */

3122    protected synchronized void setupSubclassLog() {
3123        if (mLog == null) {
3124            mLog = Logger.getLogger(getClass());
3125        }
3126    }
3127
3128
3129    /**
3130     * Generate a new transaction token, to be used for enforcing a single
3131     * request for a particular transaction.
3132     * <p/>
3133     * <p/>
3134     * Thanks to "Raul DAVIDOVICH" (R.DAVIDOVICH@caconcology.com)
3135     * </p>
3136     *
3137     * @param request The request we are processing
3138     * @return transaction token usually cryptographically created
3139     */

3140    protected String JavaDoc generateToken(ControllerRequest request) {
3141        if (request instanceof ServletControllerRequest) {
3142            ServletControllerRequest scr = (ServletControllerRequest) request;
3143            return super.generateToken((HttpServletRequest JavaDoc) scr.getServletRequest());
3144        } else {
3145            return "no-web-environment.transaction.token";
3146        }
3147    }
3148
3149
3150    /**
3151     * Return <code>true</code> if there is a transaction token stored in
3152     * the user's current session, and the value submitted as a request
3153     * parameter with this action matches it. Returns <code>false</code>
3154     * under any of the following circumstances:
3155     * <ul>
3156     * <li>No session associated with this request</li>
3157     * <li>No transaction token saved in the session</li>
3158     * <li>No transaction token included as a request parameter</li>
3159     * <li>The included transaction token value does not match the
3160     * transaction token in the user's session</li>
3161     * </ul>
3162     *
3163     * @param request The servlet request we are processing
3164     * @return boolean value if the request transaction token matched
3165     * the stored transaction token.
3166     * @see #generateToken
3167     * @see #resetToken
3168     * @see #saveToken
3169     */

3170    protected boolean isTokenValid(ControllerRequest request) {
3171        if (request instanceof ServletControllerRequest) {
3172            ServletControllerRequest scr = (ServletControllerRequest) request;
3173            return super.isTokenValid((HttpServletRequest JavaDoc) scr.getServletRequest());
3174        } else {
3175            // Also true in a new app web environment e.g command line interface
3176
return true;
3177        }
3178    }
3179
3180
3181    /**
3182     * Reset the saved transaction token in the user's session. This
3183     * indicates that transactional token checking will not be needed
3184     * on the next request that is submitted.
3185     *
3186     * @param request The servlet request we are processing
3187     * @see #saveToken
3188     * @see #isTokenValid
3189     */

3190    protected void resetToken(ControllerRequest request) {
3191        if (request instanceof ServletControllerRequest) {
3192            ServletControllerRequest scr = (ServletControllerRequest) request;
3193            super.resetToken(scr.getHttpServletRequest());
3194        }
3195    }
3196
3197    /**
3198     * Save a new transaction token in the user's current session,
3199     * creating a new session if necessary.
3200     *
3201     * @param request The servlet request we are processing
3202     * @see #resetToken
3203     * @see #isTokenValid
3204     */

3205    protected void saveToken(ControllerRequest request) {
3206        if (request instanceof ServletControllerRequest) {
3207            ServletControllerRequest scr = (ServletControllerRequest) request;
3208            super.saveToken(scr.getHttpServletRequest());
3209        }
3210    }
3211
3212    /**
3213     * Fetches array of parameter values from underlying HTTP request; use this in a web app to access
3214     * the underlying parameters in the HTTP request which have the same name; parameters with the same name are not
3215     * reflected in the hashtable maintained by ControllerRequest;
3216     *
3217     * @param request cast ControllerRequest to get required type
3218     * @param paramName key to look for among all parameters
3219     * @return an array of String objects containing all of the values the given request parameter has, or null if the parameter does not exist.
3220     */

3221    public static String JavaDoc[] getParamValues(ServletControllerRequest request, String JavaDoc paramName) {
3222        return request.getParamValues(paramName);
3223    }
3224} /* Controller */
3225
Popular Tags