Chapter 11. Using Controller Objects

Table of Contents

Introduction
MVC Architecture
Controllers Overview
Why Use Controllers?
Pitfalls to Avoid in Using Controllers
UI-Independant
Form-Handling
Controllers and EJBs
Controller Basics
Inputs
Outputs
Transitions
Blocks
Types of Controller
Controller Objects Included with Expresso
Controller Activity Explained
States
Internal States
External States
Transitioning
Extending Controllers
Background information
Extending the User Interface
Extending the default controller behaviors.
Workflow
Conclusion
Contributors

Note

If you find this EDG documentation helpful please consider DONATING! to keep the doc alive and current.

 Maintainer:David Lloyd

Expresso complements the J2EE architecture by providing a framework for a clean separation of data, presentation, and application logic.

Introduction

An Expresso Controller is a Struts Action subclass. (Contrary to what may be initial expectation, a "Controller" does not therefore extend the Struts ActionServlet.) The Expresso Controller component is thus strictly part of the front end: the web tier. Controller objects provide a means for a sequence of interactions to be encapsulated in a way that makes them available to virtually any kind of user interface (e.g. servlet, JSP, Applet, Application and others). A Controller is a finite-state machine, where the flow from one state to another is directed by the Controller itself, and by the actions the user takes.

Controller objects should be used to encapsulate the logic of your application, especially any user-interaction required. The idea is to take advantage of things that are provided by the framework and not re-invent anything that is not required. You must design your user-interface in a presentation-independent way, thinking in terms of abstract inputs and outputs, to take best advantage of Controller objects. This allows you to take advantage of the GUI-independent user-interface abilities in Expresso, easily modifying the "view" of your application even once the logic is completed.

Expresso has a built-in mechansim by which you can specify security for each of your your controllers. For example, a group of users can be allowed or not allowed to access the entire controller, or can be granted permission to only specified states within a controller. This allows a single controller to be used by a wider audience of users, where all states might be available to only certain users (say, system administrators).

MVC Architecture

Keeping the elements of Model (business tier), View (presentation and user interface) and Controller separated is a central issue of good application design. The Expresso "Controller" objects make this easy when building web applications. Expresso includes a package of components for creating several types of "Controller" objects. These Controller objects encapsulate a series of interactions with the user, in a manner similar to Session EJB's (in fact a Controller can be a Session EJB in an environment where EJB's are supported). The Controller can be utilised from any kind of client: a Servlet, a JSP, an Applet, or an Application.

The Apache Jakarta project has a sub-project called "Struts" that has complimentary aims to the Controller object in Expresso. Struts is considered by many to be the defacto MVC implmentation. Therefore Expresso is integrated with Expresso (as of v4.0) and brings the best of both worlds to Expresso. The struts-config.xml file provides the mappings required by the Struts frameworks from URL's to "Action" objects - in this case, to the Controller objects in Expresso (which are sub-classes of the Action object.) In addition to the normal struts-config.xml file, applications that have their own Controller objects can provide mappings for them in separate configuration files.

For example, eForum has an eforum-config.xml file that provides mappings for it's Controllers. In this way, the core struts-config.xml file for Expresso does not need to be adjusted as you install or develop other applications, and the individual configurations for your own applications can be easily adjusted and kept independant.

Expresso includes a package of components for creating several types of "Controller" objects. These Controller objects encapsulate a series of interactions with the user, in a manner similar to Session EJB's (in fact a Controller can be a Session EJB in an environment where EJB's are supported). The Controller can be utilized from any kind of client: a Servlet, a JSP, an Applet, or an Application. Controllers are covered in more detail in their own chapter in this guide.

Controllers Overview

Controller objects provide a means for a sequence of interactions to be encapsulated in a way that makes them available to virtually any kind of user interface (e.g. servlet, JSP, Applet, Application and others). A Controller is a finite-state machine, where the flow from one state to another is directed by the Controller itself, and by the actions the user takes. Controllers are collections of States. A State generally correspondence to a single web page. The controller can arbitrarily encapsulate a number of states, as decided by the developer, but usually states are grouped by purpose. For example, a state that prompts for some input, and the state that handles the posting of that input, would usually be grouped together in the same controller.

Furthermore, Expresso supports chaining states together in a "wizard" or "process funnel", also known as workflow. This workflow association makes the controller the ringmaster for validation, and puts the definition of workflow into the controller. See the workflow chapter in this manual.

Controllers can be secured using the security mechanisms of Expresso - a group of users can be allowed or not allowed to access the entire Controller, or can be granted permission to only specified states within a controller. This allows a single controller to be used by a wider audience of users, where all states might be available to only certain users (say, system administrators).

