In Part 1 and Part 2 of this "Introducing Spring Roo" series, we built a full-fledged
enterprise application from scratch using Spring Roo. The first two
articles focused on building web applications using
Spring Roo's rapid application development environment. We covered a
lot of features, like JPA, Selenium testing, Spring Security, email
integration, Spring Roo social features, database reverse-engineering,
etc. Now we will take a look at Spring Roo's
add-on architecture. Then we will write Spring Roo add-ons using the
addon create command. By the end of this
article, you'll be able to quickly and easily
create your own add-ons for Spring Roo.
Spring Roo add-on architecture
In its simplest form, an add-on is a software component that adds a specific ability to a software application. For example, in most web browsers, video support is provided by add-ons. Another example is Eclipse (an open source IDE that many Java™ developers use or at least know). Most of the capabilities are provided by add-ons, like JUnit support, SVN support, etc. I am using add-on as a general term for plugins and extensions.
Spring Roo also has the notion of add-ons:
- To enable third-party developers to create abilities that extend the capabilities of Spring Roo
- To help Spring Roo easily add new features
- To help Spring Roo remain a little genie (i.e., reduce the size of Spring Roo)
Spring Roo is split into two logical parts.
Spring Roo core components: To enable add-on development, Spring Roo provides a set of core components that form a hosted execution environment for different add-ons. These components are the Classpath, which supports the Process Manager and the Shell. In turn, the Process Manager supports the Project and the File Undo. The Project supports the Model, Metadata, and File Monitor. Finally, there is the Support component, which is used by all. Figure 1 offers a visual representation of these relationships.
Figure 1. Spring Roo core components
Let's talk about some of the core modules:
- Support — The
org.springframework.roo.supportmodule provides common utility classes used by all the core modules and add-ons. Some utility classes includeAssert,FileCopyUtils,XmlUtils,StringUtils,FileUtils, etc. For example, if you want to copy the content of one file to the other file, you could useFileCopyUtilsto do it for you. - Metadata — The org.springframework.roo.metadata module provides a metadata service provider interface and implementation that includes dependency registration and caching.
- File monitor — The org.springframework.roo.file.monitor module publishes events following detected file system changes (the default implementation uses auto-scaled disk polling).
- File undo — The org.springframework.roo.file.undo module provides a file undo facility for use by the process manager.
- Project — The org.springframework.roo.project module abstracts typical end-user project build systems like Apache Maven and Apache Ant.
- Process manager — The org.springframework.roo.process.manager module offers an ACID-like file system abstraction that includes disk rollback and process synchronization.
- Classpath — The org.springframework.roo.classpath module performs abstract syntax tree parsing and type binding of Java and AspectJ compilation units.
Spring Roo core components: Spring Roo provides all the functionality through add-ons. The base add-ons shipping with Roo V1.1.3:
- Add-On Creator — The org.springframework.roo.addon.creator add-on enables easy creation of third-party Roo add-ons.
- Backup — The org.springframework.roo.addon.backup add-on enables backups to be made to a ZIP file by typing backup.
- Cloud Foundry — The org.springframework.roo.addon.cloud.foundry add-on provides VMware Cloud Foundry support.
- Configurable — The org.springframework.roo.addon.configurable add-on provides support for the introduction of Spring's @Configurable annotation through an AspectJ ITD.
- Database reverse engineering — The org.springframework.roo.addon.dbre add-on provides support for the incremental reverse-engineering of existing databases.
- Data on Demand — The org.springframework.roo.addon.dod add-on provides support for the automatic creation of sample data used for integration tests.
- Email — The org.springframework.roo.addon.email add-on provides support for the integration and configuration of Spring's email support in the target project.
- Entity — The org.springframework.roo.addon.entity add-on provides extensive support for automatically maintaining Java Persistence API @Entity classes.
- Dynamic Finder — The org.springframework.roo.addon.finder creates type-safe code completion-compatible JPA query language finders.
- Git — The org.springframework.roo.addon.git add-on provides support for GIT integration in the project. Each successfully executed command will be automatically committed to a local GIT repository.
- GWT — The org.springframework.roo.addon.gwt add-on provides support for UI scaffolding using Google Web Toolkit.
- JavaBean — The org.springframework.roo.addon.javabean add-on automatically maintains JavaBean getters/setters for classes with an @RooJavaBean annotation.
- JDBC — The org.springframework.roo.addon.jdbc add-on encapsulates OSGi-compliant access to JDBC drivers shipped in different bundles (primarily used by other add-ons).
- JMS — The org.springframework.roo.addon.jms add-on provides support for configuring Java Messaging System settings in the target project.
- JPA — The org.springframework.roo.addon.jpa add-on installs a specified JPA provider and sets up JDBC accordingly.
- JSON — The org.springframework.roo.addon.json add-on adds JSON related serialization and deserialization methods to POJOs.
- Logging — The org.springframework.roo.addon.logging add-on sets up Log4j, including command-based log-level configuration.
- Pluralization — The org.springframework.roo.addon.plural add-on provides pluralization of nouns (primarily used by other add-ons).
- Property Editor — The org.springframework.roo.addon.property.editor add-on manages property editors, as required by Spring MVC.
- Property File — The org.springframework.roo.addon.propfiles add-on provides support for the management of properties files in the target project.
- RooBot Client — The org.springframework.roo.addon.roobot.client add-on provides support for add-on management through the RooBot server.
- Security — The org.springframework.roo.addon.security add-on sets up Spring Security, including login pages, filters, and dependencies.
- Serializable — The org.springframework.roo.addon.serializable add-on adds java.io.Serializable support (such as UID maintenance) to requested Java types.
- Solr — The org.springframework.roo.addon.solr add-on provides support for configuration and integration of Apache Solr features in the target project.
- Integration Test — The org.springframework.roo.addon.test add-on produces JUnit integration tests for project entities.
- ToString — The
org.springframework.roo.addon.tostring
add-on produces a valid
toString()method for any class with the@RooToStringannotation. - WebFlow — The org.springframework.roo.addon.web.flow add-on provides support for configuration and integration of Spring Web Flow features in the target project.
- Web MVC Controller — The org.springframework.roo.addon.web.mvc.controller add-on provides support for configuration and integration of Spring MVC controllers in the target project.
- Web MVC Embedded — The org.springframework.roo.addon.web.mvc.embedded add-on provides extension to the MVC add-on, which allows the addition of embedded features such as maps, videos, etc. into web pages.
- Web MVC JSP — The org.springframework.roo.addon.web.mvc.jsp add-on configures and integrates Spring MVC JSP features in the target project.
- Selenium — The org.springframework.roo.addon.web.selenium add-on provides configuration and integration of Selenium web tests in the target project.
Now that we have looked at the Spring Roo core components and base add-ons provided by Spring Roo, let's write our own add-ons.
Spring Roo is based on OSGi, which is ideal for Roo's add-on architecture. OSGi provides a very good infrastructure for developing modular and embedded service-oriented applications.
The Roo shell uses Apache Felix as its OSGi runtime framework together
with Service Component Runtime (SCR) for component management and OSGi
Bundle Repository (OBR) for bundle resolution. There are various OSGi
commands available in the Roo shell, which you can see by typing
help osgi, as shown in Listing 1.
Listing 1. Roo help for OSGi
roo> help osgi * osgi find - Finds bundles by name * osgi framework command - Passes a command directly through to the Felix shell infrastructure * osgi headers - Display headers for a specific bundle * osgi install - Installs a bundle JAR from a given URL * osgi log - Displays the OSGi log information * osgi obr deploy - Deploys a specific OSGi Bundle Repository (OBR) bundle * osgi obr info - Displays information on a specific OSGi Bundle Repository (OBR) bundle * osgi obr list - Lists all available bundles from the OSGi Bundle Repository (OBR) system * osgi obr start - Starts a specific OSGi Bundle Repository (OBR) bundle * osgi obr url add - Adds a new OSGi Bundle Repository (OBR) repository file URL * osgi obr url list - Lists the currently-configured OSGi Bundle Repository (OBR) repository file URLs * osgi obr url refresh - Refreshes an existing OSGi Bundle Repository (OBR) repository file URL * osgi obr url remove - Removes an existing OSGi Bundle Repository (OBR) repository file URL * osgi ps - Displays OSGi bundle information * osgi resolve - Resolves a specific bundle ID * osgi scr config - Lists the current SCR configuration * osgi scr disable - Disables a specific SCR-defined component * osgi scr enable - Enables a specific SCR-defined component * osgi scr info - Lists information about a specific SCR-defined component * osgi scr list - Lists all SCR-defined components * osgi start - Starts a bundle JAR from a given URL * osgi uninstall - Uninstalls a specific bundle * osgi update - Updates a specific bundle * osgi version - Displays OSGi framework version |
Spring Roo add-on create command
Spring Roo comes bundled with add-on create commands for creating
different types of add-ons. The Add-on Creator, which exposes the
addon create commands, is also a Roo add-on.
Roo currently supports four types of add-ons:
- Internationalization Add-on — It supports adding language translations for Roo's scaffolded Spring MVC applications (adding translation for the Hindi language, for example).
- Simple Add-on — Simple Add-ons support small additions to project dependencies or configuration or both (making some maven pom.xml modifications like adding some JARs or Maven plugins, for example).
-
Advanced Add-on — This add-on does the heavy work and is
used for building a full-fledged Spring Roo add-on that requires
Java code creation (building an add-on that can
write
equalsandhashcodemethods for your domain object, for example). There is already a community add-on for these functions. - Wrapper Add-on — This add-on wraps a Maven artifact to create an OSGi-compliant bundle. This is required when a dependency is required by the add-on to complete its functionality. For example, the Spring Roo database reverse-engineering add-on requires the Postgres JDBC driver to complete its tasks, so you will wrap the Postgres JDBC driver using this add-on.
These add-ons create commands to ease Roo add-on development by creating new add-ons that are:
- Integrated with Google Code SVN source-code control.
- Hosted in a public Maven repository created as a part of Google code project.
- Compliant with RooBot, a VMware-hosted service that
indexes important content in public Roo OBR files.
The OBR file is an XML-based representation of bundle metadata.
To be compliant with RooBot, add-ons should be:
- OSGi-compliant.
- PGP-signed artifacts with public keys.
- Registered through the httppgp:// protocol.
Using the Roo addon create command, you get
all the features mentioned above, automatically configured for your
add-ons. This certainly reduces the time to create and publish the
add-ons to the outside world.
Before we start writing add-ons, make sure you have a functioning Spring Roo environment. Instructions for installing Spring Roo can be found in Part 1 of this series.
I want Hindi language support (i18N add-on creation)
When you create a Spring MVC-based web application using Spring Roo, you
can add support for different languages using the
web mvc language command. Out of the box,
Spring Roo supports English, German, Spanish, Italian, Dutch, and
Swedish. The internationalization support is provided by the Web MVC
JSP Add-on, which is enabled only after you have JSPX files in your
webapp directory. The JSPX files are generated by a controller command
that converts a simple JAR-based application to a Spring MVC web
application.
Being an Indian, I wanted to add support for Hindi in my web
application. Spring Roo provides an
addon create i18n command, which provides an
extension to the
web mvc install language command adding
support for a new language like Hindi. The only thing it requires is
the translation of the messages.properties file in the desired
language.
When Spring Roo creates a Spring MVC web application, it creates two property files: application.properties and messages.properties. The application.properties file contains the application-specific properties, like application name. The messages.properties file contains the properties, which are not specific to any application, like the message "Are you sure want to delete this item?" when you click the delete button, or messages like login, logout, etc. So, when you write an i18n add-on, you have to provide translation for the messages.properties file.
Now that we have talked about the default internationalization support
in Spring Roo, let's write an i18n add-on that can add Hindi language
support. I will show
how you can set up a project on Google code, write an add-on using
the command, publish and release it to outside world, finally
registering it with the RooBot service. Registering with the RooBot
service is important because this will let RooBot index your add-on
and show it when other developers search using the
addon search command.
The Spring Roo documentation eloquently explains setting up your project and Maven repository on Google code, so I am not going to repeat it. I will just note that I will use the project name "roo-hind-addon."
Once you have set up the project, you will have an empty directory
named roo-hindi-addon. Go into that directory and type
roo command. Once in the shell, type
addon create i18n. If you press the tab
key, you will see that this command takes seven attributes. Out of the
seven, three attributes are mandatory:
topLevelPackage (top-level package of the
new add-on), locale (local abbreviations
like "it" for Italian), and messageBundle
(fully qualified path to the message_xx.properties, where xx is the
locale name). The other four are optional attributes:
language (full name of the language),
flagGraphic (full path to xx.png file, where
xx is the name of flag), description
(description of the add-on), and
projectName (name of the project (if not
provided, the top-level package name is used)). I would suggest
you use the projectName attribute
and make sure its value is the name of the project hosted on
Google code. So, in our case, it will be roo-hindi-addon.
Out of the attributes mentioned above, the most important is
messageBundle, which specifies the
translated messages.properties file. To translate the
messages.properties to Hindi language, the easiest option is to use a
service like Google translate, converting each property one by one, then
writing them to the messages_hi.properties file. However, this does
not work in our situation because the Java properties files use
ISO-8859-1 encoding, which does not support Hindi characters. To
overcome this problem, I used an Eclipse plugin called Eclipse
ResourceBundle Editor, which allows you to convert and create resource
bundles for different languages. You can install it using the
http://www.nightlabs.de/updatesites/development/ update site.
Converting the messages.properties file to the messages_hi.properties
file is not in the scope of this article. The ResourceBundle Editor
plugin is easy to use. Please note that if the language
characters were supported, it was not required to use
the ResourceBundle Editor. The messages_hi.properties file will look like
Listing 2.
Listing 2. Sample of the messages_hi.properties file
button_cancel = \u0930\u0926\u094D\u0926 button_end = \u0905\u0902\u0924 button_find = \u0916\u094B\u091C\u0947\u0902 button_home = \u0918\u0930 ... |
View the full file. Once you have the translated messages_hi.properties file, you are ready to create the add-on by just typing a single command. You can create Hindi add-on by typing the command from Listing 3 into the Roo shell.
Listing 3. Command to create the Hindi add-on
addon create i18n --locale hi --topLevelPackage org.xebia.roo.addon.i18n.hindi --messageBundle <location to messages_hi.properties> \ --language hindi --projectName roo-hindi-addon --flagGraphic <full path to flag hi.png> |
Listing 4 shows the artifacts created by the
addon create i18n command.
Listing 4. Artifacts created
Created ROOT/pom.xml Created ROOT/readme.txt Created ROOT/legal Created ROOT/legal/LICENSE.TXT Created SRC_MAIN_JAVA Created SRC_MAIN_RESOURCES Created SRC_TEST_JAVA Created SRC_TEST_RESOURCES Created SRC_MAIN_WEBAPP Created SRC_MAIN_RESOURCES/META-INF/spring Created ROOT/src/main/assembly Created ROOT/src/main/assembly/assembly.xml Created SRC_MAIN_RESOURCES/org/xebia/roo/addon/i18n/hindi Created SRC_MAIN_RESOURCES/org/xebia/roo/addon/i18n/hindi/messages_hi.properties Created SRC_MAIN_RESOURCES/org/xebia/roo/addon/i18n/hindi/hi.png Created SRC_MAIN_JAVA/org/xebia/roo/addon/i18n/hindi Created SRC_MAIN_JAVA/org/xebia/roo/addon/i18n/hindi/HindiLanguage.java |
The command generated a Maven project that can be imported into Eclipse (with m2eclipse) or the SpringSource Tool Suite via File > Import > Maven > Existing Maven projects. You don't need to import this project because we don't need to modify the add-on.
Now we can install this add-on, use it in our projects, and publish it to the outside world. But first, let's talk about some of the artifacts to gain more understanding of the generated code:
- pom.xml — This is a standard Maven project configuration. The generated pom.xml has various pre-configured plugins, which does the work related to signing artifacts using PGP, releasing the add-on using the Maven release plugin, and creating OSGi bundles using the Maven bundle plugin. It also adds OSGi and Felix dependencies to the project, which are needed for the add-on to run inside the Roo shell.
- assembly.xml — This defines the configurations used by the Maven assembly plugin for packing the add-on.
- messages_hi.properties — This is the message bundle file we provided while creating the add-on, which is copied to resources folder.
- hi.png — This is the flag PNG file we provided while creating the add-on, which is copied to resources folder.
- HindiLanguage.java — This is the only Java file created by this add-on, used to get the information corresponding to the specific language. For example, it gives the local name, message bundle resource file, etc.
Adding Hindi support to our application
Now I will show you how you can add Hindi support to your application using the add-on we just created:
- Exit the Roo shell and run the
mvn clean installcommand. During the build process, it will ask for your GPG passphrase. - After the Roo add-on has been built, open the new command line and create a directory named i18n-hindi-client. We will create a simple client for our add-on.
- Enter the i18n-hindi-client directory and type the
roocommand. - Execute the following commands in the Roo shell. This will create
a simple Spring MVC web application.
Listing 5. Roo commands to create MVC web applicationproject --topLevelPackage com.shekhar.roo.i18n.client \ --projectName i18n-hindi-client persistence setup --provider HIBERNATE --database HYPERSONIC_IN_MEMORY entity --class ~.domain.MyUser field string --fieldName name --notNull controller all --package ~.web
- Type this command and press
the tab key:
web mvc language --code de en es it nl svYou will not see the Hindi language support. This is because we have not yet installed the Hindi add-on.
- To install the add-on, type:
osgi start --url file:///<location to addon target folder>/org.xebia.roo.addon.i18n.hindi-0.1.0.BUILD-SNAPSHOT.jar
This should install and activate our Spring Roo Hindi add-on. You can view the status of the add-on using the
osgi pscommand, which displays the OSGi bundle information and its status as shown below:[ 95] [Active ] [ 1] roo-hindi-addon (0.1.0.BUILD-SNAPSHOT)
- Again, type
web mvc language –codeand press the tab key. This time, you will see a code for the Hindi language. Choose the hi code, and Hindi language support will be added to your application.web mvc language –code hi
- Exit the Roo shell and type
mvn tomcat:run. You will see the India flag in the footer. Click on the flag, and your application will be displayed in Hindi, as shown in Figure 2.
Figure 2. Hindi language support
Once you have tested that the add-on is working fine in your development system, you can push the add-on to the Google code project we created.
Now that we have seen that the add-on we have created works, let's take a look at how the Hindi language was made available to the application:
- The roo-hindi-addon was started using
osgi startcommand. - When the add-on starts, the
HindiLanguageclass that was created by theaddon create i18n commandgets registered with i18nComponent, which is an OSGi service listener for registering and unregistering i18n add-ons. TheHindiLanguageclass is marked with@Componentand@Serviceannotations, which are provided by Apache Felix. These annotations make sure that the components and services are registered with the Roo shell and are available for use. - When we typed the
web mvc install language –codecommand and pressed the tab key, the tab completion service calls the i18nComponent classgetSupportedLanguages()method, which returns thejava.util.Setof i18n add-ons. BecauseHindiLanguagewas already registered with i18nComponent, it was also returned.
Publishing add-ons to the outside world
It's easy to publish the add-on to the outside world because the
Roo create command has done most of the
things for us by installing all the required Maven plugins. Go to the
project root directory and type the commands from Listing 6.
Listing 6. Publishing the Roo project
svn add pom.xml src/ legal/ readme.txt svn commit -m "Roo Hindi Addon first version" mvn release:prepare release:perform |
The Maven release plugin will ask for the release version, tag name, and development version. You can just choose the default and proceed forward. It will take couple of minutes to release and publish the artifacts to the Google code project. You can then view the add-on artifacts in your Google code project.
Registering your add-on with RooBot
Once you have released your plugin, you can email it to s2-roobot@vmware.com to register your add-on repository. The email subject line must contain the repository.xml file. For example, for the add-on we just created, the repository.xml is http://code.google.com/p/roo-hindi-addon/source/browse/repo/repository.xml. For more information on registering with RooBot, please refer to Spring Roo documentation.
I want to Monitor my Java application (simple add-on creation)
It is a common requirement in many enterprise applications that you want to monitor your Java application to determine application performance bottlenecks. I also had the same requirement, so I decided to look at some available open source solutions. Java Application Monitor (JAMon) is a free, high-performance, thread-safe Java API that allows you to easily monitor production applications.
To add JAMon support in your web application:
- You must add jamon JAR in your pom.xml.
- You must define a
JamonPerformanceMonitorInterceptorbean in your application context file. The sample bean definition looks like the code in Listing 7.
Listing 7. JamonPerformanceMonitorInterceptor code
<bean id="jamonPerformanceMonitorInterceptor" class=\ "org.springframework.aop.interceptor.JamonPerformanceMonitorInterceptor"> <property name="trackAllInvocations" value="true"></property> <property name="useDynamicLogger" value="true"></property> </bean> <bean id="autoProxyCreator" class=\ "org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="interceptorNames"> <list> <idref bean="jamonPerformanceMonitorInterceptor" /> </list> </property> <property name="beanNames"> <list> <value>speakerController</value> <value>talkController</value> </list> </property> </bean> |
This is not a simple bean definition that any developer can easily remember, and I thought that it would be great if I could automate this boilerplate configuration. So I decided to write an add-on that can automate the process of adding the JAMon dependency and wiring the interceptor bean. How do you decide if you need a simple or advanced add-on?
- Use a simple add-on when you want to add Maven dependencies, add configuration artifacts, or add both to your project.
- Use an advanced add-on when you need to write full-fledged add-ons that enhance existing Java types, introduce new Java types and AspectJ ITDs, or do both your project.
Our requirement is to add jamon.jar to the classpath and write an application context file that will contain our bean definition. It is better to write a new Spring application context file rather than using the existing context files because it helps modularize application contexts. From these requirements, it is clear that we should write a simple add-on that can add JAMon support to any web application.
We need to do the setup for the project the same way as we did for the internationalization add-on. I will name this project "spring-roo-addon-jamon".
Once you have set up the project, you will have a directory named spring-roo-addon-jamon with just the .svn folder. Go into the spring-roo-addon-jamon directory and start the Spring Roo shell. Then, type the following command:
addon create simple --topLevelPackage org.xebia.roo.addon.jamon \ --projectName spring-roo-addon-jamon |
That's it! The add-on is created.
Installing the generated add-on
You can install the add-on using this command:
osgi start --url file://<Location to addon
target folder>/org.xebia.roo.addon.jamon-0.1.0.BUILD-SNAPSHOT.jar |
Once you have installed the add-on, create a simple client as we created for the i18n add-on to test. The generated add-on gives two commands:
-
say hello, a command that prints a welcome message on the Roo shell. This command will be always available, and you can type it anytime while the Roo shell is running. -
web mvc install tagsreplaces the default MVC tags generated when you scaffold a web application. This command will only be available when you have created a web application.
Now that you have tested the add-on, let's take a look at the files generated by this add-on, shown in Listing 8.
Listing 8. Files generated for spring-roo-addon-jamon
Created ROOT/pom.xml Created ROOT/readme.txt Created ROOT/legal Created ROOT/legal/LICENSE.TXT Created SRC_MAIN_JAVA Created SRC_MAIN_RESOURCES Created SRC_TEST_JAVA Created SRC_TEST_RESOURCES Created SRC_MAIN_WEBAPP Created SRC_MAIN_RESOURCES/META-INF/spring Created SRC_MAIN_JAVA/com/shekhar/roo/addon/jamon Created SRC_MAIN_JAVA/com/shekhar/roo/addon/jamon/JamonCommands.java Created SRC_MAIN_JAVA/com/shekhar/roo/addon/jamon/JamonOperations.java Created SRC_MAIN_JAVA/com/shekhar/roo/addon/jamon/JamonOperationsImpl.java Created SRC_MAIN_JAVA/com/shekhar/roo/addon/jamon/JamonPropertyName.java Created ROOT/src/main/assembly Created ROOT/src/main/assembly/assembly.xml Created SRC_MAIN_RESOURCES/com/shekhar/roo/addon/jamon Created SRC_MAIN_RESOURCES/com/shekhar/roo/addon/jamon/info.tagx Created SRC_MAIN_RESOURCES/com/shekhar/roo/addon/jamon/show.tagx |
The artifacts pom.xml, assembly.xml, LICENSE.TXT, and readme.txt are the same as generated by the i18n add-on. We discussed them above, so I am not going to talk about them here again. The artifacts that are of more interest are JamonCommands, JamonOperations, JamonOperationsImpl, and JamonPropertyName. Let's talk about them one by one to get an understanding of the code generated by simple add-on command.
-
JamonCommands.java:JamonCommands —
This is a class that implements a marker
interface called CommandMarker and exposes two commands, say
hello and web mvc install tags, as shown in Listing 9.
Listing 9. Generated code from JamonCommands.java:JamonCommands@Component @Service public class JamonCommands implements CommandMarker { @Reference private JamonOperations operations; @Reference private StaticFieldConverter staticFieldConverter; protected void activate(ComponentContext context) { staticFieldConverter.add(JamonPropertyName.class); } protected void deactivate(ComponentContext context) { staticFieldConverter.remove(JamonPropertyName.class); } @CliAvailabilityIndicator("say hello") public boolean isSayHelloAvailable() { return true; } @CliCommand(value = "say hello", help = "Prints welcome message to the Roo shell") public void sayHello( @CliOption(key = "name", mandatory = true, help = "State your name") String name, @CliOption(key = "contryOfOrigin", mandatory = false, help = "Country of origin") JamonPropertyName country) { log.info("Welcome " + name + "!"); log.warning("Country of origin: " + (country == null ? \ JamonPropertyName.NOT_SPECIFIED.getPropertyName() : country.getPropertyName())); log.severe("It seems you are a running JDK " + operations.getProperty("java.version")); log.info("You can use the default JDK logger anywhere in your" + " add-on to send messages to the Roo shell"); } @CliAvailabilityIndicator("web mvc install tags") public boolean isInstallTagsCommandAvailable() { return operations.isInstallTagsCommandAvailable(); } @CliCommand(value = "web mvc install tags", help="Replace default Roo MVC tags used for scaffolding") public void installTags() { operations.installTags(); } }
I have removed all the comments generated by the code generator to reduce the verbosity of the code. Let's take a look at important members of this class:
-
CommandMarker — All the command classes
should implement the CommandMarker interface and should be
annotated with
@Componentand@Serviceannotations so they are registered and their commands become available in the Roo shell. The fields annotated with@Referenceare dependencies of theJamonCommandsclass are injected by the underlying Roo OSGi container. All the classes annotated with@Componentand@Service annotationscan be injected into other add-ons. - Activate and Deactivate Methods — The
activateanddeactivatemethods are the methods called when you install the add-on using theaddon installcommand or remove the add-on using theaddon removecommand. These methods allows you to hook into the lifecycle of the add-on, which is managed by the underlying Roo OSGi container. - Methods annotated with
@CliAvailabilityIndicator— The methods annotated with@CliAvailabilityIndicatorare the methods that help hide commands when they are not available. For example, thesecurity setupcommand, which installs Spring Security into the project, is not visible until the project is a web application. It is not required that you should have such methods, but they are useful as they don't allow user to fire commands that do not make sense at that point in time. For example, executing the security setup command before the user has a web application does not make any sense. - Methods annotated with
@CliCommand— The methods annotated with@CliCommandregisters the command with the Roo shell. The@CliCommandannotation has two attributes:value, which defines the command name, andhelp, which defines the help message shown when you type thehelpcommand. Every command can define mandatory and non-mandatory attributes using the@CliOptionannotation, presented as a part of the command. For example, thesay hellocommand has a mandatory attribute callednameand non-mandatory attribute calledcountry.
-
CommandMarker — All the command classes
should implement the CommandMarker interface and should be
annotated with
- JamonOperationsImpl.java — Command classes need to perform some operations
when a command is executed. These operations are performed by
operation classes.
JamonOperationsImplis an operations class responsible for doing the actual work, like adding a dependency in the pom.xml or copying resources to the desired location. TheJamonOperationsImplclass performs its operations by using core services provided by the Spring Roo framework. For example, to add a dependency, it uses theProjectOperationsinterface. To copy the files, it uses theFileManagerinterface. Let's take a look at the code for theJamonOperationsImplclass (see Listing 10) to get a better understanding of the Roo add-on.
Listing 10. JamonOpertionsImpl@Component @Service public class JamonOperationsImpl implements JamonOperations { private static final char SEPARATOR = File.separatorChar; @Reference private FileManager fileManager; @Reference private ProjectOperations projectOperations; public boolean isInstallTagsCommandAvailable() { return projectOperations.isProjectAvailable() && fileManager.exists(projectOperations.getProjectMetadata(). getPathResolver().getIdentifier(Path.SRC_MAIN_WEBAPP, "WEB-INF" + SEPARATOR + "tags")); } public String getProperty(String propertyName) { Assert.hasText(propertyName, "Property name required"); return System.getProperty(propertyName); } public void installTags() { PathResolver pathResolver = projectOperations.getProjectMetadata().getPathResolver(); createOrReplaceFile(pathResolver.getIdentifier( Path.SRC_MAIN_WEBAPP, "WEB-INF" + SEPARATOR + "tags" + SEPARATOR + "util"), "info.tagx"); createOrReplaceFile(pathResolver.getIdentifier( Path.SRC_MAIN_WEBAPP, "WEB-INF" + SEPARATOR + "tags" + SEPARATOR + "form"), "show.tagx"); } private void createOrReplaceFile(String path, String fileName) { String targetFile = path + SEPARATOR + fileName; MutableFile mutableFile = fileManager.exists(targetFile) ? fileManager.updateFile(targetFile) : fileManager.createFile(targetFile); try { FileCopyUtils.copy(TemplateUtils.getTemplate(getClass(), fileName), mutableFile.getOutputStream()); } catch (IOException e) { throw new IllegalStateException(e); } } }
JamonOperationsImplclass has two important methods:isInstallTagsCommandAvailablechecks whether the command is available, andinstallTagsinstalls the tags into the target project. To perform these operations theJamonOperationsImplclass uses some Spring Roo core services and utilities:- The
ProjectOperations : JamonOperationsImplclass uses theProjectOperationsservice to check whether the project is available and to get path of the tags folder. - The
FileManager : JamonOperationsImplclass uses theFileManagerservice to check whether a file exists at a certain path and to get the update or create aMutableFile.MutableFileis another Roo-specific class which represents a handle to a file which can be modified. - The
TemplateUtils : TemplateUtilsutility class is used to acquire anInputStreamto template files (info.tagx and show.tagx) in our add-on bundle. - The
FileCopyUtils : FileCopyUtilsutility class is used copy the templates (fromTemplateUtils) to theMutableFile(fromFileManager).
- The
Modifying an add-on to meet our requirements
In the first section about a simple add-on, we discussed two requirements that we must meet so our add-on can configure JAMon in any Spring MVC web application. The requirements were:
- Add JAMon JAR in your pom.xml
- Define a JamonPerformanceMonitorInterceptor bean in the application context file
To meet these requirements, we will make changes in the
JamonCommands class to support the
jamon setup command. Then we will add one
operation corresponding to the jamon setup
command to do the actual work to meet the requirements.
JamonCommands
— The JamonCommands class
will now have just two methods: isInstallJamon to check whether the
jamon setup command is available,
and installJamon to install
JAMon when the jamon setup command is
fired. (See Listing 11.)
Listing 11. Code for JamonCommands
@Component
@Service
public class JamonCommands implements CommandMarker {
private Logger log = Logger.getLogger(getClass().getName());
@Reference private JamonOperations operations;
@CliAvailabilityIndicator("jamon setup")
public boolean isInstallJamon() {
return operations.isInstallJamonAvailable();
}
@CliCommand(value = "jamon setup", help = "Setup Jamon into your project")
public void installJamon() {
operations.installJamon();
}
}
|
This is all we need in JamonCommands class.
It just needs to expose the jamon setup
command, and the rest of the work is delegated to the
JamonOperationsImpl class. (See Listing
12.)
Listing 12. Code for JamonOperationsImpl
JamonOperationsImpl
JamonOperationsImpl need to implement two methods – \
isInstallJamonAvailable() and installJamon().
The isinstallJamonAvailable() method returns a boolean indicating whether
command is available at this location or not. I want to enable JAMon only for the web
applications so we need to perform the check whether the project is
a web application or not. To do that we will write the code as shown below
public boolean isInstallJamonAvailable() {
return projectOperations.isProjectAvailable() &&
fileManager.exists(projectOperations.getPathResolver()
.getIdentifier(Path.SRC_MAIN_WEBAPP, "/WEB-INF/web.xml"));
}
|
The code shown in Listing 12 uses the
ProjectOperations service to check whether
a project is available at this location and to get the path of the
web.xml file (the web.xml file only exists for web applications). The
FileManager service is used to check
whether the web.xml exists at the location specified by
ProjectOperations.
The second method we need to implement is
installJamon(), which should add JAMon
dependency in pom.xml and add the bean definition to the
webmvc-config.xml. Before we write code for this method, we must
create an XML file named configuration.xml inside the
src/main/resources/org/xebia/roo/addon/jamon folder. The folder
structure, org/xebia/roo/addon/jamon, is the same as package structure
of your Roo add-on. The configuration.xml defines the JAMon version
and JAMon JAR dependency, as shown in Listing 13.
Listing 13. Contents of configuration.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<configuration>
<jamon>
<properties>
<jamon.version>2.4</jamon.version>
</properties>
<dependencies>
<dependency>
<groupId>com.jamonapi</groupId>
<artifactId>jamon</artifactId>
<version>${jamon.version}</version>
</dependency>
</dependencies>
</jamon>
</configuration>
|
Let's write the code to add dependency in pom.xml, shown in Listing 14.
Listing 14. Contents of pom.xml
public void installJamon() {
Element configuration = XmlUtils.getConfiguration(getClass());
updatePomProperties(configuration);
updateDependencies(configuration);
}
private void updatePomProperties(Element configuration) {
List<Element> properties = \
XmlUtils.findElements("/configuration/jamon/properties/*"
, configuration);
for (Element property : properties) {
projectOperations.addProperty(new Property(property));
}
}
private void updateDependencies(Element configuration) {
List<Dependency> dependencies = new ArrayList<Dependency>();
List<Element> jamonDependencies = XmlUtils.findElements(
"/configuration/jamon/dependencies/dependency", configuration);
for (Element dependencyElement : jamonDependencies) {
dependencies.add(new Dependency(dependencyElement));
}
projectOperations.addDependencies(dependencies);
}
|
This is a standard mechanism to add a dependency in pom.xml, and you can
find it used in most of the Roo add-ons. The code shown in Listing 14
is self-explanatory and uses the Spring Roo utility class,
XmlUtils, to read the content from
configuration.xml and then the
ProjectOperations service to update the
pom.xml file.
Once we have added the pom.xml dependency, we need to create a separate
Spring configuration, web-jamon-config.xml, which will contain the
bean definition for
JamonPerformanceMonitorInterceptor.
Listing 15. Contents of web-jamon-config.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" \ xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=\ "http://www.springframework.org/schema/beans \ http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context \ http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/mvc \ http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"> <bean id="jamonPerformanceMonitorInterceptor" class=\ "org.springframework.aop.interceptor.JamonPerformanceMonitorInterceptor"> <property name="trackAllInvocations" value="true"></property> <property name="useDynamicLogger" value="true"></property> </bean> <bean id="autoProxyCreator" class=\ "org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="interceptorNames"> <list> <idref bean="jamonPerformanceMonitorInterceptor" /> </list> </property> <property name="beanNames"> <list> <value></value> </list> </property> </bean> </beans> |
In the web-jamon-config.xml file shown in Listing 15, we have configured everything required for JAMon in a Spring-based application. The only thing I have not specified are the names of the beans to monitor. The code generator cannot predict this, so it is left to the add-on developer to specify.
Now we need to write the code that will copy this web-jamon-config.xml
file to WEB_INF/spring folder, where webmvc-config.xml resides, and
code for the add import statement in
webmvc-config.xml to import web-jamon-config.xml. (See Listing 16.)
Listing 16. Code to manipulate pom.xml
public void installJamon() {
// update pom.xml code
PathResolver pathResolver = |
projectOperations.getProjectMetadata().getPathResolver(); String resolvedSpringConfigPath = pathResolver.getIdentifier(Path.SRC_MAIN_WEBAPP, |
"/WEB-INF/spring");
if (fileManager.exists(resolvedSpringConfigPath + "/web-jamon-config.xml")) {
return;
}
copyTemplate("web-jamon-config.xml", resolvedSpringConfigPath);
String webMvcConfigPath = resolvedSpringConfigPath + "/webmvc-config.xml";
new XmlTemplate(fileManager).update(webMvcConfigPath, new DomElementCallback() {
public boolean doWithElement(Document document, Element root) {
if (null ==
XmlUtils.findFirstElement\
("/beans/import[@resource='web-jamon-config.xml']",
root)) {
Element element = document.createElement("import");
element.setAttribute("resource", "web-jamon-config.xml");
root.appendChild(element);
return true;
}
return false;
}
});
}
private void copyTemplate(String templateFileName, String resolvedTargetDirectoryPath) {
try {
FileCopyUtils.copy(TemplateUtils.getTemplate(getClass(),
templateFileName), fileManager.createFile(
resolvedTargetDirectoryPath + "/" +
templateFileName).getOutputStream());
} catch (IOException e) {
throw new IllegalStateException(
"Encountered an error during copying of resources for Jamon addon.", e);
}
}
|
The XmlTemplate class used above comes from
the Spring web flow add-on. So you need to add a dependency for the
web flow add-on in your add-on. (See Listing 17.)
Listing 17. Add dependency for web flow add-on
<dependency>
<groupId>org.springframework.roo</groupId>
<artifactId>org.springframework.roo.addon.web.flow</artifactId>
<version>${roo.version}</version>
<type>bundle</type>
</dependency> |
In Listing 17, roo.version is the version of
Roo you are using.
The last thing we need to do in this add-on is to configure project logging
and set up the log level to trace. This is done using
LoggingOperations, which is an operations
class for the log4j add-on. To use this
class, we need to first add the logging add-on dependency in pom.xml
file, as shown in Listing 18.
Listing 18. XML code to add dependency for the logging add-on
<dependency>
<groupId>org.springframework.roo</groupId>
<artifactId>org.springframework.roo.addon.logging</artifactId>
<version>${roo.version}</version>
<type>bundle</type>
</dependency> |
After adding the dependency in pom.xml, add
LoggingOperation into the
JamonOperationsImpl class with the
following line:
@Reference private LoggingOperations loggingOperations; |
Next, add the following line to the
installJamon method:
loggingOperations.configureLogging(LogLevel.TRACE, LoggerPackage.PROJECT); |
That's all the code we need to write for this add-on. The full code for
JamonOperationsImpl class is shown in
Listing 19.
Listing 19. Completed code for JamonOperationsImpl
@Component
@Service
public class JamonOperationsImpl implements JamonOperations {
@Reference private FileManager fileManager;
@Reference private ProjectOperations projectOperations;
@Reference private LoggingOperations loggingOperations;
public boolean isInstallJamonAvailable() {
return projectOperations.isProjectAvailable() &&
fileManager.exists(projectOperations.getPathResolver()
.getIdentifier(Path.SRC_MAIN_WEBAPP,"/WEB-INF/web.xml"));
}
public void installJamon() {
Element configuration = XmlUtils.getConfiguration(getClass());
updatePomProperties(configuration);
updateDependencies(configuration);
PathResolver pathResolver =
projectOperations.getProjectMetadata().getPathResolver();
String resolvedSpringConfigPath =
pathResolver.getIdentifier(Path.SRC_MAIN_WEBAPP,
"/WEB-INF/spring");
if (fileManager.exists(resolvedSpringConfigPath
+ "/web-jamon-config.xml")) {
return;
}
copyTemplate("web-jamon-config.xml", resolvedSpringConfigPath);
String webMvcConfigPath = resolvedSpringConfigPath
+ "/webmvc-config.xml";
new XmlTemplate(fileManager).update(webMvcConfigPath,
new DomElementCallback() {
public boolean doWithElement(Document document, Element root) {
if (null == XmlUtils.findFirstElement(
"/beans/import[@resource='web-jamon-config.xml']",
root)) {
Element element = document.createElement("import");
element.setAttribute("resource", "web-jamon-config.xml");
root.appendChild(element);
return true;
}
return false;
}
});
loggingOperations.configureLogging(LogLevel.TRACE,
LoggerPackage.PROJECT);
}
private void copyTemplate(String templateFileName,
String resolvedTargetDirectoryPath) {
try {
FileCopyUtils.copy(
TemplateUtils.getTemplate(getClass(), templateFileName),
fileManager.createFile(resolvedTargetDirectoryPath + "/" +
templateFileName).getOutputStream());
} catch (IOException e) {
throw new IllegalStateException(
"Encountered an error during copying of resources for Jamon addon.", e);
}
}
private void updatePomProperties(Element configuration) {
List<Element> properties = XmlUtils
.findElements("/configuration/jamon/properties/*",configuration);
for (Element property : properties) {
projectOperations.addProperty(new Property(property));
}
}
private void updateDependencies(Element configuration) {
List<Dependency> dependencies = new ArrayList<Dependency>();
List<Element> jamonDependencies = XmlUtils.findElements(
"/configuration/jamon/dependencies/dependency", configuration);
for (Element dependencyElement : jamonDependencies) {
dependencies.add(new Dependency(dependencyElement));
}
projectOperations.addDependencies(dependencies);
}
}
|
You can download the full source code of this add-on from the Google code repository. Now, let's add JAMon support to our application, using the add-on we have just created:
- Exit the roo shell and run the
mvn clean installcommand. During the build process it will ask for GPG passphrase. - After the Roo add-on has been built, open a new command line and create a directory named jamon-client. We will create a simple client for our add-on.
- Go to the directory jamon-client and type the
roocommand to open a Roo shell. - Execute the commands from Listing 20 in the Roo shell. This will
create a simple Spring MVC web application.
Listing 20. Create simple MVC web applicationproject --topLevelPackage com.shekhar.roo.jamon.client --projectName jamon-client persistence setup --provider HIBERNATE --database HYPERSONIC_IN_MEMORY entity --class ~.domain.MyUser field string --fieldName name --notNull controller all --package ~.web
- To install the add-on, type:
osgi start --url <a href="../../../../">file:///</a><location \ to addon target folder > /org.xebia.roo.addon.jamon-0.1.0.BUILD-SNAPSHOT.jar
This should install and activate our JAMon add-on. You can view the status of the add-on using the
osgi pscommand. - Type the
jamon setupcommand and you will see JAMon being configured in your application. If you now run your application usingmvn tomcat:run, you will not see any logs on the console because you have not configured any bean to monitor. Let's configure ourmyUserControllerbean in web-jamon-config.xml with the code from Listing 21.
Listing 21. Configuration for myUserController in web-jamon-config.xml<bean id="autoProxyCreator" class=\ "org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="interceptorNames"> <list> <idref bean="jamonPerformanceMonitorInterceptor" /> </list> </property> <property name="beanNames"> <list> <value>myUserController</value> </list> </property> </bean>
- Now run the application using
mvn tomcat:runand you will see JAMon logs in your the maven console. A sample is shown in Listing 22.
Listing 22. Sample JAMon logTRACE MyUserController - JAMon performance statistics for method [MyUserController.populateMyUsers]: JAMon Label=MyUserController.populateMyUsers, Units=ms.: (LastValue=187.0, Hits=1.0, Avg=187.0, Total=187.0, Min=187.0, Max=187.0, Active=0.0, Avg Active=1.0, Max Active=1.0, First Access=Wed May 18 15:33:41 IST 2011, Last Access=Wed May 18 15:33:41 IST 2011)
Once you have tested that your add-on is working fine in your development system, you can push it to the Google code project we created. To publish the add-on to the outside world, follow the same procedure we used to publish the i18n add-on. Likewise, to register the add-on with RooBot, follow the i18n registration procedure.
We have looked at the Spring Roo add-on architecture and how to write internationalization and simple add-ons. The add-on architecture is important for Roo because it gives Roo the flexibility to quickly add new features. For developers, this add-on architecture is important because it allows them to meet their requirements without having to wait for features to be implemented globally. Later on, if a feature is incorporated into Roo, it is relatively easy to change out the implementation to remove the custom solution.
In Part 4 of this "Introducing Spring Roo" series, I will talk about how you can write advanced and wrapper add-ons.
Learn
- Be sure to read the rest of this Spring Roo series:
- Part 1: Building from source
- Part 2: Developing an application with Spring Roo
- Part 4: Rapid application development in cloud with Spring Roo and Cloud Foundry
- Part 5: Write advanced and wrapper Spring Roo add-ons
- Learn more about Spring Roo functions
by reading the Spring Roo documentation.
- Give and receive help in the Spring Roo Community
Forums.
-
To listen to interesting interviews and discussions for software developers, check out developerWorks podcasts.
-
Stay current with developerWorks' Technical events and webcasts.
-
Follow developerWorks on Twitter.
-
Check out upcoming conferences, trade shows, webcasts, and other Events around the world that are of interest to IBM open source developers.
-
Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products, as well as our most popular articles and tutorials.
-
Watch and learn about IBM and open source technologies and product functions with the no-cost developerWorks On demand demos.
Get products and technologies
-
Innovate your next open source development project with IBM trial software, available for download or on DVD.
- Download
IBM product evaluation versions
or explore
the online trials in the IBM SOA Sandbox and get your hands on application development tools and middleware products from
DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
Discuss
-
Participate in developerWorks blogs and get involved in the developerWorks community.
-
Get involved in the developerWorks community.
Connect with other developerWorks users while exploring the developer-driven blogs, forums, groups, and wikis.
Shekhar Gulati is a Java consultant working with Xebia India. He has six years of enterprise Java experience. He has extensive experience in Spring portfolio projects, such as Spring, Spring-WS, and Spring Roo. His interests are Spring, NoSQL databases, Hadoop, RAD frameworks like Spring Roo, cloud computing (mainly PaaS services like Google App Engine, CloudFoundry, OpenShift), Hadoop. He is an active writer and writes for JavaLobby, Developer.com, IBM developerWorks and his own blog at http://whyjava.wordpress.com/. You can follow him on twitter @ http://twitter.com/#!/shekhargulati.




