Monday, August 15, 2016

Desde el 2012 no he escrito nada en este blog. Deberia retomarlo.

Tuesday, October 2, 2012

Linux: Administraci贸n de espacio en disco


Todos nos hemos topado con el problema de espacio en disco, ya sea porque en nuestra maquina se nos acaba el espacio por tanto mp3's o videos o en el servidor que estamos administrando los backups y logs que se generan empiezan a consumir ese precioso espacio que al final nos puede afectar en el rendimiento del sistema operativo y por consiguiente de las aplicaciones que ah铆 corren. Es necesario conocer los comandos adecuados para conocer el espacio que se esta ocupando, quien lo esta ocupando y ver que podemos eliminar para poder tener mas.

Conocer el espacio usado en disco

Lo que primero hay que conocer es el total de espacio que estamos usando en el disco duro y para realizar eso usamos el comando:

df -h

El cual nos dice el espacio del Sistema de archivos en la columna Size, el espacio usado en la columna Used, el espacio disponible en la columna Avail y el porcentaje que representa el espacio usado  y por ultimo el punto de montaje de ese sistema de archivos. Se usa la opci贸n "h" (human-readable) para que nos de valores que nos arroje el comando vengan en valores de Kilobytes, Megabytes o Gigabytes y no en bloques que es como por default imprime los valores.

Conocer el espacio usado por un folder en especifico
Ahora, si lo que deseamos conocer es el espacio especifico de un folder debemos correr el comando:

du folder -ha

Donde folder es el nombre o ruta hacia el folder del cual queremos conocer el espacio que esta usando. Como salida este comando imprime el tama帽o de cada uno de los archivos y folders contenidos dentro de el. Se usa la opci贸n "h" para, igual que en el comando 'df', los valores que arroje vengan en Kilobytes, Megabytes o Gigabytes. La opci贸n "a" (all) se usa para que se impriman el tama帽o tanto folders como archivos ya que si no se usa esta opci贸n solo se imprimen folders.

Existen otras opciones a usar para este comando y como en la mayor铆a de los comandos en linux esas opciones se puede conocer ejecutando el comando de la siguiente forma:


du --help

La salida del comando 'du' se podr铆a ordenar para as铆 poder obtener, por ejemplo, los 10 folders y archivos que son los que ocupan mas espacio dentro de un cierto folder:


du folder -ka | sort -n -r | head -n10

Se usa la opcion 'k' en el comando 'du' para que en lugar de que nos imprima valores en Kilobytes, Megabytes o Gigabytes solo imprima todos los valores unificados a Kilobytes y el comando 'sort' pueda ordenarlos de manera uniforme. Y para solo obtener los 10 primeros se utiliza el comando 'head'. As铆 que si, por ejemplo, se desea obtener los primeros 5 solo hay que variar el valor en la opci贸n 'n' del comando 'head'.

Pero si lo que se desea saber es el tama帽o total de cierto folder sin tener que ver el tama帽o de su contenido se usa el comando:


du folder -hs

La opci贸n 's' (summarize) hace que solo se imprima el valor total del folder si que se muestre el tama帽o de cada folder y archivo que contiene.

Hay que tomar en cuenta que para obtener los valores de tama帽o el comando tiene que recorrer el contenido del folder para ir sumando el tama帽o de cada elemento que contiene, as铆 que, si el folder es muy grande la ejecuci贸n de este comando puede tardar.

Borrar archivos y folders

Ya que se conoce el elemento que se desea borrar se hace uso del comando 'rm' para eliminarlo. Si se trata de un archivo se usa el comando:

rm archivo

pero si se trata de un folder y nos interesa borrar el mismo y su contenido, se usa el comando:

rm -rf folder

hay que tomar en cuenta que una ves borrados ya no es posible recuperarlos as铆 que estos comandos se tienen que usar con mucho cuidado.

Tuesday, August 7, 2012

Archivo basico log4j.properties

El archivo simple de lo4j.properties con el que inicio en los proyectos y como siempre se me esta olvidando, mejor lo posteo:

Noten que esta el appender de Spring Framework, si no lo usan pues quitenlo.

Monday, April 16, 2012

Configurar Alfresco para usar MySQL

Configurar Alfresco para usar MySQL


La instalaci贸n de Alfresco Community Edition 4.0d por default configura PostgreSQL como su base de datos pero si usas MySQL, como es nuestro caso, tal vez quieras no usar tantas bases de datos y unificar a que todas las Apps de tu Sistema usen MySQL.

