Chapter 20. Tailor Add-On

20.1. Introduction

Roo has been become more and more powerful and offers more options for users on how to use Roo. This in turn makes it more challenging in some scenarios to use Roo in a consistent way throughout a project.

The tailor addon enables:

  • Teams working on large projects to ensure streamlined Roo usage according to their project's standards and guidelines

  • Single users to define the approach they usually take in one file to reuse it over multiple projects

Examples of use cases:

  • A team does not want to use the Active Record pattern for entities, but always wants developers to specify "--activeRecord false", and create a JPA repository based on every new entity.

  • A developer always uses a certain project structure to create web projects, for exmple a Maven project with 2 modules called "domain" and "web". The developer wants to be able to reuse this structure with the project command, and make sure that the shell automatically focuses on the correct module for certain commands (e.g. entity > domain, web mvc > web).

20.2. How it works

When tailoring is activated, Roo commands are intercepted by the shell and transformed to a new set of commands according to user specifications obtained from configuration file, if any exist for that particular command. The shell then executes this transformed set of commands instead of the initial command. A user can define one or multiple tailor configurations and activate and deactivate them while working with the shell.

With the tailor add-on, you can define:

  • Reusable project structures to use with the "project" command

  • Default target modules for commands.

  • Default values for command arguments.

  • Chains of commands, either triggered by an existing command or composed by an alias

Note that although a tailor configuration can save you a lot of time and effort, it cancels out some of the shell's command completion benefits at the same time. For example, some commands are only available in certain modules (e.g. JPA commands are only available in modules with JPA setup). Tailoring a default module for JPA commands like "entity jpa" means that you can execute those commands while focused on modules without JPA setup. But the tailoring only kicks in at execution time, so the shell won't know about it while you are typing. Thus, the shell won't offer command completion for these commands because it thinks they are not available.

20.3. Tailor Add-On Commands

tailor list - Shows the list of available tailor configurations. A tailor configuration defines the set of transformation you want executed for certain commands (see next section "Tailor Configuration").

roo> tailor list
Available tailor configurations:
     o webstyle - Web project with 2 modules, DOMAIN and PRESENTATION

tailor activate – Activate one of the available configurations.

roo> tailor activate --name webstyle

"tailor list" indicates which configuration is currently activated:

roo> tailor list
Available tailor configurations:
    o webstyle [ ACTIVE ] - Web project with 2 modules, DOMAIN and PRESENTATION

tailor deactivate – Deactivate the tailor mode. There is no active configuration after this command

roo> tailor deactivate

20.4. Tailor Configuration

A tailor configuration can be created in two ways:

  • XML configuration file (no add-on development required)

  • Directly in Java (requires creation and installation of a new add-on)

Each tailor configuration has one or more command configurations. A command configuration defines a set of Actions that are triggered whenever a certain command is executed. Execution of those actions results in a new list of output commands that will eventually be executed by the shell. A command configuration is triggered whenever a command that starts with a defined string is executed. E.g., if a command configuration defines "web mvc" as a trigger, then it will be used by the tailor every time a "web mvc" subcommand is executed. The order in which you define the command configurations might matter, the tailor will always take the first command configuration that matches a command.

An action is a transformation step to be applied to the command defined in a command configuration. Each action type defines a set of parameters that can be set in a tailor definition. The tailor addon can be extended with more action types by the community.

Actions are executed sequentially by the tailor, so the order in which they are declared matters.

The following actions are currently available:

20.4.1. Actions

20.4.1.1. execute

Adds a command to the list of commands to be executed. Note that each command configuration should have at least one execute action, otherwise the tailor will not lead to any command executions.

command

Command line to be executed. If empty, this action will add the original command to the list of output commands at this point. (optional)

exclude

A comma separated list of arguments that should be removed from the command before execution. This can be useful if the original command is executed ("command" argument not set), and it was enhanced with additional arguments for the benefit of the tailoring. (optional)

20.4.1.2. defaultvalue

If the Roo user does not provide a value for an argument with the given name on the shell, this default value will be chosen.

argument

Name of the Roo command's argument that will get a default value. (mandatory)

value

Default value for the argument. (mandatory)

force

If "true", the default value will be chosen even if the user specified an alternative value in the command. (optional, defaults to "false")

20.4.1.3. focus

module

Focus on a module, in form of a simple pattern to match against the module names. Does not support regular expressions, just a simple "contains" match. Use this instead of an "execute command 'module focus...'" if you do not want to hard code your module names into the reusable tailor configuration. (mandatory)

Advanced usage: Use a comma-separated list of strings to look for in module names. The comma will be interpreted as "AND" by the search for a module. Use a slash "/" before a string in the list to indicate that this next string must "NOT" be contained in the module name.

20.4.2. XML Configuration

This section describes how to create a tailor configuration with XML by examples.

The XML configuration file “tailor.xml” must be placed into the root project folder. Alternatively, you can put a "tailor.xml" into your system's user folder, to maintain tailor configurations that you want to reuse over several projects. The tailor addon will only look for this file if it does not find a tailor.xml file in the project root.

20.4.2.1. Example 1: Tailor the "project" command

