sábado, 16 de octubre de 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.

miércoles, 8 de septiembre de 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?

viernes, 2 de julio de 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.

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.

sábado, 15 de mayo de 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.

miércoles, 28 de abril de 2010

Un sencillo Add-on de Spring Roo

Ya tenia ganas de meterme un poquito a las entrañas de Spring Roo para crear un Add-on. Por esa razón estoy escribiendo este pequeño post sobre como crear un Add-on "sencillito" y carismático.

Empezaremos creando el proyecto de un Add-on con el propio Spring Roo de la siguiente forma:


roo> project --topLevelPackage com.jabaddon.practices.springroo.addons.addonsencillito.roo.addon --template ROO_ADDON_SIMPLE


Como lo pueden notar Spring Roo ya viene con un template de proyecto del tipo Add-on Simple. Es importante mencionar que el paquete del proyecto debe terminar en .roo.addon de lo contrario el propio shell les mostrara un mensaje como el siguiente:


Roo add-ons must have a package name ending in .roo.addon; eg com.mycompany.myproject.roo.addon


¿Y porque debe terminar el paquete en .roo.addon? Debido a que a la hora que inicia el Shell de Spring Roo hace un monitoreo del classpath buscando nuevos Add-ons que cargar y es justamente aquellos paquetes que terminan en .roo.addon los que busca y carga.

Ok, despues de ejecutar la creacion del proyecto la estructura de folders y archivos queda de la siguiente forma:


.
|____legal
| |____LICENSE.TXT
|____log.roo
|____pom.xml
|____readme.txt
|____src
| |____main
| | |____assembly
| | | |____assembly.xml
| | |____java
| | | |____com
| | | | |____jabaddon
| | | | | |____practices
| | | | | | |____roo
| | | | | | | |____addons
| | | | | | | | |____addonsencillito
| | | | | | | | | |____roo
| | | | | | | | | | |____addon
| | | | | | | | | | | |____Commands.java
| | | | | | | | | | | |____Operations.java
| | | | | | | | | | | |____PropertyName.java
| | |____resources
| | | |____META-INF
| | | | |____spring
| | | | | |____applicationContext.xml
| | | | | |____log4j.properties
| | |____webapp
| |____test
| | |____java
| | |____resources


El template del proyecto ha creado 3 clases: Commands.java, Operation.java y PropertyName.java; entre otros archivos.

Si abrimos la clase Commands.java notaremos que es una clase que implementa la interfaz: org.springframework.roo.shell.CommandMarker. Las clases que implementan esta interfaz son aquellas que a la hora de iniciar Spring Roo lee y carga. Ademas, esta marcada con la anotación @ScopeDevelopmentShell que le indica a Roo que la clase debe ser creada cuando el Shell de desarrollo es usado.

Notaremos también que hay métodos marcados con la anotación @CliAvailabilityIndicator. Esta anotación le indica al Shell de Roo los métodos que sirven para indicar si un cierto comando esta disponible o no. Esta anotación solo aplica para métodos públicos que no reciben argumentos y que regresan boolean. Se recomienda que la ejecución de este método no sea costosa ya que los métodos que indican si un comando esta disponible o no son llamados regularmente.

Los métodos marcados con la anotación @CliCommand son, en si, la ejecución de un comando. Esta anotación tiene dos parámetros: value, que indica el nombre del comando (pueden ser una o mas cadenas) y help, una cadena con un mensaje de ayuda para el comando.

La clase Commands tiene como atributo a la clase Operation (que recibe en el constructor). Esta clase Operations esta marcada con la anotación @ScopeDevelopment que le indica a Roo que debe instanciar estas clases e inyectarlas a los CommandMaker que tengan constructores que reciban objetos de su tipo.

Basicamente, un Add-on de Spring Roo debe crear clases "Comando" que implementen la interfaz CommandMaker y delegar a objetos "Operaciones" la ejecución de los mismos.

Ok, dejemos un poco al lado la teoría y vayamos un poco mas a la acción. Vamos a instalar el add-on de este proyecto y ver como trabaja.

Para lograr esto ejecutaremos dentro del shell de Roo el comando:


roo> perform assembly


Esto creara un .zip dentro de target el cual es el add-on a instalar con todo lo que necesita (dependencias, recursos, etc.).

Como ejercicio si quieren agreguen dependencias al proyecto, recursos, etc. y ejecuten el "perform assembly" y vean las entrañas del .zip para que vean como va empaquetado un Add-on.

Ya teniendo el .zip lo instalamos desde el shell de Roo con el comando:


