Chapter 3. Application Architecture

In this chapter we'll introduce the architecture of Roo-created projects. In later chapters we'll cover the architecture of Roo itself.

This chapter focuses on web applications created by Roo, as opposed to add-on projects.

3.1. Architectural Overview

Spring Roo focuses on the development of enterprise applications written in Java. In the current version of Roo these applications typically will have a relational database backend, Java Persistence API (JPA) persistence approach, Spring Framework dependency injection and transactional management, JUnit tests, a Maven build configuration and usually a Spring MVC-based front-end that uses JSP for its views. As such a Roo-based application is like most modern Java-based enterprise applications.

While most people will be focusing on developing these Spring MVC-based web applications, it's important to recognise that Roo does not impose any restrictions on the sort of Java applications that can be built with it. Even with Roo 1.0.0 it was easy to build any type of self-contained application. Some examples of the types of requirements you can easily address with the current version of Roo include (but are not limited to):

  • Listening for messages on a JMS queue and sending replies over JMS or SMTP (Roo can easily set up JMS message producers, consumers and SMTP)

  • Writing a services layer (perhaps annotated with Spring's @Service stereotype annotation) and exposing it using a remoting protocol to a rich client (Spring's remoting services will help here)

  • Executing a series of predefined actions against the database, perhaps in conjunction with Spring's new @Scheduled or @Async timer annotations

  • Experimentation with the latest Spring and AspectJ features with minimal time investment

One of the major differences between Roo and traditional, hand-written applications is we don't add layers of abstraction unnecessarily. Most traditional Java enterprise applications will have a DAO layer, services layer, domain layer and controller layer. In a typical Roo application you'll only use an entity layer (which is similar to a domain layer) and a web layer. As indicated by the list above, a services layer might be added if your application requires it, although a DAO layer is extremely rarely added. We'll look at some of these layering conventions (and the rationale for them) as we go through the rest of this chapter.

3.2. Critical Technologies

Two technologies are very important in all Roo projects, those being AspectJ and Spring. We'll have a look at how Roo-based applications use these technologies in this section.

3.2.1. AspectJ

AspectJ is a powerful and mature aspect oriented programming (AOP) framework that underpins many large-scale systems. Spring Framework has offered extensive support for AspectJ since 2004, with Spring 2.0 adopting AspectJ's pointcut definition language even for expressing Spring AOP pointcuts. Many of the official Spring projects offer support for AspectJ or are themselves heavily dependent on it, with several examples including Spring Security (formerly Acegi Security System for Spring), Spring Insight, SpringSource tc Server, SpringSource dm Server, Spring Enterprise and Spring Roo.

While AspectJ is most commonly known for its aspect oriented programming (AOP) features such as applying advice at defined pointcuts, Roo projects use AspectJ's powerful inter-type declaration (ITD) features. This is where the real magic of Roo comes from, as it allows us to code generate members (artifacts like methods, fields etc) in a different compilation unit (i.e. source file) from the normal .java code you'd write as a developer. Because the generated code is in a separate file, we can maintain that file's lifecycle and contents completely independently of whatever you are doing to the .java files. Your .java files do not need to do anything unnatural like reference the generated ITD file and the whole process is completely transparent.

Let's have a look at how ITDs work. In a new directory, type the following commands and note the console output:

roo> project --topLevelPackage com.aspectj.rocks
roo> jpa setup --database HYPERSONIC_IN_MEMORY --provider HIBERNATE
roo> entity jpa --class ~.Hello
Created SRC_MAIN_JAVA/com/aspectj/rocks
Created SRC_MAIN_JAVA/com/aspectj/rocks/Hello.java
Created SRC_MAIN_JAVA/com/aspectj/rocks/Hello_Roo_JpaActiveRecord.aj
Created SRC_MAIN_JAVA/com/aspectj/rocks/Hello_Roo_JpaEntity.aj
Created SRC_MAIN_JAVA/com/aspectj/rocks/Hello_Roo_ToString.aj
Created SRC_MAIN_JAVA/com/aspectj/rocks/Hello_Roo_Configurable.aj
roo> field string --fieldName comment
Managed SRC_MAIN_JAVA/com/aspectj/rocks/Hello.java
Managed SRC_MAIN_JAVA/com/aspectj/rocks/Hello_Roo_JavaBean.aj
Managed SRC_MAIN_JAVA/com/aspectj/rocks/Hello_Roo_ToString.aj