Why Use Controllers?

Controllers provide a number of advantages:

  • Encapsulate business logic separately from user interface logic: Controllers are not concerned with the user interface that presents their Inputs, Outputs and Transitions to the client - this separates the business and user interface logic cleanly, and allows each to be maintained as independently as possible, promoting good design practices.

  • Dynamic Secured Access: Every state of a controller can be secured, and the security data is easily maintainable via Expresso's built-in capabilities. This allows the security to be updated from any location, and for the changes to take effect immediately. This makes controllers ideal for situations where a user's permissions might be updated dynamically - for example, when a customer completes a controller allowing him access to some on-line information, the succesful completion of one controller could then permit him access to additional states in other controllers.

  • MVC Architecture: Controller objects provide the "Controller" portion of the MVC architecture in a way that is portable across all types of Java environments. They can scale all the way from a Java Micro-edition environment to a complex multi-server cluster using EJB's and application servers.

  • Default User Interface: There is a default ViewHandler implementation that is used where no "custom" view is defined, which can be used to run a controller without the need to program a custom GUI or design with JSPs, allowing a basic controller to be deployed very quickly, then perhaps enhanced later with a custom UI.

  • XML User Interface: In additional user interface option allows controller responses to be sent via XML, optionally transformed via an XSL stylesheet. In addition to providing great UI flexibility, this mode is also very handy for debugging.

  • Session Management: Controllers themselves do not preserve any information about their state from one invocation to another, requiring their input items and parameters to provide them with the information needed to process the next state. They can, however, use a PersistantSession object to preserve state information across invocations, and to make it available to other controller objects.

  • Test Harnesses: Useful test harnesses exist for testing controller objects, both from the command line and from a JSP.

Pitfalls to Avoid in Using Controllers

Controllers are generally run in a servlet context. This means that they remain resident in RAM, and multiple threads run through them. Controllers must be thread safe in this context. Thread un-safe code is the 'bogeyman' that haunts any servlet project, because the bugs are often intermittent and hard to reproduce and fix. And they happen under load, when stability is particularly valuable. Part of the EJB design is to remove multithreading as a programmer responsibility, by having pools of EJB objects and running their methods in a strictly controlled manner, cloning EJBs if necessary, avoiding concurrency. However, EJBs engender their own complexity too, and for some purposes, there can be smart applications of the concept of one object, many threads.

The following references and cautions apply to Controllers:

  • Understand servlets.

  • Understand concurrency.

  • Of primary concern are any data members in your Controllers, and in the supporting expresso framework. Think twice about adding data members to a controller. For example, if you need some kind of cache for efficiency, use synchronized containers like Hashtable, or use synchronized blocks for initialization of more efficient tools like the oswego collections (note that this jar is already available in the Expresso library).

  • Static data members are even more prone to thread-safety trouble. To synchronize these, the synchronization lock must be on a static object; in particular, the "synchronized" keyword in a method signature will not suffice unless that method is a static method.

  • Test code under load. Use the Morebot tool or your own custom tool to simulate the activity of many users touching the active pages in your application. If you get reports of intermittent failures, adapt the test to focus on the pages in question in order to reproduce the failure.

UI-Independant

The controller architecture is independant of the method used to present the outputs of the controller to the user - User Interfaces (UI) are available for several different web-application technologies, including:

  • JSP (Java Server Pages): This is a popular method of creating the "view" for Controller objects.

  • JSF (Java Server Faces):

  • WebMacro: This templating engine can be used to create the view as webmacro template files from Controller objects.

  • Text: Useful for testing, ordinary command-line operation of controllers is possible.

  • XSL: Where the UI may be a mobile device, WebTV, a PDF reader, or any of a number of other kinds of browsers, XML outputs can be generated from Controllers that can be transformed via the appropriate XSL stylesheet into WML, PDF, HTML, XHTML, XML, and many other formats. This function is part of the Expresso XML sub-project.

Form-Handling

Special support facilities exist to make using Controller objects easier in a web forms-based environment, providing the ability to validate forms, especially against DB Objects, suppliing error handling and retry abilities, and provide the ability for the user to navigate between forms easily.

Controllers and EJBs

