martes, 25 de mayo de 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.

No hay comentarios: