0001: /* ====================================================================
0002: * The Jcorporate Apache Style Software License, Version 1.2 05-07-2002
0003: *
0004: * Copyright (c) 1995-2002 Jcorporate Ltd. All rights reserved.
0005: *
0006: * Redistribution and use in source and binary forms, with or without
0007: * modification, are permitted provided that the following conditions
0008: * are met:
0009: *
0010: * 1. Redistributions of source code must retain the above copyright
0011: * notice, this list of conditions and the following disclaimer.
0012: *
0013: * 2. Redistributions in binary form must reproduce the above copyright
0014: * notice, this list of conditions and the following disclaimer in
0015: * the documentation and/or other materials provided with the
0016: * distribution.
0017: *
0018: * 3. The end-user documentation included with the redistribution,
0019: * if any, must include the following acknowledgment:
0020: * "This product includes software developed by Jcorporate Ltd.
0021: * (http://www.jcorporate.com/)."
0022: * Alternately, this acknowledgment may appear in the software itself,
0023: * if and wherever such third-party acknowledgments normally appear.
0024: *
0025: * 4. "Jcorporate" and product names such as "Expresso" must
0026: * not be used to endorse or promote products derived from this
0027: * software without prior written permission. For written permission,
0028: * please contact info@jcorporate.com.
0029: *
0030: * 5. Products derived from this software may not be called "Expresso",
0031: * or other Jcorporate product names; nor may "Expresso" or other
0032: * Jcorporate product names appear in their name, without prior
0033: * written permission of Jcorporate Ltd.
0034: *
0035: * 6. No product derived from this software may compete in the same
0036: * market space, i.e. framework, without prior written permission
0037: * of Jcorporate Ltd. For written permission, please contact
0038: * partners@jcorporate.com.
0039: *
0040: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0041: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0042: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0043: * DISCLAIMED. IN NO EVENT SHALL JCORPORATE LTD OR ITS CONTRIBUTORS
0044: * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0045: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
0046: * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0047: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0048: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0049: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0050: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0051: * SUCH DAMAGE.
0052: * ====================================================================
0053: *
0054: * This software consists of voluntary contributions made by many
0055: * individuals on behalf of the Jcorporate Ltd. Contributions back
0056: * to the project(s) are encouraged when you make modifications.
0057: * Please send them to support@jcorporate.com. For more information
0058: * on Jcorporate Ltd. and its products, please see
0059: * <http://www.jcorporate.com/>.
0060: *
0061: * Portions of this software are based upon other open source
0062: * products and are subject to their respective licenses.
0063: */
0064:
0065: package com.jcorporate.expresso.core.controller;
0066:
0067: import com.jcorporate.expresso.core.ExpressoConstants;
0068: import com.jcorporate.expresso.core.ExpressoSchema;
0069: import com.jcorporate.expresso.core.controller.session.PersistentSession;
0070: import com.jcorporate.expresso.core.db.DBException;
0071: import com.jcorporate.expresso.core.dbobj.Schema;
0072: import com.jcorporate.expresso.core.dbobj.SchemaFactory;
0073: import com.jcorporate.expresso.core.i18n.Messages;
0074: import com.jcorporate.expresso.core.jsdkapi.GenericDispatcher;
0075: import com.jcorporate.expresso.core.misc.ConfigManager;
0076: import com.jcorporate.expresso.core.misc.ConfigurationException;
0077: import com.jcorporate.expresso.core.misc.DateTime;
0078: import com.jcorporate.expresso.core.misc.EventHandler;
0079: import com.jcorporate.expresso.core.misc.SerializableString;
0080: import com.jcorporate.expresso.core.misc.StringUtil;
0081: import com.jcorporate.expresso.core.security.DelayThread;
0082: import com.jcorporate.expresso.core.security.User;
0083: import com.jcorporate.expresso.core.servlet.CheckLogin;
0084: import com.jcorporate.expresso.kernel.util.ClassLocator;
0085: import com.jcorporate.expresso.kernel.util.FastStringBuffer;
0086: import com.jcorporate.expresso.services.dbobj.ControllerSecurity;
0087: import com.jcorporate.expresso.services.dbobj.RegistrationDomain;
0088: import com.jcorporate.expresso.services.dbobj.Setup;
0089: import org.apache.commons.beanutils.BeanUtils;
0090: import org.apache.log4j.Logger;
0091: import org.apache.struts.Globals;
0092: import org.apache.struts.action.Action;
0093: import org.apache.struts.action.ActionForm;
0094: import org.apache.struts.action.ActionForward;
0095: import org.apache.struts.action.ActionMapping;
0096: import org.apache.struts.config.ActionConfig;
0097: import org.apache.struts.config.ForwardConfig;
0098: import org.apache.struts.upload.MultipartRequestHandler;
0099:
0100: import javax.servlet.ServletException;
0101: import javax.servlet.http.HttpServletRequest;
0102: import javax.servlet.http.HttpServletResponse;
0103: import java.io.ByteArrayOutputStream;
0104: import java.io.IOException;
0105: import java.io.PrintStream;
0106: import java.io.Serializable;
0107: import java.lang.reflect.InvocationTargetException;
0108: import java.lang.reflect.Method;
0109: import java.util.ArrayList;
0110: import java.util.Enumeration;
0111: import java.util.Hashtable;
0112: import java.util.Iterator;
0113: import java.util.Locale;
0114: import java.util.Stack;
0115: import java.util.Vector;
0116:
0117: /**
0118: * A sequence of interaction with a user is defined as a "Controller".
0119: * <p/>
0120: * An Expresso controller is best thought of as a finite state machine. The only
0121: * real difference is that because of the stateless nature of the web, anybody
0122: * can reach any state with using the "state" parameter in the URL.
0123: * </p>
0124: * <p/>
0125: * Usually for a web deployment environment, you would derive your own controller
0126: * from DBController, which adds a database-backed Security matrix to the
0127: * controller's base capabilities. Controllers (and DBControllers) do not necessarily
0128: * need to be run inside a servlet environment. If they never downcast their ControllerResponse
0129: * object to ServletControllerResponse, the Controller can also be used in products
0130: * such as Expresso Webservices, or
0131: * </p>
0132: * <p>See the <em>Expresso Developer's Guide</em> for more information on creating
0133: * and using a Controller.</p>
0134: * <p/>
0135: * A Controller takes a series of parameters (optional) and then provides
0136: * a series of Blocks, Inputs, Outputs, and Transitions to the client</p>
0137: * <itemizedlist>
0138: * <listitem>An Input specifies a piece of information we want FROM the client</listitem>
0139: * <listitem>An Output is a piece of information we supply TO the client</listitem>
0140: * <listitem>A Transition is an action the client can take from this point - e.g.
0141: * further controllers that are followups to this controller</listitem>
0142: * <listitem>A Block is a collection of Inputs, Outputs, Transitions or other Blocks</listitem>
0143: * </itemizedlist>
0144: *
0145: * @see com.jcorporate.expresso.core.controller.DBController
0146: * @see com.jcorporate.expresso.core.controller.SecureIfSetController
0147: */
0148: public abstract class Controller extends Action implements Serializable {
0149: /**
0150: * log for methods in this class alone
0151: */
0152: private static Logger log = Logger.getLogger(Controller.class);
0153:
0154: /**
0155: * if subclass calls getLogger(), this will create logger with
0156: * subclass name
0157: */
0158: protected Logger mLog = null;
0159:
0160: /**
0161: * Controller response key
0162: */
0163: public final static String RESPONSE_KEY = ExpressoConstants.CONTROLLER_RESPONSE_KEY;
0164: /**
0165: * Controller original URL key
0166: */
0167: public final static String ORIGINAL_URL_KEY = ExpressoConstants.CONTROLLER_ORIGINAL_URL_KEY;
0168: /**
0169: * Controller request key
0170: */
0171: public final static String REQUEST_KEY = ExpressoConstants.CONTROLLER_REQUEST_KEY;
0172:
0173: /**
0174: * New state exception key
0175: */
0176: public final static String NEWSTATE_EXCEPTION_KEY = ExpressoConstants.NEWSTATE_EXCEPTION_KEY;
0177:
0178: // *PP* I have not deprecated these at all Tue Jun 24 20:09:00 BST 2003
0179: public final static String CTL_SUCC_TRAN = "controllerSuccessReturn";
0180: public final static String CTL_SUCC_CTL = "controllerSuccessReturnController";
0181: public final static String CTL_SUCC_STATE = "controllerSuccessReturnState";
0182: public final static String STATE_SUCC_TRAN = "stateSuccessReturn";
0183: public final static String STATE_SUCC_CTL = "stateSuccessReturnController";
0184: public final static String STATE_SUCC_STATE = "stateSuccessReturnState";
0185: public final static String STATE_ERR_TRAN = "stateErrorReturn";
0186: public final static String STATE_ERR_CTL = "stateErrorReturnController";
0187: public final static String STATE_ERR_STATE = "stateErrorReturnState";
0188: public final static String RETURN_TO_SENDER_TRAN = "returnToSender";
0189:
0190: /**
0191: * key for putting state into parameter map
0192: */
0193: public static final String STATE_PARAM_KEY = "state";
0194:
0195: /**
0196: * key for putting controller into parameter map
0197: */
0198: public static final String CONTROLLER_PARAM_KEY = "controller";
0199:
0200: /* The name of the state which is the "initial" state of this controller */
0201: /* E.g. where the controller starts if no state parameter is specified */
0202: private static Hashtable initialStates = new Hashtable();
0203:
0204: /**
0205: * Static map of classes to schema instances
0206: */
0207: private static Hashtable schemas = new Hashtable();
0208:
0209: /**
0210: * States is a hashtable of hashtables, e.g. the states hashtable itself
0211: * is indexed by controller class name. Each entry against a classname is itself a hashtable
0212: * with a list of the valid state objects for the given controller
0213: */
0214: private static Hashtable states = new Hashtable();
0215:
0216: /**
0217: * used to specify the classes of parameters for introspection;
0218: * since these classes are constant across all controllers, we use a static
0219: */
0220: private static final Class[] newStateParams = {
0221: java.lang.String.class,
0222: com.jcorporate.expresso.core.controller.ControllerRequest.class };
0223:
0224: /**
0225: * used for reflection into the state handlers
0226: */
0227: private static final Class[] stateHandlerParams = {
0228: com.jcorporate.expresso.core.controller.ControllerRequest.class,
0229: com.jcorporate.expresso.core.controller.ControllerResponse.class };
0230:
0231: /**
0232: * used for reflection into the state handlers
0233: */
0234: private static final Class[] servletStateHandlerParams = {
0235: com.jcorporate.expresso.core.controller.ServletControllerRequest.class,
0236: com.jcorporate.expresso.core.controller.ControllerResponse.class };
0237:
0238: /**
0239: * promptStates is only updated in the constructor. The instantiation of Controller by Struts is
0240: * synchronized so we don't need to worry about threadsafety while updating this collection even
0241: * though it is shared by many threads(singleton). However, Controller is also instantiated by Transition and
0242: * Controller itself and unlike Struts these are not synchronized. Luckily, unlike Struts, these
0243: * instances are not shared between threads. So we should be OK with no sychronized access and
0244: * also with using a non-synchronized collection (ArrayList).
0245: * The main reason this collection was not static like most of the other fields in this class
0246: * is because of the way this collection is populated. Unlike the 'states' collection, this one
0247: * appends new items to the collection rather than overwriting (hashing). So if this had been
0248: * static, every time Transition/Controller instantiated a new instance of this class it would
0249: * try to append the same states to the collection making the collection inaccurate (and big).
0250: */
0251: private ArrayList promptStates = new ArrayList(3);
0252:
0253: /**
0254: * handleStates is only updated in the constructor. The instantiation of Controller by Struts is
0255: * synchronized so we don't need to worry about threadsafety while updating this collection even
0256: * though it is shared by many threads(singleton). However, Controller is also instantiated by Transition and
0257: * Controller itself and unlike Struts these are not synchronized. Luckily, unlike Struts, these
0258: * instances are not shared between threads. So we should be OK with no sychronized access and
0259: * also with using a non-synchronized collection (ArrayList).
0260: * The main reason this collection was not static like most of the other fields in this class
0261: * is because of the way this collection is populated. Unlike the 'states' collection, this one
0262: * appends new items to the collection rather than overwriting (hashing). So if this had been
0263: * static, every time Transition/Controller instantiated a new instance of this class it would
0264: * try to append the same states to the collection making the collection inaccurate (and big).
0265: */
0266: private ArrayList handleStates = new ArrayList(3);
0267:
0268: private State finalState;
0269:
0270: private Transition controllerChainingTransition = null;
0271:
0272: private Transition controllerSecurityTransition = null;
0273:
0274: /**
0275: * Default constructor
0276: */
0277: public Controller() {
0278:
0279: } /* Controller() */
0280:
0281: /**
0282: * The constructor of the child object should call addFinalState to define
0283: * the last state to be executed for this controller.
0284: * Once the final state is added then no other states can be added via the
0285: * addStatePairing method.
0286: *
0287: * @param newFinalState the new state to use as your final state.
0288: * @throws NonHandleableException if you send it an improper final state.
0289: */
0290: protected void addFinalState(State newFinalState)
0291: throws NonHandleableException {
0292: if (promptStates.contains(newFinalState)
0293: || handleStates.contains(newFinalState)) {
0294: throw new NonHandleableException(
0295: "Final state must not be the same as any of the paired states.");
0296: }
0297:
0298: finalState = newFinalState;
0299: addState(newFinalState);
0300:
0301: //Set the initial state to this final state only if it is the only state in this controller.
0302: if (StringUtil.notNull(getInitialState()).equals("")) {
0303: setInitialState(newFinalState.getName());
0304: }
0305: }
0306:
0307: /**
0308: * If nextState is a prompt state (as determined by the addStatePairing method)
0309: * then add a 'next' and 'previous' transitions as required.
0310: * A 'next' transition will refer to a prompt state's associated validate state.
0311: * A 'previous' transition will refer to the prompt state that precedes the
0312: * passed in state. The 'previous' state will not be added to the response if
0313: * if the passed in state is the first one (iniial state) in this controller.
0314: *
0315: * @param nextState in this controller the new state add
0316: * @param response the ControllerResponse that these transitions will be
0317: * added to.
0318: * @throws ControllerException upon error.
0319: */
0320: protected void addPromptTransitions(State nextState,
0321: ControllerResponse response) throws ControllerException {
0322:
0323: //Tack on the wizards Prev/Next/Finish transitions to any promptStates.
0324: if (isPromptState(nextState)) {
0325: String previousPromptState = previousPromptState(nextState);
0326:
0327: if (previousPromptState != null) {
0328: Transition previousTransition = new Transition();
0329: previousTransition.setControllerObject(getClass()
0330: .getName());
0331: previousTransition.setState(previousPromptState);
0332: previousTransition.setName("previous");
0333: previousTransition.setLabel("Previous");
0334: response.addTransition(previousTransition);
0335: }
0336:
0337: //Add the 'Next' or 'Done' buttons ('Done' is replaced with the name of the final state)
0338: Transition nextTransition = null;
0339: String nextPromptState = nextPromptState(nextState);
0340:
0341: if (nextPromptState != null) { //Would not exist if this is the last screen in wizard
0342: String nextHandleState = nextHandleState(nextState);
0343:
0344: //Safer to use no-arg constructor, this prevents the transition's ownerObject from being set.
0345: //Setting this can prevent the getParamString() method from returning a 'controller' param.
0346: //This can be problematic in situations, eg - DefaultViewHandler uses the getRequestPath()
0347: //of a request to set the form's 'action' attribute which could be a different controller
0348: //from the state's controller. This would raise an exception because no controller parm passed.
0349: nextTransition = new Transition();
0350: nextTransition
0351: .setControllerObject(getClass().getName());
0352: nextTransition.setState(nextHandleState);
0353: nextTransition.setName("next");
0354: nextTransition.setLabel("Next");
0355: nextTransition.addParam(STATE_SUCC_STATE,
0356: nextPromptState); //ie transition to next screen in wizard
0357: nextTransition.addParam(STATE_SUCC_CTL, response
0358: .getControllerClass());
0359: } else { //Final state will be last and must exist - It will auto rerun any handler states
0360: State finalState = getFinalState();
0361:
0362: if (finalState == null) {
0363: throw new ControllerException(
0364: "Final state must exist.");
0365: }
0366:
0367: nextTransition = new Transition();
0368: nextTransition
0369: .setControllerObject(getClass().getName());
0370: nextTransition.setState(finalState.getName());
0371: nextTransition.setName("next");
0372: nextTransition.setLabel(finalState.getDescription());
0373: }
0374:
0375: response.addTransition(nextTransition);
0376: }
0377: }
0378:
0379: /**
0380: * The constructor of the child object should call addState to define
0381: * each of the states available in this method.
0382: *
0383: * @param newState The State object to be added to the list of states for this
0384: * controller
0385: */
0386: protected void addState(State newState) {
0387: try {
0388: newState.setController(this );
0389: } catch (ControllerException ce) {
0390: log.error("Unable to set controller", ce);
0391: }
0392:
0393: Hashtable myStateList = (Hashtable) states.get(getClass()
0394: .getName());
0395:
0396: if (myStateList == null) {
0397: myStateList = new Hashtable();
0398: states.put(getClass().getName(), myStateList);
0399: }
0400:
0401: myStateList.put(newState.getName(), newState);
0402: } /* addState(State) */
0403:
0404: /**
0405: * The constructor of the child object should call this method with a pairing of State objects.
0406: * The sequence of calls to this method determine the runtime ordering of states.
0407: *
0408: * @param promptState The state that is part of the 'prompting' workflow
0409: * @param handleState The state that is part of the 'processing' workflow
0410: * @param stateFormClass The <code>ActionForm</code> name to use
0411: * @throws NonHandleableException if there's an error in pairing. Usually
0412: * caused by a java.lang.IllegalArumentException
0413: */
0414: protected void addStatePairing(State promptState,
0415: State handleState, String stateFormClass)
0416: throws NonHandleableException {
0417: if (promptState == null || handleState == null) {
0418: throw new NonHandleableException(
0419: "Can't add null state in pairing.");
0420: }
0421: //This check helps ensure that initialState is set correctly (ie to 1st prompt state if it exists,
0422: //otherwise it will be set to the lone state). That lone state will be the final state.
0423: if (finalState != null) {
0424: throw new NonHandleableException(
0425: "Can't add states once final state has been added.");
0426: }
0427: if (promptStates.contains(promptState)
0428: || promptStates.contains(handleState)
0429: || handleStates.contains(promptState)
0430: || handleStates.contains(handleState)) {
0431: throw new NonHandleableException(
0432: "Paired states must be unique.");
0433: }
0434:
0435: promptState.setStateFormClass(stateFormClass);
0436: handleState.setStateFormClass(stateFormClass);
0437: promptStates.add(promptState);
0438: handleStates.add(handleState);
0439: addState(promptState);
0440: addState(handleState);
0441:
0442: //Let's enter via the 1st prompt state in the controller if the value hasn't already been explicitly set.
0443: if (StringUtil.notNull(getInitialState()).equals("")) {
0444: setInitialState(promptState.getName());
0445: }
0446:
0447: //Set the default transition to return to prompt the user if any errors generated
0448: //within the handler state. This setting can be overriden at state-runtime.
0449: Transition errorTransition = new Transition();
0450: errorTransition.setState(promptState.getName());
0451: errorTransition.setControllerObject(getClass().getName());
0452: handleState.setErrorTransition(errorTransition);
0453: }
0454:
0455: /**
0456: * Return the Struts ActionForm associated with this controller as specified
0457: * in the Struts config file. Struts would have already created and populated
0458: * this form and put it either in the request or session scope.
0459: *
0460: * @param request The ControllerRequest object
0461: * @return The appropriate <code>ActionForm</code> for this controller request
0462: * @throws ControllerException if unable to find the Controller Form.
0463: */
0464: protected ActionForm findControllerForm(ControllerRequest request)
0465: throws ControllerException {
0466: ActionConfig config = ConfigManager.getActionConfig("",
0467: getClass().getName(), null);
0468: // ActionMapping mapping = ConfigManager.getMapping(getClass().getName(),
0469: // null);
0470: if (config == null) {
0471: return null;
0472: }
0473:
0474: String controllerFormAttribute = config.getAttribute();
0475:
0476: if (controllerFormAttribute == null) { //no form associated with this controller
0477: return null;
0478: }
0479:
0480: // Find the controller's form. It would have been created by Strut's form logic before getting here.
0481: PersistentSession session = request.getSession();
0482:
0483: if ("request".equals(config.getScope())) {
0484:
0485: //The getAttribute() method appears to read attributes from the HTTPRequest scope!!
0486: return (ActionForm) session
0487: .getAttribute(controllerFormAttribute);
0488: } else {
0489: return (ActionForm) session
0490: .getPersistentAttribute(controllerFormAttribute);
0491: }
0492: }
0493:
0494: /**
0495: * Return the transition that will be executed once this controller
0496: * completes without errors. The controller 'completes' once the
0497: * final state (as defined using addFinalState) returns without errors
0498: * in the error collection.
0499: *
0500: * @return The Transition for this controller chaining setup.
0501: */
0502: protected Transition getControllerChainingTransition() {
0503: return controllerChainingTransition;
0504: }
0505:
0506: /**
0507: * Return the transition that will be executed if any state in this controller
0508: * cannot be run because the user does not have sufficient authorization.
0509: *
0510: * @return The ControllerSecurity Transition
0511: */
0512: protected Transition getControllerSecurityTransition() {
0513: return controllerSecurityTransition;
0514: }
0515:
0516: /**
0517: * Return the final state for this controller. See addFinalState().
0518: *
0519: * @return the "Final" state for this workflow.
0520: */
0521: public State getFinalState() {
0522: return finalState;
0523: }
0524:
0525: /**
0526: * Get the initial state in a controller. This is the state to use if there
0527: * is no <code>state</code> parameter specified in the requesting URL or
0528: * execution container.
0529: *
0530: * @return The name of the State to use as default for this controller
0531: */
0532: public String getInitialState() {
0533: return (String) initialStates.get(getClass().getName());
0534: } /* getInitialState() */
0535:
0536: /**
0537: * Get the name of the schema object that this Controller belongs to
0538: *
0539: * @return The schema class name associated with this controller
0540: */
0541: protected final String getSchema() {
0542: Stack mySchemaStack = (Stack) schemas.get(getClass().getName());
0543:
0544: if (mySchemaStack == null) {
0545: setSchema(ExpressoSchema.class.getName());
0546: mySchemaStack = (Stack) schemas.get(getClass().getName());
0547: }
0548:
0549: String returnValue = (String) mySchemaStack.peek();
0550: return returnValue;
0551: } /* getSchema() */
0552:
0553: /**
0554: * Retrieve a full instance of the Schema object that is associated with
0555: * this controller
0556: *
0557: * @return Schema instance.
0558: */
0559: protected Schema getSchemaInstance() {
0560: return SchemaFactory.getInstance().getSchema(this .getSchema());
0561: }
0562:
0563: /**
0564: * Retrieve a stack of all schemas in a 'derived sense'
0565: *
0566: * @return java.util.Stack with the schema hierarchy ending in ExpressoSchema
0567: * at the bottom.
0568: * @deprecated 7/04; v5.5. just name changed. see getSchemaStack
0569: */
0570: public synchronized Stack getSchemaHierarchy() {
0571: return getSchemaStack();
0572: }
0573:
0574: /**
0575: * Retrieve a stack of all schemas. hands out actual object instance; singleton for this controller.
0576: *
0577: * @return java.util.Stack with the schema hierarchy ending in ExpressoSchema at the bottom. never null.
0578: */
0579: public synchronized Stack getSchemaStack() {
0580: Stack s = (Stack) schemas.get(getClass().getName());
0581: if (s == null) {
0582: s = new Stack();
0583: schemas.put(this .getClass().getName(), s);
0584: }
0585:
0586: if (s.isEmpty()) {
0587: s.push(com.jcorporate.expresso.core.ExpressoSchema.class
0588: .getName());
0589: }
0590:
0591: return s;
0592: }
0593:
0594: /**
0595: * Return a specific state
0596: *
0597: * @param stateName The name of the state to retrieve
0598: * @return the Instantiated State.
0599: */
0600: public final State getState(String stateName) {
0601: Hashtable myStateList = (Hashtable) states.get(getClass()
0602: .getName());
0603:
0604: if (myStateList == null) {
0605: return null;
0606: }
0607:
0608: State stateClass = (State) myStateList.get(stateName);
0609:
0610: if (stateClass == null) {
0611: return null;
0612: }
0613: try {
0614: State newStateClass = (State) stateClass.clone();
0615: newStateClass.setController(this );
0616:
0617: return newStateClass;
0618: } catch (CloneNotSupportedException cne) {
0619: log.error("Unable to clone " + "State object '"
0620: + stateClass + "'", cne);
0621: } catch (ControllerException ce) {
0622: log.error("Unable to clone " + "State object '"
0623: + stateClass + "'", ce);
0624: }
0625:
0626: return null;
0627: } /* getState(String) */
0628:
0629: /**
0630: * Return the hashtable of valid states for this controller
0631: *
0632: * @return a Hashtable of states for this controller
0633: */
0634: public final Hashtable getStates() {
0635: Hashtable myStateList = (Hashtable) states.get(getClass()
0636: .getName());
0637:
0638: if (myStateList == null) {
0639: return null;
0640: }
0641:
0642: return myStateList;
0643: } /* getStates() */
0644:
0645: /**
0646: * Convenience version, uses default locale of default db context
0647: * <p/>
0648: * IMPORTANT: in general, if there's a ControllerResponse object available,
0649: * use ControllerResponse.getString(String) instead, this is able
0650: * to return the local language by considering the user's locale.
0651: *
0652: * @param stringCode The properties file string code to retrieve
0653: * @return translated string, or if not found, the stringCode
0654: */
0655: protected String getString(String stringCode) {
0656: return this .getString(stringCode, new Object[0]);
0657: } /* getString(String) */
0658:
0659: /**
0660: * Convenience version, uses default locale of default db context
0661: * <p/>
0662: * IMPORTANT: In general, if there's a ControllerResponse object available,
0663: * use ControllerResponse.getString(String) instead, this is able
0664: * to return the local language by considering the user's locale.
0665: *
0666: * @param stringCode The properties file string code to retrieve
0667: * @param args The Object array for i18n formatting.
0668: * @return translated string, or if not found, the stringCode
0669: */
0670: protected String getString(String stringCode, Object[] args) {
0671: String result = stringCode;
0672: Stack s = (Stack) schemas.get(this .getClass().getName());
0673:
0674: if (s == null || s.isEmpty()) {
0675: result = Messages.getString(getSchema(), stringCode, args);
0676: } else {
0677: Locale theLocale = Messages.getDefaultLocale();
0678: result = Messages.getString(s, theLocale, stringCode, args);
0679: }
0680:
0681: return result;
0682: }
0683:
0684: /**
0685: * Return the title of this Controller
0686: *
0687: * @return java.lang.String The Title of the controller
0688: */
0689: public String getTitle() {
0690: return ("No Title");
0691: } /* getTitle() */
0692:
0693: /**
0694: * Handle an exception error that is not otherwise caught or handled
0695: * by the Controller instance itself. Typically, these will be reserved
0696: * for "system failure" type of situations, as anything that the user
0697: * can reasonable be expected to correct should be reported in the ErrorCollection
0698: * instead.
0699: *
0700: * @param req The HTTPServletRequest
0701: * @param creq The ControllerRequest Object
0702: * @param dbName The current data context
0703: * @param userName The currently logged in Username
0704: * @param theException The <code>java.lang.Throwable</code> object that
0705: * was caught
0706: * @throws ServletException upon fatal error for handling the exception
0707: */
0708: protected void handleException(HttpServletRequest req,
0709: ControllerRequest creq, String dbName, String userName,
0710: Throwable theException) throws ServletException {
0711: if (StringUtil.notNull(dbName).equals("")) {
0712: dbName = "default";
0713: }
0714:
0715: if (creq == null || dbName == null || req == null
0716: || userName == null) {
0717: log.error("Error handling exception", theException);
0718: throw new ServletException("ControllerRequest Error",
0719: theException);
0720: }
0721:
0722: try {
0723: ControllerResponse errorResponse = new ControllerResponse();
0724: errorResponse.setRequest(creq);
0725: errorResponse.setTitle(getTitle());
0726: errorResponse.setSchemaStack((java.util.Stack) this
0727: .getSchemaStack().clone());
0728: errorResponse.setControllerClass(getClass().getName());
0729:
0730: /* Stash the stackTrace, if available, in an output */
0731: boolean showStack = true;
0732:
0733: try {
0734: if (ConfigManager.getContext(dbName) != null) {
0735: showStack = ConfigManager.getContext(dbName)
0736: .showStackTrace();
0737: }
0738: } catch (ConfigurationException ce) {
0739: throw new ServletException(ce);
0740: }
0741: if (showStack) {
0742: if (theException != null) {
0743: ByteArrayOutputStream bos = new ByteArrayOutputStream();
0744: theException.printStackTrace(new PrintStream(bos));
0745: errorResponse.addOutput(new Output("stackTrace",
0746: bos.toString()));
0747: } else {
0748: errorResponse.addOutput(new Output("stackTrace",
0749: "No stack trace was available"));
0750: }
0751: } else {
0752: errorResponse.addOutput(new Output("stackTrace",
0753: "Stack trace display is not " + "enabled."));
0754: }
0755:
0756: errorResponse.addOutput(new Output("exceptionMessage",
0757: theException.getMessage()));
0758:
0759: if (theException instanceof com.jcorporate.expresso.kernel.exception.ChainedException) {
0760: com.jcorporate.expresso.kernel.exception.ChainedException c = (com.jcorporate.expresso.kernel.exception.ChainedException) theException;
0761: Throwable nestedException = c.getNested();
0762:
0763: if (nestedException != null) {
0764: String nested = StringUtil.notNull(c.getNested()
0765: .getMessage());
0766:
0767: if (theException.getMessage().indexOf(nested) == -1) {
0768: errorResponse.addOutput(new Output(
0769: "nestedMessage", nested));
0770: }
0771: }
0772: if (c.getErrorNumber() != 0) {
0773: errorResponse.addOutput(new Output("errorNumber",
0774: Integer.toString(c.getErrorNumber())));
0775: }
0776: }
0777:
0778: errorResponse.addOutput(new Output("requestedURL",
0779: StringUtil.notNull(req.getRequestURI())));
0780: errorResponse.addOutput(new Output("queryString",
0781: StringUtil.notNull(req.getQueryString())));
0782: errorResponse.addOutput(new Output("db", dbName));
0783: errorResponse.addOutput(new Output("userName", userName));
0784: errorResponse.addOutput(new Output("dbDescrip",
0785: ConfigManager.getContext(dbName).getDescription()));
0786: errorResponse.addOutput(new Output("errorClass",
0787: theException.getClass().getName()));
0788:
0789: if (theException instanceof SecurityException) {
0790: errorResponse.addOutput(new Output("errorType",
0791: "security"));
0792: addRequestedURLtoSession(req, creq);
0793: } else if (theException instanceof DBException) {
0794: errorResponse.addOutput(new Output("errorType",
0795: "database"));
0796:
0797: DBException de = (DBException) theException;
0798: errorResponse.addOutput(new Output("dbMessage", de
0799: .getDBMessage()));
0800: } else {
0801: errorResponse
0802: .addOutput(new Output("errorType", "other"));
0803: }
0804:
0805: errorResponse.addOutput(new Output("controllerClass",
0806: getClass().getName()));
0807: req.setAttribute(ExpressoConstants.CONTROLLER_RESPONSE_KEY,
0808: errorResponse);
0809:
0810: /* Now see if we also email the error anywhere */
0811: boolean mailError = false;
0812:
0813: try {
0814: String servletEventFlag = StringUtil.notNull(Setup
0815: .getValue(dbName, "ServletEvent"));
0816:
0817: if (StringUtil.toBoolean(servletEventFlag)) {
0818: mailError = true;
0819: } else if (servletEventFlag.equalsIgnoreCase("E")) {
0820: if (!(theException instanceof SecurityException)) {
0821: mailError = true;
0822: }
0823: }
0824: } catch (Exception e) {
0825: log.error("Unable to email error correctly", e);
0826: }
0827: if (!mailError) {
0828: log.info("Sending of '"
0829: + theException.getClass().getName()
0830: + "' errors via email is not enabled in "
0831: + "db/context '" + dbName + "'");
0832: } else {
0833: FastStringBuffer theMessage = FastStringBuffer
0834: .getInstance();
0835: try {
0836: theMessage.append("Error:\n");
0837: theMessage.append("\tA "
0838: + theException.getClass().getName()
0839: + " Error occurred at "
0840: + DateTime.getDateTimeForDB(dbName)
0841: + " in database/context '" + dbName
0842: + "' to user '" + userName + "'\n");
0843: theMessage.append("\tThe requested URL was '");
0844: theMessage.append(getRequestURL(req));
0845: theMessage.append("'\n");
0846:
0847: String errorMessage = theException.getMessage();
0848:
0849: if (errorMessage != null) {
0850: theMessage.append("\t");
0851: theMessage.append(errorMessage);
0852: } else {
0853: theMessage
0854: .append("No error message was available");
0855: }
0856:
0857: theMessage.append(errorResponse.getOutput(
0858: "stackTrace").getContent());
0859: String finalMessage = theMessage.toString();
0860: log.error(finalMessage, theException);
0861: EventHandler.Event(dbName, "SYSERROR",
0862: finalMessage, false);
0863: } catch (Exception ee) {
0864: log.error("Unable to send error email", ee);
0865: } finally {
0866: theMessage.release();
0867: theMessage = null;
0868: }
0869:
0870: } /* if we send an email */
0871:
0872: } catch (NullPointerException npe) {
0873: log.error("Null Pointer Exception while handling error",
0874: npe);
0875: throw new ServletException(npe);
0876: } catch (ControllerException ce) {
0877: log.error("ControllerException while handling error", ce);
0878: throw new ServletException(ce);
0879: } catch (Exception ee) {
0880: log.error("Exception while handling error", ee);
0881: throw new ServletException(ee);
0882: }
0883: } /* handleException */
0884:
0885: /* Stash the original URL that we asked for in the session,
0886: * so that if we log in and want to try again, it's available
0887: */
0888: protected void addRequestedURLtoSession(HttpServletRequest req,
0889: ControllerRequest creq) throws ControllerException {
0890: PersistentSession session = creq.getSession();
0891:
0892: if (session != null) {
0893: String origUrl = getRequestURL(req);
0894: session.setPersistentAttribute(
0895: ExpressoConstants.CONTROLLER_ORIGINAL_URL_KEY,
0896: new SerializableString(origUrl));
0897: }
0898: }
0899:
0900: /**
0901: * recreate requested URL; never null, though could be empty string
0902: */
0903: protected String getRequestURL(HttpServletRequest req) {
0904: String origUrl = StringUtil.notNull(req.getRequestURI());
0905:
0906: if (req.getQueryString() != null) {
0907: origUrl = origUrl + "?" + req.getQueryString();
0908: }
0909: return origUrl;
0910: }
0911:
0912: /**
0913: * ???????????????????????
0914: *
0915: * @param req ?
0916: * @param creq ?
0917: * @param regDomain ?
0918: */
0919: protected void addRegDomainParamtoSession(HttpServletRequest req,
0920: ControllerRequest creq, String regDomain)
0921: throws ControllerException {
0922: PersistentSession session = creq.getSession();
0923:
0924: if (session != null) {
0925: session.setPersistentAttribute("regDomain",
0926: new SerializableString(regDomain));
0927: }
0928: }
0929:
0930: /**
0931: * Factory method to create an Controller from it's name
0932: * Do not call this function directly! Use <code>ConfigManager.getControllerFactory()
0933: * .getInstance(java.lang.String)</code> instead.
0934: *
0935: * @param className The classname of the controller to instantiated
0936: * @return a fully instantiated Controller Object
0937: * @throws ControllerException upon construction failure
0938: */
0939: public static synchronized Controller instantiate(String className)
0940: throws ControllerException {
0941: StringUtil.assertNotBlank(className, "Controller class name "
0942: + " may not be blank or null here");
0943:
0944: try {
0945:
0946: Class c = ClassLocator.loadClass(className);
0947: return (Controller) c.newInstance();
0948: } catch (ClassNotFoundException cn) {
0949: throw new ControllerException("Controller object '"
0950: + className + "' not found", cn);
0951: } catch (InstantiationException ie) {
0952: throw new ControllerException("Controller object '"
0953: + className + "' cannot be instantiated", ie);
0954: } catch (IllegalArgumentException e) {
0955: throw new ControllerException(
0956: "Controller object '"
0957: + className
0958: + "' cannot be instantiated (IllegalArgumentException",
0959: e);
0960: } catch (IllegalAccessException iae) {
0961: throw new ControllerException("llegal access loading "
0962: + "Controller object '" + className + "'", iae);
0963: }
0964: } /* instantiate(String) */
0965:
0966: /**
0967: * Return True if the passed in state was added to this controller
0968: * as a final state.
0969: *
0970: * @param newState the state name to use as the final state.
0971: * @return true if this is the final state.
0972: */
0973: protected boolean isFinalState(String newState) {
0974: if (finalState == null) {
0975: return false;
0976: }
0977:
0978: return finalState.getName().equals(newState);
0979: }
0980:
0981: /**
0982: * Return True if the passed in state was added to this controller
0983: * as a handle state.
0984: *
0985: * @param nextState the state name to check against
0986: * @return true if it is supposed to handle the state.
0987: */
0988: protected boolean isHandleState(State nextState) {
0989: return handleStates.contains(nextState);
0990: }
0991:
0992: /**
0993: * Return True if the passed in state was added to this controller
0994: * as a prompt state.
0995: *
0996: * @param nextState the state name to check against
0997: * @return true if it is the prompt state.
0998: */
0999: protected boolean isPromptState(State nextState) {
1000: return promptStates.contains(nextState);
1001: }
1002:
1003: /**
1004: * Return the state's form with data from the controller's form.
1005: * The controller's form is specified in the struts config file. The state
1006: * form is specified in the state's stateFormClass attribute.
1007: *
1008: * @param nextState The next state to use
1009: * @param controllerForm the ControllerForm as specified in the struts config file.
1010: * @return a fully instantiated StateForm
1011: * @throws ControllerException if there is an error instantiating the State Form
1012: */
1013: protected StateForm loadStateForm(State nextState,
1014: ActionForm controllerForm) throws ControllerException {
1015: StateForm stateForm = null;
1016: String stateFormClass = StringUtil.notNull(nextState
1017: .getStateFormClass());
1018:
1019: if (controllerForm == null) {
1020: if (!stateFormClass.equals("")) {
1021: throw new ControllerException(
1022: "Controller needs ActionForm because it's state expects one.");
1023: }
1024:
1025: return (null);
1026: } else {
1027:
1028: //If the state's form is blank then use the controller's form - if it's of ControllerForm type
1029: if (stateFormClass.equals("")) {
1030: /** changed instanceof ControllerForm for instanceof DefaultForm *RD* Mon Jul 27 2004 */
1031: if (controllerForm instanceof DefaultForm) {
1032: return (StateForm) controllerForm;
1033: } else {
1034: return (null); //backwards compatible reqt
1035: }
1036: } else {
1037:
1038: //Populate a new instance of the state's form from the controller's form.
1039: try {
1040:
1041: Class clazz = ClassLocator.loadClass(nextState
1042: .getStateFormClass());
1043: stateForm = (StateForm) clazz.newInstance();
1044:
1045: try {
1046: BeanUtils.populate(stateForm, BeanUtils
1047: .describe(controllerForm));
1048: } catch (Exception e) {
1049: throw new ControllerException(e);
1050: }
1051: } catch (Throwable t) {
1052: throw new ControllerException(t);
1053: }
1054:
1055: return (stateForm);
1056: }
1057: }
1058: }
1059:
1060: /**
1061: * This is the method where all of the real work of the controller
1062: * is done. The client calls this to indicate what state the
1063: * controller is transitioning into.
1064: * <p/>
1065: * This is normally what is called from the command line when driving a
1066: * Controller object. If running in a struts environment, this function call
1067: * is wrapped by execute() method which does the actual running, plus then
1068: * performs tasks such as finding the next forward etc.
1069: * </p>
1070: *
1071: * @param newState The new state to transition to
1072: * @param myRequest The calling controllerRequest object.
1073: * @return a newly instantiated ControllerResponse for this state.
1074: * @throws ControllerException on Error
1075: * @throws NonHandleableException if the error should not be handled
1076: * by an error controller
1077: */
1078: public ControllerResponse newState(String newState,
1079: ControllerRequest myRequest) throws ControllerException,
1080: NonHandleableException {
1081: newState = StringUtil.notNull(newState);
1082:
1083: if (log.isDebugEnabled()) {
1084: log.debug("Transitioning to state '" + newState
1085: + "' in controller '" + getClass().getName() + "'");
1086: }
1087: if (newState.length() == 0) {
1088: if (getInitialState() != null) {
1089: return newState(getInitialState(), myRequest);
1090: } else {
1091: throw new ControllerException(
1092: "You must specify a "
1093: + "non-blank state. No default initial state is specified.");
1094: }
1095: } /* if state blank */
1096:
1097: //Used to ensure the state we are executing "newState" is in fact the one used to lookup
1098: //the controller security matrix. This should not be needed but is added for 'comfort'.
1099: String entryState = newState;
1100:
1101: /* Is this a valid state for this controller? */
1102: State nextState = getState(newState);
1103:
1104: if (nextState == null) {
1105: throw new ControllerException("State '" + newState
1106: + "' is unknown for Controller "
1107: + getClass().getName());
1108: } /* if new state exists */
1109:
1110: /* Is the user permitted to access this state? */
1111: // todo what does newstate=="" mean?
1112: if (!newState.equals("")) {
1113: if (controllerSecurityTransition == null
1114: && !stateAllowed(newState, myRequest)) {
1115: DelayThread.delay();
1116: throw new SecurityException("State '"
1117: + nextState.getDescription()
1118: + "' is not allowed for user '"
1119: + myRequest.getUser() + "' ("
1120: + myRequest.getUid() + ")" + " in controller '"
1121: + getTitle() + "' (" + getClass().getName()
1122: + ") in context '" + myRequest.getDataContext()
1123: + "'");
1124: } /* user not allowed */
1125:
1126: } /* if state not empty */
1127:
1128: setupReturnToSender(nextState, myRequest);
1129:
1130: /** added to help struts validator to find the state to go back to
1131: * if an error is found in the form *RD* Mon Jul 27 2004 */
1132: PersistentSession mySession = myRequest.getSession();
1133: mySession.removePersistentAttribute("previousState");
1134: mySession.setPersistentAttribute("previousState", newState);
1135: mySession.removePersistentAttribute("previousController");
1136: mySession.setPersistentAttribute("previousController", this
1137: .getClass().getName());
1138:
1139: //We have reached the final state so rerun any handler states before continuing.
1140: //If a handler state had to transition to some external state then it would likely set
1141: //returnToSender=True. So breaking out of the loop below and returning works great
1142: //because external state will reinvoke this Final state when it is done successfully.
1143: //If a handler state had to transition to some internal state then it would likely set
1144: //returnToSender=False because it has returned the user into some earlier screen in the
1145: //'wizard' to correct any errors. The user would then continue forward through the all
1146: //these wizard screens until the final screen which would reinvoke this Final state once again.
1147: if (isFinalState(newState)) {
1148: //Get the xml version of the return to sender transition
1149: FastStringBuffer fsb = FastStringBuffer.getInstance();
1150: String returnToSender = null;
1151: try {
1152: Transition t = nextState.getReturnToSender();
1153: returnToSender = t.toXML(fsb).toString();
1154: } finally {
1155: fsb.release();
1156: fsb = null;
1157: }
1158:
1159: for (Iterator i = handleStates.iterator(); i.hasNext();) {
1160: State oneHandleState = (State) i.next();
1161:
1162: //Any return to sender transitions while in 'final mode' will return to the 'final' state.
1163: myRequest.setParameter(RETURN_TO_SENDER_TRAN,
1164: returnToSender);
1165:
1166: ControllerResponse statusResponse = newState(
1167: oneHandleState.getName(), myRequest);
1168:
1169: if (!statusResponse.getCurrentState().getName().equals(
1170: oneHandleState.getName())) {
1171: return statusResponse; //The handler didn't return cleanly so bail out.
1172: }
1173: } //End loop on all handler states
1174:
1175: }
1176:
1177: // Use the Servlet controller response to help pure Struts developers
1178: // Peter Pilgrim Fri Nov 22 03:15:58 GMT 2002
1179: ServletControllerResponse myResponse = new ServletControllerResponse();
1180:
1181: if (myRequest == null) {
1182: throw new NonHandleableException(
1183: "Request cannot be null here");
1184: }
1185:
1186: myResponse.setRequest(myRequest);
1187: myResponse.setTitle(getTitle());
1188: myResponse.setCurrentState(nextState);
1189: myResponse.setSchemaStack((java.util.Stack) this
1190: .getSchemaStack().clone());
1191: myResponse.setControllerClass(getClass().getName());
1192:
1193: /* Do we have all of the required parameters for this state */
1194: String oneParamName = null;
1195: String oneParamValue = null;
1196:
1197: for (Enumeration pe = nextState.getParameters().elements(); pe
1198: .hasMoreElements();) {
1199: oneParamName = (String) pe.nextElement();
1200: oneParamValue = StringUtil.notNull(
1201: myRequest.getParameter(oneParamName)).trim();
1202:
1203: if (oneParamValue.equals("")) {
1204: throw new ControllerException("Value for parameter '"
1205: + oneParamName + "' is required for state "
1206: + nextState.getDescription() + " ("
1207: + nextState.getName() + ")");
1208: } /* if parameter is empty */
1209:
1210: } /* for each required parameter */
1211:
1212: processRequestTransitions(nextState, myRequest);
1213:
1214: /* Now execute the "run" method of the state */
1215:
1216: //Setup the controller and state forms
1217: ActionForm controllerForm = findControllerForm(myRequest);
1218: StateForm stateForm = loadStateForm(nextState, controllerForm);
1219: // populateStateForm(stateForm, myRequest); //This will add other params (eg hidden fields)
1220:
1221: ControllerResponse cr = null;
1222:
1223: //Double-check that our newState was not accidentally changed since we started this method.
1224: if (!newState.equals(entryState)) {
1225: throw new ControllerException(
1226: "newState was modified between method entry and security check!");
1227: }
1228:
1229: /* Is the user permitted to access this state? */
1230: if (controllerSecurityTransition != null
1231: && !stateAllowed(newState, myRequest)) {
1232: //Make it thread-safe
1233: Transition securityTransition = null;
1234:
1235: try {
1236: securityTransition = (Transition) controllerSecurityTransition
1237: .clone();
1238: } catch (CloneNotSupportedException cne) {
1239: throw new ControllerException(
1240: "Cannot clone the securityTransition class");
1241: }
1242:
1243: //Could also add a 'security-related' message in the ErrorCollection and set the
1244: //nextState.setErrorTransition(securityTransition) to display on the other screen.
1245: nextState.setSuccessTransition(securityTransition);
1246: cr = myResponse;
1247: } else { /* user allowed */
1248: //Now execute the "perform" method of the state
1249: prePerform(nextState, myRequest, myResponse);
1250: nextState.perform(stateForm, myRequest, myResponse);
1251:
1252: cr = nextState.getResponse();
1253: }
1254:
1255: //Move the state form back into the controller form
1256: unloadStateForm(stateForm, controllerForm);
1257:
1258: if (processTransitions(myRequest, cr, nextState) != null) {
1259: return cr;
1260: }
1261:
1262: addPromptTransitions(nextState, cr);
1263:
1264: //Don't call the derived class if a derived State class has already
1265: //been provided.
1266: if (nextState.getClass().getName().equals(
1267: "com.jcorporate.expresso.core.controller.State")) {
1268: Class this Class = this .getClass();
1269: Class tryClass = this Class; //For walking up the object hierarchy
1270:
1271: try {
1272:
1273: boolean methodFound = false;
1274: Method m = null;
1275:
1276: //
1277: //New as of v4.1 - Attempts to walk up the class hierarchy
1278: //to find an applicable class to execute. This should allow
1279: //better templating capabilities for deriving controllers from one another.
1280: //
1281: while (!methodFound) {
1282: try {
1283: m = tryClass.getDeclaredMethod(nextState
1284: .getHandlerName(), stateHandlerParams);
1285: methodFound = true;
1286: } catch (NoSuchMethodException nsme) {
1287: if (myRequest instanceof ServletControllerRequest) {
1288: //As of 5.3: Now try a signature for ServletControllerRequest
1289: //instead.
1290: try {
1291: m = tryClass.getDeclaredMethod(
1292: nextState.getHandlerName(),
1293: servletStateHandlerParams);
1294: methodFound = true;
1295: } catch (NoSuchMethodException ex) {
1296: if (log.isDebugEnabled()) {
1297: log
1298: .debug("Didn't find ServletControllerRequest method");
1299: }
1300: }
1301: }
1302:
1303: if (!methodFound) {
1304: tryClass = tryClass.getSuperclass();
1305:
1306: if (tryClass == null) {
1307: throw nsme;
1308: }
1309: }
1310: }
1311: }
1312: if (m == null) {
1313: throw new NonHandleableException(
1314: "Null pointer when we should have a method object");
1315: }
1316:
1317: Object[] invokeParams = { myRequest, cr };
1318: m.setAccessible(true);
1319:
1320: ControllerResponse cr2 = (ControllerResponse) m.invoke(
1321: this , invokeParams);
1322:
1323: if (cr2 != null) {
1324: ErrorCollection eold = cr.getErrors();
1325: cr = cr2;
1326: cr.saveErrors(eold);
1327: }
1328:
1329: postPerform(nextState, myRequest, myResponse);
1330: } catch (SecurityException se) {
1331: log
1332: .error(
1333: "Unable to reflect to method: "
1334: + this .getClass().getName()
1335: + "."
1336: + nextState.getHandlerName()
1337: + " setAccessible permissions need to be allowed from your security policy",
1338: se);
1339: throw new NonHandleableException(se);
1340: } catch (IllegalAccessException illae) {
1341: log.error("Unable to reflect to method: "
1342: + this .getClass().getName() + "."
1343: + nextState.getHandlerName()
1344: + "illegal access Exception", illae);
1345: throw new NonHandleableException(illae);
1346: } catch (IllegalArgumentException iae) {
1347: log.error("Illegal Argument Exception", iae);
1348: throw new NonHandleableException(iae);
1349: } catch (InvocationTargetException ite) {
1350: Throwable e = ite.getTargetException();
1351: log.error("Error in substate", e);
1352: e.printStackTrace();
1353:
1354: if (e instanceof NonHandleableException) {
1355: throw new NonHandleableException(e); //BUG BUG Ok to not create a new instance??
1356: } else if (e instanceof SecurityException) {
1357: throw new SecurityException(e.getMessage());
1358: } else {
1359: throw new ControllerException(e);
1360: }
1361: } catch (NoSuchMethodException nsme) {
1362: try {
1363:
1364: // test to see if this method exists in thisClass
1365: this Class.getDeclaredMethod("newState",
1366: newStateParams);
1367:
1368: // it does exist, since no exception thrown. give feedback
1369: if (log.isDebugEnabled()) {
1370: log
1371: .debug("Method '"
1372: + nextState.getHandlerName()
1373: + "' does not exist in controller class '"
1374: + getClass().getName()
1375: + "'. State must be handled by newState method.");
1376: }
1377: } catch (NoSuchMethodException nsme2) {
1378:
1379: // If the controller doesn't have a newState method, then
1380: // we have a problem....
1381: String errMsg = ("Method '"
1382: + nextState.getHandlerName()
1383: + "' does not exist in '"
1384: + getClass().getName()
1385: + "' and there is no 'newState' method to handle " + "transitions. Unable to transition");
1386: log.error(errMsg, nsme);
1387: throw new ControllerException(errMsg, nsme);
1388: }
1389: }
1390: }
1391:
1392: //This is the old error handling transition logic for backward compatability.
1393: ErrorCollection finalErrors = cr.getErrors();
1394: if (finalErrors != null) {
1395: if (finalErrors.getErrorCount() > 0) {
1396: String errorState = nextState.getErrorState();
1397: if (errorState != null) {
1398: cr.setFormCache(); //Is this really needed?
1399: transition(errorState, myRequest, cr);
1400: return cr;
1401: }
1402: }
1403: }
1404:
1405: return cr;
1406: } /* newState(String) */
1407:
1408: /**
1409: * Template Method, allowing a subclass to do an action after
1410: * any state in this controller is performed.
1411: * For example, a common header for the web pages
1412: * could be created here, so long as the web pages in question,
1413: * with the common header, were all in the same controller which overrides
1414: * this method.
1415: *
1416: * @param nextState the state to be performed
1417: * @param request the request object
1418: * @param response the response object
1419: */
1420: protected void prePerform(State nextState,
1421: ControllerRequest request, ControllerResponse response)
1422: throws ControllerException {
1423: }
1424:
1425: /**
1426: * Template Method, allowing a subclass to into actions after the invocation of
1427: * any state in this controller.
1428: * For example, a common footer for web pages
1429: * could be created here, so long as the web pages in question,
1430: * with the common footer, were all in the same controller which overrides
1431: * this method. (Multiple controllers which need common pre/post actions
1432: * could share a common superclass.)
1433: *
1434: * @param nextState the state to be performed
1435: * @param request the request object
1436: * @param response the response object
1437: */
1438: protected void postPerform(State nextState,
1439: ControllerRequest request, ControllerResponse response)
1440: throws ControllerException {
1441: }
1442:
1443: /**
1444: * Return the name of the handle state that is associated with the
1445: * passed in prompt state. Null will be returned if the passed in
1446: * state is not a prompt state.
1447: *
1448: * @param nextState the next state to check against
1449: * @return the name of the 'handle' state paired with the nextState parameter
1450: */
1451: protected String nextHandleState(State nextState) {
1452: String nextHandle = null;
1453: int i = promptStates.indexOf(nextState);
1454:
1455: if (i != -1) { //Valid prompt newState name passed in
1456: State handleState = (State) handleStates.get(i);
1457: nextHandle = handleState.getName();
1458: }
1459:
1460: return nextHandle;
1461: }
1462:
1463: /**
1464: * Return the name of the prompt state that is 'next' to the passed
1465: * in prompt state in the sequence for this controller. If the
1466: * passed in state is the last prompt state in this controller then
1467: * null will be returned.
1468: *
1469: * @param nextState the next state to check against
1470: * @return the name of the 'prompt' state paired with the nextState parameter
1471: */
1472: protected String nextPromptState(State nextState) {
1473: String nextPrompt = null;
1474: int i = promptStates.indexOf(nextState);
1475: i++;
1476:
1477: if ((i > 0) && (i < promptStates.size())) { //Next prompt(s) exist
1478: State promptState = (State) promptStates.get(i);
1479: nextPrompt = promptState.getName();
1480: }
1481:
1482: return nextPrompt;
1483: }
1484:
1485: /**
1486: * Used for logging time of requests.
1487: *
1488: * @param beginTimer The start execution time
1489: * @param request The HTTPServletRequest to instrument
1490: */
1491: protected void endTimer(long beginTimer, HttpServletRequest request) {
1492: if (log.isInfoEnabled()) {
1493: log.info("Request '" + getRequestURL(request)
1494: + "', total time "
1495: + (System.currentTimeMillis() - beginTimer)
1496: + " milliseconds");
1497: }
1498: }
1499:
1500: /**
1501: * Process the specified HTTP request, and create the corresponding HTTP
1502: * response (or forward to another web component that will create it).
1503: * Return an <code>ActionForward</code> instance describing where and how
1504: * control should be forwarded, or <code>null</code> if the response has
1505: * already been completed. Note, that if you wish to run a controller within
1506: * something other than a HTTP session, then call newState directly.
1507: *
1508: * @param mapping The ActionMapping used to select this instance
1509: * @param form The optional ActionForm bean for this request (if any)
1510: * @param request The HTTP request we are processing
1511: * @param response The HTTP response we are creating
1512: * @return The ActionForward or null
1513: * @throws IOException if an input/output error occurs
1514: * @throws ServletException if a servlet exception occurs
1515: */
1516: public ActionForward execute(ActionMapping mapping,
1517: ActionForm form, HttpServletRequest request,
1518: HttpServletResponse response) throws IOException,
1519: ServletException {
1520: long beginTimer = System.currentTimeMillis();
1521:
1522: /** to avoid nullPointerExceptions we initialize here the error collection *RD* Mon Jul 27 2004 */
1523: ErrorCollection actionErrors = new ErrorCollection();
1524:
1525: // Call newState directly if we were called from a dispatch in Transition.
1526: ControllerRequest queuedRequest = (ControllerRequest) request
1527: .getAttribute(ExpressoConstants.CONTROLLER_REQUEST_KEY);
1528:
1529: // TODO: Please document what is the purpose of this QUEUED REQUEST code. *PP* Tue Jan 27 11:18:40 GMT 2004
1530: if (queuedRequest != null) {
1531: request
1532: .removeAttribute(ExpressoConstants.CONTROLLER_REQUEST_KEY);
1533: String queuedState = StringUtil.notNull(request
1534: .getParameter(STATE_PARAM_KEY));
1535:
1536: // Only reset the controller form when the controller is
1537: // invoked! If state is specified then the form is not
1538: // reset (even if the state specified is the controller's
1539: // initial state).
1540: if (queuedState.equals("")) {
1541: //Reset the ControllerForm and populate from request
1542: if (form instanceof ControllerForm) {
1543: ((ControllerForm) form).resetController();
1544: }
1545: }
1546:
1547: // TODO: Why are we repopulating the bean? *PP* Tue Jan 27 11:19:15 GMT 2004
1548: /** I commented out the code and nothing seems broken. The form is already populated *RD* Mon Jul 27 2004 */
1549: /*try {
1550: BeanUtils.populate(form, queuedRequest.getParameters());
1551: } catch (Exception e) {
1552: throw new ServletException(e);
1553: }*/
1554:
1555: ControllerResponse queuedResponse = null;
1556:
1557: try {
1558: queuedRequest.setFormAttribute(mapping.getAttribute());
1559: /** test if this action must be automatically validated.
1560: * If it's the case validate the form. *RD* Mon Jul 27 2004 */
1561: /** replaced
1562: * queuedResponse = newState(queuedState, queuedRequest);
1563: * with all this *RD* Mon Jul 27 2004 */
1564: if (mapping.getValidate()) {
1565: actionErrors = (ErrorCollection) form.validate(
1566: mapping, request);
1567: }
1568: /** test if there are no validation errors *RD* Mon Jul 27 2004 */
1569: if (actionErrors.size() == 0) {
1570: /**no errors: forward to the handle state *RD* Mon Jul 27 2004 */
1571: queuedResponse = newState(queuedState,
1572: queuedRequest);
1573: } else {
1574: /** errors: forward control to the prompt state *RD* Mon Jul 27 2004 */
1575: /** retrieve the previous controller and states parameters from the session *RD* Mon Jul 27 2004 */
1576: String previousController = request.getSession()
1577: .getAttribute("previousController")
1578: .toString();
1579: String previousState = request.getSession()
1580: .getAttribute("previousState").toString();
1581: /** test if the prompt state belongs to this same controller *RD* Mon Jul 27 2004 */
1582: if (previousController.equals(this .getClass()
1583: .getName())) {
1584: queuedResponse = newState(StringUtil
1585: .notNull(previousState), queuedRequest);
1586: queuedResponse.saveErrors(actionErrors);
1587: } else {
1588: /** if not transition to the new controller *RD* Mon Jul 27 2004 */
1589: transition(StringUtil.notNull(previousState),
1590: Class.forName(StringUtil
1591: .notNull(previousController)),
1592: queuedRequest, new ControllerResponse());
1593: }
1594:
1595: }
1596: } catch (Exception e) {
1597: request.setAttribute(
1598: ExpressoConstants.NEWSTATE_EXCEPTION_KEY, e);
1599: //push back the exception
1600: return (null);
1601: }
1602:
1603: request.setAttribute(
1604: ExpressoConstants.CONTROLLER_REQUEST_KEY,
1605: queuedRequest);
1606: request.setAttribute(
1607: ExpressoConstants.CONTROLLER_RESPONSE_KEY,
1608: queuedResponse);
1609:
1610: return (null);
1611: }
1612:
1613: /* User for error handling */
1614: String dbName = null;
1615: String userName = null;
1616: ServletControllerRequest req = null;
1617:
1618: try {
1619: String requestDB = request.getParameter("db");
1620: // STRUTS 11
1621: log.debug("request.getParameter(\"db\") = `"
1622: + request.getParameter("db") + "'");
1623: CheckLogin.getInstance().checkLogin(request, requestDB);
1624:
1625: /* Get any, if they exist, parameters from the ActionForm bean */
1626: boolean multiPart = false;
1627:
1628: if (form != null) {
1629: // Call the Struts API to figure if we are dealing
1630: // with a multipart form bean, which are typical of
1631: // uploading file attachments.
1632: //
1633: // See Later: If the form is "session" scoped, we need
1634: // to reset this handler
1635: MultipartRequestHandler mp = form
1636: .getMultipartRequestHandler();
1637:
1638: if (mp != null) {
1639: // The form bean is multipart HTTP request so we
1640: // need to handle it as such. Certain parameters
1641: // in the `HttpServletRequest' may be multipart
1642: // file types.
1643: if (log.isDebugEnabled()) {
1644: log.debug("Multipart request");
1645: }
1646:
1647: multiPart = true;
1648: req = ServletControllerRequest
1649: .parseParamsMultiPart(mp, mapping, form,
1650: request, response, this );
1651: // STRUTS 1.1
1652: req.setCallingServlet(this .getServlet());
1653: }
1654: }
1655: if (!multiPart) {
1656: // The form bean is NOT multipart HTTP request so we
1657: // process as normal.
1658: if (log.isDebugEnabled()) {
1659: log.debug("Non-multipart request");
1660: }
1661: req = ServletControllerRequest.parseParams(mapping,
1662: form, request, response, this );
1663: // STRUTS 1.1
1664: req.setCallingServlet(this .getServlet());
1665: }
1666:
1667: // Check the session for an error collection.... if the error collection exists there,
1668: //
1669: // then we MOVE it to the request context This is used by
1670: // Controller when it needs to do a redirectRequest, but
1671: // cannot be kept in the session otherwise it will show up
1672: // as an error for every page which doesn't have an error
1673: // in the response.
1674: //
1675: // NOTE: Legacy Expresso Controllers, I think, stored error
1676: // collection in the session scope. *PP*
1677: ErrorCollection errors = (ErrorCollection) req.getSession()
1678: .getAttribute(Globals.ERROR_KEY);
1679: if (errors == null) {
1680: // retrieve errors from persistent attributes, if any
1681: errors = (ErrorCollection) req.getSession()
1682: .getPersistentAttribute(Globals.ERROR_KEY);
1683: // move to request
1684: if (errors != null) {
1685: req.getSession().removePersistentAttribute(
1686: Globals.ERROR_KEY);
1687: req.getSession().setAttribute(Globals.ERROR_KEY,
1688: errors);
1689: }
1690: }
1691:
1692: /*
1693: * If we were called as part of a transition request to
1694: * another controller, the parameters will contain a
1695: * 'controller' parameter with a value that indicates a
1696: * controller other than this one. If this happens, then
1697: * we do a lookup to find the ActionMapping for that
1698: * controller/state (or just the controller if there's no
1699: * mapping directly to the specified state) and forward to
1700: * that mapping
1701: */
1702: String controllerRequested = StringUtil.notNull(req
1703: .getParameter(ExpressoConstants.CONTROLLER_KEY));
1704:
1705: // Find out if there is a request state from the
1706: // abstracted "ControllerRequest" parameters. Note this
1707: // different from the "HttpServletRequest"
1708: // parameters. By the time we get here. A "state" set by a
1709: // button (Transition) will have been picked up and
1710: // set. Likewise a "state" parameter from the
1711: // "HttpServletRequest", if any. So we are checking for
1712: // missing controller state definition here.
1713: String requestedState = StringUtil.notNull(req
1714: .getParameter(STATE_PARAM_KEY));
1715:
1716: log.debug("requestedState = `" + requestedState + "'");
1717: if (!controllerRequested.equals("")) {
1718: if (!controllerRequested.equals(getClass().getName())) {
1719:
1720: // If we get here, then we have been asked to
1721: // transition to a state in a different
1722: // controller.
1723: ActionConfig mm = ConfigManager.getActionConfig("",
1724: controllerRequested, requestedState);
1725: if (mm == null) {
1726: throw new ServletException(
1727: "The controller requested: "
1728: + controllerRequested
1729: .toString()
1730: + " and/or state within that controller: "
1731: + requestedState.toString()
1732: + " are not available in configuration mapping; if you expected this to be "
1733: + "available, double-check the /config/*-config.xml files.");
1734: }
1735:
1736: // Build up dispatch URL from all the required
1737: // parameters for the external controller and its
1738: // target state method
1739: FastStringBuffer newURL = FastStringBuffer
1740: .getInstance();
1741: String urlString = null;
1742: try {
1743: newURL.append(mm.getPath());
1744: newURL.append(".do");
1745: newURL.append("?state=");
1746: newURL.append(requestedState);
1747:
1748: String oneParamName = null;
1749: String oneParamValue = null;
1750:
1751: // TODO: Is this a bug or not core developers? *PP*
1752: // BUG BUG - Should parameter names and values be URLEncoded?
1753: for (Enumeration ep = req.getParameters()
1754: .keys(); ep.hasMoreElements();) {
1755: oneParamName = (String) ep.nextElement();
1756: oneParamValue = req
1757: .getParameter(oneParamName);
1758: newURL.append("&");
1759: newURL.append(oneParamName);
1760: newURL.append("=");
1761: newURL.append(oneParamValue);
1762: }
1763:
1764: if (log.isDebugEnabled()) {
1765: log.debug("Request to controller '"
1766: + getClass().getName()
1767: + "' was for a transition "
1768: + "to class '"
1769: + controllerRequested
1770: + "', so forwarding to URL '"
1771: + newURL.toString() + "'");
1772: }
1773: if (log.isInfoEnabled()) {
1774: endTimer(beginTimer, request);
1775: }
1776:
1777: } finally {
1778: urlString = newURL.toString();
1779: newURL.release();
1780: newURL = null;
1781: }
1782:
1783: // Request dispatch forward to the target controller and state method
1784: GenericDispatcher.forward(request, response,
1785: urlString);
1786: request
1787: .removeAttribute(ExpressoConstants.USED_ROUTING_KEY);
1788: return null;
1789: }
1790: }
1791:
1792: // Add flag 'usedRouting' to request attribute to indicate
1793: // routing params have been consumed already. This will
1794: // prevent any other controllers that this controller may
1795: // dispatch to from reusing these parameters (ie the
1796: // controller and state parameters).
1797: request.setAttribute(ExpressoConstants.USED_ROUTING_KEY,
1798: "inuse");
1799:
1800: //
1801: // If we are configured to use SSL, then we must possibly
1802: // issue a redirect to get to the right place.
1803: //
1804: if (ConfigManager.getContext(req.getDataContext())
1805: .isUseSSL()) {
1806:
1807: if (log.isDebugEnabled()) {
1808: log
1809: .debug("Checking if we need to redirect to different protocol");
1810: }
1811: Hashtable myStateList = (Hashtable) states
1812: .get(getClass().getName());
1813: String stateName = req.getParameter(STATE_PARAM_KEY);
1814: if (stateName == null || stateName.length() == 0) {
1815: stateName = this .getInitialState();
1816: }
1817:
1818: if (this
1819: .checkSsl(request, response,
1820: ((State) myStateList.get(stateName))
1821: .isSecure())) {
1822: return null;
1823: }
1824:
1825: if (log.isDebugEnabled()) {
1826: log.debug("No more redirection necessary");
1827: }
1828: }
1829:
1830: if (log.isDebugEnabled()) {
1831: log.debug("Controller '"
1832: + getClass().getName()
1833: + "' to state '"
1834: + StringUtil.notNull(req
1835: .getParameter(STATE_PARAM_KEY)) + "'");
1836: }
1837:
1838: req.setFormAttribute(mapping.getAttribute());
1839:
1840: // Only reset the controller form when the controller is
1841: // invoked! If state is specified then the form is not
1842: // reset (even if the state specified is the controller's
1843: // initial state). This avoids an unwanted reset when
1844: // user clicks on 'previous' in the 2nd screen of a
1845: // wizard.
1846: if (requestedState.equals("")) {
1847: //Reset the ControllerForm and populate from request
1848: if (form instanceof ControllerForm) {
1849: ((ControllerForm) form).resetController();
1850: }
1851: }
1852:
1853: // 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
1854: /* BUG BUG
1855: the test if form instanceof DefaultForm should be done before
1856: BeanUtils.populate() otherwise the DefaultForm never gets populated
1857: This also fixes Input.setDefaultValue(ControllerResponse) which wasn't working
1858: *RD* Mon Jul 27 2004
1859:
1860: try {
1861: BeanUtils.populate(form, req.getParameters());
1862: } catch (Exception e) {
1863: if (form instanceof DefaultForm) {
1864: try {
1865: ((DefaultForm) form).setUsingHashtableParameters(req.getParameters());
1866: } catch (Exception ex) {
1867: ex.printStackTrace();
1868: throw new ServletException(ex);
1869: }
1870: } else {
1871: log.error("Error populating form", e);
1872: throw new ServletException(e);
1873: }
1874: }*/
1875: /** corrected this *RD* Mon Jul 27 2004 */
1876: /** we populate the DefaultForm with the contents of the HTML form *RD* Mon Jul 27 2004 */
1877: if (form instanceof DefaultForm) {
1878: try {
1879: ((DefaultForm) form)
1880: .setUsingHashtableParameters(req
1881: .getParameters());
1882: } catch (Exception ex) {
1883: ex.printStackTrace();
1884: throw new ServletException(ex);
1885: }
1886: }
1887: /** I commented out the code and nothing seems broken. The form is already populated *RD* Mon Jul 27 2004 */
1888: /*else {
1889: try {
1890: BeanUtils.populate(form, req.getParameters());
1891: }
1892: catch (Exception e) {
1893: log.error("Error populating form", e);
1894: throw new ServletException(e);
1895: }
1896: }*/
1897:
1898: // Remove the last controller response from the request
1899: // scope in preparation for invoking the new state method.
1900: request
1901: .removeAttribute(ExpressoConstants.CONTROLLER_RESPONSE_KEY);
1902: dbName = req.getDataContext();
1903: userName = req.getUser();
1904: if (log.isDebugEnabled()) {
1905: log.debug("Before newstate in '" + getClass().getName()
1906: + "'");
1907: }
1908:
1909: // ============================================================
1910: // ============================================================
1911: // Call the target controller's new "state method" as required
1912: // ============================================================
1913: // ============================================================
1914: ControllerResponse res = null;
1915: try {
1916: /** test if this action must be automatically validated.
1917: * If it's the case validate the form. *RD* Mon Jul 27 2004
1918: * replaced
1919: * res = newState(StringUtil.notNull(previousState), req);
1920: * with all this *RD* Mon Jul 27 2004 */
1921:
1922: if (mapping.getValidate()) {
1923: actionErrors = (ErrorCollection) form.validate(
1924: mapping, request);
1925: }
1926: /** test if there are no validation errors *RD* Mon Jul 27 2004 */
1927: if (actionErrors.size() == 0) {
1928: /** no errors: forward to the handle state *RD* Mon Jul 27 2004 */
1929: res = newState(StringUtil.notNull(requestedState),
1930: req);
1931: } else {
1932: /** errors: forward control to the prompt state *RD* Mon Jul 27 2004 */
1933: /** etrieve the previous controller and states parameters from the session *RD* Mon Jul 27 2004 */
1934: String previousController = request.getSession()
1935: .getAttribute("previousController")
1936: .toString();
1937: String previousState = request.getSession()
1938: .getAttribute("previousState").toString();
1939: /** test if the prompt state belongs to this same controller *RD* Mon Jul 27 2004 */
1940: if (previousController.equals(this .getClass()
1941: .getName())) {
1942: res = newState(StringUtil
1943: .notNull(previousState), req);
1944: res.saveErrors(actionErrors);
1945: } else {
1946: /** if not transition to the new controller *RD* Mon Jul 27 2004 */
1947: transition(StringUtil.notNull(previousState),
1948: Class.forName(StringUtil
1949: .notNull(previousController)),
1950: req, new ControllerResponse());
1951: }
1952: }
1953:
1954: } finally {
1955: if (form != null
1956: && "session".equals(mapping.getScope())) {
1957: // Here is a fix for multipart action form beans that
1958: // are "session" scoped. We need to reset the
1959: // multipart request handler for these beans in order
1960: // to prevent infinite looping in the Controller's
1961: // transition mechanism.
1962: form
1963: .setMultipartRequestHandler(/*MultipartRequestHandler*/null);
1964: }
1965: }
1966:
1967: if (log.isDebugEnabled()) {
1968: log.debug("After newstate in '" + getClass().getName()
1969: + "' controller is '"
1970: + res.getControllerClass() + "'");
1971: }
1972:
1973: // Store the controller response now
1974: res.setRequestPath(mapping.getPath());
1975: request.setAttribute(
1976: ExpressoConstants.CONTROLLER_RESPONSE_KEY, res);
1977:
1978: // Has the controller state method declared a custom
1979: // response? For example if the controller is sending down
1980: // a binary stream of a multimedia file stream its own
1981: // accord.
1982: if (res.isCustomResponse()) {
1983: // Custom response it is, then. End the timing and
1984: // return `null' to also flag this condition to the
1985: // Struts MVC framework
1986: if (log.isInfoEnabled()) {
1987: endTimer(beginTimer, request);
1988: }
1989: request
1990: .removeAttribute(ExpressoConstants.USED_ROUTING_KEY);
1991: return null;
1992: }
1993:
1994: ActionConfig finalConfig = mapping;
1995:
1996: // Now get the mapping again if needed, this time for the controller
1997: // specified by the ControllerResponse.getControllerClass(), which
1998: // may or may not be the controller we started out with ...
1999: if (!res.getControllerClass().equals(mapping.getType())) {
2000: if (log.isDebugEnabled()) {
2001: log
2002: .debug("Controller '"
2003: + getClass().getName()
2004: + "' transitioned internally to controller '"
2005: + res.getControllerClass()
2006: + "', state '"
2007: + res.getCurrentState()
2008: + "'. Getting alternate mapping...");
2009: }
2010:
2011: // mapping = ConfigManager.getMapping(res.getControllerClass(),
2012: // res.getCurrentState().getName());
2013:
2014: ActionConfig config = ConfigManager.getActionConfig("",
2015: res.getControllerClass(), res.getCurrentState()
2016: .getName());
2017:
2018: if (config == null) {
2019: throw new ControllerException(
2020: "Unable to find ActionConfig for: "
2021: + res.getControllerClass() + ":"
2022: + res.getCurrentState().getName());
2023: }
2024: finalConfig = config;
2025:
2026: if (log.isDebugEnabled()) {
2027: log.debug("Got mapping for '"
2028: + finalConfig.getPath() + "'");
2029: }
2030: } else {
2031: if (log.isDebugEnabled()) {
2032: log.debug("Controller '" + getClass().getName()
2033: + "' using type '" + finalConfig.getType()
2034: + "', transitioning to state '"
2035: + res.getCurrentState()
2036: + "' within the same controller.");
2037: }
2038: }
2039:
2040: // Retrieve the final action forward derived from the
2041: // controller state method
2042: ActionForward fwd = getActionForward(req, finalConfig, res);
2043:
2044: if (log.isInfoEnabled()) {
2045: endTimer(beginTimer, request);
2046: }
2047:
2048: // Remove the used routing key if it was set to avoid looping.
2049: request.removeAttribute(ExpressoConstants.USED_ROUTING_KEY);
2050: // Return the action forward as normal for Struts Action controller
2051: return fwd;
2052: } catch (NonHandleableException ne) {
2053: throw new ServletException(ne);
2054: /**
2055: * special case: when there is no login, redirect to login screen
2056: */
2057: } catch (SecurityException securExcep) {
2058: // Has this current user logged in?
2059: if (userName.equals(User.UNKNOWN_USER)) {
2060: // If not redirect user to login screen using the
2061: // global action forward
2062: ActionForward loginForward = mapping
2063: .findForward(ExpressoConstants.APPLICATION_LOGON_FORWARD);
2064: if (loginForward == null) {
2065: // do try/catch to capture stack trace
2066: try {
2067: throw new Exception(
2068: "cannot find forward for 'logon'; one should be in <application>-config.xml");
2069: } catch (Exception e) {
2070: e.printStackTrace(System.err);
2071: handleException(request, req, dbName, userName,
2072: e);
2073: return (mapping
2074: .findForward(ExpressoConstants.ERROR_FORWARD));
2075: }
2076: }
2077:
2078: try {
2079: addRequestedURLtoSession(request, req);
2080: } catch (Exception e) {
2081: // do not abort here; we just had problem adding an
2082: // item to session; we will live without it.
2083: log
2084: .error(
2085: "unexpected problem adding item to session: ",
2086: e);
2087: }
2088:
2089: // Do not return an action forward since the URL will
2090: // say the wrong thing. Instead, do a redirect so
2091: // that browser will show the correct login URL
2092: redirectRequest(response, ConfigManager
2093: .getContextPath()
2094: + loginForward.getPath());
2095: return null;
2096: } else {
2097: // If we get here, then we have a recognised user with
2098: // a genuine login account.
2099:
2100: // See if there is a regDomain out there assigned to
2101: try {
2102: ControllerSecurity cs = new ControllerSecurity();
2103: RegistrationDomain rd = new RegistrationDomain();
2104: ControllerSecurity oneSecurityEntry = null;
2105: String groupName = null;
2106: cs.setDataContext(dbName);
2107:
2108: // Search for the Controller which extended from
2109: // this class (the calling controller)
2110: cs
2111: .setField("ControllerClass", getClass()
2112: .getName());
2113: for (Iterator cse = cs.searchAndRetrieveList()
2114: .iterator(); cse.hasNext();) {
2115: cs.clear();
2116: oneSecurityEntry = (ControllerSecurity) cse
2117: .next();
2118: groupName = oneSecurityEntry
2119: .getField("GroupName");
2120: // Now see if there is a registration domain for that group
2121: rd.clear();
2122: rd.setField("GroupName", groupName);
2123: if (rd.find()) {
2124: // Find the action forward on the registration mapping
2125: ActionForward registrationForward = mapping
2126: .findForward(ExpressoConstants.REGISTER_FORWARD);
2127:
2128: if (registrationForward == null) {
2129: // do try/catch to capture stack trace
2130: try {
2131: throw new Exception(
2132: "cannot find forward for '"
2133: + ExpressoConstants.REGISTER_FORWARD
2134: + "'; One should be defined in <application>-config.xml");
2135: } catch (Exception e) {
2136: e.printStackTrace(System.err);
2137: handleException(request, req,
2138: dbName, userName, e);
2139:
2140: return (mapping
2141: .findForward(ExpressoConstants.ERROR_FORWARD));
2142: }
2143: }
2144:
2145: try {
2146: addRegDomainParamtoSession(request,
2147: req, rd.getField("Name"));
2148:
2149: } catch (Exception e) {
2150: // do not abort here; we just had problem adding an
2151: // item to session; we will live without it.
2152: log
2153: .error(
2154: "unexpected problem adding item to session: ",
2155: e);
2156: }
2157:
2158: // Do not return an action forward since the URL will say the wrong
2159: // thing. Instead, do a redirect so that browser will
2160: // show the login URL
2161: try {
2162: ErrorCollection ec = req
2163: .getErrorCollection();
2164: if (ec == null) {
2165: ec = new ErrorCollection();
2166: }
2167: ec.addError(securExcep.getMessage());
2168: req.getSession()
2169: .setPersistentAttribute(
2170: Globals.ERROR_KEY, ec);
2171: } catch (ControllerException ex) {
2172: log
2173: .error(
2174: "Unable to save error for a permission denied message",
2175: ex);
2176: }
2177: redirectRequest(response, ConfigManager
2178: .getContextPath()
2179: + registrationForward.getPath());
2180: return null;
2181:
2182: } else {
2183: log.warn("Registration Domain for "
2184: + groupName + " and class "
2185: + getClass().getName()
2186: + " not found");
2187: }
2188: }
2189: } catch (DBException dbe) {
2190: handleException(request, req, dbName, userName, dbe);
2191: }
2192:
2193: // standard handling--user has logged in, but tried to
2194: // do something not allowed.
2195: System.err.println(securExcep.getMessage());
2196: handleException(request, req, dbName, userName,
2197: securExcep);
2198:
2199: return (mapping
2200: .findForward(ExpressoConstants.ERROR_FORWARD));
2201: }
2202: } catch (NullPointerException npe) {
2203: npe.printStackTrace(System.err);
2204: log.error("Null Pointer Exception", npe);
2205: handleException(request, req, dbName, userName, npe);
2206: return (mapping.findForward("error"));
2207: } catch (Exception ee) {
2208: ee.printStackTrace(System.err);
2209: handleException(request, req, dbName, userName, ee);
2210: return (mapping.findForward("error"));
2211: }
2212: }
2213:
2214: /**
2215: * Determine the forward appropriate for this controller/state,
2216: * either from parameters or from configuration files, or from
2217: * remapping based on past extensions like .xsl
2218: *
2219: * @param req the ServletControllerRequest object
2220: * @param mapping the ActionConfig that defines the mapping
2221: * @param res the ControllerResponse object that has been handed back to
2222: * us by Controller.newState()
2223: * @return an ActionForward instance.
2224: * @throws NonHandleableException upon fatal error
2225: * @throws ControllerException upon error
2226: */
2227: protected ActionForward getActionForward(
2228: ServletControllerRequest req, ActionConfig mapping,
2229: ControllerResponse res) throws NonHandleableException,
2230: ControllerException {
2231:
2232: ForwardConfig fwd = null;
2233:
2234: /* If we were overridden and told to go to a specific style */
2235: if (!StringUtil.notNull(req.getParameter("style")).equals("")) {
2236: String style = req.getParameter("style");
2237: fwd = mapping.findForwardConfig(style);
2238:
2239: if (fwd == null) {
2240: fwd = mapping.getModuleConfig()
2241: .findForwardConfig(style);
2242: if (fwd == null) {
2243: throw new NonHandleableException("Style '"
2244: + req.getParameter("style")
2245: + "' was not found for controller '"
2246: + res.getControllerClass() + "'");
2247: }
2248: }
2249: } else if (res instanceof ServletControllerResponse) {
2250: /* If the response is actually an servlet controller response
2251: * see if there was an action forward defined.
2252: */
2253: ServletControllerResponse resServ = (ServletControllerResponse) res;
2254: if (resServ.getActionForward() != null) {
2255: fwd = resServ.getActionForward();
2256: }
2257: }
2258:
2259: /* If this controller returned a specific "style", then try to find the
2260: * ActionForward for this style.
2261: */
2262: if ((fwd == null) && (res.getStyle() != null)) {
2263: fwd = mapping.findForwardConfig(res.getStyle());
2264: if (fwd == null) {
2265: fwd = mapping.getModuleConfig().findForwardConfig(
2266: res.getStyle());
2267: }
2268: }
2269:
2270: /* If we didn't specify a style, or the style was not found, then
2271: * use the name of the current state to determine the forward to use
2272: */
2273: if (fwd == null) {
2274: fwd = ConfigManager.findForwardConfig(mapping, res
2275: .getCurrentState().getName());
2276:
2277: //Config Manager has it's own implementation because it seems that
2278: //mapping.findForwardConfig does NOT return the same results as
2279: //iterating through the forward array and returning the match :(
2280: // fwd = mapping.findForwardConfig(res.getCurrentState().getName());
2281: }
2282: /* If nothing yet (e.g. no error and no forward), then use default
2283: */
2284: if (fwd == null) {
2285: if (log.isDebugEnabled()) {
2286: log.debug("No forward for '"
2287: + res.getCurrentState().getName()
2288: + "' in controller '"
2289: + res.getControllerClass()
2290: + "' using mapping for '" + mapping.getPath()
2291: + "', which is for class '" + mapping.getType()
2292: + "'. Using default view.");
2293: }
2294:
2295: fwd = mapping.getModuleConfig()
2296: .findForwardConfig("default");
2297: }
2298:
2299: fwd = remapFromExtension(fwd, mapping, req);
2300:
2301: if (log.isDebugEnabled()) {
2302: if (fwd == null) {
2303: log.debug("Controller returning a null fwd object");
2304: } else {
2305: log.debug("Controller '" + res.getControllerClass()
2306: + "', state '"
2307: + res.getCurrentState().getName()
2308: + "' forwarding to view '" + fwd.getPath()
2309: + "'");
2310: }
2311: }
2312:
2313: ActionForward forward = new ActionForward(fwd.getPath(), fwd
2314: .getPath(), fwd.getRedirect(), fwd.getContextRelative());
2315:
2316: return forward;
2317: }
2318:
2319: /**
2320: * if the extension in the current forward is ".xsl" or ".xslt",
2321: * remap the forward to "xml", and set a parameter for the xsl style sheet.
2322: * author: Larry Hamel, CodeGuild, Inc.
2323: *
2324: * @param fwd The ForwardConfig object
2325: * @param mapping the ActionConfig for the particular controller instance
2326: * @param req the ServletControllerRequest for the request.
2327: * @return ForwardConfig instance.
2328: * @throws ControllerException upon error
2329: */
2330: protected ForwardConfig remapFromExtension(ForwardConfig fwd,
2331: ActionConfig mapping, ServletControllerRequest req)
2332: throws ControllerException {
2333:
2334: if (fwd != null) {
2335: String path = fwd.getPath();
2336: if (path != null) {
2337: int lastDot = path.lastIndexOf('.');
2338: if (lastDot != -1) {
2339: String ext = path.substring(lastDot);
2340: if (ext.equalsIgnoreCase(".xsl")
2341: || ext.equalsIgnoreCase(".xslt")) {
2342: fwd = mapping.getModuleConfig()
2343: .findForwardConfig("xml");
2344: if (fwd == null) {
2345: log
2346: .warn("cannot find action forward 'xml' which must be defined when using XSL forwards");
2347: }
2348: req.setParameter("xsl", path);
2349: }
2350: }
2351: }
2352: }
2353: return fwd;
2354: }
2355:
2356: /**
2357: * redirect response so that URL changes in client browser
2358: *
2359: * @param response The Servlet Response
2360: * @param redirectURL The URL to redirect the browser to.
2361: * @throws IOException upon browser error.
2362: */
2363: protected void redirectRequest(HttpServletResponse response,
2364: String redirectURL) throws IOException {
2365:
2366: try {
2367: if (log.isDebugEnabled()) {
2368: log.debug("redirecting to: " + redirectURL);
2369: }
2370:
2371: response.sendRedirect(redirectURL);
2372: } catch (java.lang.IllegalStateException e) {
2373: log.error("Error performing redirect.", e);
2374: }
2375:
2376: // don't flush so that subclasses can make use of this too;
2377: // sendRedirect() just sets headers to 301
2378: }
2379:
2380: /**
2381: * Populate the state form with any matching request parameters.
2382: *
2383: * @param stateForm The name of the state form to populate
2384: * @param request The source of the variables for the state form.
2385: * @throws ControllerException upon <code>BeanUtils.populate()</code> error
2386: */
2387: protected void populateStateForm(StateForm stateForm,
2388: ControllerRequest request) throws ControllerException {
2389: try {
2390: BeanUtils.populate(stateForm, request.getParameters());
2391: } catch (Exception e) {
2392: throw new ControllerException(e);
2393: }
2394: }
2395:
2396: /**
2397: * Return the name of the prompt state that is 'previous' to the passed
2398: * in prompt state in the sequence for this controller. If the
2399: * passed in state is the first prompt state in this controller then
2400: * null will be returned.
2401: *
2402: * @param nextState The state to check against
2403: * @return the previous prompt state
2404: */
2405: protected String previousPromptState(State nextState) {
2406: String previousPrompt = null;
2407: int i = promptStates.indexOf(nextState);
2408:
2409: if (i > 0) { //Previous prompt(s) exist
2410: State previousState = (State) promptStates.get(i - 1);
2411: previousPrompt = previousState.getName();
2412: }
2413:
2414: return previousPrompt;
2415: }
2416:
2417: /**
2418: * <p/>
2419: * This method picks up the following routing parameters from the current request
2420: * and builds transition objects from them before the state is invoked.
2421: * </p>
2422: * <p/>
2423: * 1. "Controller Success"
2424: * These parameters identify the state that should be run once this
2425: * controller completes successfully (ie the'final' state has completed without errors).
2426: * Any controller success parameters will cause this method to put a
2427: * serialized transition object specific to this controller in session scope.
2428: * </p>
2429: * <p/>
2430: * <p/>
2431: * 2. "State Success"
2432: * These parameters identify the state that should be run if this state
2433: * completes without errors.
2434: * Any state success parameters will cause this method to assign a
2435: * transition object to this currently running state.
2436: * </p>
2437: * <p/>
2438: * <p/>
2439: * 3. "State Error"
2440: * These parameters identify the state that should be run if this state
2441: * completes with errors.
2442: * Any state error parameters will cause this method to assign a
2443: * transition object to this currently running state.
2444: * </p>
2445: * <p/>
2446: * <b>Note</b>, the transition specified at this time can be overridden by
2447: * the state when it executes.
2448: *
2449: * @param nextState the next state to check process
2450: * @param request The controllerRequest object
2451: * @throws ControllerException upon error.
2452: */
2453: protected void processRequestTransitions(State nextState,
2454: ControllerRequest request) throws ControllerException {
2455: PersistentSession session = request.getSession();
2456: String sessionKey = CTL_SUCC_TRAN + getClass().getName();
2457:
2458: //process any CONTROLLER SUCCESS routing
2459: String controllerSuccessReturn = request
2460: .getParameter(CTL_SUCC_TRAN);
2461:
2462: if (controllerSuccessReturn != null) {
2463: request.removeParameter(CTL_SUCC_TRAN);
2464: session.setPersistentAttribute(sessionKey,
2465: controllerSuccessReturn);
2466: } else {
2467: String controllerSuccessReturnController = request
2468: .getParameter(CTL_SUCC_CTL);
2469:
2470: if (controllerSuccessReturnController != null) {
2471: String controllerSuccessReturnState = request
2472: .getParameter(CTL_SUCC_STATE);
2473: Transition t = new Transition();
2474: t.setState(controllerSuccessReturnState);
2475: t
2476: .setControllerObject(controllerSuccessReturnController);
2477: FastStringBuffer fsb = FastStringBuffer.getInstance();
2478: try {
2479: controllerSuccessReturn = t.toXML(fsb).toString();
2480: } finally {
2481: fsb.release();
2482: fsb = null;
2483: }
2484: session.setPersistentAttribute(sessionKey,
2485: controllerSuccessReturn);
2486: request.removeParameter(CTL_SUCC_CTL);
2487: request.removeParameter(CTL_SUCC_STATE);
2488: }
2489: }
2490:
2491: //process any STATE SUCCESS routing
2492: Transition successTransition = null;
2493: String successTran = request.getParameter(STATE_SUCC_TRAN);
2494:
2495: if (successTran != null) {
2496: request.removeParameter(STATE_SUCC_TRAN);
2497:
2498: try {
2499: successTransition = Transition.fromXML(successTran);
2500: } catch (Exception e) {
2501: throw new ControllerException(e);
2502: }
2503: }
2504:
2505: String stateSuccessReturnController = request
2506: .getParameter(STATE_SUCC_CTL);
2507:
2508: if (stateSuccessReturnController != null) {
2509: if (successTransition != null) {
2510: throw new ControllerException(
2511: "State cannot handle stateSuccessReturn Transition object "
2512: + "AND stateSuccessXXX parameters in the same call.");
2513: }
2514:
2515: String stateSuccessReturnState = request
2516: .getParameter(STATE_SUCC_STATE);
2517: successTransition = new Transition();
2518: successTransition.setState(stateSuccessReturnState);
2519: successTransition
2520: .setControllerObject(stateSuccessReturnController);
2521: request.removeParameter(STATE_SUCC_CTL);
2522: request.removeParameter(STATE_SUCC_STATE);
2523: }
2524: //Only set if not null. This allows design-time defaults if no runtime routing overrides it.
2525: if (successTransition != null) {
2526: nextState.setSuccessTransition(successTransition);
2527: }
2528:
2529: //process any STATE ERROR routing
2530: Transition errorTransition = null;
2531: String errorTran = request.getParameter(STATE_ERR_TRAN);
2532:
2533: if (errorTran != null) {
2534: request.removeParameter(STATE_ERR_TRAN);
2535:
2536: try {
2537: errorTransition = Transition.fromXML(errorTran);
2538: } catch (Exception e) {
2539: throw new ControllerException(e);
2540: }
2541: }
2542:
2543: String stateErrorReturnController = request
2544: .getParameter(STATE_ERR_CTL);
2545:
2546: if (stateErrorReturnController != null) {
2547: if (errorTransition != null) {
2548: throw new ControllerException(
2549: "State cannot handle stateErrorReturn Transition object "
2550: + "AND stateErrorXXX parameters in the same call.");
2551: }
2552:
2553: String stateErrorReturnState = request
2554: .getParameter(STATE_ERR_STATE);
2555: errorTransition = new Transition();
2556: errorTransition.setState(stateErrorReturnState);
2557: errorTransition
2558: .setControllerObject(stateErrorReturnController);
2559: request.removeParameter(STATE_ERR_CTL);
2560: request.removeParameter(STATE_ERR_STATE);
2561: }
2562: //Only set if not null. This allows design-time defaults if no runtime routing overrides it.
2563: if (errorTransition != null) {
2564: nextState.setErrorTransition(errorTransition);
2565: }
2566: }
2567:
2568: /**
2569: * This method is the traffic cop that determines which transition to execute
2570: * after a state completes.
2571: * <p/>
2572: * 1. "State Error"
2573: * The state returned with errors in the error collection. The 'error transition'
2574: * associated with the state is executed. This transition could have been set
2575: * either in processRequestTransitions() prior to state execution or in the
2576: * state itself during execution.
2577: * </p>
2578: * <p/>
2579: * <p/>
2580: * 2. "State Success"
2581: * The state returned without errors in the error collection. The following
2582: * priority is followed:
2583: * - If the 'success transition' is not null for this state then it is executed.
2584: * - If the state that just completed is a final state then:
2585: * - If the 'chaining transition' is not null for this controller then it is executed.
2586: * - If the 'controller success transition' is not null for this controller then
2587: * it is executed.
2588: * </p>
2589: *
2590: * @param request The ControllerRequest Object
2591: * @param response The ControllerResponse Object
2592: * @param nextState the state to transition to.
2593: * @return An instantiated Transition Object
2594: * @throws NonHandleableException upon a fatal error.
2595: */
2596: protected Transition processTransitions(ControllerRequest request,
2597: ControllerResponse response, State nextState)
2598: throws ControllerException, NonHandleableException {
2599: Transition successTransition = nextState.getSuccessTransition();
2600: Transition errorTransition = nextState.getErrorTransition();
2601: Transition chainingTransition = null;
2602: PersistentSession session = request.getSession();
2603: String sessionKey = CTL_SUCC_TRAN + getClass().getName();
2604:
2605: //Determine where we will transition to!!
2606: Transition nextTransition = null;
2607:
2608: if (response.hasErrors()) {
2609: nextTransition = errorTransition; //could execute this on final state.
2610: } else {
2611: nextTransition = successTransition;
2612:
2613: if ((nextTransition == null)
2614: && isFinalState(nextState.getName())) {
2615: if (getControllerChainingTransition() != null) {
2616:
2617: //Make it thread-safe
2618: try {
2619: chainingTransition = (Transition) getControllerChainingTransition()
2620: .clone();
2621: } catch (CloneNotSupportedException cne) {
2622: throw new ControllerException(
2623: "Cannot clone the chainingTransition class");
2624: }
2625:
2626: nextTransition = chainingTransition;
2627: } else {
2628: Object successObject = null;
2629:
2630: try {
2631: successObject = session
2632: .getPersistentAttribute(sessionKey);
2633: } //Can occur if user invalidated the session! (ugly)
2634: catch (NullPointerException npe) {
2635: log.debug("Invalidated Session", npe);
2636: }
2637: if (successObject != null) {
2638: String controllerSuccess = successObject
2639: .toString();
2640:
2641: try {
2642: nextTransition = Transition
2643: .fromXML(controllerSuccess);
2644: } catch (Exception e) {
2645: throw new ControllerException(e);
2646: }
2647: }
2648: }
2649: }
2650: }
2651: // Now setup context (if chaining) and then transition. Context includes any
2652: // parameters required for the destination controller as well as any return
2653: // address that the source controller delegates to the destination controller.
2654: if (nextTransition != null) {
2655: if (nextTransition == chainingTransition) {
2656:
2657: String oneParamKey = "";
2658: String oneParamValue = "";
2659: Hashtable transitionParams = nextTransition.getParams();
2660: for (Enumeration ep = transitionParams.keys(); ep
2661: .hasMoreElements();) {
2662: oneParamKey = (String) ep.nextElement();
2663: oneParamValue = (String) transitionParams
2664: .get(oneParamKey);
2665:
2666: //Assign the 'required' parameters from the calling controller.
2667: if (oneParamValue.equals("")) {
2668: oneParamValue = request
2669: .getParameter(oneParamKey);
2670: if (oneParamValue == null) {
2671: throw new ControllerException(
2672: "Required chaining parameter missing"
2673: + oneParamKey);
2674: } else { //Update the blank param with its value from the calling state's request
2675: nextTransition.addParam(oneParamKey,
2676: oneParamValue);
2677: }
2678: }
2679: }
2680:
2681: Object successObject = null;
2682: try {
2683: successObject = session
2684: .getPersistentAttribute(sessionKey);
2685: } //Can occur if user invalidated the session! (ugly)
2686: catch (NullPointerException npe) {
2687: log.debug("Invalidated Session", npe);
2688: }
2689: if (successObject != null) {
2690: String controllerSuccessReturn = successObject
2691: .toString();
2692:
2693: //pass the ctler's return address so the next controller will inherit it.
2694: nextTransition.addParam(CTL_SUCC_TRAN,
2695: controllerSuccessReturn);
2696: }
2697: }
2698:
2699: nextTransition.transition(request, response);
2700:
2701: return nextTransition;
2702: }
2703:
2704: return null; //No transition was handled here.
2705: }
2706:
2707: /**
2708: * Set the transition that will be executed when this controller completes
2709: * successfully. This will not likely be used as often as the runtime
2710: * 'controller success' routing parameters. However when used, it will
2711: * relay any 'controller success' parameters onto the next controller
2712: * so that the return path is not lost.
2713: *
2714: * @param newControllerChainingTransition
2715: * The new transition for when the
2716: * controller completes successfully.
2717: * @throws NonHandleableException upon incorrect parameters.
2718: */
2719: protected void setControllerChainingTransition(
2720: Transition newControllerChainingTransition)
2721: throws NonHandleableException {
2722: if (!newControllerChainingTransition
2723: .isExternalTransition(getClass().getName())) {
2724: throw new NonHandleableException(
2725: "Chained Transition must go to another controller.");
2726: }
2727: if (newControllerChainingTransition.isReturnToSenderEnabled()) {
2728: throw new NonHandleableException(
2729: "Chained Transition cannot return to sender.");
2730: }
2731:
2732: controllerChainingTransition = newControllerChainingTransition;
2733: }
2734:
2735: /**
2736: * Set the transition that will be executed when a user attempts to run
2737: * a state he doesn't have authorization on. As always, this transition
2738: * can have return-to-sender enabled. This could allow a login screen
2739: * to be called and then control would automatically return to the
2740: * state whose authorization failed.
2741: *
2742: * @param newControllerSecurityTransition
2743: * To execute.
2744: */
2745: protected void setControllerSecurityTransition(
2746: Transition newControllerSecurityTransition) {
2747: controllerSecurityTransition = newControllerSecurityTransition;
2748: }
2749:
2750: /**
2751: * Convenience method to be able to access the state
2752: * as a property from a JSP.
2753: *
2754: * @param newState The newState to set to.
2755: * @param params The ControllerRequest object
2756: * @return a newly instantiated ControllerResponse
2757: * @throws ControllerException upon error
2758: * @throws NonHandleableException upon fatal error
2759: */
2760: public ControllerResponse setCurrentState(String newState,
2761: ControllerRequest params) throws ControllerException,
2762: NonHandleableException {
2763: return newState(newState, params);
2764: } /* setCurrentState(String) */
2765:
2766: /**
2767: * Set what state to invoke if no state parameter is set.
2768: *
2769: * @param newInitialState The state to use as the initial state.
2770: */
2771: public void setInitialState(String newInitialState) {
2772: if (!StringUtil.notNull(newInitialState).equals("")) {
2773: if (getState(newInitialState) != null) {
2774: initialStates
2775: .put(getClass().getName(), newInitialState);
2776: } else {
2777: throw new IllegalArgumentException(
2778: "Unable to set initial state " + " to '"
2779: + newInitialState
2780: + "', this is not a valid "
2781: + "state for this controller.");
2782: }
2783: }
2784: } /* setInitialState(String) */
2785:
2786: /**
2787: * Tell this Controller object what Schema it belongs to. This is used
2788: * when the Controller tries to use it's "getString(String, Object[])"
2789: * method to prepare internationalized messages - it passes the call
2790: * along to the appropriate schema which knows how to locate the
2791: * proper message file.
2792: *
2793: * @param schemaClass The classname of the Schema to associate with.
2794: */
2795: protected void setSchema(String schemaClass) {
2796: StringUtil.assertNotBlank(schemaClass,
2797: "Must specify a non-blank schema");
2798: Stack stack = (Stack) schemas.get(getClass().getName());
2799: if (stack == null) {
2800: stack = new Stack();
2801: //We have to push Expresso on because of items such as Must Login First
2802: stack
2803: .push(com.jcorporate.expresso.core.ExpressoSchema.class
2804: .getName());
2805: }
2806:
2807: if (!stack.contains(schemaClass)) {
2808: stack.push(schemaClass);
2809: }
2810:
2811: schemas.put(getClass().getName(), stack);
2812: } /* setSchema(String) */
2813:
2814: /**
2815: * Identical to setSchema(String) but provides a typesafe way of passing
2816: * parameters. Example: <br />
2817: * <code> setSchema(com.jcorporate.expresso.core.ExpressoSchema
2818: * .<b>class</b>);</code> <br />
2819: *
2820: * @param schemaClass The Class of the schema to set
2821: */
2822: protected void setSchema(Class schemaClass) {
2823: if (schemaClass == null) {
2824: throw new IllegalArgumentException(
2825: "Parameter schemaClass must not be null");
2826: }
2827:
2828: setSchema(schemaClass.getName());
2829: }
2830:
2831: /**
2832: * Allows for DBCreate to set up proper default
2833: * values for views, etc.
2834: *
2835: * @param dbName the name of the db to add these values to.
2836: * @throws DBException upon error
2837: */
2838: public void setupDefaultValues(String dbName) throws DBException {
2839: }
2840:
2841: /**
2842: * This method is called before a state executes. It will assign a
2843: * return-to-sender transition to the state. This transition will
2844: * include the parameters that this state is being called with.
2845: * <p/>
2846: * If this state causes a transition (with return-to-sender enabled) to
2847: * be executed, then this state's return-to-sender transition will be
2848: * serialized and passed to the next controller. This controller will
2849: * then use this transition (once the controller completes successfull)
2850: * to return to this state with the same parameters that this state was
2851: * initally called with.
2852: * <p/>
2853: * This method will also check for a request parameter that overrides the
2854: * return-to-sender transition that would normally be associated with this
2855: * state. This is useful when the final state reruns all of the controller's
2856: * handle states. In that case, any transitions should return to the final
2857: * state and not the handle state that caused the transition.
2858: *
2859: * @param nextState = The state that is about to be executed
2860: * @param request The parsed ControllerRequest object
2861: * @throws ControllerException upon error.
2862: */
2863: protected void setupReturnToSender(State nextState,
2864: ControllerRequest request) throws ControllerException {
2865: Transition returnToSender = null;
2866: String tran = request.getParameter(RETURN_TO_SENDER_TRAN);
2867:
2868: if (tran != null) {
2869: request.removeParameter(RETURN_TO_SENDER_TRAN);
2870:
2871: try {
2872: returnToSender = Transition.fromXML(tran);
2873: } catch (Exception e) {
2874: throw new ControllerException(e);
2875: }
2876: }
2877: //This is the transition that will be executed from another state/controller to return to
2878: //this state. So don't call enableReturnToSender() otherwise we will run into a little
2879: //ping pong action.
2880: if (returnToSender == null) { //don't override if passed in (eg final mode)
2881: returnToSender = new Transition();
2882: returnToSender.setState(nextState.getName());
2883: returnToSender.setControllerObject(getClass().getName()); //back to here
2884:
2885: //Cache the parameters so that when/if this Transition is executed by some other state, our
2886: //input parameters to this state will be available.
2887: returnToSender.setReturnToSenderParms(request);
2888: }
2889:
2890: nextState.setReturnToSender(returnToSender);
2891: }
2892:
2893: /**
2894: * Is this state allowed for the current user? The generic controller
2895: * object can't determine this, but the DBController child of this
2896: * object checks the database to determine which users can access which
2897: * states.
2898: *
2899: * @param newState The name of the state being checked
2900: * @param params The ControllerRequestObject
2901: * @return True if the state is allowed, else false if it is not
2902: * @throws ControllerException upon error
2903: */
2904: public boolean stateAllowed(String newState,
2905: ControllerRequest params) throws ControllerException {
2906: return true;
2907: } /* stateAllowed(String) */
2908:
2909: protected void transition(String newState, ControllerRequest req,
2910: ControllerResponse res) throws ControllerException,
2911: NonHandleableException {
2912: transition(newState, req, res, true);
2913: } /* transition(String, ControllerRequest, ControllerResponse) */
2914:
2915: /**
2916: * convenience method for transition to other controller
2917: *
2918: * @param newState the new state to transition to
2919: * @param externalController the class of the external controller
2920: * @param req the ControllerRequest parameter that the state handler has
2921: * @param res the ControllerResponse object that was passed to your state method
2922: * @throws ControllerException upon error
2923: * @throws NonHandleableException upon fatal error
2924: */
2925: protected void transition(String newState,
2926: Class externalController, ControllerRequest req,
2927: ControllerResponse res) throws ControllerException,
2928: NonHandleableException {
2929:
2930: Transition trans = new Transition("", "", externalController,
2931: newState);
2932: trans.transition(req, res);
2933: }
2934:
2935: /**
2936: * Transition to an internal state. By internal state we mean that this method
2937: * is used for transitioning to another 'state' inside the same controller class
2938: * [inherited states not withstanding]. You cannot, however use this method
2939: * to transfer control between controllers
2940: *
2941: * @param newState the state to transition to
2942: * @param req The ControllerRequest object that has been given your state
2943: * handler
2944: * @param res The ControllerResponse object that has been given to your
2945: * state handler
2946: * @param clear Should existing ControllerElements (Input/Output/Block/Transition)
2947: * be removed from the resulting ControllerResponse (set to true if that is the
2948: * desired behavior)
2949: * @throws ControllerException upon error
2950: * @throws NonHandleableException upon fatal error
2951: */
2952: protected void transition(String newState, ControllerRequest req,
2953: ControllerResponse res, boolean clear)
2954: throws ControllerException, NonHandleableException {
2955: if (log.isDebugEnabled()) {
2956: log.debug("Transitioning to state '" + newState
2957: + "' in controller '" + getClass().getName() + "'");
2958: }
2959:
2960: ControllerResponse newResponse = newState(newState, req);
2961:
2962: if (clear) {
2963: res.clearOutputCache();
2964: res.clearInputCache();
2965: res.clearTransitionCache();
2966: res.clearBlockCache();
2967: }
2968:
2969: res.setDBName(newResponse.getDBName());
2970: res.setCustomResponse(newResponse.isCustomResponse());
2971: res.setCurrentState(newResponse.getCurrentState());
2972: res.setStyle(newResponse.getStyle());
2973: res.setTitle(newResponse.getTitle());
2974:
2975: Block oneBlock = null;
2976: Vector newBlocks = newResponse.getBlocks();
2977:
2978: if (newBlocks != null) {
2979: for (Enumeration eb = newBlocks.elements(); eb
2980: .hasMoreElements();) {
2981: oneBlock = (Block) eb.nextElement();
2982: oneBlock.setControllerResponse(res);
2983: res.addBlock(oneBlock);
2984: }
2985: }
2986:
2987: Output oneOutput = null;
2988: Vector newOutputs = newResponse.getOutputs();
2989:
2990: if (newOutputs != null) {
2991: for (Enumeration eo = newOutputs.elements(); eo
2992: .hasMoreElements();) {
2993: oneOutput = (Output) eo.nextElement();
2994: oneOutput.setControllerResponse(res);
2995: res.addOutput(oneOutput);
2996: }
2997: }
2998:
2999: Vector newInputs = newResponse.getInputs();
3000: Input oneInput = null;
3001:
3002: if (newInputs != null) {
3003: for (Enumeration ei = newInputs.elements(); ei
3004: .hasMoreElements();) {
3005: oneInput = (Input) ei.nextElement();
3006: oneInput.setControllerResponse(res);
3007: res.addInput(oneInput);
3008: }
3009: }
3010:
3011: Vector newTransitions = newResponse.getTransitions();
3012:
3013: if (newTransitions != null) {
3014: Transition oneTransition = null;
3015:
3016: for (Enumeration et = newResponse.getTransitions()
3017: .elements(); et.hasMoreElements();) {
3018: oneTransition = (Transition) et.nextElement();
3019: oneTransition.setControllerResponse(res);
3020: res.addTransition(oneTransition);
3021: }
3022: }
3023:
3024: res.setControllerClass(newResponse.getControllerClass());
3025: } /* transition(String, ControllerRequest, ControllerResponse) */
3026:
3027: /**
3028: * Move the state's form data back into the controller's form.
3029: * The controller's form is specified in the struts config file. The state's
3030: * form is specified in the state's stateFormClass attribute.
3031: *
3032: * @param stateForm The stateForm to unload
3033: * @param controllerForm The associated controllerForm
3034: * @throws ControllerException upon error transferring the data between
3035: * the forms.
3036: */
3037: protected void unloadStateForm(StateForm stateForm,
3038: ActionForm controllerForm) throws ControllerException {
3039: if (stateForm != controllerForm) {
3040: try { //stateForm could be null here.
3041: BeanUtils.populate(controllerForm, BeanUtils
3042: .describe(stateForm));
3043: } catch (Exception e) {
3044: if (com.jcorporate.expresso.core.controller.DefaultForm.class
3045: .getName().equals(
3046: controllerForm.getClass().getName())) {
3047: return;
3048: }
3049: throw new ControllerException(e);
3050: }
3051: }
3052: }
3053:
3054: /**
3055: * redirect response so that URL changes in client browser
3056: *
3057: * @param request The ControllerRquest object.
3058: * @param response The ControllerResponse object
3059: * @param redirectURL The URL to redirect the browser to.
3060: * @throws IOException upon redirect error.
3061: */
3062: public void redirectRequest(ControllerRequest request,
3063: ControllerResponse response, String redirectURL)
3064: throws IOException {
3065: response.setCustomResponse(true); // tell expresso not to forward to jsp after finishing controller method
3066: ServletControllerRequest sr = (ServletControllerRequest) request;
3067: HttpServletResponse httpResponse = (HttpServletResponse) sr
3068: .getServletResponse();
3069: this .redirectRequest(httpResponse, redirectURL);
3070: }
3071:
3072: /**
3073: * Checks to see if SSL should be toggled for this
3074: * action
3075: *
3076: * @param aRequest The current request object
3077: * @param aResponse The current response object
3078: * @param isSecure should the request be secure.
3079: * @return true if this state should be toggled for protocol
3080: */
3081: private boolean checkSsl(HttpServletRequest aRequest,
3082: HttpServletResponse aResponse, boolean isSecure) {
3083:
3084: String redirectString = null;
3085: com.jcorporate.expresso.core.misc.ConfigExpresso config = ConfigManager
3086: .getConfig();
3087: redirectString = SecureRequestUtils.getRedirectString(aRequest,
3088: config.getHttpPort(), config.getSslPort(), isSecure);
3089:
3090: if (redirectString != null) {
3091: try {
3092: // Redirect the page to the desired URL
3093: this .redirectRequest(aResponse, redirectString);
3094: return true;
3095: } catch (java.io.IOException ioe) {
3096: log.warn("IOException in redirect: ", ioe);
3097: // System.out.println("IOException in redirect" + ioe.getMessage());
3098: }
3099: }
3100:
3101: return false;
3102: }
3103:
3104: /**
3105: * Call from subclass to log into Category with subclass name
3106: * will create logger with subclass name as necessary.
3107: * @return org.apache.logj.Logger
3108: */
3109: public synchronized Logger getLogger() {
3110: if (mLog == null) {
3111: setupSubclassLog();
3112: }
3113:
3114: return mLog;
3115: }
3116:
3117: /**
3118: * setup a subclass logger separately from the base controller logger.
3119: * Is this right? *PP*
3120: */
3121: protected synchronized void setupSubclassLog() {
3122: if (mLog == null) {
3123: mLog = Logger.getLogger(getClass());
3124: }
3125: }
3126:
3127: /**
3128: * Generate a new transaction token, to be used for enforcing a single
3129: * request for a particular transaction.
3130: * <p/>
3131: * <p/>
3132: * Thanks to "Raul DAVIDOVICH" (R.DAVIDOVICH@caconcology.com)
3133: * </p>
3134: *
3135: * @param request The request we are processing
3136: * @return transaction token usually cryptographically created
3137: */
3138: protected String generateToken(ControllerRequest request) {
3139: if (request instanceof ServletControllerRequest) {
3140: ServletControllerRequest scr = (ServletControllerRequest) request;
3141: return super .generateToken((HttpServletRequest) scr
3142: .getServletRequest());
3143: } else {
3144: return "no-web-environment.transaction.token";
3145: }
3146: }
3147:
3148: /**
3149: * Return <code>true</code> if there is a transaction token stored in
3150: * the user's current session, and the value submitted as a request
3151: * parameter with this action matches it. Returns <code>false</code>
3152: * under any of the following circumstances:
3153: * <ul>
3154: * <li>No session associated with this request</li>
3155: * <li>No transaction token saved in the session</li>
3156: * <li>No transaction token included as a request parameter</li>
3157: * <li>The included transaction token value does not match the
3158: * transaction token in the user's session</li>
3159: * </ul>
3160: *
3161: * @param request The servlet request we are processing
3162: * @return boolean value if the request transaction token matched
3163: * the stored transaction token.
3164: * @see #generateToken
3165: * @see #resetToken
3166: * @see #saveToken
3167: */
3168: protected boolean isTokenValid(ControllerRequest request) {
3169: if (request instanceof ServletControllerRequest) {
3170: ServletControllerRequest scr = (ServletControllerRequest) request;
3171: return super .isTokenValid((HttpServletRequest) scr
3172: .getServletRequest());
3173: } else {
3174: // Also true in a new app web environment e.g command line interface
3175: return true;
3176: }
3177: }
3178:
3179: /**
3180: * Reset the saved transaction token in the user's session. This
3181: * indicates that transactional token checking will not be needed
3182: * on the next request that is submitted.
3183: *
3184: * @param request The servlet request we are processing
3185: * @see #saveToken
3186: * @see #isTokenValid
3187: */
3188: protected void resetToken(ControllerRequest request) {
3189: if (request instanceof ServletControllerRequest) {
3190: ServletControllerRequest scr = (ServletControllerRequest) request;
3191: super .resetToken(scr.getHttpServletRequest());
3192: }
3193: }
3194:
3195: /**
3196: * Save a new transaction token in the user's current session,
3197: * creating a new session if necessary.
3198: *
3199: * @param request The servlet request we are processing
3200: * @see #resetToken
3201: * @see #isTokenValid
3202: */
3203: protected void saveToken(ControllerRequest request) {
3204: if (request instanceof ServletControllerRequest) {
3205: ServletControllerRequest scr = (ServletControllerRequest) request;
3206: super .saveToken(scr.getHttpServletRequest());
3207: }
3208: }
3209:
3210: /**
3211: * Fetches array of parameter values from underlying HTTP request; use this in a web app to access
3212: * the underlying parameters in the HTTP request which have the same name; parameters with the same name are not
3213: * reflected in the hashtable maintained by ControllerRequest;
3214: *
3215: * @param request cast ControllerRequest to get required type
3216: * @param paramName key to look for among all parameters
3217: * @return an array of String objects containing all of the values the given request parameter has, or null if the parameter does not exist.
3218: */
3219: public static String[] getParamValues(
3220: ServletControllerRequest request, String paramName) {
3221: return request.getParamValues(paramName);
3222: }
3223: } /* Controller */
|