Chapter 11. Web MVC Add-On

The Web MVC add-ons allow you to conveniently scaffold Spring MVC controllers and JSP(X) views for an existing domain model. Currently this domain model is derived from the Roo supported JPA integration through the entity jpa and related field commands. As shown in the Introduction and the Beginning With Roo: The Tutorial the Web MVC scaffolding can deliver a fully functional Web frontend to your domain model. The following features are included:

The following sections will offer further details about available commands to generate Web MVC artifacts and also the new JSP(X) round-tripping model introduced in Roo 1.1.

11.1. Controller commands

The Web MVC addon offers a number of commands to generate and maintain various Web artifacts:

  1. ~.Person roo> web mvc setup

    The first time the web mvc setup command is executed Roo will install all artifacts required for the Web UI.

  2. ~.Person roo> web mvc scaffold --class com.foo.web.PersonController

    The controller scaffold command will create a Spring MVC controller for the Person entity with the following method signatures:

    Method Signature Comment 
    public String create(@Valid Person person, BindingResult result, ModelMap modelMap) {..}The create method is triggered by HTTP POST requests to /<app-name>/people. The submitted form data will be converted to a Person object and validated against JSR 303 constraints (if present). Response is redirected to the show method.
    public String createForm(ModelMap modelMap) {..}The create form method is triggered by a HTTP GET request to /<app-name>/people?form. The resulting form will be prepopulated with a new instance of Person, referenced Cars and datepatterns (if needed). Returns the Tiles view name.
    public String show(@PathVariable("id") Long id, ModelMap modelMap) {..}The show method is triggered by a HTTP GET request to /<app-name>/people/<id>. The resulting form is populated with a Person instance identifier by the id parameter. Returns the Tiles view name.
    public String list(@RequestParam(value = "page", required = false) Integer page, @RequestParam(value = "size", required = false) Integer size, ModelMap modelMap) {..}The list method is triggered by a HTTP GET request to /<app-name>/people. This method has optional parameters for pagination (page, size). Returns the Tiles view name.
    public String update(@Valid Person person, BindingResult result, ModelMap modelMap) {..}The update method is triggered by a HTTP PUT request to /<app-name/people. The submitted form data will be converted to a Person object and validated against JSR 303 constraints (if present). Response is redirected to the show method.
    public String updateForm(@PathVariable("id") Long id, ModelMap modelMap) {The update form method is triggered by a HTTP GET request to /<app-name>/people/<id>?form. The resulting form will be prepopulated with a Person instance identified by the id parameter, referenced Cars and datepatterns (if needed). Returns the Tiles view name.
    public String delete(@PathVariable("id") Long id, @RequestParam(value = "page", required = false) Integer page, @RequestParam(value = "size", required = false) Integer size) {..}The delete method is triggered by a HTTP DELETE request to /<app-name>/people/<id>. This method has optional parameters for pagination (page, size). Response is redirected to the list method.
    public Collection<Car> populateCars() {..}This method prepopulates the 'car' attribute. This method can be adjusted to handle larger collections in different ways (pagination, caching, etc).
    void addDateTimeFormatPatterns(ModelMap modelMap) {..}Method to register date and time patterns used for date and time binding for form submissions.

    As you can see Roo implements a number of methods to offer a RESTful MVC frontend to your domain layer. All of these methods can be found in the PersonController_Roo_Controller.aj ITD. Feel free to push-in any (or all) of these methods to change default behaviour implemented by Roo.

    The web mvc scaffold command offers a number of optional attributes which let you refine the way paths are managed and which methods should be generated in the controller. The --disallowedOperations attribute helps you refine which methods should not be generated in the scaffolded Roo controller. If you want to prevent several methods from being generated provide a comma-separated list (i.e.: --disallowedOperations delete,update,create). You can also specify which methods should be generated and which not in the PersonController.java source:

    @RooWebScaffold(path = "people", formBackingObject = Person.class, create = false, 
                    update = false, delete = false)
    @RequestMapping("/people")
    @Controller
    public class PersonController {}

    If you don't define a custom path Roo will use the plural representation of the simple name of the form backing entity (in our case 'people'). If you wish you can define more complex custom paths like /public/people or /my/special/person/uri (try to to stick to REST patterns if you can though). A good use case for creating controllers which map to custom paths is security. You can, for example create two controllers for the Person entity. One with the default path (/people) for public access (possibly with delete, and update functionality disabled) and one for admin access (/admin/people). This way you can easily secure the /admin/* path with the Spring Security addon.

  3. roo> web mvc all --package ~.web

    The web mvc all command provides a convenient way to quickly generate Web MVC controllers for all JPA entities Roo can find in your project. You need to specify the --package attribute to define a package where these controllers should be generated. While the web mvc all command is convenient, it does not give you the same level of control compared to the web mvc scaffold command.

  4. roo> web mvc controller --class com.foo.web.CarController --preferredMapping /public/car
    Created SRC_MAIN_JAVA/com/foo/web/CarController.java
    Created SRC_MAIN_WEBAPP/WEB-INF/views/public/car
    Created SRC_MAIN_WEBAPP/WEB-INF/views/public/car/index.jspx
    Managed SRC_MAIN_WEBAPP/WEB-INF/i18n/application.properties
    Managed SRC_MAIN_WEBAPP/WEB-INF/views/menu.jspx
    Created SRC_MAIN_WEBAPP/WEB-INF/views/public/car/views.xml
    

    The web mvc controller command is different from the other two controller commands shown above. It does not generate an ITD with update, create, delete and other methods to integrate with a specific form backing entity. Instead, this command will create a simple controller to help you get started for developing a custom functionality by stubbing a simple get(), post() and index() method inside the controller:

    @RequestMapping("/public/car/**")
    @Controller
    public class CarController {
    
        @RequestMapping
        public void get(ModelMap modelMap, HttpServletRequest request, 
                        HttpServletResponse response) {
        }
    
        @RequestMapping(method = RequestMethod.POST, value = "{id}")
        public void post(@PathVariable Long id, ModelMap modelMap, HttpServletRequest request, 
                         HttpServletResponse response) {
        }
    
        @RequestMapping
        public String index() {
            return "public/car/index";
        }
    }
    

    In addition, this controller is registered in the Web MVC menu and the application Tiles definition. Furthermore, a simple view (under WEB-INF/views/public/car/index.jspx).

  5. roo> web mvc finder add --class ~.web.PersonController --formBackingType ~.domain.Person

    The web mvc finder add command used from the Roo shell will introdroduce the @RooWebFinder annotation into the specified target type.

  6. roo> web mvc finder all

    The web mvc finder all command used from the Roo shell will introdroduce the @RooWebFinder annotations to all existing controllers which have a form backing type that offers dynamic finders.

11.2. Application Conversion Service

Whenever a controller is created for the first time in an application, Roo will also install an application-wide ConversionService and configure it for use in webmvc-config.xml as follows:

<mvc:annotation-driven conversion-service="applicationConversionService"/>
...
<bean id="applicationConversionService" class="com.springsource.vote.web.ApplicationConversionServiceFactoryBean"/>

Spring MVC uses the ConversionService when it needs to convert between two objects types -- e.g. Date and String. To become more familiar with its features we recommend that you review the (brief) sections on "Type Conversion" and "Field Formatting" in the Spring Framework documentation.

The ApplicationConversionServiceFactoryBean is a Roo-managed Java class and it looks like this:

@RooConversionService
public class ApplicationConversionServiceFactoryBean extends FormattingConversionServiceFactoryBean {

    @Override
    protected void installFormatters(FormatterRegistry registry) {
        super.installFormatters(registry);
        // Register application converters and formatters
    }

}

As the comment indicates you can use the installFormatters() method to register any Converters and Formatters you wish to add. In addition to that Roo will automatically maintain an ITD with Converter registrations for every associated entity that needs to be displayed somewhere in a view. A typical use case is where entities from a many-to-one association need to be displayed in one of the JSP views. Rather than using the toString() method for that, a Converter defines the formatting logic for how to present the associated entity as a String.

In some cases you may wish to customize how a specific entity is formatted as a String in JSP views. For example suppose we have an entity called Vote. To customize how it is displayed in the JSP views add a method like this:

@RooConversionService
public class ApplicationConversionServiceFactoryBean extends FormattingConversionServiceFactoryBean {

    // ...

    public Converter<Vote, String> getVoteConverter() {
        return new Converter<Vote, String>() {
            public String convert(Vote source) {
                return new StringBuilder().append(
                       source.getIp()).append(" ").append(source.getRegistered()).toString();
            }
        };
    }
}

At this point Roo will notice that the addition of the method and will remove it from the ITD much like overriding the toString() method in a Roo entity works.

Note, in some cases you may create a form backing entity which does not contain any suitable fields for conversion. For example, the entity may only contain a field indicating a relationship to another entity (i.e. type one-to-one or one-to-many). Since Roo does not use these fields for its generated converters it will simply omit the creation of a converter for such form backing entities. In these cases you may have to provide your own custom converter to convert from your entity to a suitable String representation in order to prevent potential converter exceptions.

11.3. JSP Views

As mentioned in the previous section, Roo copies a number of static artifacts into the target project after issuing the controller command for the first time. These artifacts include Cascading Style Sheets, images, Tiles layout definitions, JSP files, message property files, a complete tag library and a web.xml file. These artifacts are arranged in different folders which is best illustrated in the following picture:

The i18n folder contains translations of the Web UI. The messages_XX.properties files are static resources (which will never be adjusted after the initial installation) which contain commonly used literals which are part of the Web UI. The application.properties file will be managed by Roo to contain application-specific literals. New types or fields added to the domain layer will result in new key/value combinations being added to this file. If you wish to translate the values generated by Roo in the application.properties file, just create a copy of this file and rename it to application_XX.properties (where XX represents your language abbreviation).

Roo uses XML compliant JSP files (JSPX) instead of the more common JSP format to allow round-tripping of views based on changes in the domain layer of your project. Not all jspx files in the target project are managed by Roo after the initial installation (although future addons may choose to do so). Typically jspx files in sub folders under WEB-INF/views are maintained in addition to the menu.jspx.

Here is an example of a typical roo managed jspx file (i.e.: WEB-INF/views/people/update.jspx):

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<div 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" version="2.0">
  <jsp:output omit-xml-declaration="yes"/>
    
  <form:update id="fu_com_foo_Person" modelAttribute="person" path="/people" 
               z="3lX+WZW4CQVBb7OlvB0AvdgbGRQ=">
      <field:datetime dateTimePattern="${person_birthday_date_format}" field="birthDay" 
                      id="c_com_foo_Person_birthDay" z="dXnEoWaz4rI4CKD9mlz+clbSUP4="/>
      <field:select field="car" id="c_com_foo_Person_car" itemValue="id" items="${cars}" 
                    path="/cars" z="z2LA3LvNKRO9OISmZurGjEczHkc="/>
      <field:select field="cars" id="c_com_foo_Person_cars" itemValue="id" items="${cars}" 
                    multiple="true" path="/cars" z="c0rdAISxzHsNvJPFfAmEEGz2LU4="/>
  </form:update>
</div>

You will notice that this file is fairly concise compared to a normal jsp file. This is due to the extensive use of the tag library which Roo has installed in your project in the WEB-INF/tags folder. Each tag offeres a number of attributes which can be used to customize the appearance / behaviour of the tag - please use code completion in your favourite editor to review the options or take a peek into the actual tags.

All tags are completely self-reliant to provide their functionality (there are no Java sources needed to implement specific behaviour of any tag). This should make it very easy to customize the behaviour of the default tags without any required knowledge of traditional Java JSP tag development. You are free to customize the contents of the Roo provided tag library to suit your own requirements. You could even offer your customized tag library as a new addon which other Roo users could install to replace the default Roo provided tag library.

Most tags have a few common attributes which adhere with Roo conventions to support round-tripping of the jspx artifacts. The following rules should be considered if you wish to customize tags or jspx files in a Roo managed project:

  • The id attribute is used by Roo to find existing elements and also to determine message labels used as part of the tag implementation. Changing a tag identifier will result in another element being generated by Roo when the Roo shell is active.

  • Roo provided tags are registered in the root element of the jspx document and are assigned a namespace. You should be able to see element and attribute code completion when using a modern IDE (i.e. SpringSource Tool Suite)

  • The z attribute represents a hash key for a given element (see a detailed discussion of the hash key attribute in the paragraph below).

The hash key attribute is important for Roo because it helps determining if a user has altered a Roo managed element. This is the secret to round-trip support for JSPX files, as you can edit anything at any time yet Roo will be able to merge in changes to the JSPX successfully. The hash key shown in the "z" attribute is calculated as shown in the following table:

Included in hash key calculation Not included in hash key calculation 
Element name (name only, not namespace)Namespace of element name
Attribute names present in elementWhite spaces used in the element
Attribute values present in the elementPotential child elements
 The z key and its value
 Any attribute (and value) whose name starts with '_'
 The order of the attributes does not contribute to the value of a hash key

The hash code thus allows Roo to determine if the element is in its "original" Roo form, or if the user has modified it in some way. If a user changes an element, the hash code will not match and this indicates to Roo that the user has customized that specific element. Once Roo has detected such an event, Roo will change the "z" attribute value to "user-managed". This helps clarify to the user that Roo has adopted a "hands off" approach to that element and it's entirely the user's responsibility to maintain. If the user wishes for Roo to take responsibility for the management of a "user-managed" element once again, he or she can simply change the value of "z" to "?". When Roo sees this, it will replace the questionmark character with a calculated hash code. This simple mechanism allows Roo to easily round trip JSPX files without interfering with manual changes performed by the user. It represents a significant enhancement from Roo 1.0 where a file was entirely user managed or entirely Roo managed.

Roo will order fields used in forms in the same sequence they appear in the domain object. The user can freely change the sequence of form elements without interfering with Roo's round tripping approach (Roo will honour user chosen element sequences as long as it can detect individual elements by their id).

The user can nest Roo managed elements in in any structure he wishes without interfering with Roo jspx round tripping. For example elements can be enclosed by HTML div or span tags to change visual or structural appearance of a page.

Most default tags installed by Roo have a render attribute which is of boolean type. This allows users to completely disable the rendering of a given tag (and potential sub tags). This is useful in cases where you don't wish individual fields in a form to be presented to the user but rather have them autopopulated through other means (i.e. input type="hidden"). The value of the render attribute can also be calculated dynamically through the Spring Expression Language (SpEL) or normal JSP expression language. The generated create.jspx in Roo application demonstrates this.

Scaffolding of JPA reference relationships

The Roo JSP addon will read JSR 303 (bean validation API) annotations found in a form-backing object. The following convention is applied for the generation of create and update (and finder) forms:

Data type / JPA annotationScaffolded HTML Element
String (sizeMax < 30; @Size)Input
String (sizeMax >=30, @Size)Textarea
Number (@Min, @Max, @DecimalMin & @DecimalMax are recognized)Input
BooleanCheckbox
Date / Calendar (@Future & @Past are recognized) (Spring's @DateTimeFormat in combination with the style or pattern attributes is recognized)Input (with JS Date chooser)
Enum / @EnumeratedSelect
@OneToOneSelect
@ManyToManySelect (multi-select)
@ManyToOneSelect
@OneToMany *Nothing: A message is displayed explaining that this relationship is managed from the many-side

* As mentioned above, Roo does not scaffold a HTML form element for the 'one' side of a @OneToMany relationship. To make this relationship work, you need to provide a @ManyToOne annotated field on the opposite side:

field set --fieldName students --type com.foo.domain.Person --class com.foo.domain.School --cardinality ONE_TO_MANY

field reference --fieldName school --type com.foo.domain.School --class com.foo.domain.Person --cardinality MANY_TO_ONE

In case a field is annotated with @Pattern, the regular expression is passed on to the tag library where it may be applied through the use of the JS framework of choice.

Automatic Scaffolding of dynamic finders

Roo will attempt to scaffold Spring MVC JSP views for all dynamic finders registered in the form backing object. This is done by using the web mvc finder all or web mvc finder add command.

Due to file name length restrictions by many file systems (see http://en.wikipedia.org/wiki/Comparison_of_file_systems) Roo can only generate JSP views for finders which have 244 characters or less (including folders). If the finder name is longer than 244 characters Roo will silently skip the generation of jsp view artifacts for the dynamic finder in question). More detail can be found in ticket ROO-1027.