DBObjects are not Enterprise Java Beans. However, if you have Expresso Framework 5 downloaded on your webserver, you can work with Enterprise Java Beans if you want to. There are no restrictions. However since Expresso does not create or manipulate EJBs directly (at the moment), but instead talks to the database using DBObjects, you will have to do the extra work yourself. Briefly, EJB technology is designed to be distributed component based, transaction safe, scalable across virtual machines, and portable across J2EE application servers. This is important if you run Enterprise mission-critical applications. In detail, EJB provides the following features:

  • Distributed Component-based: EJB is a component build technology that permits deployment across local and foreign servers.

  • Transaction safe: J2EE specifies level of transaction control for Enterprise JavaBeans so that two separate database transactions can be undertaken without serious race conditions, double insertions, or phantom reads.

  • Scalable: J2EE specifies that application servers can deploy EJBs across virtual machines on different CPUs and across networks. So if you buy an application server you can take advantage of clustering features, load balancing, etc. for your EJBs. (For example, if the server in London crashes, then the one in New York can take over running a bank business 24/7/365 over the web. Moreover, if you find that web business doubles overnight then you can buy more applications servers and spread the work.)

  • Portable: J2EE certified application servers can deploy EJBs that are written on another EJB server. Your mileage may vary if you use proprietary server's features of course, but that's life.

DBObjects (in Expresso 5.X) do not have any of the above Enterprise features. However DBObjects are better than nothing if you have no Object Relational layer. DBObjects are good because you can manipulate database tables within Java, the language we all love. DBObjects are an implementation of a well-recognized software design pattern (see Mark Grand, J2EE Enterprise Patterns, Wiley 2001) For more info look at TheServerSide.com for Chuck Cavaness' preview book chapter on ``EJB and Struts''.

Controller Basics

A Controller object can be thought of as a finite-state machine, transitioning from state to state and performing the appropriate business logic at each state. These states comprise the "Model" of your application - it is here that the application interacts with the database, with the user, with files and other resources, and does it's processing. So if each state represents a particular step or unit of processing, the controller can be thought of as a (finite-state) machine, and the function of transitioning from one state to another is how the controller gets it's work done.

What is a (deterministic) finite state machine? Mathematically, it is a directed graph, where the nodes are "system states" and the arrows are labelled with "user actions". Thus given any state and a user action, exactly one state results and the finte state machine has the responsibility of determining what that resultant state is. Intuitively, you pick some state, follow some user action - via an arrow out of that state - and the node at the end of that arrow tells you what state results due to that action. One state is designated as the "initial state" and some nonempty subset of states as the "final states". Intuitively, the final states are thought of as "successful states": reached via successful completions of (possibly) different user actions.

Controllers are basically a collection of "States", where each state represents a particular step or unit of processing. Thus, the controller is really a finite-state machine, and the function of transitioning from one state to another is how the controller gets it's work done.

As a controller transitions to a new state, it generates a ControllerResponse object. This object contains a group of ControllerElement objects, of 4 types:

Inputs

An Input object is a request from the Controller for the client to supply some information. Some additional attributes of the Input object can provide some formatting "hints", which the client may or may not use, but the actual presentation to the user is up to the UI portion of the application. Input objects may also provide "valid values", which allow the UI to present a list of values to the user for him/her to select from. Again, the nature of this list is up to the UI layer.

Normally, if a particular state of a Controller requests an input, the subsequent states will require it as a parameter.

Outputs

An Output object represents a response from the Controller to be presented to the user. This can be as simple as a single String, or as complex as a nested tree of items, that the UI might choose to represent as a table, for example. Every Output may have zero or more other Outputs "nested" within it, to represent structure in the returned items. For example, an Output called "invoice" might have a number of "line item" outputs nested within it.

Output's also have "Attributes", which are a group of arbitrary name/value pairs that help further describe the Output to the client application - for example, the "invoice" Output might have an attribute called "count", which might give the number of line items associated with this invoice.

Transitions

A Transition object represents a choice for the client to transition to a new state - only states that are appropriate given the current state and allowed given the current user's permission have action items generated for them.

Blocks

For easier use in JSP's (and other environments) where there are a lot of inputs, outputs and transitions, the concept of "blocks" is available to help keep the controller response organized.

A Block object's purpose is to act as a container for other ControllerElement objects such as Transition, Input, Output and other Block objects. The idea behind a Block object is that it is a logical grouping of other ControllerElement objects for presentation purposes. Think about a HTML page which has multiple sections (not necessarily HTML frames), with each section having its own text messages, buttons/links and forms.  by name, which Input, Output and Transition items were to be grouped together.  With Block objects,  the Controller writer would  group each logical set of Input, Output and Transition objects within a Block, with each logical section of the HTML page getting allocated its own Block.  The JSP writer in turn, would simple get an Enumeration of all the Blocks from the controller,  iterate over each ControllerElement and then create appropriate HTML for each object.  Since a Block returns each ControllerElement in the order it was put into the Block, if a JSP writer did not care, the ControllerElements could be placed in the same order that the Controller writer put them in.  Of course, the JSP-writer is still free to access each ControllerElement by name as previously and then do a custom HTML presentation.

Here's an example.  Let's assume that the HTML page consists of several lines of messages, followed by some links, followed by a form. In some Controller.someState():

//The first logical block 
Block b1 = new Block("Header"); // The parameter sets the name of the Block 
Output o1 = New Output("Msg1", "This is message 1"); 
b1.add(o1); //Nest the action within the block 
Output o2 = new Output("Msg2", "This is message 2"); 
b1.add(o2); 
Output o3 = New Output("Msg3", "This is message 3"); 
b1.add(o3); 
addBlock(b1); //Add the block to the controller. 
 
// The second logical block 
Block b2 = new Block("Links"); 
Transition a1 = new Transition("Take action 1", "/servlets/ControllerServlet?trx=......."); 
a1.setName("Transition"); 
b2.add(a1); 
Transition a2 = new Transition("Take action 2", "/servlets/ControllerServlet?trx=......."); 
a2.setName("Transition"); 
b2.add(a2); 
Output o4 = new Output("Msg4", "Please choose one of the links above"); 
b2.add(o4); 
addBlock(b2); 
 
// The third logical block 
Block b3 = new Block("Form:Form1"); 
Output o5 = new Output("Msg5", "Please fill in the information below"); 
b3.add(o5); 
Input i1 = new Input("Name"); 
i1.setLabel("Name"); 
i1.setType("text"); 
b3.add(i1);
Transition a3 = new Transition("Submit", "/servlets/ControllerServlet?controller=......"); 
a3.setName("Submit");
b3.add(a3);
addBlock(b3);

In a corresponding HTML generator (whether it is another utility class, a bean to be accessed from a JSP, adapter classes for WebMacro/Freemarker, or a JSP itself), one would write something similar to:


 String blockName = ".....";    // Name as in the controller 
 for (Enumeration e = controller.getBlock(blockName).getContents().elements(); 
                  e.hasMoreElements(); ) { 
     ControllerElement oneElement = ( ControllerElement)e.nextElement(); 
     if (oneElement instanceof Transition) { 
         handleTransition((Transition)oneElement); 
     } else if (oneElement instanceof Input) {
         handleInput((Input)oneElement); 
     } else if (oneElement instanceof Output) {
         handleOutput((Output)oneElement); 
     } else if (oneElement instanceof Block) {
         handleBlock((Block)oneElement); 
     } else { 
         throw new Exception("Cannot handle object of type:" 
                            + oneElement.getClass().getName()); 
     }
}

One could also use the controller.getBlocks() call to get an enumeration of all the Block objects to iterate over. Note that the exact mechanics of the HTML output generation is not shown here to keep the example simple and to the point.

Types of Controller

There are several types of controllers available, all extending the base class com.javacorporate.common.controller.Controller. They each have a specific use:

  • com.jcorporate.expresso.core.controller.Controller

    Controller is the base class for all Controller object, and can be directly extended for. Controllers have no built in security of their own and access to all states must be manually set.

  • com.jcorporate.expresso.core.controller.DBController

    DBController extends the base controller and provides integrated security. [Probably a better name would be SecuredController, but due to a legacy issue unknown to the other, DBController it is. ]. When you run DBCreate on a Schema that has DBControllers, a security matrix is generated for each DBController which is checked against before passing the control on to the DBController implementation class itself. Most Expresso-based controllers will derive from DBController since it is usually vital in webapps to have some sort of security system built in.

  • com.jcorporate.expresso.core.controller.SecureIfSetController

    The SecureIfSetController provides a compromise between Controller's insecurity and DBController's strict security. What happens is that SecureIfSet is available to anybody if there is no security set up in the system. An example of this would be an initial "Setup Wizard" that would allow the admin to decide how to set up the machine graphically before it went live. Once DBCreate is run and a security system is up and running, then SecureIfSetController performs in a secure way just like DBController, thus allowing the management controllers to be still accessible for Administrators (for example), but not for everyone in the Internet.

Controller Objects Included with Expresso

The following table lists some of the important Controller classes included with Expresso. All of them belong to the com.jcorporate.expresso.services.controller package.

Table 11.1. Controller Objects Included with Expresso

