Introducing Spring Roo, Part 3: Developing Spring Roo add-ons

Spring Roo is a RAD tool that lets you build applications (mainly web) quickly and easily. Under the hood, Spring Roo is based on OSGI add-on architecture, which makes it easy to extend Spring Roo by adding add-ons. Spring Roo provides commands to create add-ons that can be very easily made available to the Spring Roo user community. In this article, we first talk about Spring Roo architecture, talking about how Spring Roo leverages its own add-on architecture to provide different features, then we will create add-ons using the Roo shell and modify them to suit our needs.

Shekhar Gulati, Senior Consultant, Xebia

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.



10 April 2012 (First published 16 August 2011)

Also available in Chinese Russian Japanese Vietnamese Portuguese

Getting started

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:

  1. To enable third-party developers to create abilities that extend the capabilities of Spring Roo
  2. To help Spring Roo easily add new features
  3. To help Spring Roo remain a little genie (i.e., reduce the size of Spring Roo)

Spring Roo logical components

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
Diagram shows the relationships between Spring Roo components

Let's talk about some of the core modules:

  • Support— The org.springframework.roo.support module provides common utility classes used by all the core modules and add-ons. Some utility classes include Assert, FileCopyUtils, XmlUtils, StringUtils, FileUtils, etc. For example, if you want to copy the content of one file to the other file, you could use FileCopyUtils to 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 @RooToString annotation.
  • 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.


OSGi runtime environment

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:

  1. Internationalization Add-on— It supports adding language translations for Roo's scaffolded Spring MVC applications (adding translation for the Hindi language, for example).
  2. 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).
  3. 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 equals and hashcode methods for your domain object, for example). There is already a community add-on for these functions.
  4. 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:

httppgp://

Spring Roo V1.1 introduced Pretty Good Privacy (PGP), allowing users to specify which developers were trusted to sign software that would be downloaded and activated in the Roo shell. In fact, every release of Roo itself is now PGP-signed. A new protocol handler called httppgp:// was introduced into Roo to indicate that a URL also has a PGP armour-detached signature available. This provides an open form of security to help protect users from malicious downloads. These standards also allow use of stand-alone PHP tools to independently verify Roo's operations.

  • 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:
    1. OSGi-compliant.
    2. PGP-signed artifacts with public keys.
    3. 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.

Project setup

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

Creating an i18N add-on

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:

  1. Exit the Roo shell and run the mvn clean install command. During the build process, it will ask for your GPG passphrase.
  2. 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.
  3. Enter the i18n-hindi-client directory and type the roo command.
  4. 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 application
    project --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
  5. Type this command and press the tab key: web mvc language --code de en es it nl sv

    You will not see the Hindi language support. This is because we have not yet installed the Hindi add-on.

  6. 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 ps command, which displays the OSGi bundle information and its status as shown below:

    [ 95] [Active ] [ 1] roo-hindi-addon (0.1.0.BUILD-SNAPSHOT)
  7. Again, type web mvc language –code and 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
  8. 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
    Screenshot shows the application running in a browser with several text items shown in Hindi

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.

Under the hood

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:

  1. The roo-hindi-addon was started using osgi start command.
  2. When the add-on starts, the HindiLanguage class that was created by the addon create i18n command gets registered with i18nComponent, which is an OSGi service listener for registering and unregistering i18n add-ons. The HindiLanguage class is marked with @Component and @Service annotations, 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.
  3. When we typed the web mvc install language –code command and pressed the tab key, the tab completion service calls the i18nComponent class getSupportedLanguages() method, which returns the java.util.Set of i18n add-ons. Because HindiLanguage was 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:

  1. You must add jamon JAR in your pom.xml.
  2. You must define a JamonPerformanceMonitorInterceptor bean 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?

  1. Use a simple add-on when you want to add Maven dependencies, add configuration artifacts, or add both to your project.
  2. 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.

Project setup

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

Creating a simple add-on

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:

  1. 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.
  2. web mvc install tags replaces the default MVC tags generated when you scaffold a web application. This command will only be available when you have created a web application.

Looking at the generated code

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.

  1. 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:

    1. CommandMarker— All the command classes should implement the CommandMarker interface and should be annotated with @Component and @Service annotations so they are registered and their commands become available in the Roo shell. The fields annotated with @Reference are dependencies of the JamonCommands class are injected by the underlying Roo OSGi container. All the classes annotated with @Component and @Service annotations can be injected into other add-ons.
    2. Activate and Deactivate Methods— The activate and deactivate methods are the methods called when you install the add-on using the addon install command or remove the add-on using the addon remove command. These methods allows you to hook into the lifecycle of the add-on, which is managed by the underlying Roo OSGi container.
    3. Methods annotated with @CliAvailabilityIndicator— The methods annotated with @CliAvailabilityIndicator are the methods that help hide commands when they are not available. For example, the security setup command, 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.
    4. Methods annotated with @CliCommand— The methods annotated with @CliCommand registers the command with the Roo shell. The @CliCommand annotation has two attributes: value, which defines the command name, and help, which defines the help message shown when you type the help command. Every command can define mandatory and non-mandatory attributes using the @CliOption annotation, presented as a part of the command. For example, the say hello command has a mandatory attribute called name and non-mandatory attribute called country.
  2. JamonOperationsImpl.java— Command classes need to perform some operations when a command is executed. These operations are performed by operation classes. JamonOperationsImpl is an operations class responsible for doing the actual work, like adding a dependency in the pom.xml or copying resources to the desired location. The JamonOperationsImpl class performs its operations by using core services provided by the Spring Roo framework. For example, to add a dependency, it uses the ProjectOperations interface. To copy the files, it uses the FileManager interface. Let's take a look at the code for the JamonOperationsImpl class (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); 
    			} 
    		} 
    	}

    JamonOperationsImpl class has two important methods: isInstallTagsCommandAvailable checks whether the command is available, and installTags installs the tags into the target project. To perform these operations the JamonOperationsImpl class uses some Spring Roo core services and utilities:

    1. The ProjectOperations : JamonOperationsImpl class uses the ProjectOperations service to check whether the project is available and to get path of the tags folder.
    2. The FileManager : JamonOperationsImpl class uses the FileManager service to check whether a file exists at a certain path and to get the update or create a MutableFile. MutableFile is another Roo-specific class which represents a handle to a file which can be modified.
    3. The TemplateUtils : TemplateUtils utility class is used to acquire an InputStream to template files (info.tagx and show.tagx) in our add-on bundle.
    4. The FileCopyUtils : FileCopyUtils utility class is used copy the templates (from TemplateUtils) to the MutableFile (from FileManager).

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:

  1. Add JAMon JAR in your pom.xml
  2. 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:

  1. Exit the roo shell and run the mvn clean install command. During the build process it will ask for GPG passphrase.
  2. 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.
  3. Go to the directory jamon-client and type the roo command to open a Roo shell.
  4. 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 application
    project --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
  5. 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 ps command.

  6. Type the jamon setup command and you will see JAMon being configured in your application. If you now run your application using mvn tomcat:run, you will not see any logs on the console because you have not configured any bean to monitor. Let's configure our myUserController bean 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>
  7. Now run the application using mvn tomcat:run and you will see JAMon logs in your the maven console. A sample is shown in Listing 22.
    Listing 22. Sample JAMon log
    TRACE 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.


Conclusion

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.

Resources

Learn

Get products and technologies

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.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=752040
ArticleTitle=Introducing Spring Roo, Part 3: Developing Spring Roo add-ons
publish-date=04102012