Roo provides an add-on creator add-on which simplifies developing custom add-ons. You can either create a simple or an advanced add-on using the commands exposed by the add-on creator.
A simple add-on is meant to add project dependencies in the pom.xml
file or to add configuration artifacts to the project. For instance, in Chapter 4, Web Application Development with Spring Web MVC we saw that Roo installs JSP custom tags when scaffolding a Spring Web MVC application. Instead of using Roo-installed JSP custom tags, you can create a simple Roo add-on which replaces Roo-installed JSP custom tags with the tags that you have tailored based on your application requirements.
An advanced add-on, on the other hand, is required in scenarios in which you want to create new Java classes, interfaces, and AspectJ ITD files. For instance, a Portlet add-on will scaffold controllers and JSPs from JPA entities.
In this recipe, we'll look at the addon
create
simple
command, which creates a simple Roo add-on that replaces some of the tags installed by Roo for a Spring MVC application with custom tags. We'll also see how we can use the newly created add-on in a Roo project.
As mentioned in Chapter 1, Getting Started with Spring Roo, Roo is built on top of the Apache Felix OSGi container and Roo add-ons represent OSGi bundles. As we go through Roo add-on development recipes in this chapter, we'll touch upon some of the OSGi concepts you need to know to understand how add-ons work.
Create a new directory C:
oo-cookbookch07-simple-add-on
in your system and start the Roo shell from the ch07-simple-add-on
directory.
The following steps will demonstrate how to develop a simple add-on:
addon
create
simple
command, as shown here, to create a com.roo.addon.mysimple
add-on project:roo> addon create simple --topLevelPackage com.roo.addon.mysimple --description "Mysimple addon" --projectName "Mysimple addon" Created ROOTpom.xml Created ROOT eadme.txt Created ROOTlegal Created ROOTlegalLICENSE.TXT Created SRC_MAIN_JAVA...MysimpleCommands.java Created SRC_MAIN_JAVAcom...MysimpleOperations.java Created SRC_MAIN_JAVA...MysimpleOperationsImpl.java Created SRC_MAIN_JAVA...MysimplePropertyName.java Created ROOTsrcmainassemblyassembly.xml Created SRC_MAIN_RESOURCEScom ooaddonmysimpleinfo.tagx Created SRC_MAIN_RESOURCEScom ooaddonmysimpleshow.tagx
perform
eclipse
command to create Eclipse IDE-specific configuration files:roo> perform eclipse
com.roo.addon.mysimple
Eclipse project into the Eclipse IDE.The addon
create
simple
command creates a Roo add-on, which contributes commands to the Roo shell and defines operations which are invoked in response to the execution of these commands. The package
argument specifies the top-level package of the add-on and is also used as the name of the add-on project. The following classes and interfaces are generated by the addon
create
simple
command, and the <last-part-of-top-level-package>
refers to the text after the last index of '.' in the value of topLevelPackage
argument. In the case of our example, the topLevelPackage
argument value is com.roo.addon.mysimple
, which makes value of <last-part-of-top-level-package>
as mysimple
:
<last-part-of-top-level-package>
Commands.java
class: defines methods that are contributed to the Roo shell by the add-on<last-part-of-top-level-package>
Operations.java
interface: defines methods that contains the majority of processing logic corresponding to Roo commands<last-part-of-top-level-package>
OperationsImpl.java
class: implements the *Operations.java
interface<last-part-of-top-level-package>
PropertyName.java
enum type: defines the possible values for an argument passed to a Roo commandThe add-on generated via the addon
create
simple
command gives you the starting point for custom add-on development. The generated add-on doesn't do much, except show the classes and interfaces that you'll typically create in an add-on. You'll need to modify the generated add-on to perform functions specific to your requirements.
Let's begin with looking at the Java classes and interfaces (created by addon
create
simple
command) which define commands and operations for the com.roo.addon.mysimple
add-on.
The following code listing shows the MysimpleCommands
class, which defines the commands that the add-on contributes to the Roo shell:
import java.util.logging.Logger; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.springframework.roo.shell.converters.StaticFieldConverter; @Component @Service public class MysimpleCommands implements CommandMarker { private Logger log = Logger.getLogger(getClass().getName()); @Reference private MysimpleOperations operations; @Reference private StaticFieldConverter staticFieldConverter; protected void activate(ComponentContext context) { staticFieldConverter.add(MysimplePropertyName.class); } protected void deactivate(ComponentContext context) { staticFieldConverter.remove(MysimplePropertyName.class); } ... }
The following are some of the important points to note about the MysimpleCommands
class:
MysimpleCommands
class defines the Roo commands that the mysimple add-on contributes to the Roo shell via @CliCommand
annotated methods. We'll discuss commands contributed by a mysimple add-on later in this section. When a Roo command is executed from the shell, it results in execution of the corresponding @CliCommand
annotated method in the MysimpleCommands
class.MysimpleCommands
class is annotated with @Component
and @Service
Apache Felix annotations. @Component
and @Service
annotations have source-level retention and are used by Apache
Felix Maven SCR Plugin
(http://felix.apache.org/site/apache-felix-maven-scr-plugin.html
) to generate XML configuration required by OSGi's Service Component Runtime (SCR) - responsible for managing the lifecycle of the MysimpleCommands
component and registering it as a service with OSGi service registry. Annotating MysimpleCommands
with @Component
and @Service
annotations ensures that you can access MysimpleCommands
object (using @Reference
Apache Felix annotation) from other Roo add-ons, if required.MysimpleCommands
class implements Roo's CommandMarker
interface. CommandMarker
is a marker interface, that is, it doesn't declare any methods. Roo looks for components implementing CommandMarker
interface to identify components that contribute commands to the Roo shell.@Reference
annotation of Apache Felix is like @Autowired
annotation of Spring, and is used to resolve service dependencies of a component. MysimpleOperations
and StaticFieldConverter
are service dependencies of the MysimpleCommands
component. MysimpleOperations
and StaticFieldConverter
services are accessible to MysimpleCommands
via the @Reference
annotation because the classes implementing MysimpleOperations
and StaticFieldConverter
interfaces are also annotated with @Service
and @Component
annotations—making them accessible to other Roo add-ons.MysimpleOperations
defines methods that implement the major part of the functionality performed by Roo commands contributed by the add-on. These methods are invoked by the methods defined in the MysimpleCommands
class.StaticFieldConverter
represents a Spring Converter
that provides type-safety for the argument values that are passed from the Roo shell to the corresponding @CliCommand
annotated methods in the MysimpleCommands
class.activate
and deactivate
methods represent lifecycle methods that are called by the OSGi container to activate and deactivate the Roo add-on, respectively. In the case of the mysimple add-on, the activate
method adds MysimplePropertyName
enum type to the StaticFieldConverter
implementation. In the deactivate
method, the mysimple add-on removes the MysimplePropertyName
enum
type from the StaticFieldConverter
implementation.MysimpleCommands
class in mysimple add-on, let's look at the methods in the MysimpleCommands
class that register commands with the Roo shell and process these Roo commands when they are executed from the Roo shell.The following code listing shows the methods of the MysimpleCommands
class that define Roo commands exposed by the mysimple add-on:
import org.springframework.roo.shell.CliCommand; import org.springframework.roo.shell.CliOption; ... @Reference private MysimpleOperations operations; @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 = "countryOfOrigin", mandatory = false, help = "Country of orgin") MysimplePropertyName country) { log.info("Welcome " + name + "!"); ... } @CliCommand(value = "web mvc install tags", help="Replace default Roo MVC tags used for scaffolding") public void installTags() { .installTags(); }
In the preceding code, we can see that:
@Reference
annotation performs autowiring by type and binds the reference to the service that implements the MysimpleOperations
interface. As MysimpleOperationsImpl
implements the MysimpleOperations
interface, reference to the MysimpleOperationsImpl
object is injected into the MysimpleCommands
instance.@CliCommand
is a method-level Roo annotation which identifies methods which contribute commands to the Roo shell. The value
attribute specifies the name of the command that is contributed by the add-on to the Roo shell. For instance, the mysimple add-on contributes say
hello
and web
mvc
install
tags
commands to the Roo shell. The help
attribute specifies the help text that is displayed against the command when you execute the help
Roo command. When a Roo command is executed from the Roo shell, the corresponding @CliCommand
annotated method is executed by Roo. For instance, if you execute the say
hello
command from the Roo shell, Roo executes the sayHello
method of the MysimpleCommands
class, which prints a welcome message on the Roo shell using Java Logging API. Similarly, if you execute the web
mvc
install
tags
command, Roo executes the installTags
method of the MysimpleCommands
class. The installTags
method invokes the installTags
method of the MysimpleOperationsImpl
class, which copies info.tagx
and show.tagx
tag files into your Roo project. Later in this recipe we'll look in detail at the installTags
method of the MysimpleOperationsImpl
class.@CliOption
method-parameter-level Roo annotation specifies the arguments that a Roo command accepts. The key
attribute specifies the name of the command argument, the mandatory
attribute specifies if the argument is mandatory or optional, and the help
attribute specifies the help text associated with the argument. For instance, the say
hello
command accepts two arguments—name
and countryOfOrigin
. The name
argument is mandatory and countryOfOrigin
is optional.The Java type of an argument can be a simple String
or it could be a complex type. In the case of the say
hello
command, the name
argument is of type String
and countryOfOrigin
is of type MysimplePropertyName
. Roo provides converters for common Java types, such as String
, Date
, Enum
, Locale
, boolean
, and so on. You can also create your custom converters and register them with Roo as an OSGi service. Roo makes use of registered converters to convert the value specified for the argument into the Java type expected by the method. In the case of the mysimple add-on, Roo converts the value entered for the countryOfOrigin
argument of the say
hello
Roo command to the MysimplePropertyName
type. We'll see later in this recipe that using MysimplePropertyName
(an enum
) as the Java type of the countryOfOrigin
Roo argument provides tab-completion feature for the argument value. We saw earlier that the MysimplePropertyName
class is added to the StaticFieldConverter
instance in the activate
method. This is to allow StaticFieldConverter
to convert the value of the countryOfOrigin
argument in the say
hello
Roo command to the MysimplePropertyName
type.
Let's now look at how to make a Roo command unavailable to the Roo shell if certain pre-conditions are not met.
If certain pre-conditions are not met, you may want to make a Roo command unavailable to the Roo shell. For instance, if you have not yet created a Roo project using the project
command, then Roo doesn't allow you to set up a JPA persistence provider using the persistence
setup
command.
@CliAvailabilityIndicator
is a method-level Roo annotation that lets you specify the pre-conditions that must be met for a Roo command to be available to the Roo shell. The following code shows the methods in the MysimpleCommands
class that define the availability conditions for the say
hello
and web
mvc
install
tags
commands:import org.springframework.roo.shell.CliAvailabilityIndicator; ... @Reference private MysimpleOperations operations; ... @CliAvailabilityIndicator("say hello") public boolean isSayHelloAvailable() { return true; } @CliAvailabilityIndicator("web mvc install tags") public boolean isInstallTagsCommandAvailable() { return operations.isInstallTagsCommandAvailable(); }
In the code, @CliAvailabilityIndicator
annotated methods define the availability of the say
hello
and web
mvc
install
tags
commands. The value specified in the @CliAvailabilityIndicator
annotation identifies the name of the Roo command for which the method is executed to determine the command's availability. For instance, the isSayHelloAvailable
method defines the availability of the say
hello
command and the isInstallTagsCommandAvailable
method defines the availability of the web
mvc
install
tags
command. The return type of @CliAvailabilityIndicator
annotated methods is boolean
and the method must be a public
method which doesn't accept any arguments. If the value returned by the @CliAvailabilityIndicator
annotated method is true
, then it means that the corresponding command is available, else it is unavailable.
As the isSayHelloAvailable
method always returns true, the say
hello
command is always available to the Roo shell. On the other hand, the isInstallTagsCommandAvailable
method consults the MysimpleOperations
implementation to determine the availability of the web
mvc
install
tags
command.
Let's now look at the MysimpleOperationsImpl
class, which defines the majority of the logic executed when the mysimple add-on Roo commands are executed from the Roo shell.
The MysimpleOperations
interface defines three methods as described in the following table:
The MysimpleOperationsImpl
class implements the MysimpleOperations
interface. The methods defined in the MysimpleCommands
class mainly delegate processing of logic to the implementation of the MysimpleOperations
interface. The following code shows the MysimpleOperationsImpl
class (methods have not been shown for brevity):
import org.springframework.roo.process.manager.FileManager; import org.springframework.roo.project.ProjectOperations; @Component @Service public class MysimpleOperationsImpl implements MysimpleOperations { private static final char SEPARATOR = File.separatorChar; @Reference private FileManager fileManager; @Reference private ProjectOperations projectOperations; ... }
The MysimpleOperationsImpl
class is annotated with @Component
and @Service
Apache Flex annotations, which means OSGi's SCR is responsible for managing the lifecycle of the MysimpleOperationsImpl
component and registering it as a service with the OSGi service registry. Like MysimpleCommands
, you can access the MysimpleOperationsImpl
instance from other add-ons using the @Reference
Apache Felix annotation.
Roo provides many built-in services which simplify add-on development. FileManager
and ProjectOperations
types represent services provided by Roo for managing files (like creating, reading, updating files, undo capability, and so on) and performing actions on the Roo project (like adding dependencies to the pom.xml
file, updating project type, and so on), respectively. FileManager
service and ProjectOperations
are provided by the Process Manager and Project core modules of Roo, respectively. It is important to note that add-ons are different from core modules in Roo. The core modules provide vital features of the Spring Roo tool, like file system monitoring, registering commands with the Roo shell, and so on. Roo commands provided by add-ons are executed by Spring Roo users for code generation but Roo commands provided by core modules are primarily meant for accessing internal features of Spring Roo, like obtaining metadata, setting polling speed, and so on.
We saw earlier that the isInstallTagsCommandAvailable
method of MysimpleOperations
is invoked by the isInstallTagsCommandAvailable
method of the MysimpleCommands
class to check the availability of the web
mvc
install
tags
command. The following code shows the isInstallTagsCommandAvailable
method of MysimpleOperationsImpl
:
public boolean isInstallTagsCommandAvailable() { return projectOperations.isProjectAvailable() && fileManager.exists(projectOperations.getProjectMetadata() .getPathResolver().getIdentifier(Path.SRC_MAIN_WEBAPP, "WEB-INF" + SEPARATOR + "tags")); }
In the code, the isInstallTagsCommandAvailable
method makes use of ProjectOperation
services to check if a Roo project exists. The method also makes use of the FileManager
service to check if a tags
sub-directory exists in the SRC_MAIN_WEBAPP/WEB-INF
directory of your Roo project. If a tags
directory doesn't exist or you haven't yet created a Roo project, then the method returns false
. This means the web
mvc
install
tags
Roo command is not available to the Roo shell if you haven't yet created a Roo project which contains a tags
sub-directory inside the SRC_MAIN_WEBAPP/WEB-INF
directory.
We saw earlier that the installTags
method of MysimpleCommands
invokes the installTags
method of MysimpleOperations
. The following code shows the installTags
method as implemented by the MysimpleOperationsImpl
class:
import org.springframework.roo.process.manager.MutableFile; import org.springframework.roo.project.Path; import org.springframework.roo.project.PathResolver; import org.springframework.roo.project.ProjectOperations; import org.springframework.roo.support.util.FileCopyUtils; import org.springframework.roo.support.util.TemplateUtils; ... public void installTags() { PathResolver pathResolver = projectOperations.getProjectMetadata().getPathResolver(); createOrReplaceFile(..., "info.tagx"); createOrReplaceFile(..., "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); } }
In the code, the installTags
method copies info.tagx
and show.tagx
files from the SRC_MAIN_RESOURCE/com/roo/addon/mysimple
directory of the mysimple add-on to the SRC_MAN_WEBAPP/WEB-INF/tags
directory of your Roo project. The createOrReplace
method is the method, which is used by the installTags
method to copy the files. The following table describes the classes used by the installTags
and createOrReplace
methods for copying tag files:
Let's now look at the MysimplePropertyName
enum type, which defines constants for the countryOfOrigin
argument of web
mvc
install
tags
command.
The following code shows the MysimpleNameProperty
enum type, which defines constants for the countryOfOrigin
argument value of the web
mvc
install
tags
command:
public enum MysimplePropertyName { AUSTRALIA("Australia"), UNITED_STATES("United States"), GERMANY("Germany"), NOT_SPECIFIED("None of your business!"); private String propertyName; private MysimplePropertyName(String propertyName) { Assert.hasText(propertyName, "Property name required"); this.propertyName = propertyName; } ... }
In the code, constant AUSTRALIA
is associated with value Australia
, UNITED_STATES
is associated with value United
States
, and so on. We saw earlier that the countryOfOrigin
argument is of type MysimplePropertyName
. We can only pass String
type values for an argument from the Roo shell, so what we should specify as the value of the countryOfOrigin
argument, and how it'll get converted to MysimplePropertyName
. When you enter a partial value for the countryOfOrigin
argument and press the Tab key, Roo internally refers to MysimplePropertyName
to find a matching constant. For instance, if you enter au
as the value of the countryOfOrigin
argument, Roo attempts to find the constant that matches au
in MysimpleNameProperty
and auto-completes the value. As the matching is case-insensitive, the value au
of the countryOfOrigin
argument is completed by the Roo shell as AUSTRALIA
.
The following diagram summarizes how a simple add-on works:
The figure shows that CommandMarker
is an interface provided by Roo, and it is implemented by the MysimpleCommands
class. The MysimpleCommands
class invokes methods of the MysimpleOperationsImpl
class to process the commands exposed by the mysimple add-on. The MysimpleCommands
and MysimpleOperationsImpl
classes use services provided by Roo to perform the desired functionality.
In this section we'll look at:
enum
type and a Java class@CliAvailabilityIndicator
annotation can be used for a method to define availability of multiple Roo commands exposed by the *Command
classpom.xml
file of an add-onOnce you have created an add-on, you may want to test its functionality, before making the add-on available to other developers. In this section, we'll look at how to locally deploy and test the mysimple add-on that we created using the create
addon
simple
command.
To use the mysimple add-on, you need to convert it into an OSGi-compliant JAR bundle. To do so, execute mvn
clean
install
from the directory which contains your mysimple add-on project, as shown here:
C:
oo-cookbookch07-simple-add-on> mvn clean install -Dgpg.passphrase=<thephrase>
Here, <thephrase>
is the password phrase that you provide for signing add-ons using GnuPG (also referred to as GPG). Refer to the Setting up GnuPG for add-on development recipe for information on how to set up GnuPG and create a password phrase for signing add-ons.
Executing the mvn
clean
install
command creates a com.roo.addon.mysimple-0.1.0.BUILD-SNAPSHOT.jar
add-on OSGi bundle in the target
directory of the mysimple add-on project. Now, let's look at how to use the mysimple add-on in a Roo project.
The following steps will demonstrate how to use an add-on:
C:
oo-cookbookch07-addon-test
in your system and start the Roo shell from this directory.ch07_web_app.roo
script to create a flight-app
Spring Web MVC project.osgi
start
command to install and activate the mysimple add-on, as shown here:roo> osgi start --url file:///C:/roo-cookbook/ch07-simple-add-on/target/com.roo.addon.mysimple-0.1.0.BUILD-SNAPSHOT.jar
Here the url
argument specifies the location of the add-on OSGi bundle you want to install and activate.
The osgi
start
command installs the mysimple add-on. This command is also used to download and install Roo add-ons that are located on a website by specifying the http://
or httppgp://
URL to the add-on JAR file as the value of the url
argument.
say
at the Roo shell and press the Tab key. Roo should autocomplete the command to say
hello
, as shown here:roo> say hello
name
argument of the say
hello
command, as shown here:roo> say hello --name
Ron
as the value of name
argument and type --
followed by the Tab key to view the optional arguments of the say
hello
command:roo> say hello --name Ron --
say
hello
command is countryOfOrigin
, it is displayed on the Roo shell:roo> say hello --name Ron --countryOfOrigin
countryOfOrigin
argument. You'll see the following output:roo> say hello --name Ron --countryOfOrigin AUSTRALIA GERMANY NOT_SPECIFIED UNITED_STATES
The output shows that countryOfOrigin
can accept only one of the four possible values: AUSTRALIA
, GERMANY
, NOT_SPECIFIED
, and UNITED_STATES
.
aus
as the value of the countryOfOrigin
argument and press the Tab key to let Roo perform autocompletion of the value, as shown here:roo> say hello --name Ron --countryOfOrigin aus roo> say hello --name Ron --countryOfOrigin AUSTRALIA
As shown, Roo performs autocompletion of value for the countryOfOrigin
argument. The possible values for the countryOfOrigin
argument come from the MysimplePropertyName
enum type.
say
hello
command. You'll see an output like the following:~.web.controller roo> say hello --name Ron --countryOfOrigin AUSTRALIA Welcome Ron! Country of origin: Australia It seems you are a running JDK 1.6.0_23 You can use the default JDK logger anywhere in your add-on to send messages to the Roo shell
When the say
hello
command is executed, it is processed by the sayHello
method of the MysimpleCommands
class.
web
mvc
install
tags
of the mysimple add-on to install the info.tagx
and show.tagx
files to the flight-app
Roo project:roo> web mvc install tags Created SRC_MAIN_WEBAPPWEB-INF agsutilinfo.tagx Updated SRC_MAIN_WEBAPPWEB-INF agsformshow.tagx
The output of executing web
mvc
install
tags
shows that the info.tagx
file is added to the flight-app
project and the show.tagx
file is replaced. The web
mvc
install
tags
command is processed by the installTags
method of the MysimpleCommands
class, which delegates to the installTags
method of the MysimpleOperationsImpl
class.
If you make modifications to the mysimple add-on and want to re-deploy it to Spring Roo, then use the osgi
update
command, as shown here:
roo> osgi update --url file:///C:/roo-cookbook/ch07-simple-add-on/target/com.roo.addon.mysimple-0.1.0.BUILD-SNAPSHOT.jar
If you want to uninstall the mysimple add-on, then use the osgi
uninstall
command, as shown here:
roo> osgi uninstall --bundleSymbolicName com.roo.addon.mysimple
The bundleSymbolicName
argument identifies the name of the add-on to be uninstalled from Spring Roo. Once an add-on is uninstalled, Roo commands exposed by that add-on are no longer available to the Roo shell.
The MysimplePropertyName
enum type defines constants, which represent the possible values that an argument of a Roo command can accept. You are not limited to using enum types to define constants for argument values. Let's say that instead of using the MySimplePropertyName
enum type we want to use a Country
class that defines constants for countries. The following code shows the Country
class that can be used in place of the MySimplePropertyName
enum type:
public class Country { public static final Country AUSTRALIA = new Country("Australia"); public static final Country NOT_SPECIFIED = new Country("None of your business!"); public static final Country UNITED_STATES = new Country("United States"); public static final Country GERMANY = new Country("Germany"); private String countryName; public Country(String countryName) { this.countryName = countryName; } public String getCountryName() { return countryName; } }
The preceding code shows that the Country
class defines constants for each country representing a possible value of the countryOfOrigin
argument. Now, to use the Country
class instead of the MysimplePropertyName
enum, all you need to do is to replace references to it with Country
in the MysimpleCommands
class, as shown here:
public class MysimpleCommands implements CommandMarker { @Reference private StaticFieldConverter staticFieldConverter; protected void activate(ComponentContext context) { staticFieldConverter.add(Country.class); } protected void deactivate(ComponentContext context) { staticFieldConverter.remove(Country.class); } @CliCommand(value = "say hello", help = "Prints welcome message to the Roo shell") public void sayHello(..., @CliOption(key = "countryOfOrigin", mandatory = false, help = "Country of orgin") Country country) { log.info("Welcome " + name + "!"); log.warning("Country of origin: " + (country == null ? Country.NOT_SPECIFIED.getCountryName() : country.getCountryName())); ... } ... }
The preceding code shows that the Country
class must be added to the StaticFieldConverter
service and must be specified as the type of countryOfOrigin
argument in sayHello
method.
In mysimple add-on, separate @CliAvailabilityIndicator
annotated methods are used to indicate availability of the say
hello
and web
mvc
install
tags
commands. If you want to use a single @CliAvailabilityIndicator
annotated method to indicate availability of multiple Roo commands offered by the mysimple add-on, then specify the command array in the @CliAvailabilityIndicator
annotation. For instance, you can define the following method in MysimpleCommands
to indicate that the say
hello
and web
mvc
install
tags
commands are always available to the Roo shell:
@CliAvailabilityIndicator({"say hello", "web mvc install tags"}) public boolean isCommandAvailable() { return true; }
In the preceding code the @CliAvailabilityIndicator
annotation specifies an array of Roo commands (say
hello
and web
mvc
install
tags
) whose availability is checked by the isCommandAvailable
method.
Templates in an add-on project are resources that are copied to the Roo project when one or more commands of the add-on are executed. For instance, when you execute the web
mvc
install
tags
command, the info.tagx
and show.tagx
files are copied from add-on to the Roo project. Templates can also be images, XML files, properties files, and so on, which the add-on commands copy to the Roo project.
Templates are located inside the SRC_MAIN_RESOURCES
directory of an add-on project. For instance, in the case of the mysimple add-on, the info.tagx
and show.tagx
files are located in the SRC_MAIN_RESOURCES/com/roo/addon/mysimple
directory. Add-ons access templates using the TemplateUtils
class and then copy it to the Roo project using the FileCopyUtils
class. TemplateUtils
defines the following two static methods which are used to access templates in the add-on:
String
getTemplatePath
(Class<?>
clazz,
String
templateFilename)
: this method returns the path to the template file specified via the templateFilename
argument. The clazz
argument's package information is used to obtain the sub-directory inside the SRC_MAIN_RESOURCES
directory that contains the template file. For instance, if the clazz
argument represents a class whose package name is com.roo.addon.mysimple
and the templateFileName
argument value is show.tagx
, the getTemplatePath
method returns the path to the SRC_MAIN_RESOURCES/com/roo/addon/mysimple/show.tagx
file. You can also specify the relative path to the template file as the value of the templateFilename
argument. For instance, if you specify the value of templateFilename
as WEB-INF/myconfig.xml
, then the path to the template file becomes SRC_MAIN_RESOURCES/com/roo/addon/mysimple/WEB-INF/myconfig.xml
.
InputStream
getTemplate
(Class<?>
clazz,
String
templateFilename)
: this method returns java.io.InputStream
to the template file. In the case of the mysimple add-on, the createOrReplaceFile
method of the MySimpleOperationsImpl
class makes use of the TemplateUtils
class to obtain InputStream
to the info.tagx
and show.tagx
files.In some add-ons, a template file may be an XML file which the add-ons need to modify before copying it to the Roo project. To modify XML templates, Roo provides an XMLUtils
class which add-ons can use to modify the content of XML template files. Let's look at a scenario that shows how add-ons can modify the content of an XML template file before copying it to the Roo project.
The following config.xml
file shows a Spring application context XML file which represents a template XML file of an add-on:
beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" ...> <context:component-scan base-package=""/> ... </beans>
In the config.xml
file, the <component-scan>
element of Spring's context
namespace specifies the packages (via the base-package
attribute) that are scanned by Spring. The classes in these packages (and their sub-packages) that are annotated with the @Component
, @Service
, and @Repository
Spring annotations are auto-registered with Spring's application context. As the add-on copies the config.xml
file to a Roo project when a Roo command is executed, the add-on doesn't know in advance the value that needs to be specified for the base-package
attribute. This is the reason why the value of the base-package
attribute is empty in the config.xml
file.
The following code shows how an add-on can read the config.xml
file, modify it, and then write the modified config.xml
to the Roo project:
import org.springframework.roo.metadata.MetadataService; import org.springframework.roo.project.PathResolver; import org.springframework.roo.project.ProjectMetadata; import org.springframework.roo.support.util.XmlUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import java.io.ByteArrayOutputStream; import java.io.OutputStreamWriter; @Component @Service public class FileWriterOperationsImpl implements FileWriterOperations { @Reference private MetadataService metadataService; ... public void copyApplicationContextXML() { ProjectMetadata projectMetadata = (ProjectMetadata) metadataService.get(ProjectMetadata. getProjectIdentifier()); InputStream templateInputStream = TemplateUtils.getTemplate(getClass(), "config.xml"); Document config; try { config = XmlUtils.getDocumentBuilder(). parse(templateInputStream); } catch (Exception ex) {...} Element rootElement = (Element) config.getDocumentElement(); XmlUtils.findFirstElementByName("context:component-scan", rootElement).setAttribute("base-package", projectMetadata.getTopLevelPackage(). getFullyQualifiedPackageName()); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); XmlUtils.writeXml(XmlUtils.createIndentingTransformer(), outputStream, config); String xmlContent = outputStream.toString(); FileCopyUtils.copy(xmlContent, new OutputStreamWriter(...)); } ... }
The FileWriterOperationsImpl
class is similar to the MysimpleOperationsImpl
class of the mysimple add-on. It defines the copyApplicationContextXML
method which is responsible for copying the config.xml
file from the add-on to the Roo project.
The MetadataService
class represents a service provided by Roo for retrieving metadata information for the Roo project, Java types, fields, methods, and so on. Metadata is obtained from Roo's MetadataService
using a metadata identification string, which has the format: MID:<fully-qualified-class-name>#<instance-identification-key>
, where <fully-qualified-class-name>
is the metadata type and <instance-identification-key>
is the Java type to which the metadata applies. If the metadata is not associated with a Java type, then the metadata string format is: MID:<fully-qualified-class-name>
. If the metadata identification string has the format MID:<fully-qualified-class-name>#<instance-identification-key>
, then it is referred to as an instance-level metadata identification string. If the metadata identification string has the format MID:<fully-qualified-class-name>
, then it is referred to as a class-level metadata identification string. In the FileWriterOperationsImpl
class, ProjectMetadata
represents a metadata type which holds the Roo project's details, like project name, top-level package name, dependencies, and so on. The getProjectIdentifier()
method of ProjectMetadata
returns a metadata identification string for the Roo project and is then passed to MetadataService
to retrieve the ProjectMetadata
instance.
The TemplateUtils
class is used to obtain java.io.InputStream
to the config.xml
file. The XmlUtils
class is then used to parse the config.xml
file to build the org.w3c.dom.Document
instance. The findFirstElementByName
method of XmlUtils
is used to find the first occurrence of the <context:component-scan>
element in config.xml
. The findFirstElementByName
method returns an instance of org.w3c.dom.Element
. The setAttribute
method of Element
is used to set the value of the base-package
attribute of the <context:component-scan>
element to the top-level package of the Roo project. The writeXml
method of XmlUtils
writes the Document
object to java.io.OutputStream
. The createIndentingTransformer
method of XmlUtils
creates a javax.xml.transform.Transformer
instance, which indents entries in the Document
object by 4 characters. If you want to perform a custom transformation of XML, you can create a custom Transformer
implementation and pass it to the writeXml
method of XmlUtils
class.
Let's say you have a Roo project named flight-app
whose top-level package is com.sample.flightapp
. Now assume that you execute a Roo command which results in the execution of the copyApplicationContextXML
method of the FileWriterOperationsImpl
class of the add-on. The copyApplicationContextXML
method will read the config.xml
template file, set the base-package
attribute of the <context:component-scan>
element to com.sample.flightapp
and write the modified config.xml
to the flight-app
Roo project.
Now let's look at some of the important configurations defined in the pom.xml
file of the mysimple add-on project.
The pom.xml
file of an add-on created via the addon
create
simple
command contains the following configurations:
pom.xml
file. If your add-on makes use of other add-ons, then you'll need to configure it in the pom.xml
file.perform assembly
Roo command or assembly:single
goal of the assembly plugin to package the add-on as a ZIP file. The assembly description, assembly.xml
, is located in the src/main/assembly
folder of add-on.pom.xml
file) by executing the mvn release:prepare release:perform
Maven command.<packaging>
element's value is specified as bundle
in the pom.xml
file of add-on, which means that the add-on is packaged as an OSGi bundle.Once you have deployed an add-on, you can check if it was successfully installed or not by using the following OSGi commands from the Roo shell:
osgi
ps
: lists the OSGi bundles and their status. If you have successfully installed the mysimple add-on, then executing the osgi
ps
command should show the mysimple add-on as active, as shown here: [Active] [1] Mysimple addon (0.1.0.BUILD-SNAPSHOT)
osgi
log
: shows the OSGi container logs. If your add-on fails to install successfully, you can refer to the container logs to troubleshoot installation issues.osgi
scr
list
: lists services and components registered with the OSGi container. If you have successfully installed the mysimple add-on, then executing the osgi
scr
list
command should show commands and operation types, as shown here:[181] [active] com.roo.addon.mysimple.MysimpleOperationsImpl [180] [active] com.roo.addon.mysimple.MysimpleCommands
In the preceding output, numbers 180
and 181
denote the component IDs assigned by the OSGi container to the MysimpleCommands
and MysimpleOperations
types respectively.
osgi
scr
info
: shows detailed information about a component or service registered with the OSGi container. This command accepts a mandatory argument, componentId
. You can use this command to find unresolvable dependencies of a component. If you have successfully installed the mysimple add-on, then executing osgi
scr
info
--componentId
180
(substitute the component ID of MysimpleCommands
as displayed by executing the osgi
scr
list
command) should show if the dependencies of MysimpleCommands
were satisfied or not, as shown here:ID: 180 Name: com.roo.addon.mysimple.MysimpleCommands State: active Services: org.springframework.roo.shell.CommandMarker ... Reference: staticFieldConverter Satisfied: satisfied Service Name: org.springframework.roo.shell.converters.StaticFieldConverter ... Reference: operations Satisfied: satisfied Service Name: com.roo.addon.mysimple.MysimpleOperations
The output shows that the StaticFieldConverter
and MysimpleOperations
dependencies of MysimpleCommands
were resolved successfully.
3.12.162.37