Introducing Spring Roo, Part 5: Write advanced and wrapper Spring Roo add-ons

Increase the power of Spring Roo

Spring Roo Advanced add-ons provide the mechanisms to add Java™ code in applications (for example, building an add-on that can write equals and hashcode methods for your domain object). With the addon create command, you can create a template of an advanced add-on. You then can extend the template to meet developer requirements. This article walks through the steps to create an advanced add-on.

Shekhar Gulati, Senior Consultant, Xebia

Photo of Shekar GulatiShekhar 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

Also available in Chinese Russian Japanese Vietnamese

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.

  1. 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.
  2. 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.
  3. 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. To play with the latest Spring Roo snapshot, either build the Spring Roo code or download the nightly snapshot from Spring Roo repository.


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:

  1. Call java.util.Collections.sort and java.util.Collections.binarySearch
  2. Call java.util.Arrays.sort and java.util.Arrays.binarySearch
  3. Use objects as keys in a java.util.TreeMap
  4. 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 implements CommandMarker interface and exposes two kinds of method—one with CliAvailablityIndicator annotation and other with CliCommand annotation. The CliAvailablityIndicator 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 : The ComparetoCommands class delegates all of the work to ComparetoOperationsImpl class. The four methods generated in this class are:
    • isCommandAvailable(): This method is called by method annotated with CliAvailabilityIndicator ComparetoCommands annotation in ComparetoCommands 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 by setup() method in ComparetoCommands 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 the annotateAll() 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 append RooJCompareto annotation to it.
    • annotateAll(): This method finds all the types which are annotated with RooJavaBean annotation and call annotateType() method on all those types. Use this when all entities should have RooCompareto 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 with RooCompareto annotation.
  • ComparetoMetadata.java: This class is responsible for generating the ITD corresponding to the add-on. In the generated code, it uses a class called ItdTypeDetailsBuilder 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 a compareTo 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 because commons-lang provides a builder class called CompareToBuilder which will be used to build the compareTo 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.

  1. First, add the Maven commons-lang dependency in the target project. Update the configuration.xml file to have the commons-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>
  2. Next, modify the implementation of the setup() method in ComparetoOperationsImpl class to read the commons-lang Maven dependency instead of Spring batch Maven dependency, as in Listing 7. The method annotateType and annotateAll() 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:

  1. If o is not instanceOf Book then return -1

    Before you add the instanceOf check, create the compareTo 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 the compareTo method metadata using the MethodMetadataBuilder class. MethodMetadataBuilder is a Spring Roo-provided Builder class for building method metadata. To build the method metadata, first construct a MethodMetadataBuilder object, passing arguments such as access modifier, method name, return type, parameter list, or a method body builder to build metadata for the compareTo 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(); 
        }
  2. If o is instanceOfBook then type cast o to Book

    The 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 + ";");
  3. 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. The ComparetoMetadata class does not contain any information about the type so it cannot get fields of the class. This information can be provided by the ComparetoMetadataProvider 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 the compareTo 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.

  1. Exit the roo shell and run the mvn clean install command. During the build process it will ask you for the GPG passphrase.
  2. After the Roo add-on is built, open a new command line and create a directory named bookshop.
  3. Go into the bookshop directory and type the roo command to open a Roo shell.
  4. 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
  5. 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.

  6. 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
  7. 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]
  8. Once you run the compareto setup command the next logical step is to add the compareTo 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 the compareTo method to all the entity classes in the sample bookshop application (See Downloads.) 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(); 
        } 
        
    }
  9. 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 the compareTo 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 class setup() 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:

  10. Build the add-on again by running mvn clean install.
  11. Regenerate the client as you generated it in step 4.
  12. To remove the old add-on, type this command on Roo shell:
    addon remove --bundleSymbolicName
    org.xebia.roo.addon.compareto
  13. Install the add-on again by running the osgi install command.
  14. Once the add-on is installed, run the compareto setup and compareto 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:

  1. 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
  2. Create a new directory called oracle-wrapper-addon and from the command line go to the directory.
  3. 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

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


Downloads

DescriptionNameSize
Sample codebookshop.zip14KB
Sample codespring-dw-roo-compareto-addon.zip18KB

Resources

Learn

Get products and technologies

  • Visit the Spring Roo repository to download the nightly code snapshot.
  • 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.
  • Access IBM trial software (available for download or on DVD) and innovate in your next open source development project using software especially for developers.

Discuss

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, Java technology
ArticleID=809239
ArticleTitle=Introducing Spring Roo, Part 5: Write advanced and wrapper Spring Roo add-ons
publish-date=04102012