Controller
What Is a Controller?
Controller is the center piece of the three-tier MVC architecture. It sits in between models and views. Its function is to retrieve data from the model for displaying on the view and to save data from the view to the model.
One controller class may contain multiple action methods.
How Controllers Are Invoked?
Controller is selected for invocation by a processor. Which controller is selected depends on which processor is used and request uri. There are two types of processors in Scooter Framework, BaseRequestProcessor and RestfulRequestProcessor. You can specify which one to use in the web.xml file under the WEB-INF directory.
Using BaseRequestProcessor
BaseRequestProcessor requires each request uri follows a special pattern: $controller/$action.
Example requests:/posts/list.do --- display all posts (controller: PostsController, action: list) /posts/show.do?id=101 --- show details of the post with id 101 (controller: PostsController, action: show)
Both controller name and action method name are decoded from the request URL. BaseRequestProcessor then determines the name of the controller class and invoke its action method.
You may use this processor if your application requires pure action-based requests and every request uri has an extension such as ".do". You can specify the request extension with action.extension property in environment.properties file.
Using RestfulRequestProcessor
RestfulRequestProcessor inherits from BaseRequestProcessor and includes REST-style requests. In REST-style requests, the HTTP method of the request is used to determine the action name of the request.
Example requests:HTTP Method Request URI Description ----------- ----------- --------------------------------------- GET /posts display all posts POST /posts create a new post GET /posts/101 show details of the post with id 101 GET /posts/101.xml return details of the post with id 101 in xml PUT /posts/101 update content of the post with id 101 DELETE /posts/101 delete the post with id 101
The patterns of request uri and the order to which they are selected are specified in route.properties file.
You should not use RestfulRequestProcessor for action-based requests with extensions like ".do", because request extension is used by the processor to determine the mime type of the response.
RestfulRequestProcessor is the default processor. We strongly recommand to use this processor.
Naming Convention
By default, a controller class is in controllers directory. Its name is ended with Controller. You can change this naming convention by using controller.class.prefix and controller.class.suffix properties in environment.properties file.
Action Method
An action method is a public method with no input parameter and returns a special tagged String. The tags of return values are used by the processor to dtermine what to do.
List of supported tags are in the ActionResult class. That class also has methods for easier use of each tag.
Tag Followed-by Return String Examples Description ------------ ------------- ---------------------- --------------------------------------------------- forwardTo=> uri forwardTo=>/posts Forward request to /posts forwardTo=> uri forwardTo=>/posts/mylist.jsp Forward request to /posts/mulist.jsp redirectTo=> uri redirectTo=>/posts/1 Redirect request to /posts/1 error=> error message error=>No login id Show error message "No login id" html=> html text html=><h1>Warning</h1> Show "Warning" in font h1 text=> text message text=>Good job Show text message "Good job" xml=> xml message xml=><title>Java In Action</title> Show xml message "<title>Java In Action</title>"An action method example:
//Displays a big "Hello World" text in browser public String hello() { return "html=><h1>Hello World</h1>"; }
See "Render A View" for more helper methods on writing an action method.
Writing Controllers
In Scooter, controllers are POJOs. You do not need to explicitly use any servlet classes in javax.servlet package.
A Simple Controller
Here is an example of a very simple controller:public class GreetingController { public String hello() { return "html-><h1>Hello World</h1>"; } }
You can invoke this controller by this uri:
/greeting/hello --- display "Hello World" in <h1> font size
Importing ActionControl
A better way of writing a controller is to static import from ActionControl class which provides many static help methods. The following is a list of categories of helper methods that the ActionControl class provides.
- Request data access
- Upload files
- Scope management: thread, parameter, request, session, context, global
- Flash
- Validation
- Request Info
- View
- Pagination
The next few sections describe the methods in the above categories in details.
Request Data Access
Normally you would retrieve data from HttpServletRequest by its getParameter and getAttribute methods. Scooter Framework encapsulates data access to HttpServletRequest. Therefore you can retrieve data from a url request as if retrieving from a hash map.
Request Data Access APIs and Examples:
Methods Description ------------------------- ------------------------------------------------------- params("username") retrieves value of "username" parameter or attribute p("username") retrieves value of "username" parameter or attribute paramsIgnoreCase("id") retrieves value of id field whether it is "ID" or "Id" params() returns all request data as a map paramsWithPrefix("order_") returns a map of name/value pairs from request data for all fields with names starting with "order_" pkparams(Post.class) returns a map of name/value pairs from request data for all primary key fields of post model pkparams("user_", Post.class) returns a map of name/value pairs from request data for all primary key fields of post model with names starting with "user_"
Upload files
ActionControl provides many helper methods for file uploading.
Methods Description ------------------------- ------------------------------------------------------- isFileUploadRequest Checks if this is a file-upload request getUploadFile Returns an upload file getUploadFiles Returns a list of upload files getUploadFilesMap Returns a map of upload files getUploadFilesAsFiles Returns a list of upload files paramsFile Alias of method getUploadedFile(String key) paramsFiles Alias of method getUploadFiles() paramsFilesMap Alias of method getUploadFilesMap() pFile Alias of method paramsFile(String key) pFiles Alias of method paramsFiles() pFilesMap Alias of method paramsFilesMap()
Scopes
Scooter Framework supports data in six scopes:
- thread scope: data is cached in current thread
- parameter scope: data is accessible from getParameter method of HttpServletRequest
- request scope: data is accessible from getAttribute method of HttpServletRequest
- session scope: data is in a user's http session
- context scope: data is in application context of the container for all users
- global scope: data is in a cache place of the framework for all users
When data for a key is to be retrieved without specifying the scope, the above order of scopes will be used for searching the data for the key.
ActionControl provides helper methods for accessing data in the above scopes.
Methods Description ------------------------- ------------------------------------------------------- getFrom{scope}Data returns data stored in a scope getFromSessionData returns data stored in session scope removeFrom{scope}Data removes data from a scope (except parameter scope) removeFromSessionData removes data from session scope remove(key) removes data from the first scope the data is found storeTo{scope} stores data into a scope (except parameter scope) storeToSession stores data into session scope
Flash
The flash is a special method that only passes data to the next request. Once the next screen is displayed, flash data no longer exists. Therefore, flash is very useful for storing status message or error message.
To send a message to the next request, you simply call flash:
public class PostsController { public String delete() { ActiveRecord record = Post.findFirst("id=" + p("id")); if (record != null) { record.delete(); flash("notice", "Post deleted"); } else { flash("error", "The post you want to delete does not exist."); } return redirectTo("/posts/list", "forum_id=" + params("forum_id") + "&topic_id=" + params("topic_id")); } ... }
The flash method in the above example stores a notice type of message for the next request when a post is deleted successfully and an error type of message when the post does not exist at all. In either case, the processor redirects the request to the post list page for the specific topic in a particular forum.
You can call flash method multiple times to store more than one message.
The following is an example of displaying the last flash message on the screen. This is actually what we do in status.jsp file under views/layouts/includes directory.
<% if (!M.isEmpty(ACH.getAC().getLatestFlashMessage("notice"))) { %> <div id="notice" style="color: green"><%= ACH.getAC().getLatestFlashMessage("notice") %></div> <% } %> <% if (!M.isEmpty(ACH.getAC().getLatestFlashMessage("error"))) { %> <div id="error" style="color: red"><%= ACH.getAC().getLatestFlashMessage("error") %></div> <% } %>
Validation In Controller
See Validations
Request Info
You can get helpful information about the request from the following methods:
Methods Description ------------------------- ------------------------------------------------ getController() returns controller name for the request getModel() returns model name associated with the request getResource() returns resource name associated with the request when RestfulRequestProcessor is used
View Helpers
One important responsibility of a controller is to send response to a proper view. The location of the view files is defined by the webpage.directory.name property in the environment.properties file. The default value for that property is WEB-INF/views.
ActionControll provides viewPath helper methods so that we do not need to worry where the view files are.
So instead of hard coding the location of the view file like this:
public class PostsControl { //create a new post record public String create() { ActiveRecord newRecord = null; try { newRecord = ...; newRecord.save(); flash("notice", "Successfully created a new record."); return ActionResult.redirectTo("/posts/index"); } catch(Exception ex) { log.error("Error in create() caused by " + ex.getMessage()); flash("error", "There was a problem creating the new record."); } //return back to entry screen return forwardTo("/WEB-INF/views/posts/add.jsp"); } ... }
we can use the following:
public class PostsControl { //create a new post record public String create() { ActiveRecord newRecord = null; try { newRecord = ...; newRecord.save(); flash("notice", "Successfully created a new record."); return ActionResult.redirectTo("/posts/index"); } catch(Exception ex) { log.error("Error in create() caused by " + ex.getMessage()); flash("error", "There was a problem creating the new record."); } //return back to entry screen return forwardTo(viewPath("add")); } ... }
The benefit of using viewPath helper method is that the location of view files is not hard coded anymore in your Java code.
Better yet, you can use renderView methods in stead of viewPath. See "Render A View" for more details.
Pagination
You can use the jdbcPaginator helper method for pagination:
public class PostsController { /** * index method returns a list of posts records. * If the value of paged parameter is true, a paginated list is returned. */ public String index() { if ("true".equals(p(Constants.PAGED))) { Paginator page = jdbcPaginator(Post.class, params()); setViewData("post_page", page); return renderView("paged_list"); } setViewData("posts", Post.findAll()); return null; } }
Default CRUD Controllers
Default controllers can be used when a controller is not specified or available. This is good for quick prototyping. Scooter Framework provides two default CRUD controllers, one for each request processor type.
CRUDController
When BaseRequestProcessor type is specified in web.xml file, the CRUDController class in the com.scooterframework.builtin package will be used. Its default view files are in {path to}/WEB-INF/views/builtin/crud directory.
To use this default controller, you must set auto.crud property in environment.properties file to true. Since all property files in the Scooter Framework are runtime configurable, you can update this value without restarting your web server.
RestfulCRUDController
When RestfulRequestProcessor type is specified in web.xml file, the RestfulCRUDController class in the com.scooterframework.builtin package will be used. Its default view files are in {path to}/WEB-INF/views/builtin/crud_restful directory.
To use this default controller, you must set auto.rest property in routes.properties file to true. Again, since all property files in the Scooter Framework are runtime configurable, you can update this value without restarting your web server.