addon install --url file://[ruta hacia el .zip]


Esto copiara el .zip a $ROO_HOME/add-ons (si no funciona el comando copien el archivo directamente).

Ahora si reinician el shell de Roo y corren el comando:


roo> addon clean


tendrán la salida:


Loaded com.jabaddon.practices.roo.addons.addonsencillito.roo.addon.Commands; try the 'welcome' commands
____ ____ ____
/ __ \/ __ \/ __ \
/ /_/ / / / / / / /
/ _, _/ /_/ / /_/ /
/_/ |_|\____/\____/ 1.0.0.RELEASE [rev 564]


Welcome to Spring Roo. For assistance press TAB or type "hint" then hit ENTER.


Noten el mensaje de la carga del Addon.

¿Quien imprimió ese mensaje? Solo hay que ver el constructor de la clase Commands.


public Commands(StaticFieldConverter staticFieldConverter, Operations operations) {
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");
}


Ok, ahora si escribimos "welcome" en el shell y damos "tab" el shell de Roo nos mostrara los comandos disponibles que empiezan con "welcome".


roo> welcome

welcome property welcome write hej welcome write hello


Esos comandos son justamente los declarados en la clase Commands.

Si ejecutamos el comando:


roo> welcome property


Obtendremos como salida:


roo> welcome property
Username : Abaddon


Y si escribimos "welcome property --name" y damos "tab" obtendremos como salida:


roo> welcome property --name

HOME_DIRECTORY USERNAME


Si vemos el código del método que atiende el comando veremos que el comando recibe un argumento llamado "name" (especificado con la anotación @CliOption) el cual es asignado a un objeto del tipo PropertyName. Y si abrimos la clase PropertyName notaremos que tienen dos atributos públicos finales y estáticos que son justamente los que se muestran cuando damos "tab".


@ScopeDevelopmentShell
public class Commands implements CommandMarker {

...

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

...
}



public class PropertyName implements Comparable {

private String propertyName;

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

...
}


En fin, el modelo de Add-on de Spring Roo esta muy interesante y espero darme el tiempo para ver en futuros post como acceder a los metadatos de un proyecto Roo como: las entidades existentes, las propiedades que tienen, sus relaciones, etc.

sábado, 10 de abril de 2010

De Maven a Ant sin dejar Maven

En este pequeño post vamos a hacer un ejercicio para convertir un proyecto Maven a un proyecto ANT.

En la compañía donde trabajo (siguenos en twitter @itbrain) usamos como estandar Maven para todos nuestros proyectos ya que hacemos integración continua, ejecutamos pruebas unitarias y obtenemos reportes del avance del proyecto con ayuda de Maven. Sin embargo, hemos tenido clientes a los cuales tenemos que entregarles los proyectos con scripts en ANT (debido a sus propios procesos) para que estos los puedan construir y empaquetar.

A pesar de que hemos recomendado el uso de Maven a nuestros clientes no es posible cambiar los procesos de estos de la noche a la mañana.

