Tuesday, May 25, 2010

Add-ons en Spring Roo 1.1.0.M1

Si ven las notas de la nueva liberación de Spring Roo 1.1.0.M1 encontraran entre otras cosas la siguiente nueva característica:

>> Rebuild Roo and its add-ons using OSGi infrastructure

¿Que quiere decir? pues que reestructuraron la creación de add-ons para seguir la infraestructura de OSGi.

Ok, no se nada de OSGi y su infraestructura pero hagamos unos experimentos para entender un poco como los add-ons en el nuevo Spring Roo 1.1.0.M1. (NOTA MENTAL: Hay que estudiar OSGi de todas formas :P).

Si ya han bajado la nueva versión notaran que en el folder de "samples" existe un script addon.roo (si no la han bajado tienen que hacerlo ya antes de continuar con el post). Usaremos este script para crear nuestro proyecto inicial para crear un addon.

El script luce asi:


project --topLevelPackage com.jabaddon.test.roo.addon --template ROO_ADDON_SIMPLE
perform eclipse
perform package

; Install using this command: felix shell start file:///somewhere/target/com.mycompany.myproject.roo.addon-0.1.0-SNAPSHOT.jar

; Verify success vía "osgi ps" and look for an entry at the bottom such as:
; [ 52] [Active ] [ 1] com.jabaddon.test.roo.addon (0.1.0.SNAPSHOT)

; You'll also have the new add-on's commands available (type 'welcome' and hit TAB)

; Now you're ready to import the project into Eclipse and start fine-tunng the add-on

; You can uninstall via: osgi uninstall --bundleSymbolicName com.jabaddon.test.roo.addon

; After uninstalling, you'll see the "welcome" commands have disappeared


Yo he sustituido el nombre del paquete para dejarlo como: com.jabaddon.test.roo.addon.

Este script lo tienen que correr en el shell de Roo de la siguiente forma:


roo> script --file [ruta-al-archivo]/addon.roo


Una ves ejecutado el script el proyecto esta listo para ser importado a un proyecto eclipse. Yo estoy usando el SpringSource Tool Suite 2.3.0 (creo que ya hay nueva version), pero cualquier eclipse debe servir.

Muy bien, notaran que en el paquete com.jabaddon.test.roo.addon existen las clases Commands.java, Operations.java, OperationsImpl.java y PropertyName.java; pues bien, borremos la clase PropertyName.java y dejemos la clase Commands.java como sigue:


package com.jabaddon.test.roo.addon;

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.osgi.service.component.ComponentContext;
import org.springframework.roo.project.Dependency;
import org.springframework.roo.shell.CliAvailabilityIndicator;
import org.springframework.roo.shell.CliCommand;
import org.springframework.roo.shell.CliOption;
import org.springframework.roo.shell.CommandMarker;

@Component
@Service
public class Commands implements CommandMarker {

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

@Reference
private Operations operations;

@CliAvailabilityIndicator({"jabaddon agregar dependencia", "jabadon listar dependencias", "jabaddon remover dependencia"})
public boolean isAddDependencyPropertyAvailable() {
// siempre esta disponible este comando
return operations.isPomXml();
}

@CliCommand(value="jabaddon agregar-dependencia", help="Agrega una dependencia al pom.xml")
public void addDependency(
@CliOption(key="groupId", mandatory=true, help="Id del grupo") String groupId,
@CliOption(key="artifactId", mandatory=true, help="Id del artefacto") String artifactId,
@CliOption(key="version", mandatory=true, help="Version") String version) {
logger.info("Agregando una dependencia...[" + groupId + ':' + artifactId + ':' + version + ']');
operations.addDependency(new Dependency(groupId, artifactId, version));

}

@CliCommand(value="jabaddon remover-dependencia", help="Remueve una dependencia al pom.xml")
public void removeDependency(
@CliOption(key="groupId", mandatory=true, help="Id del grupo") String groupId,
@CliOption(key="artifactId", mandatory=true, help="Id del artefacto") String artifactId,
@CliOption(key="version", mandatory=true, help="Version") String version) {
logger.info("Removiendo una dependencia...[" + groupId + ':' + artifactId + ':' + version + ']');
operations.removeDependency(new Dependency(groupId, artifactId, version));

}

@CliCommand(value="jabaddon listar-dependencias", help="Muestra las dependencias del pom.xml")
public void listDependencies() {
logger.info("Listando dependencias...");
operations.listDependencies();
}
}


luego la clase Operations.java como sigue:


package com.jabaddon.test.roo.addon;

