Contents
- Introduction
- Advanced add-on introduction
- Advanced add-on in action
- Include compareTo() method in My Entity Class
- Project Setup
- Creating an advanced add-on
- Modifying add-on to meet the requirements
- Make non-OSGi JDBC drivers OSGi compliant with wrapper add-on
- Conclusion
- Downloadable resources
- Related topics
- Comments
Introducing Spring Roo, Part 5
Write advanced and wrapper Spring Roo add-ons
Increase the power of Spring Roo
Content series:
This content is part # of # in the series: Introducing Spring Roo, Part 5
This content is part of the series:Introducing Spring Roo, Part 5
Stay tuned for additional content in this series.
Part 3 of this "Introducing Spring Roo" series
discusses Spring Roo add-on architecture and how to create
internationalization and simple add-ons using the
addon create
command. This article focuses on
the remaining two types of add-ons supported by Spring Roo, that is, advanced
add-on and wrapper add-on. It is recommended that you first read Part 3 before continuing.
Advanced add-on introduction
Advanced add-on allows Spring Roo to do everything a simple add-on can do, like updating a Maven POM file with dependencies or plugins, update or add configuration files plus enhance existing Java types, and introduce new Java types using AspectJ ITDs. The ability to add source code makes advanced add-ons very powerful compared to all other add-ons. Before you create a Spring Roo advanced add-on, look at an existing advanced add-on provided by Spring Roo.
Advanced add-on in action
One advanced add-on is JPA, which performs persistence-related work, that is, adding support for databases and creating new entities. To see this in action, open the Roo shell and execute the commands in Listing 1. In this article, I use Spring Roo version 1.2.0.M1.
Listing 1. JPA example
project --topLevelPackage com.dw.demo --projectName entity-demo jpa setup --database FIREBIRD --provider HIBERNATE entity --class ~.domain.Book
Both the jpa setup
command and
entity
command correspond to an advanced add-on
called org.springframework.roo.addon.jpa
. The
output of the jpa setup
command and entity
command on the Roo shell allows clear distinguishment between simple and
advanced add-ons. Listing 2 shows the output of the JPA setup command.
Listing 2. Output of JPA setup
Created SRC_MAIN_RESOURCES/META-INF/spring/database.properties Updated ROOT/pom.xml [added dependencies ...] Updated SRC_MAIN_RESOURCES/META-INF/spring/applicationContext.xml Created SRC_MAIN_RESOURCES/META-INF/persistence.xml
The output of jpa setup
command shows it is
performing configuration functions, such as adding dependencies in
pom.xml, updating Spring applicationContext.xml, and creating
persistence-specific persistence.xml. It is assumable that the JPA setup
command corresponds to a simple add-on because it is not creating or
updating Java source code. Use a simple add-on for scenarios similar to the
setup shown above.
Listing 3 shows the output of the entity
command.
Listing 3. Output of entity command
Created SRC_MAIN_JAVA/com/dw/demo/domain Created SRC_MAIN_JAVA/com/dw/demo/domain/Book.java Created SRC_MAIN_JAVA/com/dw/demo/domain/Book_Roo_Configurable.aj Created SRC_MAIN_JAVA/com/dw/demo/domain/Book_Roo_Jpa_Entity.aj Created SRC_MAIN_JAVA/com/dw/demo/domain/Book_Roo_Entity.aj Created SRC_MAIN_JAVA/com/dw/demo/domain/Book_Roo_ToString.aj
The output shows creation of one Java file called Book.java, and four *.aj files. The golden rule to identify an advanced add-on is the generation of Java files, or *.aj files, or both, as in the case of entity command. These *Roo_*.aj are called Inter-type Declarations (or ITDs). ITDs allows one type (an aspect) to make declarations for another type, that is, you can modify the static structure of any type by adding methods, fields, or changing their type hierarchy. Roo uses an ITD as a code-generation artifact and manages them throughout their lifetime. ITDs allow Roo to generate code in a separate compilation unit, but they are combined into the same compiled class.
After viewing the output of entity command, consider how these artifacts (.java and .aj files) are generated by Spring Roo. See Listing 4 for a sample of a Book.java file.
Listing 4. A Book.java file
package com.dw.demo.domain; import org.springframework.roo.addon.entity.RooEntity; import org.springframework.roo.addon.javabean.RooJavaBean; import org.springframework.roo.addon.tostring.RooToString; @RooJavaBean @RooToString @RooEntity public class Book { }
The Java file looks normal, except for annotations on the class.
Looking at the names of the annotation and .aj files, it is apparent some
of these annotations correspond to functionality added by .aj files. For
example, RooToString
corresponds to
Book_Roo_ToString.aj file and add toString()
method. RooEntity
corresponds to
Book_Roo_Entity .aj, Book_Roo_Jpa_Entity
, and
persistence-related methods. You can leave the rest for now. To
understand how annotation leads to generation of ITDs, explore
how Spring Roo provides advance add-on functionalities.
- On Roo shell startup, it will scan all the classes and register all
those classes which implement
CommandMarker
interface.CommandMarker
interface tells Roo that these classes will define commands which this add-on can perform. - All the advanced add-ons register their services to the OSGi runtime
provided by Spring Roo. These services specify conditions on which it
will trigger code generation. For all advanced add-ons, the triggering
point is the presence of an annotation. For example, one
advanced add-on for
toString()
method generation is only triggered if a Java type has RooToString annotation. This is the case for other annotations. - Upon using
entity --class ~.domain.Book
, the add-on will create a Java file called Book.java with annotations. Other add-ons trigger when a Java type has these annotations, and .aj files are written for them.
More clarification will be evident when you create your own advanced add-on.
The org.springframework.roo.addon.jpa
add-on is
just one example of advanced add-ons provided by Spring Roo. Other
advanced add-ons include GWT, controller, JSON, and many more.
The Spring Roo 1.2.0 release contains two more advanced
add-ons—addon-equals and addon-jsf. The addon-equals provides implementation
for equals and hashcode method for an entity and addon-jsf provides
JSF support in Spring Roo applications.
Include compareTo() method in My Entity Class
The value objects or entities are commonly required to implement the
java.lang.Comparable interface and provide
implementation for the compareTo()
method.
Comparable interface imposes total ordering on the objects of each class
implementing it. When you implement Comparable, you can:
- Call
java.util.Collections.sort
andjava.util.Collections.binarySearch
- Call
java.util.Arrays.sort
andjava.util.Arrays.binarySearch
- Use objects as keys in a
java.util.TreeMap
- Use objects as elements in a
java.util.TreeSet
In this article, you will build an advanced add-on which will provide an
implementation of compareTo()
for entities
created in your application. Because you want to add Java code in your
application, you have to create an advanced add-on.
Project Setup
The Spring Roo documentation eloquently explains how to set up a project and Maven repository on Google code, so it is unnecessary to repeat it here. Note that I will use "spring-dw-roo-compareto-addon" as the project name.
If you are not using the latest version of Spring Roo, 1.2.0.RC1, download it from the project website. Unzip and install it as explained in Part 1.
Spring Roo deprecated or removed some of the classes used in earlier versions.
Creating an advanced add-on
After you set up the projectp, you will see a directory named spring-dw-roo-compareto-addon with just a .svn folder. From the command line, go into spring-dw-roo-compareto-addon directory and start the Roo shell. Then type the following command:
addon create advanced --topLevelPackage org.xebia.roo.addon.compareto
--projectName spring-dw-roo-compareto-addon
That's it! You just created an advanced add-on.
Next, on the Roo shell, fire perform package
command
to create an add-on jar.
Listing 5 shows the files generated by the
addon create advanced
command.
Listing 5. Files generated by addon create advanced command
Created ROOT/pom.xml Created SRC_MAIN_JAVA Created SRC_MAIN_RESOURCES Created SRC_TEST_JAVA Created SRC_TEST_RESOURCES Created SPRING_CONFIG_ROOT Created ROOT/readme.txt Created ROOT/legal Created ROOT/legal/LICENSE.TXT Created SRC_MAIN_JAVA/org/xebia/roo/addon/compareto Created SRC_MAIN_JAVA/org/xebia/roo/addon/compareto/ComparetoCommands.java Created SRC_MAIN_JAVA/org/xebia/roo/addon/compareto/ComparetoOperations.java Created SRC_MAIN_JAVA/org/xebia/roo/addon/compareto/ComparetoOperationsImpl.java Created SRC_MAIN_JAVA/org/xebia/roo/addon/compareto/ComparetoMetadata.java Created SRC_MAIN_JAVA/org/xebia/roo/addon/compareto/ComparetoMetadataProvider.java Created SRC_MAIN_JAVA/org/xebia/roo/addon/compareto/RooCompareto.java Created ROOT/src/main/assembly Created ROOT/src/main/assembly/assembly.xml Created SRC_MAIN_RESOURCES/org/xebia/roo/addon/compareto Created SRC_MAIN_RESOURCES/org/xebia/roo/addon/compareto/configuration.xml
Some of the files generated like pom.xml, readme.txt, and license.txt, do not need any introduction as they were discussed in Part 3 and are self-explanatory. The more interesting artifacts are:
- ComparetoCommands.java
- ComparetoOperations.java
- ComparetoOperationsImpl.java
- ComparetoMetadata.java
- ComparetoMetadataProvider.java
- RooCompareto.java
Now look at the generated artifacts one by one.
ComparetoCommands.java
: This class implementsCommandMarker
interface and exposes two kinds of method—one withCliAvailablityIndicator
annotation and other withCliCommand
annotation. TheCliAvailablityIndicator
annotation tells Spring Roo when the command should be visible. For example the 'entity
' command will not be made available before the user defines persistence settings in the Roo shell or directly in the project. The methods annotated with@CliCommand
register 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 the help command is typed. For a detailed explanation of the*Commands
class, refer to Part 3.ComparetoOperationsImpl.java
: TheComparetoCommands
class delegates all of the work toComparetoOperationsImpl
class. The four methods generated in this class are:isCommandAvailable()
: This method is called by method annotated withCliAvailabilityIndicator ComparetoCommands
annotation inComparetoCommands
class to check if the command should be visible or not. This is to ensure commands are aware of context. This method performs various validations. For example, if the project has been created, then only the command is visible, or if persistence has been setup, then only command should be visible. It is not mandatory to have a condition for command to be visible. Simply return true to make sure command is always visible.setup()
: This method is called bysetup()
method inComparetoCommands
class which is annotated with@CliCommand
. The code makes it clear this class is responsible for performing setup related tasks such as adding Maven dependencies, adding Maven repositories, or creating or updating Spring context files (as done with the Jamon Roo add-on in Part 3).annotateType()
: This method and theannotateAll()
are new methods which do not exist in simple add-ons. The functionality of this method is to add an annotation (RooCompareto
) on the specified Java type. This method uses some Spring Roo-provided services to fetch the Class details for the given Java type and appendRooJCompareto
annotation to it.annotateAll()
: This method finds all the types which are annotated withRooJavaBean
annotation and callannotateType()
method on all those types. Use this when all entities should haveRooCompareto
annotation.
RooCompareto.java
: The presence of this annotation triggers the add-on to generate the code.ComparetoMetadataProvider.java
: This class is a Spring Roo service and is called by Roo to retrieve the metadata for the add-on. This class registers triggers for adding and removing metadata. No changes are required in this class, but remember that this class has one method called getMetadata() which will be called by Spring Roo whenever there is any Java type withRooCompareto
annotation.ComparetoMetadata.java
: This class is responsible for generating the ITD corresponding to the add-on. In the generated code, it uses a class calledItdTypeDetailsBuilder
to create an ITD with a field and method. Later in this article, you will change the default generated code to meet the requirements of adding acompareTo
method and implementing Comparable interface.
Modifying add-on to meet the requirements
You want to create an add-on which should add the
compareTo
method to entity classes. You will:
- Add Maven dependency of
commons-lang
version 3.1 in the target project. This is required becausecommons-lang
provides a builder class calledCompareToBuilder
which will be used to build thecompareTo
method. - Make the entity class implement the
Comparable
interface. - Create an ITD for the
compareTo
method.
Adding the Maven dependency
To meet these requirements, changes are required in the
ComparetoOperationsImpl
and
ComparetoMetadata
classes. Make these
changes one by one.
- First, add the Maven
commons-lang
dependency in the target project. Update the configuration.xml file to have thecommons-lang
dependency instead of the default provided Spring batch dependency, as in Listing 6.Listing 6. Update configuration.xml file
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <configuration> <dependencies> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.1</version> </dependency> </dependencies> </configuration>
- Next, modify the implementation of the
setup()
method inComparetoOperationsImpl
class to read thecommons-lang
Maven dependency instead of Spring batch Maven dependency, as in Listing 7. The methodannotateType
andannotateAll()
are not shown here because they don't have any changes.Listing 7. Modify implementation of setup() method
@Component @Service public class ComparetoOperationsImpl implements ComparetoOperations { @Reference private ProjectOperations projectOperations; @Reference private TypeLocationService typeLocationService; @Reference private TypeManagementService typeManagementService; /** {@inheritDoc} */ public void setup() { // Install the add-on Google code repository needed to get the annotation projectOperations.addRepository("", new Repository("Compareto Roo add-on repository", "Compareto Roo add-on repository", "https://spring-dw-roo-compareto-addon.googlecode.com/svn/repo")); List<Dependency> dependencies = new ArrayList<Dependency>(); // Install the dependency on the add-on jar ( dependencies.add(new Dependency("org.xebia.roo.addon.compareto", "org.xebia.roo.addon.compareto", "0.1.0.BUILD-SNAPSHOT", DependencyType.JAR, DependencyScope.PROVIDED)); Element configuration = XmlUtils.getConfiguration(getClass()); for (Element dependencyElement : XmlUtils.findElements( "/configuration/dependencies/dependency", configuration)) { dependencies.add(new Dependency(dependencyElement)); } projectOperations.addDependencies("", dependencies); } }
The changes made so far are similar to the changes used to create a Jamon simple add-on in Part 3.
Make the entity class implement the Comparable interface
After making changes in the code to add Maven dependency, you need to
make sure that your entity class implements java.lang.Comparable interface.
To do that, modify the ITD generated by the
ComparetoMetadata
class. The metadata classes
generate ITD using the ItdTypeDetailsBuilder
class which provides various add methods for adding methods, fields,
annotations, interface, and more to an ITD. To make a Java type implements an interface, use the
addImplementsType
method in the ItdTypeDetailsBuilder
class,
as in Listing 8. I only show the
ComparetoMetadata
constructor because
construction of the ITD happens in the constructor.
Listing 8. Implementing java.lang.Comparable interface
public ComparetoMetadata(String identifier, JavaType aspect Name, PhysicalTypeMetadata governorPhysicalTypeMetadata) { super(identifier, aspect Name, governorPhysicalTypeMetadata); Assert.isTrue(isValid(identifier), "Metadata identification string '" + identifier + "' does not appear to be a valid"); JavaType comparableInterface = new JavaType("java.lang.Comparable"); builder.addImplementsType(comparableInterface); itdTypeDetails = builder.build(); }
Create an ITD for compareTo method
After making a Java type implements Comparable interface, you have to
provide implementation of compareTo method. CompareToBuilder class
provides a fluent interface for creating compareTo method. Spring Roo
equals add-on uses EqualsBuilder and HashcodeBuilder to provide
implementation for equals and hashcode method. Let's take an example
to make sure it is clear how CompareToBuilder
assists in creating the compareTo
method.
Suppose you have a entity called Book and you want to provide compareTo
implementation for it using CompareToBuilder
.
Listing 9 shows the Book class
and compareTo
method.
Listing 9. Book class and compareTo method
import org.apache.commons.lang3.builder.CompareToBuilder; public class Book implements Comparable { private String title; private String author; private double price; public Book(String title, String author, double price) { this.title = title; this.author = author; this.price = price; } // getters and setters public int compareTo(Book o) { if(!(o instanceof Book)){ return -1; } Book book = (Book)o return new CompareToBuilder().append(this.title, book.title).append(this.author, book.author).append(this.price, book.price).toComparison(); } @Override public String toString() { return "Book [title=" + title + ", author=" + author + ", price=" + price + "]"; } }
The compareTo
method in Listing 9 does the
following:
- If o is not
instanceOfBook
then return -1 - If o is
instanceOfBook
then type cast o to Book - Create an object of
CompareToBuilder
class and then call append method on fields
Build the compareTo
method
incrementally with these steps:
- If o is not instanceOf Book then return -1
Before you add the
instanceOf
check, create thecompareTo
method. See Listing 10.Listing 10. Create compareTo method
public ComparetoMetadata(String identifier, JavaType aspect Name, PhysicalTypeMetadata governorPhysicalTypeMetadata) { super(identifier, aspect Name, governorPhysicalTypeMetadata); Assert.isTrue(isValid(identifier), "Metadata identification string '" + identifier + "' does not appear to be a valid"); JavaType comparableInterface = new JavaType("java.lang.Comparable"); builder.addImplementsType(comparableInterface); builder.addMethod(getCompareToMethod()); itdTypeDetails = builder.build(); } private MethodMetadata getCompareToMethod() { final JavaType parameterType = JavaType.OBJECT; final List<JavaSymbolName> parameterNames = Arrays.asList(new JavaSymbolName("obj")); final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder(); bodyBuilder.appendFormalLine("return -1;"); final MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(getId(), Modifier.PUBLIC, new JavaSymbolName("compareTo"), JavaType.INT_PRIMITIVE, AnnotatedJavaType.convertFromJavaTypes(parameterType), parameterNames, bodyBuilder); return methodBuilder.build(); }
The
getCompareToMethod()
generates thecompareTo
method metadata using theMethodMetadataBuilder
class.MethodMetadataBuilder
is a Spring Roo-providedBuilder
class for building method metadata. To build the method metadata, first construct aMethodMetadataBuilder
object, passing arguments such as access modifier, method name, return type, parameter list, or a method body builder to build metadata for thecompareTo
method as in Listing 11.Listing 11. instanceOf check
private MethodMetadata getCompareToMethod() { final JavaType parameterType = JavaType.OBJECT; String parameterName = "obj"; final List<JavaSymbolName> parameterNames = Arrays.asList(new JavaSymbolName(parameterName)); final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder(); final String typeName = destination.getSimpleTypeName(); bodyBuilder.appendFormalLine("if (!(" + parameterName + " instanceof " + typeName + ")) {"); bodyBuilder.indent(); bodyBuilder.appendFormalLine("return -1;"); bodyBuilder.indentRemove(); bodyBuilder.appendFormalLine("}"); bodyBuilder.appendFormalLine("return -1;"); final MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(getId(), Modifier.PUBLIC, new JavaSymbolName("compareTo"), JavaType.INT_PRIMITIVE, AnnotatedJavaType.convertFromJavaTypes(parameterType), parameterNames, bodyBuilder); return methodBuilder.build(); }
- If o is
instanceOfBook
then type cast o to BookThe next step is a cast so you can build the
compareTo
method. To do this, append this line after the instanceOf check:bodyBuilder.appendFormalLine(typeName + " rhs = (" + typeName + ") " + OBJECT_NAME + ";");
- Create object of CompareToBuilder class and then call append method on
fields
To build
compareTo
method, you need access to all the fields of a class. TheComparetoMetadata
class does not contain any information about the type so it cannot get fields of the class. This information can be provided by theComparetoMetadataProvider
as in Listing 12.Listing 12. ComparetoMetadataProvider
protected ItdTypeDetailsProvidingMetadataItem getMetadata(String metadataId, JavaType aspect Name, PhysicalTypeMetadata governorPhysicalTypeMetadata, String itdFilename) { final String[] excludeFields = {}; final MemberDetails memberDetails = getMemberDetails(governorPhysicalTypeMetadata); if (memberDetails == null) { return null; } final JavaType javaType = governorPhysicalTypeMetadata.getMemberHoldingTypeDetails().getName(); final List<FieldMetadata> compareToFields = locateFields(javaType, excludeFields, memberDetails, metadataId); return new ComparetoMetadata(metadataId, aspect Name, governorPhysicalTypeMetadata, compareToFields); } private List<FieldMetadata> locateFields(final JavaType javaType, final String[] excludeFields, final MemberDetails memberDetails, final String metadataIdentificationString) { final SortedSet<FieldMetadata> locatedFields = new TreeSet<FieldMetadata>(new Comparator<FieldMetadata>() { public int compare(final FieldMetadata l, final FieldMetadata r) { return l.getFieldName().compareTo(r.getFieldName()); } }); final List<?> excludeFieldsList = CollectionUtils.arrayToList(excludeFields); final FieldMetadata versionField = persistenceMemberLocator.getVersionField(javaType); for (final FieldMetadata field : memberDetails.getFields()) { if (excludeFieldsList.contains(field.getFieldName().getSymbolName())) { continue; } if (Modifier.isStatic(field.getModifier()) || Modifier.isTransient(field.getModifier()) || field.getFieldType().isCommonCollectionType() || field.getFieldType().isArray()) { continue; } if (versionField != null && field.getFieldName().equals(versionField.getFieldName())) { continue; } locatedFields.add(field); metadataDependencyRegistry.registerDependency( field.getDeclaredByMetadataId(), metadataIdentificationString ); } return new ArrayList<FieldMetadata>(locatedFields); }
After you have the fields, pass them to the
ComparetoMetadata
so that it can build thecompareTo
method, as in Listing 13.Listing 13. Passing metadata to build compareTo method
private List<FieldMetadata> compareToFields; public ComparetoMetadata(String identifier, JavaType aspectName, PhysicalTypeMetadata governorPhysicalTypeMetadata, List<FieldMetadata> compareToFields) { super(identifier, aspectName, governorPhysicalTypeMetadata); Assert.isTrue(isValid(identifier), "Metadata identification string '" + identifier + "' does not appear to be a valid"); this.compareToFields = compareToFields; if (!CollectionUtils.isEmpty(compareToFields)) { JavaType comparableInterface = new JavaType("java.lang.Comparable"); builder.addImplementsType(comparableInterface); builder.addMethod(getCompareToMethod()); } itdTypeDetails = builder.build(); } private MethodMetadata getCompareToMethod() { final JavaType parameterType = JavaType.OBJECT; String parameterName = "obj"; final List<JavaSymbolName> parameterNames = Arrays.asList(new JavaSymbolName(parameterName)); final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder(); final ImportRegistrationResolver imports = builder.getImportRegistrationResolver(); imports.addImport( newJavaType("org.apache.commons.lang3.builder.CompareToBuilder") ); final String typeName = destination.getSimpleTypeName(); bodyBuilder.appendFormalLine("if (!(" + parameterName + " instanceof " + typeName + ")) {"); bodyBuilder.indent(); bodyBuilder.appendFormalLine("return -1;"); bodyBuilder.indentRemove(); bodyBuilder.appendFormalLine("}"); bodyBuilder.appendFormalLine(typeName + " rhs = (" + typeName + ") " + parameterName + ";"); final StringBuilder builder = new StringBuilder(); builder.append("return new CompareToBuilder()"); for (final FieldMetadata field : compareToFields) { builder.append(".append(" + field.getFieldName() + ", rhs." + field.getFieldName() + ")"); } builder.append(".toComparison();"); bodyBuilder.appendFormalLine(builder.toString()); final MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(getId(), Modifier.PUBLIC, new JavaSymbolName("compareTo"), JavaType.INT_PRIMITIVE, AnnotatedJavaType.convertFromJavaTypes(parameterType), parameterNames, bodyBuilder); return methodBuilder.build(); }
Testing
This completes the implementation of the
compareTo
add-on. You can download the full
source code of this add-on from the
Google code repository.
Now, you can test the compareTo
that you just created.
- Exit the roo shell and run the
mvn clean install
command. During the build process it will ask you for the GPG passphrase. - After the Roo add-on is built, open a new command line and create a directory named bookshop.
- Go into the bookshop directory and type the
roo
command to open a Roo shell. - Execute the commands from Listing 14 in the Roo shell.
Listing 14. Creating the add-on
project --topLevelPackage com.xebia.roo.bookshop --projectName bookshop jpa setup --database HYPERSONIC_IN_MEMORY --provider HIBERNATE entity jpa --class ~.domain.Book field string --fieldName title --notNull field string --fieldName author --notNull field number --fieldName price --type double --notNull
- To install and start the add-on, type the following on the Roo
shell:
osgi start --url file://<location to compareTo addon jar>
This should install and activate your compareTo add-on. You can view the status of the add-on using the OSGi ps command.
- Type
compareto
and press tab to see the three compareto addon commands in Listing 15.Listing 15. View compareto addon commands
roo> compareto compareto add compareto all compareto setup
- The steps set out in Listing 15 will confirm that the compareto add-on
is properly installed. The next step is to run setup command which
will configure the required dependencies. See Listing 16.
Listing 16. Run setup command
roo> compareto setup Updated ROOT/pom.xml [added repository https://spring-dw-roo-compareto-addon.googlecode.com/svn/repo; added dependencies org.xebia.roo.addon.compareto:org.xebia.roo.addon.compareto: 0.1.0.BUILD, org.apache.commons:commons-lang3:3.1; removed dependency org.apache.commons:commons-lang3:3.0.1]
- Once you run the
compareto setup
command the next logical step is to add thecompareTo
method to entity classes. This can be done either by compareto add or compareto all depending on whether you want to generate compareTo method for only one entity class or all the entity classes. Let's add thecompareTo
method to all the entity classes in the sample bookshop application (See Downloadable resources.) See Listing 17.Listing 17. Add compareTo method to all the entity classes
roo> compareto all Updated SRC_MAIN_JAVA/com/xebia/roo/bookshop/domain/Book.java Created SRC_MAIN_JAVA/com/xebia/roo/bookshop/domain/Book_Roo_Compareto.aj
As you can see above in the output of compareto all command, an ITD named Book_Roo_Compareto.aj is generated. This file will contain the compareTo method. Listing 18 shows the Book_Roo_Compareto.aj.
Listing 18. Book_Roo_Compareto.aj
import org.apache.commons.lang.builder.CompareToBuilder; privileged aspect Book_Roo_Compareto { declare parents: Book implements java.lang.Comparable; public int Book.compareTo(java.lang.Object obj) { if (!(obj instanceof Book)) { return -1; } Book rhs = (Book) obj; return new CompareToBuilder().append(author, rhs.author).append(id, rhs.id).append(price, rhs.price).append(title, rhs.title).toComparison(); } }
- Run
perform package
command on the Roo shell to check if everything compiled correctly after adding the add-on. To your surprise, the build will fail because Maven is not able to resolve Spring Roo bundle dependencies. These bundle dependencies come from thecompareTo
add-on. You need the dependency on the add-on because your entities have to be annotated with Compareto annotation. This is the only thing you need from the add-on. The best way I found is to create another Maven module and have all add-on dependencies there. This is on similar lines to what Spring Roo does. Spring Roo does not dependency each and every add-on used. It has a common Spring Roo annotations jar which contains all the dependencies. I created a project xebia-spring-roo-addon-annotation and put the Compareto annotation in this module. Next, I updated the configuration.xml to add this jar to the client project instead of add-on jar. Listing 19 shows configuration.xml.Listing 19. configuration.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <configuration> <dependencies> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.1</version> </dependency> <dependency> <groupId>org.xebia.roo.addon</groupId> <artifactId>xebia-spring-roo-addon-annotations</artifactId> <version>0.0.1</version> </dependency> </dependencies> <repositories> <repository> <id>spring-roo-addon-annoations</id> <name>Spring Roo Addon Annotation</name> <url>https://xebia-spring-roo-addon-annotations.googlecode.com/svn/repo</url> </repository> </repositories> </configuration>
Update the
ComparetoOperationsImpl
classsetup()
method to read the new dependencies and repositories specified in updated configuration.xml file. See Listing 20.Listing 20. Update the ComparetoOperationsImpl class setup() method
public void setup() { bu List<Dependency> dependencies = new ArrayList<Dependency>(); Element configuration = XmlUtils.getConfiguration(getClass()); for (Element dependencyElement : XmlUtils.findElements("/configuration/dependencies/dependency", configuration)) { dependencies.add(new Dependency(dependencyElement)); } projectOperations.addDependencies("", dependencies); List<Element> repositories = XmlUtils.findElements( "/configuration/repositories/repository", configuration); for (Element repositoryElement : repositories) { Repository repository = new Repository(repositoryElement); projectOperations.addRepository(projectOperations.getFocusedModuleName(), repository); } }
Then follow these steps:
- Build the add-on again by running
mvn clean install
. - Regenerate the client as you generated it in step 4.
- To remove the old add-on, type this command on Roo shell:
addon remove --bundleSymbolicName org.xebia.roo.addon.compareto
- Install the add-on again by running the
osgi install
command. - Once the add-on is installed, run the
compareto setup
andcompareto all
commands.You will see the compareTo ITDs. Run the perform package command and everything will work fine.
Once you test that your add-on is working in your development environment, you can push it to the Google code project you created. To publish the add-on to the outside world, follow the same procedure used to publish the i18n add-on in Part 3. Likewise, to register the add-on with RooBot, follow the Spring Roo documentation.
Make non-OSGi JDBC drivers OSGi compliant with wrapper add-on
A common use case of wrapper add-on is for the conversion of non-OSGi JDBC drivers into OSGi compliant bundles. One place where you need to wrap a JDBC driver is when you have to reverse engineer an Oracle database using Spring Roo. Spring Roo does not provide OSGi Oracle JDBC drivers because of copyright issues. Before reverse-engineering an Oracle database, first make the driver OSGi compliant. To create a wrapper addon for an Oracle JDBC driver:
- Install the Oracle JDBC jar in your local machine Maven repository by
typing this command.
mvn install:install-file -Dfile=ojdbc5.jar -DgroupId=com.oracle -DartifactId=ojdbc5 -Dversion=11.2.0.2 -Dpackaging=jar
- Create a new directory called oracle-wrapper-addon and from the command line go to the directory.
- Open the Roo shell and execute the wrapper add-on command:
addon create wrapper --topLevelPackage com.oracle.wrapper.jdbc --groupId com.oracle --artifactId ojdbc5 --version 11.2.0.2 --vendorName Oracle --licenseUrl oracle.com
This command will generate only pom.xml files that will be used to convert a non-OSGi Oracle JDBC driver to an OSGi driver
- From inside the Roo shell, run this command to create the OSGi bundle:
perform command --mavenCommand bundle:bundle
That's it. You have now successfully created an OSGi bundle of a non-OSGi jar.
Conclusion
In this article, you learned about advanced and wrapper add-on in Spring Roo. you also learned to create advanced and wrapper add-ons. This completes your exploration into a very important feature of Spring Roo: writing add-ons. Anytime you want to extend the functionality of Spring Roo, remember to think about creating add-ons.
In the next part of this "Introducing Spring Roo" series, I will talk about how you to write GWT applications using Spring Roo.
Downloadable resources
- PDF of this content
- Sample code (bookshop.zip | 14KB)
- Sample code (spring-dw-roo-compareto-addon.zip | 18KB)
Related topics
- 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 3: Developing Spring Roo add-ons
- Part 4: Rapid application development in cloud with Spring Roo and Cloud Foundry
- Part 6: Develop Spring MVC and GWT apps using Spring Roo 1.2 and deploy them on Cloud Foundry
- Part 7: Develop Spring MongoDB Applications using Spring Roo
- Learn about the java.lang.Comparable interface.
- Read the Spring Roo documentation about how to set up a project and Maven repository on Google code.
- Follow the Spring Roo documentation to register the add-on with RooBot.
- Download the latest version of Spring Roo, 1.2.0.RC1 from the project website.
- Download the full source code of the compareTo add-on from the Google code repository.