Controller ClassDescription
CacheControlControls the CacheManager. Instead of controlling the CacheManager directly, this allows a user to clear either one or all of the caches and to display current status of the cache.
ControllerSecurityMatrixProvides an easy conduit for controlling access to controllers and the states within the controllers.
DBMaintA servlet/controller that enables database maintenance of a specified DBObject.
DBSecurityMatrixControls access to DBObjects by authorized people.
EditUserPreferenceAllows a user to edit his/her preferences either for a single object or all of he user's own preferences.
ErrorHandlerDisplays error to user, logs error and triggers email notification to specificed list of users both local and remote.
JobSecurityMatrixAllows easy administration of the security rights of user groups to get to certain Jobs and to certain methods in those Jobs (assuming the job has more than one method).
LogAllows for log administration including archiving current log, starting a new empty log, and viewing of both the database log and the text log.
LoginControllerValidates login/logouts, establishing and releasing session, and basic interaction with the registration systems
LoginListenerA skeleton for a developer to extend.
NavigationProvides a framework for navigating around an application. It does this by using the schema object's information
QueueJobActs as a traffic cop for any named job that the user has access to queue.
RegistrationProvides a mechanism for self registration of for new system users.
StatusShows various information about the servlet systems, the back-end database connections, and server-task tasks.
ValidationControllerMonitors and reacts to the user interaction of the URL being clicked from the validation job.

Controller Activity Explained

In the diagram above, you see a simple representation of a Controller with 5 states. Let's follow the execution of this controller to see how the interactions work:

  • The Controller begins (let's say it's called from a JSP page for the purpose of our discussion) at the "prompt" state at the top. Entering this state causes the controller to generate an Input item, called "Student Id", and two Transition items. The action items indicate that the user can choose to select a course (Transition 2) or show the calendar (Transition 1) from this point (we're assuming that whatever user is logged in does in fact have permission to get to all of these states). Control then returns to the user interface.

  • Let's say the user enters an appropriate Student Id and chooses Transition 2, "select course". The Controller transitions from the "prompt" state to the "select course" state. The "select course" state has a required parameter, the "Student Id" that the previous state prompted for. If this parameter is not present, the transition to the new state will not be successful (and the user should be notified in whatever manner the UI designer chooses - ideally by populating an ErrorCollection object and transitioning back to the previous state so that the user can try their entry again.). In this case, the "Student Id" was present, so the controller enters the "select course" state.

  • This state produces another Input item - this time asking the user to choose from a list of courses. The Input item could be supplied with a list of valid values (in this case, the courses available to this student, for example), and might be represented by the UI as a drop-down list. The "select course" state also generates two action items, Transition 3 and Transition 4. If the user did not have permission to access state "enroll", for example, then Transition 3 would not be generated.

  • Let's say the user selects a course from a list and selects Transition 4 - to transition to the "display grades" state. In this case, the state "display grades" would require two parameters: the "Student Id" from the first state and the course selected by the second state. As both of these are present, the transition occurs, and the "display grades" state generates an Output item (probably a group of output items using nesting) that presents the requested information to the user.

This example is of course very simplistic, and a real controller would have many other connections between states, and probably more complex behaviors in each state, but it serves to illustrate the use of the Controller model. It is important to realize that this Controller could actually be used in a number of different ways - for example, from a JSP page that showed a list of students enrolled in a particular course, an authorized user could select a link that said "Show Grades". This link might invoke our example controller, but not at the initial state: It would go directly to the "display grades" state, and assuming that permissions were OK, and that the course and Student Id were supplied (as these are the required parameters to enter this state), our sample controller would respond with the same output that it provided when it was invoked in the way we describe above. This kind of "interaction" with other controllers increases the utility of the Controller model considerably, and promotes re-use without sacrificing portability or data integrity.

Of course, a Controller object typically interacts with one or more database objects (or other controllers) to do it's job - but these interactions are completely concealed within the states themselves, and the client does not need to be aware of them.

States

It is the states within a Controller that actually contain the logic for the controller to perform it's functions. These states can be coded simply as methods within the Controller object, or can be their own independent objects, inheriting from the "State" superclass. For simple states, it is often easier to code the state as a single method. The controller will automatically invoke the proper state if a specific naming convention is followed: the state method's name should be "run" followed by the name of the state followed by "State". For example, a state called "prompt" would look for a method called runPromptState. The state's method must take two parameters, a ControllerRequest followed by a ControllerResponse, both of which are passed to the method already initialized. The method must be declared void and should be protected to the Controller class itself. Separate State objects are often superior, as they allow a clean separation between each state.

Each state must be completely independant of any others and "thread safe" - e.g. they should not count on shared elements that are local variables to the Controller. Instead, each state simply works on it's parameters and produces it's own Outputs, Inputs and Transitions. It is common to set parameters in the Transitions between states to pass on information from one state to the next.

Note

Using outside classes derived from the State class does not require

Internal States

If states are to be coded "internal" to the Controller class, then the state objects are created internally in the constructor of the method. You then define your state handler with a particular naming convention. An example of this is below:

/** 
 * Our constructor declares the states this 
 * controller supports 
 */ 
public DBSecurityMatrix() { 
   State prompt = new State("prompt", "Prompt for Schema and User Group"); 
   addState(prompt); 
   setInitialState("prompt"); 

   State dbobjmatrix = new State("dbobjmatrix", "Enter/View Database Object permissions"); 
   dbobjmatrix.addParameter("SchemaClass"); 
   dbobjmatrix.addParameter("GroupName"); 
   addState(dbobjmatrix);
} /* DBSecurityMatrix() */

ControllerResponse runPromptState(ControllerRequest request, ControllerResponse response)
        throws ControllerException {
    return response; 
}
 
/*
  runPromptState(ControllerRequest, ControllerResponse);
*/

In the code above, the State object is create, and then the "addState" method used to "register" that state as belonging to the controller. The logic for the state, however, is coded into methods built right into the controller object itself.

To do this, you create a special function that follows a particular naming convention. The naming convention is similar to that of the JavaBeans naming convention, but with a twist to help maintain backwards compatibility. Name your function as run<StateName with first letter capitalized>State(ControllerRequest, ControllerResponse). If you keep to this convention, then Expresso will automatically find your internal state function through the use of the Java Reflection API's.

Servlet Container Specific Controllers [Expresso 5.3]

Controllers are inherently made to be able to be run outside a servlet environment. Such examples are: webservices, command line programs, and even jobs. But what if you wanted to use servlet specific features? In older versions of Expresso you would have the following code in your internal state handler:

1. ControllerResponse runPromptState(ControllerRequest request, ControllerResponse response)
  throws ControllerException {
2.  ServletControllerRequest scr = (ServletControllerRequest)request;
3. /** Get HttpServlet Request through: scr.getHttpServletRequest() **/ .....
4 } 

Since Expresso 5.3, you can now simply change the signature of your state handler to receive ServletControllerRequest directly:

ControllerResponse runPromptState(ServletControllerRequest request,
                                  ControllerResponse response)
        throws ControllerException { 
    /** Get HttpServlet Request through: request.getHttpServletRequest() **/

    .....
}
/*
  runPromptState(ControllerRequest, ControllerResponse);
*/

Expresso will only look for the ServletControllerRequest version of the function if it cannot find a ControllerRequest version of the function. So if you happen to have both, then only the ControllerRequest version will be called.

Note

This feature is not yet available for External States as described below

External States

In a more complex controller, or one with more states, it is often better to create individual "State" objects, rather than the "internal states" methodology discussed above. Again the superclass handles the transitioning to the correct state, dynamically invoking the appropriate State object's "run()" method when each state is entered.

When using "external" state objects, the declaration of the Controller is a bit different:

1. public Upload() {
2. PromptBrowser browser = new
  PromptBrowser("browser", "Prompt for Upload from Browser");
  3. browser.addParameter("resource", false);
  4. addState(browser);
  5.
  6. DoBrowser dobrowser = new DoBrowser("dobrowser", "Process Upload from Browser");
  7. dobrowser.addParameter("action");
  9.
  addState(dobrowser);
  10.
  11. } /* Upload() */

In the listing above, PromptBrowser and DoBrowser are objects defined external to the Controller class that extend the State object. For example, PromptBrowser could look like this:

(package and import statements omitted)
public class PromptBrowser extends State {

   public PromptBrowser(String stateName, String descrip) {
       super(stateName, descrip);
   }

   public void run() throws ControllerException {
       Controller myController = getController();
       String currentNumber = 
                 StringUtil.notNull(myController.getParameter("number")); // here the State will do
                                                                          // whatever logic it needs to
   }/* run() */
}

In the listing above, the PromptBrowser class extends State, defining it as one of the possible states for a Controller, and implements the "run()" method. The "run()" method is called by the superclass of the controller object whenever the Controller transitions into the specified state - it is where all of the work of the state will get done, where the Inputs, Outputs, and Transitions are generated, etc.

In order for a State object to have access to it's Controller's parameters, session, and other information, it has available to it the "getController()" method. This method returns a Controller object - the Controller object that contains this state, specifically. This allows the state to use "getParameter(name)" and other methods to interact with the controller itself, and to use the Controller's "add" methods to return it's Inputs, Outputs and Transitions as required. At the end of the "run()" method, control returns to the Controller superclass, which then processes the new Inputs, Outputs and Transitions appropriately.

As a part of it's processing, a State object often uses one or more DBObjects, and of course it can also submit jobs, access other controllers, and generally perform whatever processing is required of it.

Transitioning

Controller sometimes also need to transition from one state to another programmatically - in other words, not in response to a user clicking a button or selecting a link, but in response to logic within a state. This can be done in one of several ways:

Transitioning in an internal state method

From a state method within a controller it is very easy to transition to another state in the same Controller. Simply call: transition("newstate", req, res);, where "newstate" is the name of the new state (the state name, not the method name), and req and res are the ControllerRequest and ControllerResponse objects that were passed in to the method originally. It is important also to call "return" immediately after the transition, as you usually don't want to do any further processing in the current state.

Transitioning in an external State object

In an external state object, the transition call is identical to the transition method from within a controller. Again, the first argument must be the name of another state within the same controller, and you should still call "return" immediately afterwards.

Transitioning to another Controller

Transitioning into another state is done via the Transition object. You prepare a transition object in exactly the same way as you would if you were going to add the transition to the response object for the user to click. For example:

1. Transition t = new Transition(); 
2. t.setName("goSomewhere"); 
3. t.addParameter("controller", "com.something.SomeOtherController");
4. t.addParameter("state", "someState"); 
5. t.addParameter("otherParameter", "etc"); 
6. t.transition(req, res); 
7. return;

In the code above, we prepare a transition, including a reference to another controller "SomeOtherController" and a specific state of that controller. Once the transition is called, we return from the state method.

Extending Controllers

Expresso has a very good framework to develop your web applications, however, there are many ways to extend it's boxed functionality to better suit your application's needs

Expresso includes many pre-built controllers that address many basic functionality needs: Login, Registration, Download, Database Maintenance. However, often we need to modify the capabilities of these controllers to better fit our own needs. Specifically we'll talk about providing custom User Interfaces easily and how to change the default behavior of a controller.

Background information

As of Expresso 5.0, developers can easily derive their own controllers from existing controllers. The mechanics of this is a rewrite of the reflection mechanism that Expresso controllers utilize to call various states. Now if you do not override any particular state, Expresso's controller walks up the inheritance hierarchy looking for other classes that implement the requested state. If an object in the hierarchy is found to implement the state, it will invoke that state and return. In the past, if your own controller didn't override EVERY state, Expresso's reflection mechanism would fail.

Now, you have the ability to derive your own controller and ONLY override the states where you want to change the default behavior of the base controller.

Extending the User Interface

One of the annoying aspects of dealing with frameworks like Expresso is the fact that normally to change the user interface, you'd have to change the default jsp pages that ship with Expresso. Although this is fine initially, with each release of Expresso, you would have to re-merge your changes. This problem is exacerbated by anybody working with a CVS copy, where CVS will mark any changed files and try to merge any updates, thus preventing any effective development with CVS snapshots.

The solution is to create your own controller, and then create your own JSP pages based upon this new controller

Step 1 - Derive your own Controller Class

The first step is to derive your own controller. We will use Expresso's own login controller as a code example. Here's all that is necessary to create your own controller.

package com.jcorporate.myproject.controller; 
import  com.jcorporate.expresso.core.controller.Controller; 
import  com.jcorporate.expresso.core.controller.ControllerException; 
public class MyLoginController extends com.jcorporate.expresso.services.controller.LoginController { 
   public MyLoginController () {
       super();
       setSchema(com.jcorporate.myproject.MyProjectSchema.class); 
   }
   public String getTitle() {
       return "Sample Controller Login";
   }
}
All state handlers use the base class, and since, for this example, we want the default behavior, we won't be changing anything there.

Step2 - Make your own struts-config file entries

Now that you have your own controller, you must create your own configuration file so that the underlying controller/Struts framework can be aware of your new controller and what files/servlets should do the rendering of your controller's output. Here's myproject-config.xml:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE struts-config PUBLIC 
          "-//Apache Software Foundation//DTD Struts Configuration 1.0//EN"
          "http://jakarta.apache.org/struts/dtds/struts-config_1_0.dtd">
<struts-config>
 <action-mappings>
  <action path="/MyAppLogin"
          type="com.jcorporate.myproject.controller.MyLoginController"
          name="default" scope="request" validate="false">
   <!-- This sample only changes the promptLogin screen -->
   <forward name="promptLogin"
            path="/expresso/components/myapp/login.jsp"/> 
   <forward name="processLogin"
            path="/expresso/jsp/register/redirect.jsp"/> 
   <forward name="processLogout"
            path="/expresso/jsp/register/logout.jsp"/> 
   <forward name="promptChangePassword"
            path="/expresso/jsp/register/change.jsp"/>
   <forward name="processChangePassword"
            path="/expresso/jsp/register/status.jsp"/>
   <forward name="emailValidate"
            path="/expresso/jsp/register/status.jsp"/>
   <forward name="promptSendPassword"
            path="/expresso/jsp/register/sendPassword.jsp"/>
   <forward name="processSendPassword"
            path="/expresso/jsp/register/status.jsp"/>
   <!-- you can use XSLT transforms here too. any path which ends with 
        .xsl or .xslt will be handled appropriately. -->
   <forward name="someOtherMethod"
            path="/expresso/xsl/blah.xsl"/>;
  </action>
 </action-mappings>
</struts-config>
Notice that we've changed the promptLogin state to forward to a different JSP, assuming that your app is conforming to standard layout of an Expresso-based component. This configuration file is placed in your Expresso's config directory and will be automatically picked up by the configuration system.

Now, as your app progresses, you can one by one change the state to forward to jsps that you have worked up with your own application's look and feel. And you haven't modified the default pages one iota!

Step 3- Copy Message Resources [Pre Expresso 5.0.4 Only]

Note

Expresso 5.0.4 has a new mechanism for finding messages that makes this step unnecessary. Simply write in your own MessagesBundle any keys that you wish to 'overwrite' and any messages you wish to add, and you can skip the rest of this step.

If the controller you are deriving from is hooked into a different schema, you will want to find the messages that it uses and add them to your own schema. If you don't then the superclass controller will attempt to look in your MessagesBundle.properties and will throw an exception if it can't find what it's looking for. If the superclass controller relies heavily on Messages (which as time goes on, most controllers will rely more and more on Messages) this portion can be tedious at best, and we are working on a better resolution for this.

Step 4 - Login Controller Only

There are various parts of Expresso that need to know where to send the login requests. So to accomplish this, we need to define a Class Handler for login controller. Note that this step is also only necessary if you're using the <loginTag> if you aren't, or you aren't dealing with LoginController, you can skip this step.

To do this, you need to add a class handler entry in your expresso-config.xml file. Insert the code outlined below RIGHT before your first <context> entry.

<class-handler
  name="login"
  classHandler="com.jcorporate.myproject.controller.MyLoginController"/>

Extending the default controller behaviors.

Any object oriented course teaches that you should be able to derive an object and override it's behavior. Expresso Controllers are no different. State handlers in Expresso should be of protected visibility, so you should be able to override any state handler to perform what you wish. We have a program listing that shows that kind of behavior:

package com.jcorporate.myproject.controller; 
import com.jcorporate.expresso.core.controller.*; 
public class MyLoginController
       extends com.jcorporate.expresso.services.controller.LoginController { 
   public MyLoginController () { 
       super(); 
   } 
   public String getTitle() { 
       return "Sample Controller Login";
   }
   
   /**
    *Overridden getRegistraitonController() 
    */
   public Controller getRegistrationController() throws ControllerException { 
       return ConfigManager.getControllerFactory()
              .getController(MyRegistrationController.class.getName());
   }
   /**
    * Overridden state that simply adds an additional output to the response.
    */
   protected void runPromptLoginState(ControllerRequest request,
                                      ControllerResponse response) throws ControllerException {
       super.runPromptLoginState(request,response); 
       response.add(new Output("myaddition","This is my added text message"); 
   }
}

Now what you see in the above code, we've overridden the rumPromptLogin state in such a way as the normal promptLoginState is run, but we add our own Outputs after the fact. Oftentimes this can be the easiest way to accomplish a simple extension since in your jsp page layout you can always not render any ControllerElements (Inputs, Outputs, Transitions, Blocks) that you don't want to see on the screen. If, however, you, wish for more radical differences in your own work, you can always omit the call to super() and perform whatever behavior you need.

So what about the functions getRegistrationController() and getLoginController()?? Well, it turns out that LoginController is a strange beast compared to a normal controller. The reason being is that Login and Registration are fairly tightly coupled classes at this point in time. If you derive your own Registration class, for example, Login will need to know what Class to forward registration requests to. By overriding this method, we're now causing our controller's links to point to our own derived Registration Controller. Most other controllers will not need this done.

Workflow

Expresso supports chaining states together in a "wizard" or "process funnel", also known as workflow. See the Workflow chapter. (Chap. 28 as of this writing. Workflow is experimental.)

Conclusion

Contributors

The following persons have contributed their time to this chapter:

Note

Was this EDG documentation helpful? Do you wish to express your appreciation for the time expended over years developing the EDG doc? We now accept and appreciate monetary donations. Your support will keep the EDG doc alive and current. Please click the Donate button and enter ANY amount you think the EDG doc is worth. In appreciation of a $35+ donation, we'll give you a subscription service by emailing you notifications of doc updates; and donations $75+ will also receive an Expresso T-shirt. All online donation forms are SSL secured and payment can be made via Credit Card or your Paypal account. Thank you in advance.

Copyright © 2001-2004 Jcorporate Ltd. All rights reserved.