import org.springframework.roo.project.Dependency;

public interface Operations {
void addDependency(Dependency dependency);
boolean isPomXml();
void listDependencies();
void removeDependency(Dependency dependency);
}


y por ultimo la clase OperationsImpl.java como sigue:


package com.jabaddon.test.roo.addon;

import java.util.List;
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.osgi.service.component.ComponentContext;
import org.springframework.roo.process.manager.FileManager;
import org.springframework.roo.project.Dependency;
import org.springframework.roo.project.Path;
import org.springframework.roo.project.PathResolver;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.support.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

@Component
@Service
public class OperationsImpl implements Operations {

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

@Reference private FileManager fileManager;
@Reference private PathResolver pathResolver;
@Reference private ProjectOperations projectOperations;

public boolean isPomXml() {
return fileManager.exists(getPomXmlIdentifier());
}

private String getPomXmlIdentifier() {
return pathResolver.getIdentifier(Path.ROOT, "pom.xml");
}

public void listDependencies() {
Document pomXmlDoc;
try {
pomXmlDoc = XmlUtils.getDocumentBuilder().parse(fileManager.getInputStream(getPomXmlIdentifier()));
}
catch (Exception e) {
throw new IllegalStateException(e);
}

Element rootElement = pomXmlDoc.getDocumentElement();
List dependencies = XmlUtils.findElements("//dependency", rootElement);
for (Element element : dependencies) {
logger.info(element.getElementsByTagName("groupId").item(0).getTextContent() + ':' +
element.getElementsByTagName("artifactId").item(0).getTextContent() + ':' +
element.getElementsByTagName("version").item(0).getTextContent());
}
}

public void addDependency(Dependency dependency) {
projectOperations.addDependency(dependency);
}

public void removeDependency(Dependency dependency) {
projectOperations.removeDependency(dependency);
}
}


Antes de entender el código vamos a compilar y empaquetar el addon:


roo> perform package


luego a instalar el addon con el siguiente comando:


roo> felix shell start file:///[ruta-al-proyecto]/target/com.jabaddon.test.roo.addon-0.1.0-SNAPSHOT.jar


Como ya había explicado un poco antes un mi post sobre Un sencillo Add-on de Spring Roo aquellas clases que implementan la interfaz org.springframework.roo.shell.CommandMarker son las clases que el shell de Spring Roo cargara para buscar definiciones de comandos con las anotaciones @CliAvailabilityIndicator para ver si los comandos están activos y @CliCommand para ejecutar el comando.

Hasta la version de Spring Roo 1.0.x, se usaron las anotaciones @ScopeDevelopmentShell y @ScopeDevelopment para marcar los componentes y como se inyectaban uno con otro. En esta nueva versión ahora se usan las anotaciones @Component y @Service para marcar los componentes que el Shell debe instanciar y para hacer las inyecciones se usa la anotación @Reference. Notaran que estas anotaciones son del framework Felix de Apache que es una implementación de la especificación de OSGi.

Ahora, si revisan el código con calma verán que tanto la clase Commands.java como OperationsImpl.java están marcados con las anotaciones @Component y @Service.

También verán que en la clase Commmands.java hay un @Reference a la interfaz Operations.java. Y en la clase OperationsImpl.java hay varios @Reference a componentes propios del framework Spring Roo como son: FileManager, PathResolver y ProjectOperations.

FileManager es un componente que nos ayuda a crear archivos y directorios, abrir archivos, borrar arhivos, actualizarlos entre otras operaciones.

PathResolver es un componente que nos ayuda a obtener los paths hacia los diversos recursos del proyecto.

ProjectOperations es un componente que nos ayuda a administrar dependencias, plugins, propiedades del pom.xml del proyecto.

¿Como se inyecto la dependencia de OperationsImpl.java (que implementa Operations.java) en Commands.java? Podríamos deducir que Spring Roo a la hora de crear las instancias de las clases marcadas con @Component y @Service ve que interfaces implementa y de acuerdo a eso inyecta la instancia adecuada. Pero no es así, el pom.xml tiene configurados plugins que a la hora de construir el proyecto generan unos archivos en /target/scr-plugin-generated/OSGI-INF/scrinfo.xml y /target/scr-plugin-generated/OSGI-INF/serviceComponents.xml (básicamente tienen el mismo contenido los dos archivos) donde vienen las datos de los componentes de OSGi como son: su nombre, la clase, interfaces que implementa, relacion con otros componentes. Estos datos son utilizados por la implementación de OSGi para instanciar e inyectar las dependencias de los componentes.