The following configuration defines a chain of commands that will be triggered by the project command, to create a parent project with packaging “pom” with two modules named “projectname-domain” and “projectname-data”.

Note how you can use argument values from the input command as placeholders by using “${argumentname}”.

tailor.xml:

<tailor name="mywebstyle" description="Standards for web projects with 2 modules">
   <config command="project">
      <action type="defaultvalue" argument="packaging" value="pom" />
      <action type="execute" />
      <action type="execute" command="module create --moduleName ${projectName}-domain --topLevelPackage ${topLevelPackage}"/>
      <action type="focus" module="~"/>
      <action type="execute" command="module create --moduleName ${projectName}-web --topLevelPackage ${topLevelPackage} --packaging war"/>
      <action type="focus" module="${projectName}-domain"/>
   </config>
</tailor>

Shell:

tailor activate --name mywebstyle
project --topLevelPackage com.foo.sample --projectName myapp

Will result in:

project --topLevelPackage com.foo.sample --projectName mywebapp --packaging pom
module create --moduleName myapp-domain --topLevelPackage com.foo.sample
module focus --moduleName ~
module create --moduleName myapp-web --topLevelPackage com.foo.sample --packaging war
module focus --moduleName myapp-domain

20.4.2.2. Example 2: Default target modules and default values

The following example shows how to tailor the “entity jpa” command with a default value for the "activeRecord" argument, and a default module to put all entities in.

Note that the module name value for the "focus" action is interpreted as "module name contains x". That is why this example works with the project setup described in the previous example, which sets up a module named "${projectName]-domain".

tailor.xml:

<config command="entity jpa">
   <action type="focus" module="domain"/>
   <action type="defaultvalue" argument="--activeRecord" value="false"/>
   <action type="execute"/>
</config>

Shell:

entity jpa --class ~.Customer

Results in:

module focus --moduleName webapp-domain
entity jpa --class ~.Customer --activeRecord false

20.4.2.3. Example 3: Alias command to create layers

In this example, the tailor configuration defines a new alias command that will trigger a set of other commands to scaffold repository, service and web layer for an entity. Note that this configuration does not define the "execute" action to execute the original "layer" command.

Although "layer" is not a command known to the shell, it won’t produce an error, because the tailor will transform it into a set of different commands, excluding the original. The downside is that you won’t get command completion support for this alias from the shell.

tailor.xml:

<config command="layer">
   <action type="focus" module="domain"/>
   <!-- Create spring data JPA repository -->
   <action type="execute" command="repository jpa --interface ${entity}Repository --entity ${entity}"/>
   <!-- Create service interface and implementation class-->
   <action type="execute" command="service --interface ${entity}Service --class ${entity}ServiceImpl --entity ${entity}"/>
   <action type="focus" module="web"/>
   <action type="execute" command="web mvc scaffold --class ${entity}Controller --backingType ${entity}"/>
</config>

Shell:

layer --entity ~.Customer

Results in:

module focus --moduleName webapp-domain
repository jpa --interface ~.CustomerRepository --entity ~.Customer
service --interface ~.CustomerService --class ~.CustomerServiceImpl --entity ~.Customer
module focus --moduleName webapp-web
web mvc scaffold --class ~.CustomerController --backingType ~.Customer

20.4.3. Configuration Addon

A new tailor configuration can also be defined in Java, instead of XML. This requires the creation of a new simple addon that you would need to build and install as a bundle in your Roo installation. Once your tailor extension bundle is running, the “tailor” commands will recognize all tailor configurations you implemented in that addon.

This is a more static and elaborate way of creating tailor configurations. However, it might be useful if you want to distribute a configuration to a large group of users.

After you created a new (simple) addon, you need to do the following:

Add dependency to addon-tailor
<dependency>
     <groupId>org.springframework.roo</groupId>
     <artifactId>org.springframework.roo.addon.tailor</artifactId>
  </dependency>
Create a class that implements TailorConfigurationFactory
@Component
@Service
public class TailorWebSimpleConfiguration implements TailorConfigurationFactory {
   ...
}
Override createTailorConfiguration()
@Override
public TailorConfiguration createTailorConfiguration() {
      String description = "Web project with 2 modules DOMAIN-PRESENTATION";
      TailorConfiguration configuration = new TailorConfiguration("webstyle-simple", description);
      configuration.addCommandConfig(createCommandConfigProject());
      configuration.addCommandConfig(createCommandConfigJpaSetup());
      return configuration;
}
Implement and add the CommandConfiguration objects you want to support.

Add a chain of actions similar to how you would do in an XML configuration file, as described above.

private CommandConfiguration createCommandConfigJpaSetup() {
      CommandConfiguration config = new CommandConfiguration();
      config.setCommandName("jpa setup");
      config.addAction(ActionConfigFactory.focusAction(
           "domain"));
      config.addAction(ActionConfigFactory.defaultArgumentAction(
           "database", "HYPERSONIC_IN_MEMORY"));
      config.addAction(ActionConfigFactory.defaultArgumentAction(
                  "provider", "HIBERNATE"));
      config.addAction(ActionConfigFactory.executeAction());
      return config;
}