Notice how there is a standard Hello.java file, as well as a series of Hello_Roo_*.aj files. Any file ending in *_Roo_*.aj is an AspectJ ITD and will be managed by Roo. You should not edit these files directly, as Roo will automatically maintain them (this includes even deleting files that aren't required, as we'll see shortly).

The Hello.java is just a normal Java file. It looks like this:

package com.aspectj.rocks;

import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.tostring.RooToString;
import org.springframework.roo.addon.entity.RooJpaActiveRecord;

@RooJavaBean
@RooToString
@RooJpaActiveRecord
public class Hello {

    private String comment;
}

As shown, there's very little in the .java file. There are some annotations, plus of course the field we added. Note that Roo annotations are always source-level retention, meaning they're not compiled into your .class file. Also, as per our usability goals you'll note that Roo annotations also always start with @Roo* to help you find them with code assist.

By this stage you're probably wondering what the ITD files look like. Let's have a look at one of them, Hello_Roo_ToString.aj:

package com.aspectj.rocks;

import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

privileged aspect Hello_Roo_ToString {

    public String Hello.toString() {
          return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE);
    }
    
}

Notice how the ITD is very similar to Java code. The main differences are that it is declared with "privileged aspect", plus each member identifies the target type (in this case it is "Hello.toString", which means add the "toString" method to the "Hello" type). The compiler will automatically recognize these ITD files and cause the correct members to be compiled into Hello.class. We can see that quite easily by using Java's javap command. All we need to do is run the compiler and view the resulting class. From the same directory as you created the project in, enter the following commands and observe the final output:

$ mvn compile
$ javap -classpath target/classes/.:target/test-classes/. com.aspectj.rocks.Hello
Compiled from "Hello.java"
public class com.aspectj.rocks.Hello extends java.lang.Object implements org.springframework.beans.factory.aspectj.ConfigurableObject{
    transient javax.persistence.EntityManager entityManager;
    public com.aspectj.rocks.Hello();
    public static java.lang.String ajc$get$comment(com.aspectj.rocks.Hello);
    public static void ajc$set$comment(com.aspectj.rocks.Hello, java.lang.String);
    public static java.lang.Long ajc$get$id(com.aspectj.rocks.Hello);
    public static void ajc$set$id(com.aspectj.rocks.Hello, java.lang.Long);
    public static java.lang.Integer ajc$get$version(com.aspectj.rocks.Hello);
    public static void ajc$set$version(com.aspectj.rocks.Hello, java.lang.Integer);
    static {};
    public static long countHelloes();
    public static final javax.persistence.EntityManager entityManager();
    public static java.util.List findAllHelloes();
    public static com.aspectj.rocks.Hello findHello(java.lang.Long);
    public static java.util.List findHelloEntries(int, int);
    public void flush();
    public java.lang.String getComment();
    public java.lang.Long getId();
    public java.lang.Integer getVersion();
    public com.aspectj.rocks.Hello merge();
    public void persist();
    public void remove();
    public void setComment(java.lang.String);
    public void setId(java.lang.Long);
    public void setVersion(java.lang.Integer);
    public java.lang.String toString();
}

While the javap output might look a little daunting at first, it represents all the members that Roo has added (via AspectJ ITDs) to the original Hello.java file. Notice there isn't just the toString method we saw in the earlier ITD, but we've also made the Hello class implement Spring's ConfigurableObject interface, provided access to a JPA EntityManager, included a range of convenient persistence methods plus even getters and setters. All of these useful features are automatically maintained in a round-trip compatible manner via the ITDs.

A careful reader might be wondering about the long field names seen for introduced fields. You can see that these field names start with "ajc$" in the output above. The reason for this is to avoid name collisions with fields you might have in the .java file. The good news is that you won't ever need to deal with this unless you're trying to do something clever with reflection. It's just something to be aware of for introduced fields in particular. Note that the names of methods and constructors are never modified.