Configuraci贸n para usar MySQL


Crear una base de datos 'alfresco' al igual que un usuario 'alfresco' con privilegios suficientes para realizar cambios al esquema 'alfresco' (esto es debido a que al iniciar Alfresco crea las tablas/datos necesarios, despu茅s se podr铆an quitar los privilegios para alterar la base de datos).

CREATE USER alfresco IDENTIFIED BY 'alfresco';

CREATE SCHEMA alfresco;

GRANT ALL TO alfresco@localhost IDENTIFIED BY 'alfresco';

Editar el archivo [instalacion_alfresco]/tomcat/shared/classes/alfresco-global.properties:

db.name=alfresco
db.username=alfresco
db.password=alfresco
db.host=localhost
db.port=3306
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://${db.port}/${db.name}
db.pool.validate.query=select 1

Colocar el jar del driver MySQL en el folder lib de Tomcat, que esta ubicado en [intalacion_alfresco]/tomcat/lib.

NOTA: Se puede usar otro usuario, password, y nombre de esquema distinto al especificado aqu铆, no hay restricci贸n. Solo hay que colocar correctamente los nombres que se lleguen a usar en el archivo alfresco-global.properties.

Reconstruir los indices de Solr


Posiblemente hay que obligar a Solr a que reindexe, para lograr eso hay que realizar lo siguiente:

  1. borrar el folder en [instalacion_alfresco]/alf_data/solr/archive/SpacesStore/index
  2. borrar el folder en [instalacion_alfresco]/alf_data/solr/workspace/SpacesStore/index
  3. borrar los archivos en [instalacion_alfresco]/alf_data/solr/archive-SpaceStore/alfrescoModels/*
  4. borrar los archivos en [instalacion_alfresco]/alf_data/solr/workspace-Spacestore/alfrescoModels/*
  5. Reiniciar Alfresco

Tuesday, March 8, 2011

Clean Coders

Hace unos d铆as Robert C. Martin (mejor conocido como Uncle Bob) libero un sitio con v铆deos para profesionales de Software llamado cleancoders.com.

Hasta el momento solo tiene 2 v铆deos, uno de ellos es introductorio a lo que vamos a poder encontrar en el sitio y el segundo habla de las buenas practicas al usar "nombres" en nuestro c贸digo. Los v铆deos no son gratis, hay que comprarlos y el 煤nico m茅todo por ahora para poder adquirirlos es teniendo una cuenta en paypal.

Si ya han visto el estilo de Uncle Bob para dar presentaciones sabr谩n que es muy agradable y divertido para exponer los temas. En el caso de los v铆deos que encontramos en cleancoders.com, va mucho mas all谩 ya que se apoya de mas recursos multimedia y efectos para llamar nuestra atenci贸n. Debo confesar que en varias ocasiones viendo estos v铆deos si solt茅 varias carcajadas.

Una cosa que no me gusto (o no he visto bien) es que los v铆deos solo se puede ver en linea, no puedes bajarlos, ni meterlos a tu celular, ipod, o lo que sea. Claro que se entiende porque no dejan bajarlos ya que f谩cilmente podr铆amos "compartirlos" con alguien mas.

El v铆deo introductorio solo tiene el costo de $1 dolar, el segundo ya cuesta $12 dolares. Al principio no se me hizo caro, pero me puse a pensar que si llegase a sacar 10 v铆deos ya serian $120 dolares y eso ya es una lana.

En fin, los v铆deos esta muy recomendables, si son caros o baratos pues ya depender谩 de cada quien, pero de que se aprende algo se aprende.

De hecho, a partir de ver estos v铆deos regrese a ver mi lista de wishlist en Amazon y por fin me anime a pedir mi copia de libro Clean Code del propio Robert C. Martin.

Saturday, October 16, 2010

Ingenier铆a reversa de base de datos con Spring Roo

Desde hace ya tiempo quer铆a probar esta nueva funcionalidad de Spring Roo.

La versi贸n de Roo que estoy usando es: 1.1.0.RC1.

Hace ya algunas versiones anteriores de Spring Roo, los desarrolladores de esta herramienta hab铆an prometido implementar alg煤n mecanismo para poder hacer ingenier铆a reversa de un esquema de base de datos existente y generar c贸digo Java a partir de el. Pues ya han cumplido y han agregado los siguientes comandos a la consola de Roo:


  • database reverse engineer. Este comando es el que realiza la 'magia' de crear/sincronizar las entidades a partir de una base de datos existente.

  • database introspect. Este comando muestra los metadatos almacenados por Spring Roo de la base de datos.



Se recomienda que cuando estas haciendo un proyecto en el cual se esta usando un framework ORM como Hibernate o JPA el dise帽o del esquema de la base sea un derivado del dise帽o de las clases que contendr谩 el proyecto. Pero no siempre es as铆, ya sea porque en el equipo se tiene a una o varias personas especializadas en dise帽o de base de datos o porque se trata de un proyecto para el cual ya se tiene una base de datos existente.

Para empezar crearemos un proyecto con el cl谩sico comando 'project':


roo> project --topLevelPackage com.jabaddon.roo.dbtopojo


La ejecuci贸n de este comando genera lo siguiente:


Created /Volumes/AbaddonData/Abaddon/Projects/JAbaddon/SpringRooProjects/BDToPojos/pom.xml
Created SRC_MAIN_JAVA
Created SRC_MAIN_RESOURCES
Created SRC_TEST_JAVA
Created SRC_TEST_RESOURCES
Created SRC_MAIN_WEBAPP
Created SRC_MAIN_RESOURCES/META-INF/spring
Created SRC_MAIN_RESOURCES/META-INF/spring/applicationContext.xml
Created SRC_MAIN_RESOURCES/log4j.properties


Lo siguiente es configurar la persistencia del proyecto:


roo> persistence setup --provider HIBERNATE --database MYSQL --databaseName roo_db_pojo --userName xxxx --password yyyy


Donde 'xxxx' se refiere al nombre del usuario y 'yyyy' al password del mismo para conectarse a la base de datos con nombre 'roo_db_pojo'.

La ejecucion del comando para configurar la persistencia genera lo siguiente:


anaged SRC_MAIN_RESOURCES/META-INF/spring/applicationContext.xml
Created SRC_MAIN_RESOURCES/META-INF/persistence.xml
Please enter your database details in src/main/resources/META-INF/spring/database.properties.
Created SRC_MAIN_RESOURCES/META-INF/spring/database.properties
Managed ROOT/pom.xml [Added dependency mysql:mysql-connector-java:5.1.13]
Managed ROOT/pom.xml [Added dependency org.hibernate:hibernate-core:3.5.5-Final]
Managed ROOT/pom.xml [Added dependency org.hibernate:hibernate-entitymanager:3.5.5-Final]
Managed ROOT/pom.xml [Added dependency org.hibernate.javax.persistence:hibernate-jpa-2.0-api:1.0.0.Final]
Managed ROOT/pom.xml [Added dependency org.hibernate:hibernate-validator:4.1.0.Final]
Managed ROOT/pom.xml [Added dependency javax.validation:validation-api:1.0.0.GA]
Managed ROOT/pom.xml [Added dependency cglib:cglib-nodep:2.2]
Managed ROOT/pom.xml [Added dependency javax.transaction:jta:1.1]
Managed ROOT/pom.xml [Added dependency org.springframework:spring-jdbc:${spring.version}]
Managed ROOT/pom.xml [Added dependency org.springframework:spring-orm:${spring.version}]
Managed ROOT/pom.xml [Added dependency commons-pool:commons-pool:1.5.4]
Managed ROOT/pom.xml [Added dependency commons-dbcp:commons-dbcp:1.3]
Managed ROOT/pom.xml


Si vemos el contenido del archivo 'src/main/resources/META-INF/spring/database.properties' debe lucir as铆:


database.password=yyyy
database.url=jdbc\:mysql\://localhost\:3306/roo_db_pojo
database.username=xxxx
database.driverClassName=com.mysql.jdbc.Driver


Muy bien, hasta aqu铆 ya tenemos nuestro proyecto vac铆o el cual ya esta configurado para conectarse a una base de datos. Asi pues, lo siguiente es crear la base de datos (si aun no lo han hecho).

Ya con nuestra base de datos y nuestro proyecto de Spring Roo creado vamos a crear una tabla sencilla para almacenar personas:


CREATE TABLE `persona` (
`id_persona` int(11) NOT NULL,
`nombre` varchar(50) character set latin1 NOT NULL,
`apellido` varchar(50) character set latin1 NOT NULL,
PRIMARY KEY (`id_persona`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_spanish_ci;


Si ejecutamos el comando para hacer ingenier铆a reversa como sigue:


roo> database reverse engineer --package com.jabaddon.roo.dbtopojo.dominio


Obtendremos lo siguiente:


Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Persona.java
Created com.jabaddon.roo.dbtopojo.dominio.Persona
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Persona_Roo_Entity.aj
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Persona_Roo_DbManaged.aj
Managed SRC_MAIN_RESOURCES/META-INF/persistence.xml
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Persona_Roo_Configurable.aj
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Persona_Roo_ToString.aj


La primera vez que se ejecute el comando de ingenier铆a inversa es necesario especificar el paquete en el cual las entidades ser谩n creadas.

Como ven Roo ha creado una entidad Persona y ha generado los cl谩sicos .aj derivados del archivo .java. Aunque existe un nuevo .aj llamado 'Persona_Roo_DbManaged.aj', este aspecto es el que contiene los campos de nuestra entidad que mapean a las columnas que Roo encontro en la tabla 'PERSONA'.


package com.jabaddon.roo.dbtopojo.dominio;

import java.lang.String;
import javax.persistence.Column;
import javax.validation.constraints.NotNull;

privileged aspect Persona_Roo_DbManaged {

@Column(name = "nombre", length = 50)
@NotNull
private String Persona.nombre;

@Column(name = "apellido", length = 50)
@NotNull
private String Persona.apellido;

public String Persona.getNombre() {
return this.nombre;
}

public void Persona.setNombre(String nombre) {
this.nombre = nombre;
}

public String Persona.getApellido() {
return this.apellido;
}

public void Persona.setApellido(String apellido) {
this.apellido = apellido;
}

}


Ahora supongamos que se nos olvido agregarle una columna a nuestra tabla PERSONA y en esa columna deseamos guardar la fecha de nacimiento.

Si agregamos la columna de fecha de nacimiento a nuestra tabla y corremos de nueva cuenta el comando 'database reverse engineer' obtendremos lo siguiente:


Managed ROOT/.roo-dbre
Managed SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Persona_Roo_DbManaged.aj
Managed SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Persona_Roo_ToString.aj


Y si abrimos el archivo 'Persona_Roo_DbManaged.aj' veremos que ya tiene un nuevo campo de fecha de nacimiento:


@Column(name = "fecha_nacimiento")
@NotNull
@Temporal(TemporalType.DATE)
@DateTimeFormat(style = "S-")
private Date Persona.fechaNacimiento;


Si se est谩n preguntando ¿que pasa si yo agrego un campo al archivo Persona.java?, pues la respuesta es lo mismo que ha hecho Roo desde su primera version y es que simplemente regenerara los archivos .aj necesarios para que reflejen el nuevo campo. Por ejemplo si modificamos el archivo Pesona.java para agregarle una nueva propiedad 'nick':


package com.jabaddon.roo.dbtopojo.dominio;

import javax.persistence.Entity;
import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.tostring.RooToString;
import org.springframework.roo.addon.entity.RooEntity;
import org.springframework.roo.addon.dbre.RooDbManaged;
import javax.persistence.Table;

@Entity
@RooJavaBean
@RooToString
@RooEntity(versionField = "")
@RooDbManaged(automaticallyDelete = true)
@Table(name = "persona", catalog = "roo_db_pojo")
public class Persona {

@NotNull
@Column(name = "nick", length = 10)
private String nick;
}


Al momento de salvar el archivo, Roo monitorea el cambio y genera el .aj JavaBean y administra el .aj ToString:


[Timer-0] Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Persona_Roo_JavaBean.aj
[Timer-0] Managed SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Persona_Roo_ToString.aj


Interesante, ¿no lo creen?

Hasta ahorita se creo una tabla muy sencilla que Roo fue capaz de detectar y generar una entidad a partir de la definici贸n de la tabla, pero pongamos a prueba esta funcionalidad de Roo generando tablas con relaciones 1:N, N:1 y el N:M.

Para la relaci贸n 1:N, agregaremos otra tabla que represente los tel茅fonos de una persona y su debida relaci贸n con un Foreign key:


CREATE TABLE `telefono` (
`id_telefono` int(11) NOT NULL,
`numero` varchar(10) character set latin1 NOT NULL,
`id_tipo_telefono` int(11) NOT NULL,
`id_persona` int(11) NOT NULL,
PRIMARY KEY (`id_telefono`),
KEY `id_persona` (`id_persona`),
CONSTRAINT `fk_telefono2persona` FOREIGN KEY (`id_persona`) REFERENCES `persona` (`id_persona`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_spanish_ci;


Y si ejecutamos de nueva cuenta el comando 'database reverse engineer' obtendremos:


Managed ROOT/.roo-dbre
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Telefono.java
Created com.jabaddon.roo.dbtopojo.dominio.Telefono
Managed SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Persona_Roo_DbManaged.aj
Managed SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Persona_Roo_ToString.aj
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Telefono_Roo_Entity.aj
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Telefono_Roo_DbManaged.aj
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Telefono_Roo_Configurable.aj
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Telefono_Roo_ToString.aj


Roo a creado una nueva entidad Telefono y ha modificado la entidad Persona. Si abrimos los archivos Persona_Roo_DbManaged.aj y Telefono_Roo_DbManaged.aj:

NOTA: Voy a omitir los metodos get y set.


privileged aspect Persona_Roo_DbManaged {

@OneToMany(mappedBy = "persona")
private Set Persona.telefonoes;

@Column(name = "nombre", length = 50)
@NotNull
private String Persona.nombre;

@Column(name = "apellido", length = 50)
@NotNull
private String Persona.apellido;

@Column(name = "fecha_nacimiento")
@NotNull
@Temporal(TemporalType.DATE)
@DateTimeFormat(style = "S-")
private Date Persona.fechaNacimiento;

....
}



privileged aspect Telefono_Roo_DbManaged {

@ManyToOne
@JoinColumn(name = "id_persona", referencedColumnName = "id_persona")
private Persona Telefono.persona;

@Column(name = "numero", length = 10)
@NotNull
private String Telefono.numero;

@Column(name = "id_tipo_telefono")
@NotNull
private Integer Telefono.idTipoTelefono;

...
}


Perfecto! Roo ha relacionado las entidades de Persona y Telefono con un @OneToMany de Persona a Telefono y un @ManyToOne de Telefono a Persona.

Continuemos con la relaci贸n N:1 creando un catalogo para los tipos de telefono:


CREATE TABLE `cat_tipo_telefono` (
`id_tipo_telefono` int(11) NOT NULL,
`nombre` varchar(50) NOT NULL,
PRIMARY KEY (`id_tipo_telefono`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_spanish_ci;


y modificando la tabla de Telefono para que tenga la relacion con su catalogo de tipo de telefono:


CREATE TABLE `telefono` (
`id_telefono` int(11) NOT NULL,
`numero` varchar(10) character set latin1 NOT NULL,
`id_tipo_telefono` int(11) NOT NULL,
`id_persona` int(11) NOT NULL,
PRIMARY KEY (`id_telefono`),
KEY `index_id_persona` (`id_persona`),
KEY `index_id_tipo_telefono` (`id_tipo_telefono`),
CONSTRAINT `fk_telefono2persona` FOREIGN KEY (`id_persona`) REFERENCES `persona` (`id_persona`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `fk_tipo2catalogo` FOREIGN KEY (`id_tipo_telefono`) REFERENCES `cat_tipo_telefono` (`id_tipo_telefono`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_spanish_ci;


De nueva cuenta si ejecutamos de nueva cuenta el comando 'database reverse engineer' obtendremos:


Managed ROOT/.roo-dbre
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/CatTipoTelefono.java
Created com.jabaddon.roo.dbtopojo.dominio.CatTipoTelefono
Managed SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Telefono_Roo_DbManaged.aj
Managed SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Telefono_Roo_ToString.aj
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/CatTipoTelefono_Roo_Entity.aj
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/CatTipoTelefono_Roo_DbManaged.aj
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/CatTipoTelefono_Roo_Configurable.aj
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/CatTipoTelefono_Roo_ToString.aj


Roo ha creado una entidad que CatTipoTelefono y modifico la entidad Telefono. Si abrimos todos los archivos Persona_Roo_DbManaged.aj, Telefono_Roo_DbManaged.aj y CatTipoTelefono_Roo_DbManaged.aj:


privileged aspect Persona_Roo_DbManaged {

@OneToMany(mappedBy = "persona")
private Set Persona.telefonoes;

@Column(name = "nombre", length = 50)
@NotNull
private String Persona.nombre;

@Column(name = "apellido", length = 50)
@NotNull
private String Persona.apellido;

@Column(name = "fecha_nacimiento")
@NotNull
@Temporal(TemporalType.DATE)
@DateTimeFormat(style = "S-")
private Date Persona.fechaNacimiento;
...
}



privileged aspect Telefono_Roo_DbManaged {

@ManyToOne
@JoinColumn(name = "id_persona", referencedColumnName = "id_persona")
private Persona Telefono.persona;

@ManyToOne
@JoinColumn(name = "id_tipo_telefono", referencedColumnName = "id_tipo_telefono")
private CatTipoTelefono Telefono.catTipoTelefono;

@Column(name = "numero", length = 10)
@NotNull
private String Telefono.numero;
...
}



privileged aspect CatTipoTelefono_Roo_DbManaged {

@OneToMany(mappedBy = "catTipoTelefono")
private Set CatTipoTelefono.telefonoes;

@Column(name = "nombre", length = 50)
@NotNull
private String CatTipoTelefono.nombre;
...
}


Muy interesante.

NOTA: Obviamente no es una buena idea que la entidad CatTipoTelefono tenga una colecci贸n de los tel茅fonos que tiene relacionados. Esto es algo que tiene que mejorar Spring Roo. Me llevo de tarea investigar si hay alguna forma de decirle a Roo que evite hacer esto.

Ahora vamos a ver como se comporta con una relacion N:M. Asi que, vamos a crear una tabla de Solicitudes, otra de documentos y una que defina los documentos que tiene una solicitud:


CREATE TABLE `solicitud` (
`id_solicitud` int(11) NOT NULL,
`folio` varchar(10) NOT NULL,
PRIMARY KEY (`id_solicitud`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;



CREATE TABLE `documento` (
`id_documento` int(11) NOT NULL,
`nombre` varchar(50) NOT NULL,
PRIMARY KEY (`id_documento`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;



CREATE TABLE `solicitud_documento` (
`id_solicitud` int(11) NOT NULL,
`id_documento` int(11) NOT NULL,
PRIMARY KEY (`id_documento`,`id_solicitud`),
CONSTRAINT `fk_documento2documento` FOREIGN KEY (`id_documento`) REFERENCES `documento` (`id_documento`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `fk_solicitud2solicitud` FOREIGN KEY (`id_solicitud`) REFERENCES `solicitud` (`id_solicitud`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_spanish_ci;


al correr el comando 'database reverse engineer' obtenemos:


Managed ROOT/.roo-dbre
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Documento.java
Created com.jabaddon.roo.dbtopojo.dominio.Documento
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Solicitud.java
Created com.jabaddon.roo.dbtopojo.dominio.Solicitud
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Solicitud_Roo_Entity.aj
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Solicitud_Roo_DbManaged.aj
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Solicitud_Roo_Configurable.aj
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Solicitud_Roo_ToString.aj
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Documento_Roo_Entity.aj
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Documento_Roo_DbManaged.aj
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Documento_Roo_Configurable.aj
Created SRC_MAIN_JAVA/com/jabaddon/roo/dbtopojo/dominio/Documento_Roo_ToString.aj


Roo ha creado las entidades Solicitud y Documento y si vemos los archivos Solicitud_Roo_DbManaged.aj y Documento_Roo_DbManaged.aj lucen asi:


privileged aspect Solicitud_Roo_DbManaged {

@ManyToMany(mappedBy = "solicituds")
private Set Solicitud.documentoes;

@Column(name = "folio", length = 10)
@NotNull
private String Solicitud.folio;

...
}



privileged aspect Documento_Roo_DbManaged {

@ManyToMany
@JoinTable(name = "solicitud_documento", joinColumns = { @JoinColumn(name = "id_documento") }, inverseJoinColumns = { @JoinColumn(name = "id_solicitud") })
private Set Documento.solicituds;

@Column(name = "nombre", length = 50)
@NotNull
private String Documento.nombre;

...
}


Roo detecto la relaci贸n N:M entre Solicitud y Documento y puso una relaciones @ManyToMany en las entidades utilizando como @JoinTable la tabla SOLICITUD_DOCUMENTO.

Por ultimo, veamos que hace el comando 'database introspect':


roo> database introspect --schema roo_db_pojo


El comando 'database introspect' pide como argumento el esquema de la base de datos y nos da como salida:
























































































Roo tiene almacenado la estructura de la base de datos en el archivo ROOT/.roo-dbre (este archivo apareci贸 varias veces al ejecutar el comando 'database reverse engineer').

Muy interesante y util esta funcionalidad de poder hacer ingenieria reversa de una base de datos existente con Spring Roo.

Creo que es todo para este post, por favor comenten.

Wednesday, September 8, 2010

Reunion 17a. de SpringHispano y presentaci贸n de Spring Roo

El s谩bado pasado 4 de Septiembre del 2010 fue la 17a. reuni贸n de la comunidad de SpringHispano y 2do aniversario de la misma realizando reuniones mensuales.

Primero me gustar铆a felicitar a Domingo Suarez (@domix) y Juan Jos茅 Reyes (@neodevelop) por este 2do aniversario y por el esfuerzo que han realizado creando y manteniendo esta comunidad. Sigan as铆 chavos! reconozco que no ha de ser f谩cil aportar su tiempo y recursos para poder organizar estas reuniones pero se les agradece que hayan creado un espacio para compartir conocimiento y experiencias, salud!

Por otro lado en esa misma reuni贸n fui invitado a dar una peque帽a presentaci贸n de Spring Roo que les comparto aqu铆 mismo por si se la perdieron :P.



Lamento mucho no haber podido quedarme mas tiempo y me perd铆 la platica de Scrum de JJ! :(, pero bueno ya saben que el trabajo es trabajo.

Espero aportar mas a esta comunidad pr贸ximamente, pero por lo pronto ya conocen del proyecto SpringHispano Add-ons de Spring Roo?

Friday, July 2, 2010

Clojure: Lo B谩sico

"Aprende un lenguaje de programacion nuevo".

Lo dice en el libro The Pragmatic Programmer y tambi茅n es una de las 97 cosas que un programador debe saber.

Pues quiero aprender Clojure y otros lenguajes (Groovy, Scala, Ruby, etc.) asi que he decido que conforme vaya leyendo y practicando sobre estos lenguajes ir茅 posteando en mi blog una seria de "notas" sobre mi avance. Asi pues, este post es sobre lo b谩sico de Clojure.

Clojure es un lenguaje de programaci贸n que compila bytecode, es decir, corre bajo la JVM. Es un lenguaje que aplica el paradigma de la programaci贸n funcional.

Podemos bajar Clojure de aqui.

Para programar con Clojure necesitamos abrir el Clojure REPL (Read Evaluate Print Loop). Para abrir el REPL necesitamos correr el siguiente comando en la linea de comandos:


java -jar clojure-X.Y.Z.jar


Donde X.Y.Z es la versi贸n que tengan instalada de Clojure. Creo que la versi贸n actual es la 1.1.

Ok, al ejecutar el comando anterior se abre el REPL y nos muestra el prompt 'user=>' para empezar a tirar codigo.

Muy bien, si escribimos nuestro "Hola Mundo":


user=> (println "Hola Mundo!")


Como resultado tenemos:


Hola Mundo!
nil


Todo programa en Clojure se construye a partir de Formas. Una Forma en Clojure es una unidad de c贸digo que puede ser evaluada y regresar un valor. Hay cuatro tipos de formas:


  1. Literales: Son formas que se evaluan a si mismas, como: cadenas, n煤meros y caracteres.

  2. Simbolos: Son formas que se evaluan a un valor. Pueden verse como si fueran variables pero en Clojure se maneja distinto. Por ejemplo los nombres de las funciones son s铆mbolos y algunos operadores son s铆mbolos tambi茅n.

  3. Formas Compuestas: Como su nombre lo dicen son formas que contienen otras formas. Estas formas usan parentesis (), <>, y {}. Las formas que usan <> eval煤an a un vector y las que usan {} a un mapa y las que usan () a listas. En Clojure las listas son evaluadas como llamadas a funci贸n. El primer elemento en la lista es la llamada a una funci贸n, y el resto de elementos son los argumentos de esa funci贸n. Por ejemplo, cuando en otros lenguajes llamar a una funcion que recibe 2 argumentos 'a' y 'b' se escribe de esta forma: funcion(a, b), en Clojure es (funcion a b).

  4. Formas Especiales: Son un tipo particular de Formas Compuestas. Estas son los bloques mas b谩sicos para construir un programa en Clojure, ya que son usadas para controlar el flujo de un programa, definir funciones nuevas, definir variables, entre otras cosas.



Su pueden crear archivos con el c贸digo Clojure que deseamos ejecutar. Por ejemplo si creamos un archivo holaMundo.clojure en la ruta que deseemos lo podemos ejecutar de la siguiente forma:


user=> (load-file "/holaMundo.clojure")


Otra forma de ejecutar el archivo holaMundo.clojure puede ser asi:


java -jar clojure-X.Y.Z.jar /holaMundo.clojure


Para definir "variables" se utiliza la Forma Especial "def".


(def nombre-variable valor-variable)


Donde "nombre-variable" es el nombre que le vamos a dar (o el s铆mbolo al cual se va a ligar el valor) y "valor-variable" puede ser cualquier tipo de forma de Clojure (literal, s铆mbolos, compuestas y especiales).

Clojure define reglas para nombrar a los s铆mbolos:


  1. Se puede usar cualquier car谩cter alfanum茅rico y tambi茅n ?,+,*,!,-, y _.

  2. No pueden empezar con n煤meros (como en Java).

  3. Pueden usar el car谩cter :, pero no al principio ni al fin del nombre del s铆mbolo y tampoco se puede repetir.



Dadas estas reglas entonces los siguientes nombres de s铆mbolos son correctos: variable, var123, *nombre*, nombre+apellido, esMujer?, etc.

Por convenci贸n, los nombres de s铆mbolos en Clojure son con letras en min煤sculas donde cada palabra es separada con guion medio '-'. Tambi茅n, los s铆mbolos que representan constantes o par谩metros globales deben empezar y terminar con '*'.

Namespaces



Cuando se definen variables (o s铆mbolos ligados a un valor) con 'def' este nombre se mantiene dentro de un espacio de nombres o Namespace. Cuando escribimos programas y sobre todo programas grandes es muy probable que ciertas variables que definamos puedan llegar a tener problemas porque en alguna otra parte del programa existe otra variable que se llama igual, para evitar esto usamos los Namespaces.

Recuerdan el prompt 'user=>' que sale al inicar el REPL?, pues es el Namespace por default 'user'. Asi, una variable dentro de un cierto Namespace se puede hacer referencia a su valor usando el Namespace como parte del nombre de la siguiente forma: namespace/nombre.


user=> (def mi-variable 12)
#'user/mi-variable
user=> user/mi-variable
12
user=> (print mi-variable)
12nil
user=> (print user/mi-variable)
12nil
user=>


Lo anterior muestra como definimos una variable 'mi-variable' con el valor 12, y ver谩n que se puede hacer referencia a esta usando el Namespace 'user' o no.

Para crear un nuevo Namespace usamos 'ns' de la siguiente forma:


user=>
user=> (def mi-variable 12)
#'user/mi-variable
user=> (ns mi-namespace)
nil
mi-namespace=> (def mi-variable 21)
#'mi-namespace/mi-variable
mi-namespace=> mi-variable
21
mi-namespace=> mi-namespace/mi-variable
21
mi-namespace=> user/mi-variable
12
mi-namespace=>


En lo anterior ver谩n que 'ns' se utiliza para crear nuevos Namespaces y ademas se puede ver como se puede hacer referencia a variables en otros Namespaces.

If-else



Todo lenguaje de programaci贸n debe proveer estructuras condicionales para alterar el comportamiento de la ejecuci贸n de un programa dependiendo de ciertas situaciones.

En Clojure el famoso "if" se escribe asi:


user=> (if (= 1 1) "Son iguales" "No son iguales")
"Son iguales"
user=> (if (= 1 2) "Son iguales" "No son iguales")
"No son iguales"


Noten como el "if" y la comparaci贸n "=" realmente son llamadas a funciones que reciben par谩metros. Es decir, "if" es una funci贸n que recibe en su primer par谩metro la Forma que se eval煤a a true o false y si es true devuelve lo que recibe en el segundo par谩metro y si no lo que recibe en el tercer par谩metro.

Existe otra forma del if y es "if-not" y se usa as铆:


user=> (if-not (= 1 2) "No son iguales" "Si son iguales")
"No son iguales"
user=> (if-not (= 1 1) "No son iguales" "Si son iguales")
"Si son iguales"


Bueno, le voy a parar aqu铆 continuare en otro post con otras cosas b谩sicas como funciones y ciclos.

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.