Ken remembers someone once lamenting about the trials and tribulations of web application development. They wanted to do all of this really cool stuff, integrating with other systems, doing complex graphical work, and meaty programming. But they said, “It’s ridiculous. What do we always do, day in and day out? Suck data out of a database, show it to the user, and let them change it. Period.” Yep, that’s right. You spend 90% of your time fetching, displaying, modifying, creating‚ and deleting that pesky data!
Why is manipulating database data so much work? Most of your Spring coding tra
ditionally involves wiring up data access objects, services, controllers‚ and views to present and modify data. Shouldn’t it be easier to do this? And less manual? That’s where Spring Roo can really help.
In this section we’ll delve into the world of scaffolded Roo controllers. You’ll see how to generate a scaffolded course controller, and we’ll review each of the generated controller methods and views for the list, create, update, and delete operations. We’ll also look at how Roo integrates finders into these controllers.
5.3.1 Creating the course scaffold
Fully functional Spring Roo controllers that can handle create, read, update‚ and delete operations can be generated automatically by using the web mvc scaffold com
mand. To generate a Roo scaffolded controller and views for a Course entity, just enter the following command in the Roo shell:
roo> web mvc scaffold --class ~.web.CourseController ➥
--backingType ~.model.Course
The options, --class and --entity, specify the name of the new controller and the entity to use, respectively. Again, Roo responds by performing a number of actions.
Assuming you’ve already set up the MVC framework with the first web mvc controller command, the output will look like this:
Created SRC_MAIN_JAVA/org/rooinaction/coursemanager/➥
web/CourseController.java
Created SRC_MAIN_JAVA/org/rooinaction/coursemanager/web/➥
CourseController_Roo_Controller.aj
Created SRC_MAIN_WEBAPP/WEB-INF/views/courses
Created SRC_MAIN_WEBAPP/WEB-INF/views/courses/list.jspx Created SRC_MAIN_WEBAPP/WEB-INF/views/courses/views.xml Created SRC_MAIN_WEBAPP/WEB-INF/views/courses/show.jspx Managed SRC_MAIN_WEBAPP/WEB-INF/views/courses/views.xml Created SRC_MAIN_WEBAPP/WEB-INF/views/courses/create.jspx Managed SRC_MAIN_WEBAPP/WEB-INF/i18n/application.properties Managed SRC_MAIN_WEBAPP/WEB-INF/views/menu.jspx
Managed SRC_MAIN_WEBAPP/WEB-INF/views/courses/views.xml Created SRC_MAIN_WEBAPP/WEB-INF/views/courses/update.jspx Managed SRC_MAIN_WEBAPP/WEB-INF/views/courses/views.xml Managed SRC_MAIN_WEBAPP/WEB-INF/i18n/application.properties Managed SRC_MAIN_WEBAPP/WEB-INF/views/menu.jspx
139 Web scaffolding for entities
Again, that’s a lot of processing. Of course, this time Spring Roo generates a few key files, including
The controller, CourseController.java
The AspectJ ITD, CourseController_Roo_Controller.aj
Menu items to list and create courses in the shared file, menu.jspx
Views to manage listing, creating, reading, updating‚ and deleting courses con
tained in WEB-INF/views/courses
We’ll focus on a number of these components, but first let’s start by identifying the views contained within WEB-INF/views/courses in table 5.2.
Table 5.2 Key scaffolded views
File/Directory Use
list.jspx A view that displays a list of entities in a tabular format.
show.jspx A view that displays a single entity row in a form-based view.
create.jspx A view that generates an editable form that can create a new entity.
update.jspx A view that generates an editable form that can edit an existing entity.
views.xml The Apache Tiles configuration for all views in this directory. This file defines the Tiles layout to use, which is defined in /WEB-INF/layouts/layouts.xml.
These view files are generated and configured using the Roo JSPX tag libraries, which are installed in WEB-INF/tags. You’ve already seen the util:panel tag in listing 5.2;
but various page wrapper tags, such as page:create, page:update, and page:list‚ will wrap the various view pages to provide the proper forms and container elements.
All fields available in the entity are generated as fields in these various view files.
Just as it did with entities, Roo creates the CourseController.java class, but also generates an AspectJ ITD, CourseController_Roo_Controller.aj. This aspect con
tains all scaffolded logic to manage creating, reading, updating‚ and deleting Course entities.
Roo also edits WEB-INF/i18n/application.properties with labels for all fields and the entity name itself for form rendering purposes, and adds menu items to menu.jspx, which was created when Roo generated the web application.
Just as the Course.java Roo entity itself seems a bit simple and empty, so does the actual Java controller. But that’s because the magic is in the generated ITD file.
Let’s review the generated Controller class, CourseController.java, in the follow
ing listing.
Listing 5.3 The CourseController.java Roo scaffold controller package org.rooina.coursemanager.web;
import org.rooina.coursemanager.model.Course;
import org.springframework.roo.addon.web.mvc.controller➥
.scaffold.RooWebScaffold;
import org.springframework.stereotype.Controller;
Establish import org.springframework.web.bind.annotation.RequestMapping;
scaffold
@RooWebScaffold(path = "courses",➥
formBackingObject = Course.class)
@RequestMapping("/courses") Maps /courses
@Controller
public class CourseController { Spring bean annotation }
This controller class manages all operations against the Course entity. The code to implement the controller actions is stored within the AspectJ ITD. Roo generates and maintains the JSPX pages based on the @RooWebScaffold annotation.
CODE GENERATION? ICK, I’VE SEEN THIS BEFORE! Yeah, we’ve seen this before as well. It’s very difficult to write a good system and then keep it up to date by forward- and reverse-engineering changes to the software. We think that Roo is a different animal because it makes a distinction between user-editable arti
facts and generated ITDs. If Roo generates a normal file, such as a class like CourseController, or a localization file like application.properties, it won’t overwrite it later. But if Roo creates a Roo-managed intermediate file, such as the Course_Roo_Controller.aj AspectJ ITD, it’ll manage it entirely via the Roo shell.
The gray area in all of this is the view technology. Since Roo generated the views as user-editable elements, any changes made by a developer must generally be honored. In fact, if you change your templates around, modify the HTML code, or otherwise modify the boilerplate code, Roo will allow this to happen. But if you change fields within forms, special considerations must be made, which we’ll review in chapter 6, section 1, “Customizing Roo CRUD views.”
Now, let’s take a look at the various views and controllers generated by your web mvc scaffold operation. We’ll review the files based on the operations they provide. First, let’s take a look at the GET operation.
5.3.2 Fetching courses
The GET operations your scaffolded controller supports are both a list of all of the Course objects and the display of a single Course. Both operations are supported by different generated controller methods and views. We’ll look at both in turn, starting with the listing operation.
LISTING COURSES
The list operation is called by performing HTTP GET operations on http://localhost :8080/coursemanager/courses. First, review the next listing to see the list method Roo generates in the CourseController_Roo_Controller.aj file.
141 Web scaffolding for entities
Listing 5.4 The list method in CourseController_Roo_Controller.aj
Paging params
Calculate page size
Simple fetch all
@RequestMapping(method = RequestMethod.GET) Map to GET public String CourseController.list(➥
@RequestParam(value = "page", required = false) Integer page,➥
@RequestParam(value = "size", required = false) Integer size,➥
Model uiModel) { Inject model
if (page != null || size != null) {
int sizeNo = size == null ? 10 : size.intValue();
final int firstResult = page == null ? 0 : (page.intValue() - 1) * sizeNo;
Limit uiModel.addAttribute("courses",
results Course.findCourseEntries(
page == null ? 0 : (page.intValue() - 1) * sizeNo, sizeNo));
float nrOfPages = (float) Course.countCourses() / sizeNo;
uiModel.addAttribute("maxPages",
(int) ((nrOfPages > (int) nrOfPages || nrOfPages == 0.0) ?
nrOfPages + 1 : nrOfPages)); Set max
} else { page #
uiModel.addAttribute("courses", Course.findAllCourses());
}
return "courses/list"; Render list view }
This method is mapped to /courses. When you ask to GET tasks based on this URL, the controller fetches all of the tasks and places them in ${courses} to render in the view.
The controller supports paging results if the page and size parameters are passed as parameters to the URL; otherwise it fetches all rows. The controller also tells the view how many pages of rows were available, if paging was enabled.
ROO BUILDS RESTFUL CONTROLLERS Roo URLs are all based on the REST web URL philosophy, which treats URLs as nouns (resources) and HTTP verbs such as GET, POST, PUT, and DELETE as actions. Getting the /courses resource returns all courses, whereas getting the /courses/1 resource returns the course with the primary key of 1. You’ll see the syntax {id} in many mapped URLs; this refers to the primary key in the path.
These verbs are also used by other actions, such as creating (POST of / courses), updating (PUT of /courses/1 with updated field values), and delet
ing (DELETE of /courses/1).2
The list method returns the value courses/list, which resolves to the JSPX view file list.jspx in the WEB-INF/views/courses directory. Let’s review the contents of list.jspx.
If you’re familiar with Spring MVC and the form taglib, you’ll be pleasantly surprised by the use of tag libraries, which shorten the amount of code in the next listing.
For more information about REST, start with the excellent Wikipedia article at http://mng.bz/1PMN.
2
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<div xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:page="urn:jsptagdir:/WEB-INF/tags/form"
xmlns:table="urn:jsptagdir:/WEB-INF/tags/form/fields" version="2.0">
<jsp:directive.page contentType="text/html;charset=UTF-8"/>
<jsp:output omit-xml-declaration="yes"/>
<page:list
id="pl_org_rooinaction_coursemanager_model_Course"
items="${courses}"
z="96XNQVUu9T1imC4uiLwuHR/mKek=">
<table:table data="${courses}"
id="l_org_rooinaction_coursemanager_model_Course"
path="/courses"
z="qVCdFgjW5phyYDfJQjmF1RSutAU=">
<table:column
id="c_org_rooinaction_coursemanager_model_Course_name"
property="name"
z="+Od93IeY6yNEhIqYKMtNvd3VFhI="/>
<table:column
id="c_org_rooinaction_coursemanager_model_Course_description"
property="description"
z="xzNe6X4uX1G8fjujNE/WAf2Sllc="/>
...
<table:column
id="c_org_rooinaction_coursemanager_model_Course_courseType"
property="courseType"
z="v6SsRm1w+xYp8X4Uj1zzOsOOQ3Q="/>
<table:column
id="c_org_rooinaction_c..._model_Course_trainingProgram"
property="trainingProgram"
z="/tgEAE0TzwtEI8UwkYG5h9Q7nyM="/>
<table:column
id="c_org_rooinaction_coursemanager_model_Course_tags"
property="tags"
z="LGrxj3NPcOmRo83ddIG8NpSCWgo="/>
</table:table>
</page:list>
</div>
The first thing you might notice is that you’re not defining a full JSP page. In fact, the file looks a lot more like an XML document than an HTML page. That’s because the combination of using Apache Tiles to render page fragments and the heavy use of JSP tag libraries reduces the view to a tighter, more compact version of the usual view.
That is, except things like those strangely long id and z fields. More about those later.
Listing 5.5 Displaying courses with list.jspx
Wrap page/add title bar
Define result table
143 Web scaffolding for entities
Roo composes this tile fragment using the JSP tag page:list. This tag sets up the con- tainer for the next tag, table:table, which renders an HTML table of results. The results, coming from ${courses} in the controller, are rendered using table:column elements. All of these tags are available for review and editing in webapp/web-inf/tags.
The resulting output of the course listing view should be similar in appearance to figure 5.4.
As you can see, the results are paginated, with alternating grey and white bars for the rows. The list is wrapped with a box that’s entitled List all Courses, and if you click on the List all Courses drop-down arrow, it collapses the entire view. Icons are shown for various actions, which all result in further calls to methods in the course control- ler’s ITD. These icons are
—Adds a new course. Displays a form that you can use to enter the data for the new course.
—Deletes a course. Prompts the user before deletion.
—Displays the course in a single-object view page. Useful for showing larger fields such as comments.
—Updates/edits a course. Displays a form, populated with data values, that persists updates to the given course.
Clicking on any of these icons navigates to the other actions.
You should spend some time getting familiar with the custom tag libraries, such as list.tagx, table.tagx‚ and column.tagx. We’ll continue to customize the user inter- face as we go through the CourseController example.
Now let’s see how you can review an individual course.
SHOWING A SINGLE COURSE
If a user clicks on the icon for a given course in the web application, the browser navigates to another URL, passing the primary key as part of the path. If the key value selected is 42, the URL generated is /courses/42. The method mapped to this URL pattern is called show. Let’s take a look at it:
Figure 5.4 The course list view
@RequestMapping(value = "/ /courses/{id}
{id}", method = RequestMethod.GET)
public String CourseController.show(@PathVariable("id") Long id,
Capture
Model uiModel) { path
uiModel.addAttribute("course", Course.findCourse(id));
Fetch into
uiModel.addAttribute("itemId", id);
"course" return "courses/show"; Selected key
}
There’s a bit of interplay going on between this view and the list view in listing 5.5.
The list view’s table:table tag generates a table of results, each of which contains a link to edit an individual Course.
The show method, mapped to the /tasks/{id} URL pattern, takes the course pri
mary key from the incoming URL path directly after courses/ and places it in the show method as the parameter id. Then it retrieves a course using the Course.find
Course(id) entity method, storing the result in the Model as course, which will be ref
erenced by the JSP view below.
The show method returns the value courses/show, which resolves to the view file in the WEB-INF/views/tasks directory named show.jspx. The following listing reviews this file.
Listing 5.6 Showing a single course with show.jspx
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<div xmlns:field="urn:jsptagdir:/WEB-INF/tags/form/fields"
xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:page="urn:jsptagdir:/WEB-INF/tags/form" version="2.0">
<jsp:directive.page contentType="text/html;charset=UTF-8"/>
<jsp:output omit-xml-declaration="yes"/>
<page:show id="ps_org_rooinaction_..._model_Course" Uses show tag object="${course}"
path="/courses" Renders
z="Qrf8vecfH2gWwxWHG3q4QtjF0Zg="> view-only <field:display field="name" field id="s_org_rooinaction_..._model_Course_name"
object="${course}"
z="k6vilxSyD8vQwd4un1clBGrNjXI="/>
...
</page:show>
</div>
The page looks similar to the list view above, but you’ll notice that the page is now sur
rounded by a page:show tag, and that each field is no longer rendered by table :column, but by a field:display tag. This form is rendered as a read-only view of a single Course. Roo automatically shows this view if you click on the icon for a given row in the list view, or once you create or update a row using the Create New Course menu item or click the icon.
Of course, it would be rather difficult to list or show tasks without actually creating one. Let's see how to create a Course, using the HTTP POST operation.
Web scaffolding for entities 145
5.3.3 Creating a new course
Spring MVC follows a very specific pattern for form-based processing, illustrated in figure 5.5.
Display Course create form
view
Display Course show
view Attempt
to create Course
entity POST
Success:
browser GET redirect
Failure:
load errors and show view
Figure 5.5 Creating a resource with form processing
As you see above, creating new entities requires first the display of a form that can edit the data. To create a new empty Course and edit it with the form, users would select the Create New Course menu item, which requests /courses?form. This URL maps to the createForm controller method in the Course_Roo_Controller.aj ITD:
@RequestMapping(params = "form", method = RequestMethod.GET) public String CourseController.createForm(Model uiModel) { uiModel.addAttribute("course", new Course());
return "courses/create";
}
The params = "form" code in the @RequestMapping annotation is what makes Roo map this form using /courses?form. In this method, Roo calls the new Course() con
structor to create a single Course entity instance. This instance is then added to the model map as course and the JSPX Tiles view rendered is courses/create, which ren
ders the view fragment located in WEB-INF/views/course/create.jspx, as shown next.
Listing 5.7 Creating a new course with create.jspx
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<div xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:field="urn:jsptagdir:/WEB-INF/tags/form/fields"
xmlns:form="urn:jsptagdir:/WEB-INF/tags/form"
xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:spring="http://www.springframework.org/tags" version="2.0">
<jsp:directive.page contentType="text/html;charset=UTF-8"/>
<jsp:output omit-xml-declaration="yes"/>
Map POST/courses <form:create id="fc_org_rooina_coursemanager_model_Course"
modelAttribute="course" path="/courses"
render="${empty dependencies}"
z="1dbo16WMREluRULwzHVH0bkl1Rs="> Editable TextArea <field:textarea field="name"
id="c_org_rooinaction_coursemanager_model_Course_name"
required="true"
z="yGVxN/bavsqgDzN24/udCm+MpYw="/>
<field:textarea field="description"
id="c_org_rooinaction_coursemanager_model_Course_description"
required="true"
z="dulpIS46tuOqeAFu0bbOlchgsLE="/>
<field:input field="maximumCapacity"
id="c_org_rooinaction_coursemanager_model_Course_maximumCapacity"
max="9999" min="1" required="true"
validationMessageCode="field_invalid_integer"
z="bw8OLox9ujpvV3FDaCqbKFOkycs="/>
...
<field:select field="courseType"
id="c_org_rooinaction_coursemanager_model_Course_courseType"
items="${coursetypeenums}"
path="coursetypeenums"
required="true"
z="9Wjhb/YldSfBx/NGqUHc0L81498="/>
<field:select field="trainingProgram"
id="c_org_rooinaction_coursemanager_model_Course_trainingProgram"
itemValue="id"
items="${trainingprograms}"
path="/trainingprograms"
z="PID0Sj5EPAbkyL0Br07UHh7MVcE="/>
<field:selectfield="tags"
id="c_org_rooinaction_coursemanager_model_Course_tags"
itemValue="id"
items="${tags}"
multiple="true"
path="/tags"
z="OjMaU8+t56vaoiVt+RZTpV7kZ9U="/>
<field:simple field="offerings"
id="c_org_rooinaction_coursemanager_model_Course_offerings"
messageCode="entity_reference_not_managed"
messageCodeAttribute="Offering"
z="2RF1uqlkibELo0Aa2snrwO7TTJA="/>
</form:create>
<form:dependency dependencies="${dependencies}"
id="d_org_rooinaction_coursemanager_model_Course"
render="${not empty dependencies}"
z="wjqVoEysCSIfkBbzWw9vmQjMvhg="/>
</div>
Roo generates a fully functional HTML form page. The HTML form tag is generated by the <form:create> tag, which establishes that the form will be submitted using the POST method to the URI /courses.
Users with JavaScript-capable browsers will see the automatic rich field generation, including date pop-ups for date fields, and automatic rule validation. Try clicking in a field, and entering invalid data. Experiment with skipping required fields. You should get feedback from the web page immediately upon leaving the field.
There are several field types used in the form above. Table 5.3 lists the field types available in the Roo tag library.
Editable input field
Single select box
Multiselect box
Manages relationship links