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   &nb