Naturally as a normal Roo user you won't need to worry about the internals of ITD source code and the resulting .class files. Roo automatically manages all ITDs for you and you never need deal with them directly. It's just nice to know how it all works under the hood (Roo doesn't believe in magic!). The benefit of this ITD approach is how easily and gracefully Roo can handle code generation for you.

To see this in action, go and edit the Hello.java in your favourite text editor with Roo running. Do something simple like add a new field. You'll notice the Hello_Roo_ToString.aj and Hello_Roo_JavaBean.aj files are instantly and automatically updated by Roo to include your new field. Now go and write your own toString method in the .java file. Notice Roo deletes the Hello_Roo_ToString.aj file, as it detects your toString method should take priority over a generated toString method. But let's say you want a generated toString as well, so change the Hello.java's @RooToString annotation to read @RooToString(toStringMethod="generatedToString"). Now you'll notice the Hello_Roo_ToString.aj file is immediately re-created, but this time it introduces a generatedToString method instead of the original toString. If you comment out both fields in Hello.java you'll also see that Roo deletes both ITDs. You can also see the same effect by quitting the Roo shell, making any changes you like, then restarting the Roo shell. Upon restart Roo will automatically perform a scan and discover if it needs to make any changes.

Despite the admittedly impressive nature of ITDs, AspectJ is also pretty good at aspect oriented programming features like pointcuts and advice! To this end Roo applications also use AspectJ for all other AOP requirements. It is AspectJ that provides the AOP so that classes are dependency injected with singletons when instantiated and transactional services are called as part of method invocations. All Roo applications are preconfigured to use the Spring Aspects project, which ships as part of Spring Framework and represents a comprehensive "aspect library" for AspectJ.

3.2.2. Spring

Spring Roo applications all use Spring. By "Spring" we not only mean Spring Framework, but also the other Spring projects like Spring Security and Spring Web Flow. Of course, only Spring Framework is installed into a user project by default and there are fine-grained commands provided to install each additional Spring project beyond Spring Framework.

All Roo applications use Spring Aspects, which was mentioned in the AspectJ section and ensures Spring Framework's @Configurable dependency injection and transactional advice is applied. Furthermore, Roo applications use Spring's annotation-driven component scanning by default and also rely on Spring Framework for instantiation and dependency injection of features such as JPA providers and access to database connection pools. Many of the optional features that can be used in Roo applications (like JMS and SMTP messaging) are also built upon the corresponding Spring Framework dependency injection support and portable service abstractions.

Those Roo applications that include a web controller will also receive Spring Framework 3's MVC features such as its conversion API, web content negotiation view resolution and REST support. It is possible (and indeed encouraged) to write your own web Spring MVC controllers in Roo applications, and you are also free to use alternate page rendering technologies if you wish (i.e. not just JSP).

Generally speaking Roo will not modify any Spring-related configuration or setting file (e.g. properties) unless specifically requested via a shell command. Roo also ensures that whenever it creates, modifies or deletes a file it explicitly tells you about this via a shell message. What this means is you can safely edit your Spring application context files at any time and without telling Roo. This is very useful if the default configuration offered by Roo is unsuitable for your particular application's needs.

Because Spring projects are so extensively documented, and Roo just uses Spring features in the normal manner, we'll refrain from duplicating Spring's documentation in this section. Instead please refer to the excellent Spring documentation for guidance, which can be found in the downloadable distribution files and also on the Spring web site.

3.3. Entity Layer

When people use Roo, they will typically start a new project using the steps detailed in the Beginning With Roo: The Tutorial chapter. That is, they'll start by creating the project, installing some sort of persistence system, and then beginning to create entities and add fields to them. As such, entities and fields represent the first point in a Roo project that you will be expressing your problem domain.

The role of an entity in your Roo-based application is to model the persistent "domain layer" of your system. As such, a domain object is specific to your problem domain but an entity is a special form of a domain object that is stored in the database. By default a single entity will map to a single table in your database, and a single field within your entity class will map to a single column within the corresponding table. However, like most things in Roo this is easily customised using the relevant standard (in this case, JPA annotations). Indeed most of the common customisation options (like specifying a custom column or table name etc) can be expressed directly in the relevant Roo command, freeing you from even needing to know which annotation(s) should be used.

Let's consider a simple entity that has been created using the entity jpa command and following it with a single field command:

package com.springsource.vote.domain;

import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.tostring.RooToString;
import org.springframework.roo.addon.entity.RooJpaActiveRecord;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@RooJavaBean
@RooToString
@RooJpaActiveRecord
public class Choice {

    @NotNull
    @Size(min = 1, max = 30)
    private String namingChoice;

    @Size(max = 80)
    private String description;
}

The above entity is simply a JPA entity that contains two fields. The two fields are annotated with JavaBean Validation API (JSR 303) annotations, which are useful if your JPA provider supports this standard (as is the case if you nominate Hibernate as your JPA provider) or you are using a Roo-scaffolded web application front end (in which case Roo will use Spring Framework 3's JSR 303 support). Of course you do not need to use the JavaBean Validation API annotations at all, but if you would like to use them the relevant Roo field commands provide tab-completion compatible options for each. The first time you use one of these Roo field commands, Roo will add required JavaBean Validation API libraries to your project (i.e. these libraries will not be in your project until you decide to first use JavaBean Validation).

What's interesting about the above entity is what you can actually do with it. There are a series of methods automatically added into the Choice.class courtesy of Roo code-generated and maintained AspectJ ITDs. These include static methods for retrieving instances of Choice, JPA facade methods for persisting, removing, merging and flushing the entity, plus accessors and mutators for both the identifier and version properties. You can fine-tune these settings by modifying attributes on the @RooJpaActiveRecord annotation. You can also have Roo remove these services by simply removing the @RooJpaActiveRecord annotation from the class, in which case you'll be left with a normal JPA @Entity that you'll need to manage by hand (e.g. provide your own persistence methods, identifier, version etc).

The @RooJavaBean annotation causes an accessor and mutator (getter and setter) to automatically be generated for each field in the class. These accessors and mutators are automatically maintained in an AspectJ ITD by Roo. If you write your own accessor or mutator in the normal .java file, Roo will automatically remove the corresponding generated method from the ITD. You can also remove the @RooJavaBean annotation if you don't want any generated accessors or mutators (although those related to the version and identifier fields will remain, as they are associated with @RooJpaActiveRecord instead of @RooJavaBean).

Finally, the @RooToString annotation causes Roo to create and maintain a public String toString() method in a separate ITD. This method currently is used by any scaffolded web controllers if they need to display a related entity. The generated method takes care to avoid circular references that are commonly seen in bidirectional relationships involving collections. The method also formats Java Calendar objects in an attractive manner. As always, you can write your own toString() method by hand and Roo will automatically remove its generated toString() method, even if you still have the @RooToString annotation present. You can of course also remove the @RooToString annotation if you no longer wish to have a generated toString() method.

Before leaving this discussion on entities, it's worth mentioning that you are free to create your own entity .java classes by hand. You do not need to use the Roo shell commands to create entities or maintain their fields - just use any IDE. Also, you are free to use the @RooToString or @RooJavaBean (or both) annotations on any class you like. This is especially useful if you have a number of domain objects that are not persisted and are therefore not entities. Roo can still help you with those objects.

3.4. Web Layer

Roo 1.0 can optionally provide a scaffolded Spring MVC web layer. The scaffolded MVC web layer features are explored in some depth in the Beginning With Roo: The Tutorial chapter, including how to customise the appearance. From an architectural perspective, the scaffolded layer includes a number of URL rewriting rules to ensure requests can be made in accordance with REST conventions. Roo's scaffolding model also includes Apache Tiles, Spring JavaScript, plus ensures easy setup of Spring Security with a single command.

In Spring Roo 1.1 we also added comprehensive support for Google Web Toolkit (GWT). This allows you to build Generation IV web HTML5-based web front-ends. These front-ends access the Spring backend using highly optimized remoting protocols, and the GWT application represents the GWT team's recommended best practice architecture. In fact, the GWT team at Google wrote most of the Roo GWT add-on, so you can be sure it uses the best GWT 2.1 features.

Scaffolded web controllers always delegate directly to methods provided on an @RooJpaActiveRecord class. For maximum compatibility with scaffolded controllers, it is recommended to observe the default identifier and version conventions provided by @RooJpaActiveRecord implementations. If you write a web controller by hand (perhaps with the assistance of the web mvc controller command), it is recommended you also use the methods directly exposed on entities. Most Roo applications will place their business logic between the entities and web controllers, with only occasional use of services layers. Please refer to the services layer section for a more complete treatment of when you'd use a services layer.

3.5. Optional Services Layer

As discussed at the start of this chapter, web applications are the most common type of application created with Roo 1.0.0. A web application will rarely require a services layer, as most logic can be placed in the web controller handle methods and the remainder in entity methods. Still, a services layer makes sense in specific scenarios such as:

  • There is business logic that spans multiple entities and that logic does not naturally belong in a specific entity

  • You need to invoke business logic outside the scope of a natural web request (e.g. a timer task)

  • Remote client access is required and it is therefore more convenient to simply expose the methods via a remoting protocol

  • An architectural policy requires the use of a services layer

  • A higher level of cohesion is sought in the web layer, with the web layer solely responsible for HTTP-related management and the services layer solely responsible for business logic

  • A greater level of testing is desired, which is generally easier to mock than simulating web requests

  • it is preferred to place transactional boundaries and security authorization metadata on the services layer (as opposed to a web controller)

As shown, there are a large number of reasons why services layers remain valuable. However, Roo does not code generate services layers because they are not strictly essential to building a normal web application and Roo achieves separation of concern via its AspectJ ITD-based architecture.

If you would like to use a services layer, since release 1.2.0 Roo offers automatic service layer integration for your application. Please refer to the service layer section in the application layering chapter for further details.

3.6. Goodbye DAOs

One change many existing JEE developers will notice when using Roo-based applications is that there is no DAO layer (or "Repository" layer). As with the services layer, we have removed the DAO layer because it is not strictly essential to creating the typical web applications that most people are trying to build.

If we reflect for a moment on the main motivations for DAOs, it is easy to see why these are not applicable in Roo applications:

  • Testing: In a normal application a DAO provides an interface that could be easily stubbed as part of unit testing. The interesting point about testing is that most people use mocking instead of stubbing in modern applications, making it attractive to simply mock the persistence method or two that you actually require for a test (rather than the crudeness of stubbing an entire DAO interface). In Roo-based applications you simply mock the persistence-related methods that have been introduced to the entity. You can use normal mocking approaches for the instance methods on the Roo entity, and use Spring Aspect's @MockStaticEntityMethods support for the static finder methods.

  • Separation of concern: One reason for having a DAO layer is that it allows a higher cohesion object-oriented design to be pursued. The high cohesion equates to a separation of concern that reduces the conceptual weight of implementing the system. In a Roo-based application separation of concern is achieved via the separate ITDs. The conceptual weight is also reduced because Roo handles the persistence methods rather than force the programmer to deal with them. Therefore separation of concern still exists in a Roo application without the requirement for a DAO layer.

  • Pluggable implementations: A further benefit of DAOs is they simplify the switching from one persistence library to another. In modern applications this level of API abstraction is provided via JPA. As Roo uses JPA in its generated methods, the ability to plug in an alternate implementation is already fully supported despite there being no formal DAO layer. You can see this yourself by issuing the jpa setup command and specifying alternate implementations.

  • Non-JPA persistence: It is possible that certain entities are stored using a technology that does not have a JPA provider. In this case Roo does not support those entities out of the box. However, if only a small number of entities are affected by this consideration there is no reason one or more hand-written ITDs could not be provided by the user in order to maintain conceptual parity with the remainder of the Roo application (which probably does have some JPA). If a large number of entities are affected, the project would probably benefit from the user writing a Roo add-on which will automatically manage the ITDs just as Roo does for JPA.

  • Security authorisation: Sometimes DAOs are used to apply security authorisation rules. It is possible to protect persistence methods on the DAOs and therefore go relatively low in the control flow to protecting the accessibility of entities. In practice this rarely works well, though, as most authorisation workflows will target a use case as opposed to the entities required to implement a use case. Further, the approach is unsafe as it is possible to transitively acquire one entity from another without observing the authorisation rules (e.g. person.getPartner().getChildren().get(1).setFirstName("Ben")). It is also quite crude in that it does not support transparent persistence correctly, in that the example modification of the first name would flush to the database without any authorisation check (assuming this mutative operation occurred within the context of a standard transactional unit of work). While it's possible to work around many of these issues, authorisation is far better tackled using other techniques than the DAO layer.

  • Security auditing: In a similar argument to authorisation, sometimes DAOs are advocated for auditing purposes. For the same types of reasons expressed for authorisation, this is a suboptimal approach. A better way is to use AOP (e.g. AspectJ field set pointcuts), a JPA flush event handle, or a trigger-like model within the database.

  • Finders: If you review existing DAOs, you'll find the main difference from one to another is the finder methods they expose. Dynamic finders are automatically supported by Roo and introduced directly to the entity, relieving the user from needing DAOs for this reason. Furthermore, it is quite easy to hand-write a finder within the entity (or an ITD that adds the finder to the entity if a separate compilation unit is desired).

  • Architectural reasons: Often people express a preference for a DAO because they've always done it that way. While maintaining a proven existing approach is generally desirable, adopting Roo for an application diminishes the value of a DAO layer to such an extent that it leaves little (if any) engineering-related reasons to preserve it.

It's also worth observing that most modern RAD frameworks avoid DAO layers and add persistence methods directly to entities. If you compare similar technologies to Roo, you will see this avoidance of a DAO layer is commonplace, mainstream and does not cause problems.

Naturally you can still write DAOs by hand if you want to, but the majority of Roo add-ons will not be compatible with such DAOs. As such you will not receive automated testing or MVC controllers that understand your hand-written DAOs. Our advice is therefore not to hand write DAOs. Simply use the entity methods provided by @RooJpaActiveRecord, as it's engineering-wise desirable and it's also far less effort for you to write and maintain.

If you are interested in DAO support despite the above Roo offers support for different repository layers as of release 1.2.0. Please refer to the application layering chapter for details.

3.7. Maven

3.7.1. Packaging

Roo supports a number of Maven packaging types out of the box, such as jar, war, pom, and bundle. These are provided via Roo's PackagingProvider interface. If you wish to customise the POMs or other artifacts that Roo generates for a given packaging type when creating a project or module, either for one of the above packaging types or a completely different one, you can implement your own PackagingProvider that creates exactly the files you want with the contents you want. The procedure for doing this is as follows:

  • In a new directory, start Roo and run "addon create simple" to create a simple addon.

  • Delete:

    • the four .java files created in src/main/java

    • the two .tagx files created in src/main/resources

  • Create your custom packaging class (e.g. MyPackaging.java) in your preferred package.

  • Pick a unique ID for the Roo shell to use when referring to your PackagingProvider (e.g. "custom-jar"). Do not use any of the core Maven packaging type names, as these are reserved for use by Roo.

  • Make your packaging class implement the o.s.r.project.packaging.PackagingProvider interface, either by:

    • Implementing PackagingProvider directly, with full control over (but no assistance with) artifact generation, or

    • Extending o.s.r.project.packaging.AbstractPackagingProvider to have Roo create the POM from a template you specify, with various substitutions made automatically (e.g. groupId and artifactId). This approach requires you to:

      • Create your custom POM template in src/main/resources plus whatever package you chose above.

      • Create a public no-arg constructor that calls the AbstractPackagingProvider constructor with the following arguments:

        • The unique ID of your custom packaging type (see above).

        • The Maven name of your packaging type (typically jar/war/ear/etc, but could be something else if you've extended Maven to support custom packaging types).

        • The path to your POM template relative to your concrete PackagingProvider (e.g. "my-pom-template.xml" if it's in the same package). Note that this POM can contain as much or as little content as you like, with the following caveats:

          • It must have the standard Maven "project" root element with all the usual namespace details.

          • If you extend AbstractPackagingProvider, that class will ensure that the POM's coordinates can be resolved either from a "parent" element or from explicit "groupId", "artifactId", and "version" elements.

  • Add the Felix annotations @Component and @Service to your concrete PackagingProvider, so that it's detected by Roo's PackagingProviderRegistry.

  • Build and install the addon in the usual way, i.e.:

    • Run "mvn install" in the addon directory to create the OSGi bundle.

    • Change to the directory of the project that will be using the custom packaging provider.

    • Run "osgi start --url file:///path/to/addon/project/target/com.example.foo-0.1.0.BUILD-SNAPSHOT.jar"

    • Run "osgi scr list"; your custom PackagingProvider component should appear somewhere in the list.

  • Whenever you run the "project" or "module create" commands, your custom PackagingProvider's ID should appear in the list of possible completions for the "--packaging" option

3.7.2. Multi-Module Support

Since version 1.2.0, Roo supports multi-module Maven projects, i.e. those containing multiple projects in a nested directory structure, each with their own POM. The non-leaf POMs have "pom" packaging and the leaf POMs usually have an artifact creation packaging (jar, war, etc). If you're not familiar with multi-module projects and want to see how they're structured, there's an embedded multimodule.roo script that generates a simple multi-module project; used as follows:

  • At your operating system prompt, type "roo script multimodule.roo".

  • Change into the "ui/mvc"" directory.

  • Run "mvn tomcat:run" or "mvn jetty:run".

  • Point your browser to http://localhost:8080/mvc.

The rest of this section assumes that you are familiar with multi-module projects, in particular the difference between POM inheritance (one POM has another as its parent) and project nesting (one project is in a sub-directory of another, i.e. is a module of that parent project).

3.7.2.1. Features

Roo's multi-module support has the following features (a formal list of Roo's Maven-related commands appears in Appendix C):

  • Roo now has the concept of a module, which in practice means a directory tree whose root contains a Maven POM. A project consists of zero or more modules. When you run Roo from the operating system prompt, you do so from the directory of the root module.

    Once any modules exist, one of them always has the "focus", in other words will be used as the context for any shell commands that interact with the user project (as opposed to housekeeping commands such as "osgi ps"). For example, running the "web flow" command will add Spring Web Flow support to the currently focused module.

  • The "module focus" command, available once the project contains more than one module, changes the currently focused module. Tab completion is available, with the module name "~" signifying the root module.

  • The "module create" command creates a new module as a sub-directory of the currently focused module. The latter module's POM will be updated to ensure it has "pom" packaging, allowing the Maven reactor to properly recurse the module tree at build time. Note that the newly created POM will by default not inherit from the parent module's POM. If the new module's POM should have a parent, specify it via the "module create" command's optional "parent" parameter. The parent POM need not be located within the user project. A typical use case is that a development team might have a standard base POM from which all their projects inherit, or a standard web POM from which all their web modules inherit. As with the "project" command, the new module's Maven packaging can be specified via the optional "packaging" parameter. Custom packaging behaviour is supported, as described above.

3.7.2.2. Limitations

Roo's multi-module support has the following limitations:

  • Limited automatic creation of dependencies between modules. If your project needs any inter-module dependencies beyond those added by Roo, simply create them using the "dependency add" command.

  • No command for removing a module; this is in line with the absence of commands for removing other project artifacts such as classes, enums, JSPs, and POMs. In any event, it's simple enough to do manually; just delete the directory, delete the relevant "<module>" element from the parent module's POM, and delete the module as a dependency from any other modules' POMs.

  • One area where there's considerable scope for improvement is in the management of dependencies in general. In an ideal Maven project, dependency information in the form of both "dependencyManagement" entries and live "dependency" elements themselves would be pushed as far up the POM inheritance hierarchy as possible, in order to minimise duplication and reduce the incidence of version conflicts. As it stands, Roo adds and removes dependencies to and from the currently focused module in response to shell commands, regardless of what dependencies are in effect for other modules in the project.

  • Likewise, plugin management is currently quite basic. Roo adds/removes plugins to the POM of the currently focused module with no attempt to rationalise them in concert with the POMs of other modules (for example, two Spring MVC modules will independently have the Jetty plugin declared in their own POMs rather than having this plugin declared in the lowest common ancestor POM). As with dependencies (see above), this is an area in which Roo could conceivably take some of the load off developers.

  • There's no Roo command for changing a module’s packaging between two arbitrary values, as this could require too many other changes to the user’s project. However, Roo does change a module's packaging in two specific circumstances:

    • Adding a module to the currently focused module changes the latter's packaging to "pom", as described above under the "module create" command.

    • Adding web support to a module changes its packaging to "war".

  • Roo does not create any parent-child relationships between different modules’ Spring application contexts; the user can always create these relationships manually, and Roo will not remove them.