Afortunadamente para nosotros existe el plugin de Ant para Maven (http://maven.apache.org/plugins/maven-ant-plugin/index.html) el cual tiene los fabulosos comandos:


  • ant:ant - Para generar los archivos build de Ant.

  • ant:clean - Para limpiar los archivos build de Ant.



los cuales usaremos en este post para hacer el pequeño ejercicio de convertir el proyecto con Maven a scripts de ANT.

Comenzamos creando un proyecto simple en maven:

 
mvn archetype:create -DgroupId=com.jabaddon.practices.maven -DartifactId=maven2ant


Terminada la ejecución de la creación del proyecto agregaremos unas cuantas dependencias al pom.xml para hacer nuestra prueba:

 

org.springframework
spring
2.5.6


log4j
log4j
1.2.15



Ok, el siguiente paso entonces es ejecutar el comando para crear los scripts de ANT:

 
mvn ant:ant


Al terminar la ejecución del comando tendremos 3 nuevos archivos en el proyecto:

 
build.xml
maven-build.properties
maven-build.xml


El archivo build.xml básicamente solo importa al archivo maven-build.xml:










El archivo maven-build.xml es el que tiene los "target" de ant que también tenemos en Maven y son:


    clean - Para limpiar el proyecto.
    compile - Para compilar el proyecto
    compile-tests - Para compilar las pruebas del proyecto (depende de 'compile').
    test - Para ejecutar todas las pruebas del proyecto.
    javadoc - Para generar el javadoc del Proyecto.
    package - Para empaquetar el JAR, WAR, EAR del Proyecto (depende de 'compile' y 'test').


Y por ultimo el archivo maven-build.propeties tiene definidas propiedades que indican cosas como: la ruta del código principal, la ruta del código de pruebas, la ruta hacia las librerías, etc.


project.build.outputDirectory=${maven.build.outputDir}
project.build.directory=${maven.build.dir}
maven.test.reports=${maven.build.dir}/test-reports
maven.build.finalName=maven2ant-1.0-SNAPSHOT
maven.reporting.outputDirectory=${maven.build.dir}/site
maven.build.testResourceDir.0=src/test/resources
maven.build.outputDir=${maven.build.dir}/classes
maven.build.resourceDir.0=src/main/resources
maven.build.testOutputDir=${maven.build.dir}/test-classes
maven.repo.local=${user.home}/.m2/repository
maven.settings.offline=false
maven.build.dir=target
maven.settings.interactiveMode=true
maven.build.testDir.0=src/test/java
maven.build.srcDir.0=src/main/java


Y si tuviésemos la necesidad de agregar mas "target" en la misma pagina del plugin de Ant recomiendan que se agreguen al archivo build.xml y no al maven-build.xml.

jueves, 11 de febrero de 2010

Relación One-to-Many en Spring Roo

En un pasado post hable un poco de los primeros pasos con Sprign Roo.

En este post hare un muy pequeño proyecto con Spring Roo pero me enfocare en ver como hacer entidades con relaciones de uno a muchos (maestro-esclavo).

Ok, pues empecemos.

Primero crearemos el proyecto indicándole el paquete raíz del mismo.


roo> project --topLevelPackage prueba.springroo.relonetomany --projectName relacion-onetomany
Created D:\Abaddon\Projects\JAbaddon\Code\SpringRoo\RelacionOneToMany\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\META-INF\spring\log4j.properties


Ahora hay que definir el método de persistencia para el proyecto. Usaremos MySQL con Hibernate.


prueba.springroo.relonetomany roo> persistence setup --provider HIBERNATE --database MYSQL --databaseName rel_one_many
Created SRC_MAIN_RESOURCES\META-INF\persistence.xml
Created SRC_MAIN_RESOURCES\META-INF\spring\database.properties
please enter your database details in src/main/resources/database.properties
Managed SRC_MAIN_RESOURCES\META-INF\spring\applicationContext.xml
Managed ROOT\pom.xml
Managed SRC_MAIN_RESOURCES\META-INF\spring\database.properties


El nombre de base de datos que usamos fue "rel_one_many" así que hay que asegurarnos de tener una base de datos con ese nombre en nuestro MySQL.

En el comando "persistence setup" tambien se puede especificar el usuario y password que usaremos para conectarnos a la base de datos con "--userName" y "--password" respectivamente. Como aqui no lo especificamos asi sera necesario especificarlo en el archivo "src/main/resources/database.properties" para que podamos conectarnos a la base de datos sin problemas.

Lo siguiente sera configurar el logging a un nivel de DEBUG.


prueba.springroo.relonetomany roo> logging setup --level DEBUG
Managed SRC_MAIN_RESOURCES\META-INF\spring\log4j.properties


Ahora para propositos de este ejemplo usaremos usaremos las entidades Grupo y Alumno para representar una relación de uno a muchos donde el Grupo tiene uno o mas Alumnos.

Empecemos creando la entidad Grupo con las propiedades: nombre y fecha de registro.


prueba.springroo.relonetomany roo> entity --class prueba.springroo.relonetomany.dominio.Grupo --testAutomatically
Created SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio
Created SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Grupo.java
Created SRC_TEST_JAVA\prueba\springroo\relonetomany\dominio
Created SRC_TEST_JAVA\prueba\springroo\relonetomany\dominio\GrupoDataOnDemand.java
Created SRC_TEST_JAVA\prueba\springroo\relonetomany\dominio\GrupoIntegrationTest.java
Created SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Grupo_Roo_Entity.aj
Created SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Grupo_Roo_ToString.aj
Created SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Grupo_Roo_Configurable.aj
Created SRC_TEST_JAVA\prueba\springroo\relonetomany\dominio\GrupoIntegrationTest_Roo_Configurable.aj
Created SRC_TEST_JAVA\prueba\springroo\relonetomany\dominio\GrupoDataOnDemand_Roo_DataOnDemand.aj
Created SRC_TEST_JAVA\prueba\springroo\relonetomany\dominio\GrupoIntegrationTest_Roo_IntegrationTest.aj
Created SRC_TEST_JAVA\prueba\springroo\relonetomany\dominio\GrupoDataOnDemand_Roo_Configurable.aj
~.dominio.Grupo roo> field string --fieldName nombre --notNull
Managed SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Grupo.java
Created SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Grupo_Roo_JavaBean.aj
Managed SRC_TEST_JAVA\prueba\springroo\relonetomany\dominio\GrupoDataOnDemand_Roo_DataOnDemand.aj
Managed SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Grupo_Roo_ToString.aj
~.dominio.Grupo roo> field date --fieldName fechaRegistro --type java.util.Date --notNull
Managed SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Grupo.java
Managed SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Grupo_Roo_JavaBean.aj
Managed SRC_TEST_JAVA\prueba\springroo\relonetomany\dominio\GrupoDataOnDemand_Roo_DataOnDemand.aj
Managed SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Grupo_Roo_ToString.aj


La siguiente entidad que crearemos sera Alumno, el cual tendrá las propiedades: nombre, apellido, fecha de nacimiento y numero de credencial.


~.dominio.Grupo roo> entity --class prueba.springroo.relonetomany.dominio.Alumno --testAutomatically
Created SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Alumno.java
Created SRC_TEST_JAVA\prueba\springroo\relonetomany\dominio\AlumnoDataOnDemand.java
Created SRC_TEST_JAVA\prueba\springroo\relonetomany\dominio\AlumnoIntegrationTest.java
Created SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Alumno_Roo_Entity.aj
Created SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Alumno_Roo_ToString.aj
Created SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Alumno_Roo_Configurable.aj
Created SRC_TEST_JAVA\prueba\springroo\relonetomany\dominio\AlumnoIntegrationTest_Roo_Configurable.aj
Created SRC_TEST_JAVA\prueba\springroo\relonetomany\dominio\AlumnoDataOnDemand_Roo_DataOnDemand.aj
Created SRC_TEST_JAVA\prueba\springroo\relonetomany\dominio\AlumnoIntegrationTest_Roo_IntegrationTest.aj
Created SRC_TEST_JAVA\prueba\springroo\relonetomany\dominio\AlumnoDataOnDemand_Roo_Configurable.aj
~.dominio.Alumno roo> field string --fieldName nombre --notNull
Managed SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Alumno.java
Created SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Alumno_Roo_JavaBean.aj
Managed SRC_TEST_JAVA\prueba\springroo\relonetomany\dominio\AlumnoDataOnDemand_Roo_DataOnDemand.aj
Managed SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Alumno_Roo_ToString.aj
~.dominio.Alumno roo> field date --fieldName fechaNacimiento --type java.util.Date --notNull
Managed SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Alumno.java
Managed SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Alumno_Roo_JavaBean.aj
Managed SRC_TEST_JAVA\prueba\springroo\relonetomany\dominio\AlumnoDataOnDemand_Roo_DataOnDemand.aj
Managed SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Alumno_Roo_ToString.aj
~.dominio.Alumno roo> field number --fieldName numeroBoleta --type java.lang.Long --notNull
Managed SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Alumno.java
Managed SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Alumno_Roo_JavaBean.aj
Managed SRC_TEST_JAVA\prueba\springroo\relonetomany\dominio\AlumnoDataOnDemand_Roo_DataOnDemand.aj
Managed SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Alumno_Roo_ToString.aj
~.dominio.Alumno roo> field string --fieldName apellido --notNull
Managed SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Alumno.java
Managed SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Alumno_Roo_JavaBean.aj
Managed SRC_TEST_JAVA\prueba\springroo\relonetomany\dominio\AlumnoDataOnDemand_Roo_DataOnDemand.aj
Managed SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Alumno_Roo_ToString.aj


Ya teniendo las dos entidades ahora vamos a relacionarlas. Lo primero que haremos sera indicar que la entidad Grupo tendrá muchos Alumnos con el comando "field set":


~.dominio.Alumno roo> field set --fieldName alumnos --element prueba.springroo.relonetomany.dominio.Alumno --class prueba.springroo.relonetomany.dominio.Grupo --mappedBy grupo
Managed SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Grupo.java
Managed SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Grupo_Roo_JavaBean.aj
Managed SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Grupo_Roo_ToString.aj


Hay que notar aquí que en el momento en que escribimos el comando Spring Roo estaba bajo el contexto de la entidad Alumno (noten la cadena que hay antes del promt de "roo>" y que dice "~.dominio.Alumno"), por esa razón es necesario usar la opción "--class" para indicarle a que clase se le agregara el campo que se esta creando.

Otro punto en el que hay que poner atención es la opción "--mappedBy" la cual la nombramos como "grupo". Esto le dice a Spring Roo que la relación one-to-many estará mapeada por la propiedad "grupo" en los elementos que están del lado "many" de la relación (los contenidos).

Ahora en con la entidad Alumno hacemos la relación hacia el Grupo usando "field reference":


~.dominio.Grupo roo> field reference --fieldName grupo --class prueba.springroo.relonetomany.dominio.Alumno --type prueba.springroo.relonetomany.dominio.Grupo --notNull
Managed SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Alumno.java
Managed SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Alumno_Roo_JavaBean.aj
Managed SRC_TEST_JAVA\prueba\springroo\relonetomany\dominio\AlumnoDataOnDemand_Roo_DataOnDemand.aj
Managed SRC_MAIN_JAVA\prueba\springroo\relonetomany\dominio\Alumno_Roo_ToString.aj


La opción "--type" indica el tipo de la referencia que estamos creando en la entidad.

Ok, las relaciones ya están configuradas. Ahora vamos a crear los controladores web con el comando "controller all" (para crear todos los controladores de cada una de las entidades) dentro del paquete "prueba.springroo.relonetomany.web"


~.dominio.Alumno roo> controller all --package prueba.springroo.relonetomany.web
Created SRC_MAIN_JAVA\prueba\springroo\relonetomany\web
Created SRC_MAIN_JAVA\prueba\springroo\relonetomany\web\GrupoController.java
Created SRC_MAIN_WEBAPP\WEB-INF\spring
Created SRC_MAIN_WEBAPP\WEB-INF\spring\webmvc-config.xml
Created SRC_MAIN_JAVA\prueba\springroo\relonetomany\web\GrupoController_Roo_Controller.aj
Created SRC_MAIN_WEBAPP\images
Created SRC_MAIN_WEBAPP\images\nl.png
Created SRC_MAIN_WEBAPP\images\update.png
Created SRC_MAIN_WEBAPP\images\es.png
Created SRC_MAIN_WEBAPP\images\delete.png
Created SRC_MAIN_WEBAPP\images\add.png
Created SRC_MAIN_WEBAPP\images\resultset_previous.png
Created SRC_MAIN_WEBAPP\images\resultset_next.png
Created SRC_MAIN_WEBAPP\images\favicon.ico
Created SRC_MAIN_WEBAPP\images\banner-graphic.png
Created SRC_MAIN_WEBAPP\images\resultset_last.png
Created SRC_MAIN_WEBAPP\images\gb.png
Created SRC_MAIN_WEBAPP\images\springsource-logo.png
Created SRC_MAIN_WEBAPP\images\it.png
Created SRC_MAIN_WEBAPP\images\show.png
Created SRC_MAIN_WEBAPP\images\sv.png
Created SRC_MAIN_WEBAPP\images\list.png
Created SRC_MAIN_WEBAPP\images\resultset_first.png
Created SRC_MAIN_WEBAPP\images\de.png
Created SRC_MAIN_WEBAPP\styles
Created SRC_MAIN_WEBAPP\styles\standard.css
Created SRC_MAIN_WEBAPP\styles\alt.css
Created SRC_MAIN_WEBAPP\WEB-INF\classes
Created SRC_MAIN_WEBAPP\WEB-INF\classes\standard.properties
Created SRC_MAIN_WEBAPP\WEB-INF\classes\alt.properties
Created SRC_MAIN_WEBAPP\WEB-INF\layouts
Created SRC_MAIN_WEBAPP\WEB-INF\layouts\default.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\layouts\layouts.xml
Created SRC_MAIN_WEBAPP\WEB-INF\views
Created SRC_MAIN_WEBAPP\WEB-INF\views\views.xml
Created SRC_MAIN_WEBAPP\WEB-INF\views\resourceNotFound.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\index.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\uncaughtException.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\dataAccessFailure.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\controller-index.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\tags
Created SRC_MAIN_WEBAPP\WEB-INF\tags\language.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\theme.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\tags\pagination.tagx
Created SRC_MAIN_WEBAPP\WEB-INF\i18n
Created SRC_MAIN_WEBAPP\WEB-INF\i18n\messages_sv.properties
Created SRC_MAIN_WEBAPP\WEB-INF\i18n\messages.properties
Created SRC_MAIN_WEBAPP\WEB-INF\i18n\messages_nl.properties
Created SRC_MAIN_WEBAPP\WEB-INF\i18n\messages_it.properties
Created SRC_MAIN_WEBAPP\WEB-INF\i18n\messages_es.properties
Created SRC_MAIN_WEBAPP\WEB-INF\i18n\messages_de.properties
Created SRC_MAIN_WEBAPP\WEB-INF\i18n\application.properties
Managed SRC_MAIN_WEBAPP\WEB-INF\i18n\application.properties
Created SRC_MAIN_WEBAPP\WEB-INF\views\grupo
Created SRC_MAIN_WEBAPP\WEB-INF\views\grupo\list.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\grupo\show.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\grupo\create.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\menu.jspx
Managed SRC_MAIN_WEBAPP\WEB-INF\i18n\application.properties
Managed SRC_MAIN_WEBAPP\WEB-INF\views\menu.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\grupo\update.jspx
Managed SRC_MAIN_WEBAPP\WEB-INF\i18n\application.properties
Managed SRC_MAIN_WEBAPP\WEB-INF\views\menu.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\grupo\views.xml
Created SRC_MAIN_WEBAPP\WEB-INF\urlrewrite.xml
Created SRC_MAIN_WEBAPP\WEB-INF\web.xml
Managed SRC_MAIN_WEBAPP\WEB-INF\web.xml
Managed ROOT\pom.xml
Created SRC_MAIN_JAVA\prueba\springroo\relonetomany\web\AlumnoController.java
Managed SRC_MAIN_WEBAPP\WEB-INF\web.xml
Managed ROOT\pom.xml
Created SRC_MAIN_JAVA\prueba\springroo\relonetomany\web\AlumnoController_Roo_Controller.aj
Created SRC_MAIN_WEBAPP\WEB-INF\views\alumno
Created SRC_MAIN_WEBAPP\WEB-INF\views\alumno\list.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\alumno\show.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\alumno\create.jspx
Managed SRC_MAIN_WEBAPP\WEB-INF\i18n\application.properties
Managed SRC_MAIN_WEBAPP\WEB-INF\views\menu.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\alumno\update.jspx
Managed SRC_MAIN_WEBAPP\WEB-INF\i18n\application.properties
Managed SRC_MAIN_WEBAPP\WEB-INF\views\menu.jspx
Created SRC_MAIN_WEBAPP\WEB-INF\views\alumno\views.xml


Ahora hay que probar todo el código para ver si todo esta bien. Recuerden crear la base de datos, configurar el usuario y password de la base de datos y sobre todo levantar la base de datos para que las pruebas se puedan conectar y ejecutar correctamente. Entonces, para ejecutar las pruebas usamos el comando:


~.web roo> perform tests


el cual nos debe indicar, entre otras cosas, un mensaje "BUILD SUCCESSFUL" y si no fue asi algo hicimos mal (asi que hay que volver a empezar desde el principio de este post).

Para ver todo esto corriendo ya en Tomcat simplemente hay que salirnos de la consola de Spring Roo y en el mismo folder del proyecto ejecutar el comando de maven:


$ mvn tomcat:run


Una vez que este corriendo Tomcat hay que ir a la direccion http://localhost:8080/relacion-onetomany/ y naveguen en la pequeña aplicación para que vean la funcionalidad de la relación one-to-many con Spring Roo.



Notaran que al crear Alumnos si no hay Grupos registrados no les muestra la propiedad para indicar el Grupo al que pertenece el Alumno, pero si crean Grupos y después crean Alumnos ya tienen la posibilidad de seleccionar a que grupo va a pertenecer el Alumno que estén creando.

martes, 9 de febrero de 2010

Ruby Básico - Manejo de Cadenas

En el anterior post sobre Ruby Básico se hablo de una muy breve introducción a Ruby.

En este post profundizaré en el manejo de cadenas con Ruby.

Como se comento antes se puede crear una cadena en Ruby con comillas simples o comillas dobles. Por ejemplo si creamos un archivo llamado "cadenas00.rb" con el siguiente código:

cadena1 = "Esta es una cadena con comillas dobles"
cadena2 = 'Esta es una cadena con comillas simples'
puts cadena1
puts cadena2

y si lo corremos con Ruby:

$ ruby cadenas00.rb
Esta es una cadena con comillas dobles
Esta es una cadena con comillas simples

Sin embargo, también es posible crear cadenas que este delimitadas por un par de caracteres iguales (que no sean letras) que estén precedidos por el símbolo %. Por ejemplo, si creamos un archivo llamado "cadenas01.rb" con el siguiente código:

puts %!Esta es una cadena!
puts %{Esta es otra cadena}
puts %[Esta también es una cadena]
puts %-Esta sera una cadena?-
puts %!Esta es una cadena como el primer ejemplo pero usando el caracter \! con secuencia de escape!

tenemos como salida:

$ ruby cadenas01.rb
Esta es una cadena
Esta es otra cadena
Esta también es una cadena
Esta sera una cadena?
Esta es una cadena como el primer ejemplo pero usando el caracter ! con secuencia de escape

Este estilo para crear cadenas en Ruby es muy útil para cuando se crean cadenas con mucho texto.

Uno de los muchos usos que les damos a las cadenas en nuestros programas es para imprimir valores de variables. Ruby nos permite hacer sustitucion de expresiones dentro de una cadena simplemente encerrando nuestra expresión entre #{ y } dentro de la cadena misma. Por ejemplo, si creamos un archivo llamado "cadenas02.rb" con el siguiente código:

x = 2
y = "expresión"
z = 2.3

puts "El valor de x es #{x}"
puts "El valor de y es #{y}"
puts "El valor de z es #{z}"

# Sera lo mismo que lo siguiente?

puts "El valor de x es " + x
puts "El valor de y es " + y
puts "El valor de z es " + z

tenemos como salida:

$ ruby cadenas02.rb
El valor de x es 2
El valor de y es expresión
El valor de z es 2.3
cadenas02.rb:11:in `+': can't convert Fixnum into String (TypeError)
from cadenas02.rb:11:in `
'

Notaran que Ruby con el uso de #{} hace automáticamente la transformación adecuada de las variables para poder meterlas a las cadenas.

Como ya había comentado Ruby es un lenguaje orientado a objetos puro de tal forma que una cadena entre comillas es un objeto y se pueden llamar métodos de la clase String. Por ejemplo, si creamos un archivo llamado "cadenas03.rb" con el siguiente código:

cadena1 = "MAYUSCULAS".downcase
cadena2 = String.new("minusculas").upcase

puts "#{cadena1}"
puts "#{cadena2}"

tenemos como salida:

$ ruby cadenas03.rb
mayusculas
MINUSCULAS

Los metodos de la clase String downcase y upcase regresan una copia de la cadena en minúsculas y mayúsculas respectivamente. El método new crea una nueva instancia de la cadena que se le pasa como argumento.

El siguiente código (que sera guardado en un archivo llamado "cadenas04.rb") muestra el uso de algunos de los métodos de la clase String.

str1 = String.new("Mensaje 1")
str2 = String.new("Mensaje 2")
str3 = String.new("MeNSajE 2")
str4 = String.new(" Cadena con espacios ")

puts "Las cadenas son iguales? #{str1 == str2}"
puts "Las cadenas son diferentes? #{str1 != str2}"
puts "Que pasa si multiplico la cadena? #{str1 * 2}"
puts "Comparacion de cadenas case-sensitive #{str2.eql?(str3)}"
puts "Comparacion de cadenas case-insensitive #{str2.casecmp(str3)}"
puts "Mi cadena esta vacia? #{str2.empty?}"
puts "Donde esta el '2' en mi cadena? #{str2.index("2")}"
puts "Si le quito los espacios a la derecha? [#{str4.rstrip}]"
puts "Si le quito los espacios a la izquierda? [#{str4.lstrip}]"
puts "Si le quito los espacios? [#{str4.strip}]"
puts "Mi cadena a la inversa! #{str2.reverse}"
puts "Substring! [#{str4.slice(4..22)}]"
puts "Substring! [#{str4.slice(4..-5)}]"

entonces al correr el código se tiene como salida:

$ ruby cadenas04.rb
Las cadenas son iguales? false
Las cadenas son diferentes? true
Que pasa si multiplico la cadena? Mensaje 1Mensaje 1
Comparacion de cadenas case-sensitive false
Comparacion de cadenas case-insensitive 0
Mi cadena esta vacia? false
Donde esta el '2' en mi cadena? 8
Si le quito los espacios a la derecha? [ Cadena con espacios]
Si le quito los espacios a la izquierda? [Cadena con espacios ]
Si le quito los espacios? [Cadena con espacios]
Mi cadena a la inversa! 2 ejasneM
Substring! [Cadena con espacios]
Substring! [Cadena con espacios]

¿Notan como el uso del un numero negativo en el método slice de la clase String cuenta los indices de cada carácter en la cadena de derecha a izquierda?.

Como mencione, el código anterior solo muestra algunos de los métodos de String así que si se requiere de hacer algo con una cadena primero hay que verificar si existe algún método ya en la clase String que lo haga.

viernes, 5 de febrero de 2010

Ruby Básico (IRb, Comentarios, Enteros y Cadenas)

Dicen que "Ruby es el mejor amigo del programador" y la verdad no entendía porque hasta que empece a usar un poco Ruby primero para aprender y luego para hacer pequeños programas que utilizo como herramientas en mi labor diaria.

Estos pequeños programas van desde analizadores de logs hasta programas que me ayudar a generar paquetes de instalación de una forma personalizada.

Ruby es un lenguaje orientado a objetos y fue creado en 1993 por Yukihiro Matsumoto. Es muy popular en estos tiempos y existe muchas librerías de apoyo que van desde acceder a base de datos hasta manejar XML.

Ruby viene con una consola interactiva llamada IRb (Interactive Ruby) que sirve de mucho para poder experimentar con el lenguaje. Para correr la consola interactiva simplemente hay que teclear el comando "irb".


irb(main):006:0* mensaje = "Hola Mundo!"
=> "Hola Mundo!"
irb(main):007:0> puts mensaje
Hola Mundo!
=> nil
irb(main):008:0> print mensaje
Hola Mundo!=> nil
irb(main):009:0>


Los identificadores en Ruby son aquellos nombres que les damos a las variables, nombres de métodos, clases, constantes, etc. Estos nombres si hacen distinción entre mayúsculas y minúsculas y pueden ser creados usando caracteres alfanuméricos y el guion bajo '_'.

Como todo lenguaje Ruby tiene palabras reservadas que no pueden ser usadas al nombrar nuestras variables, métodos, clases, etc., no listare las palabras reservadas aquí pero las pueden encontrar en cualquier sitio en internet.

Hay dos tipos de comentarios en Ruby: para comentar una sola linea y para comentar muchas lineas.


# Este es un comentario de una sola linea

=begin
Este es un comentario
para muchas
lineas
=end


Hay 5 tipos de variables en Ruby: globales, de instancia, locales, de clase y constantes. En este post hablare de las globales y constantes, en futuros posts donde les hable de como usar Ruby con clases y objetos ya hablaremos de los otros tipos de variables.

Las variables globales en Ruby se declaran colocando el símbolo $ al inicio del nombre de la variable


$variable_global = 3.1416


Y las constantes simplemente se declaran usando puras mayúsculas en el nombre, y cualquier intento de modificar una constante resulta en un error.


PI = 3.1416
RUTA_DESTION=/tmp/



irb(main):010:0> CONSTANTE = 3.1416
=> 3.1416
irb(main):011:0> CONSTANTE = 2
(irb):11: warning: already initialized constant CONSTANTE
=> 2
irb(main):012:0>


Los enteros en Ruby se pueden escribir con un signo al inicio y también se les puede indicar el tipo de base que se desee para el numero: 0b para binarios, 0x para hexadecimal y 0 para octal.


irb(main):012:0> numero1 = 12354
=> 12354
irb(main):013:0> numero2 = 1
=> 1
irb(main):014:0> numero1 + numero2
=> 12355
irb(main):015:0> numero3 = 0b0010
=> 2
irb(main):016:0> numero2 + numero3
=> 3
irb(main):017:0> numero4 = 0x0F
=> 15
irb(main):018:0> numero3 + numero4
=> 17
irb(main):019:0> numero5 = 1_000_000
=> 1000000
irb(main):020:0>


Como notaran el guion bajo '_' es ignorado al declaran enteros.

Ruby maneja dos clases para los enteros: Fixnum y Bignum. La diferencia entre estas clases es que Fixnum esta hecho para manejar enteros en el rango de -230 to 230-1 or -262 to 262-1 (depende de la computadora donde este corriendo Ruby) y el Bignum se usa para cuando los rangos son mayores a los anteriores.

Para los números flotantes Ruby usa la clase: Float. Los números flotantes puede ser declarados usando notación científica.


irb(main):022:0* flotante = 1.2345
=> 1.2345
irb(main):023:0> flotante2 = 1.34e4
=> 13400.0
irb(main):024:0>


La clase de Ruby String es la que maneja las cadenas. Las cadenas puede ser declaradas usando comillas simples o comillas dobles.


irb(main):026:0* cadena = 'Esta es una cadena'
=> "Esta es una cadena"
irb(main):027:0> cadena2 = "Esta es otra cadena pero con comillas dobles"
=> "Esta es otra cadena pero con comillas dobles"
irb(main):028:0>