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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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).
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.
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.