<components xmlns:scr="http://www.osgi.org/xmlns/scr/v1.0.0">
<scr:component enabled="true" name="com.mycompany.myproject.roo.addon.Commands">
<implementation class="com.mycompany.myproject.roo.addon.Commands"/>
<service servicefactory="false">
<provide interface="org.springframework.roo.shell.CommandMarker"/>
</service>
<property name="service.pid" value="com.mycompany.myproject.roo.addon.Commands"/>
<reference name="staticFieldConverter" interface="org.springframework.roo.shell.converters.StaticFieldConverter" cardinality="1..1" policy="static" bind="bindStaticFieldConverter" unbind="unbindStaticFieldConverter"/>
<reference name="operations" interface="com.mycompany.myproject.roo.addon.Operations" cardinality="1..1" policy="static" bind="bindOperations" unbind="unbindOperations"/>
</scr:component>
<scr:component enabled="true" name="com.mycompany.myproject.roo.addon.OperationsImpl">
<implementation class="com.mycompany.myproject.roo.addon.OperationsImpl"/>
<service servicefactory="false">
<provide interface="com.mycompany.myproject.roo.addon.Operations"/>
</service>
<property name="service.pid" value="com.mycompany.myproject.roo.addon.OperationsImpl"/>
<reference name="metadataService" interface="org.springframework.roo.metadata.MetadataService" cardinality="1..1" policy="static" bind="bindMetadataService" unbind="unbindMetadataService"/>
<reference name="fileManager" interface="org.springframework.roo.process.manager.FileManager" cardinality="1..1" policy="static" bind="bindFileManager" unbind="unbindFileManager"/>
</scr:component>
</components>


Estos archivos xml que describen como se instancian e inyectan las relaciones entre los componentes se agregan al JAR del addon.

Ok, ahora veamos que hace el addon.

Tiene el comando 'jabaddon listar dependencias' que lo que hace es abrir el archivo pom.xml con ayuda de los componentes FileManager y PathResolver, y hace una busqueda de todos los elementos xml dentro del archivo que son 'dependency' para después solo imprimir el groupId, artifactId y versión. Vean el método 'OperationsImpl.listDependencies'.

Por ultimo, los comandos 'jabaddon agregar dependencia' y 'jabaddon remover dependencia' hacen uso del componente ProjectOperations para agregar una dependencia al pom.xml o removerla. Vean los métodos 'OperationsImpl.addDependency' y 'OperationsImpl.removeDependendy'.

Como pueden ver a cambiado un poco la forma en que se crean Addons en Spring 1.1.0.M1 con respecto a la versión anterior. Queda pendiente estudiar sobre OSGi para entender por completo como trabajan pero creo que con este pequeño experimento nos damos una idea clara de que se puede hacer.

Lamentablemente aun no existe mucha documentación sobre el desarrollo de Addons para Spring Roo, y por ejemplo, si miran el pdf que viene junto con la distribución de Roo verán que las secciones que deberían explicar todo esto aun están por definirse.

Pero por lo mientras si deseen adentrase mas en la construcción de Addons bien pueden bajar el código de Spring Roo 1.1.0.M1 en con Git en:


git clone git://git.springsource.org/roo/roo.git


O tambien pueden bajar el código de un Addon para Flex que esta haciendo Jeremy Grelle (quien es Ingeniero de Software en SpringSource) y que se encuentra aqui.

Esperemos que pronto esa documentación que estamos esperando este lista.

Si conocen de mas addons por favor compartan la liga para poder bajarlos.

Saturday, May 15, 2010

Usabilidad de la consola de Spring Roo

A todos nos ha pasado. Cuando sale un nuevo framework o herramienta y leemos un poco de lo que hace y esto nos emociona, corremos a bajar el software y lo instalamos inmediatamente. Pero si al estar usandolo un rato encontramos que para lograr que un programa ejecute o para que un simple 'hola mundo' despliegue algo en pantalla tenemos que configurar muchas cosas o realizar muchos pasos nuestra emoción muere rápido. La simplicidad y usabilidad de un producto es una característica muy importante, es el punto de entrada para que nuestro software venda mucho y/o sea muy popular.

En este corto post hablare un poco de la gran usabilidad que tiene la consola de Spring Roo y como esa simplicidad de uso es una de las razones de la popularidad que esta tomando esta herramienta.

Empecemos entrando a la consola de Roo.

Si damos TAB aparecerá algo como:


roo>

*/ /* // ; addon date development exit help hint metadata poll project
quit reference script system version


Hay que notar los comandos que aparecen.

Por el momento Roo solo nos brinda ayuda (help), guia (hint), la fecha del sistema (date), la versión de Roo (version), entre otros comandos.

Si ya han usando Roo antes recordaran que hay comandos para crear entidades (entity) y sus controllers web (controller), pero en este momento Roo no nos muestra esos comandos, ¿porque? la consola de Roo tiene la capacidad de ser consciente del contexto actual bajo el cual se esta ejecutando un comando. Por esta razón oculta comandos como entity y controller, porque para poder crear una entidad antes es necesario tener creado un proyecto y tener configurado el método de persistencia, solo cuando esas condiciones se cumplen Roo nos muestra los comandos.

Por ponerlo en otras palabras, la consola de Spring Roo oculta los comandos que no aplican

Por ejemplo, tratemos de ejecutar el comando entity en la consola de Roo en este momento. Como pueden notar no hemos creado un proyecto ni configurado su persistencia. Veamos que pasa:


roo> entity
Command 'entity' was found but is not currently available (type 'help' then ENTER to learn about this command)


Roo nos dice que el comando 'entity' no esta disponible por el momento.

Ok, ahora vamos a crear el proyecto con el comando 'project' pero no vamos a teclear todo el comando solo la letra 'p' y vamos a dar TAB, veamos que pasa:


roo> p

poll now poll speed poll status project


Roo nos muestra todos aquellos comandos disponibles por el momento que empiecen por la letra 'p'. Esta es otra característica de la consola de Roo la capacidad de completar comandos con TAB, pero no solo nos sirve para completar comandos parcialmente escritos también nos ayuda a que Roo nos muestre todas aquellas opciones que son 'requeridas' por un comando o si tecleamos '--' y TAB nos muestre las opciones (requeridos y no requeridas) del comando. Por ejemplo, tecleemos 'project' y demos TAB:


roo> project --topLevelPackage


Roo nos completo la opción 'topLevelPackage' del comando 'project' ya que esta opción es requerida. ¿Como nos podemos dar cuenta de que esta opción es requerida?, es simple, si tecleamos el comando 'project' y damos ENTER:


roo> project
You must specify a default option (otherwise known as option 'topLevelPackage') for this command


Si bien Roo tiene muchos comandos y conforme salgan mas liberaciones y mejoras tendrá muchos mas, no debemos preocuparnos ya que con la gran usabilidad que tiene podemos obtener fácilmente ayuda y guía de como usarlos.

Siempre podemos ver las opciones y descripción completa de un comando usando 'help':


roo> help project
Keyword: project
Description: Creates a new project
Keyword: ** default **
Keyword: topLevelPackage
Help: The uppermost package name (this becomes the in Maven and also the '~' value when using Roo's shell)
Mandatory: true
Default if specified: '__NULL__'
Default if unspecified: '__NULL__'

Keyword: projectName
Help: The name of the project (last segment of package name used as default)
Mandatory: false
Default if specified: '__NULL__'
Default if unspecified: '__NULL__'

Keyword: java
Help: Forces a particular major version of Java to be used (will be auto-detected if unspecified; specify 5 or 6 or 7 only)
Mandatory: false
Default if specified: '__NULL__'
Default if unspecified: '__NULL__'

Keyword: template
Help: The type of project to create (defaults to STANDARD_PROJECT)
Mandatory: false
Default if specified: 'STANDARD_PROJECT'
Default if unspecified: 'STANDARD_PROJECT'


Keyword: project
Description: Creates a new project
Keyword: ** default **
Keyword: topLevelPackage
Help: The uppermost package name (this becomes the in Maven and also the '~' value when using Roo's shell)
Mandatory: true
Default if specified: '__NULL__'
Default if unspecified: '__NULL__'

Keyword: projectName
Help: The name of the project (last segment of package name used as default)
Mandatory: false
Default if specified: '__NULL__'
Default if unspecified: '__NULL__'

Keyword: java
Help: Forces a particular major version of Java to be used (will be auto-detected if unspecified; specify 5 or 6 or 7 only)
Mandatory: false
Default if specified: '__NULL__'
Default if unspecified: '__NULL__'

Keyword: template
Help: The type of project to create (defaults to STANDARD_PROJECT)
Mandatory: false
Default if specified: 'STANDARD_PROJECT'
Default if unspecified: 'STANDARD_PROJECT'

* project - Creates a new project

** Type 'hint' (without the quotes) and hit ENTER for step-by-step guidance **


Es todo por ahora. Comenten por favor.