jueves, marzo 11, 2010

... Volver, con la frente marchita, las nieves del tiempo platearon mi sien...



Today I am going to talk about building and adding a simple addon to Spring-Roo. I will not talk about what is Roo or about their benefits.

Roo is built on an add-on architecture that enables different types of Java applications to be built. For this reason is so important to know how to build new addons for Spring-Roo.

Our addon:

First of all install Spring-Roo. After correctly installed (see Spring Roo documentation).

After installed, create a new directory called listfiles-addon.

Enter into directory and start roo.

Then type into roo console project --topLevelPackage com.test.roo.addon --template ROO_ADDON_SIMPLE

This sequence will create a project for creating a simple addon, with all required classes.

Let's examine what have created and how a modification will be applied for creating our addon.

In directory src/main/resources/META-INF/spring two files are present, log4j.properties and applicationContext.xml. applicationContext file is a Spring configuration file that configure Spring for work with annotations (@Configurable, @Component, ....).

First class to take a look:


src/main/java/com/test/roo/addon/PropertyName.java

This class represents attributes of options of our command. For example, logger addon, the command is logging setup and then --level is the option, and that option has many attributes (FATAL, ERROR ...), these attributes are what are defined in this class. Must implements Comparable.

Let's take a look of important points:

private String propertyName; [1]

public static final PropertyName USERNAME = new PropertyName("Username"); [2]
public static final PropertyName HOME_DIRECTORY = new PropertyName("Home Directory");

private PropertyName(String propertyName) { [3]
Assert.hasText(propertyName, "Property name required");
this.propertyName = propertyName;
}

public String getPropertyName() {
return propertyName;
}

public final boolean equals(Object obj) {
return obj != null && obj instanceof PropertyName && this.compareTo((PropertyName) obj) == 0;
}

public final int compareTo(PropertyName o) { [4]
if (o == null)
return -1;
int result = this.propertyName.compareTo(o.propertyName);

return result;
}

public final String getKey() { [5]
return this.propertyName;
}


[1]: Property name. This value will be used in our Command class, for now, imagine as a key that is unique.
[2]: A list of public static final attributes must be defined. Their type must be the same of class we are implementing, and their name will be used for autocompletation. In case of logger addon (FATAL, ERROR, INFO, ...).
[3]: Good practice to generate constructor as private in this kind of classes.
[4]: Class must implements compareTo. Because our key (propertyName) is unique, this attribute is used, and as usually equals is also implemented using compareTo.
[5]: Method that must returns the key (propertyName). Must be called getKey()

Next important class:

src/main/java/com/test/roo/addon/Commands.java

This class represents the command, and what options accept. This class must implement CommandMarker.

Let's take a look of important points:

@ScopeDevelopmentShell [1]
public class Commands implements CommandMarker {

private static Logger logger = Logger.getLogger(Commands.class.getName());

private Operations operations; [2]

public Commands(StaticFieldConverter staticFieldConverter, Operations operations) { [3]
Assert.notNull(staticFieldConverter, "Static field converter required");
Assert.notNull(operations, "Operations object required");
staticFieldConverter.add(PropertyName.class);
this.operations = operations;
logger.warning("Loaded " + Commands.class.getName() + "; try the 'welcome' commands");
}

@CliAvailabilityIndicator("welcome property") [4]
public boolean isPropertyAvailable() {
return true; // it's safe to always see the properties we expose
}

@CliCommand(value="welcome property", help="Obtains a pre-defined system property") [5]
public String property(@CliOption(key="name", mandatory=false, specifiedDefaultValue="USERNAME", unspecifiedDefaultValue="USERNAME", help="The property name you'd like to display") PropertyName propertyName) [6] {
return operations.getProperty(propertyName); [7]
}

....


[1]: Annotation for registering Command with automatic classpath scan.
[2]: Can be called the Business Object of our Command object. Contains logic of command, options and attributes.
[3]: Constructor, with two arguments, first of all StaticFieldConverter, which will be used for registering our previous, attributes class. (PropertyName.class). The second one is the business object class.
[4]: Annotates a method that can indicate whether a particular command is presently available or not. The string represents the name of the command or commands that this availability indicator represents.
[5]: Annotates a method indicating which method must be called when the command have to execute. value attribute is the name of the command (what user must enter), in case of logging is logging setup, and help attribute that shows the message when help is required.
[6]: Each option is annotated with CliOption. This method can contain as many CliOption annotations as required. Of all annotation attributes, one is so important, key. The attribute value must be the same as returned in getKey() function of PropertyName.class. Another important thing, look the METHOD attribute, it is our attribute class defined before. Spring Roo will inject the static final instance chosen by user.
[7]: Calling business object.

Last important class is:

src/main/java/com/test/roo/addon/Operations.java

This plain annotated class, is responsible of implementing command logic. Because of annotation, spring-roo can use constructor inject for providing some specific type instances. Those classes can be FileManager, PathResolver, MetadataService, ProjectOperations, ... Zero or more of these classes can be used in constructor. Let's take generated class as example:

@ScopeDevelopment [1]
public class Operations {

private static Logger logger = Logger.getLogger(Operations.class.getName());

private FileManager fileManager;
private MetadataService metadataService;

public Operations(FileManager fileManager, MetadataService metadataService) { [2]
Assert.notNull(fileManager, "File manager required");
Assert.notNull(metadataService, "Metadata service required");
this.fileManager = fileManager;
this.metadataService = metadataService;
}

public String getProperty(PropertyName propertyName) { [3]
Assert.notNull(propertyName, "Property name required");
String internalName = "user.name";
if (PropertyName.HOME_DIRECTORY.equals(propertyName)) {
internalName = "user.home";
}
return propertyName.getPropertyName() + " : " + System.getProperty(internalName);
}


[1]: Annotation used by Spring-Roo.
[2]: Constructor only using two spring-roo classes. FileManager and MetadataService, both of them will be injected by spring-roo.
[3]: Business Method called by Command class.

Another example could be LoggingOperations. That's the class used by logging addon.

LoggingOperations(FileManager fileManager, PathResolver pathResolver, MetadataService metadataService) where other class rather than used in our exmaple, is passed, PathResolver.

Now I am going to explain what is the purpose of most used injected classes in Operations class.

- FileManager, used for modifying the underlying disk storage. Can be used for scanning directories, create files, directories, delete, ...
- PathResolver, is primary mechanism to resolve paths.
- MetadataService is used for returnig metadata information. Usually used as
ProjectMetadata projectMetadata = (ProjectMetadata) metadataService.get(ProjectMetadata.getProjectIdentifier());
and ProjectMetadata offers convenience methods for acquiring the project name, top level project package name, registered dependencies and path name resolution services.
- ProjectOperations provides common project operations, such as adding dependencies, update project type, ...

And as an overview, that's all. Finally perform assembly command is executed and we have our addon ready.

Final step move generated jar to $ROO_HOME/dist and restart roo.

In next entries I will write how to modify applicationContext, generate specific files, ITD, …

0 comentarios: