domingo, 30 de octubre de 2011

Una cosa es una cosa y....

Una cosa es una cosa y otra cosa es otra cosa.

laberinto

Me gustan jugar con la aparente obviedad de las cosas, y es que, además de gustarme el transfondo filosófico que podemos encontrar en el sufismo, creo que, cuando algo parece obvio es cuando más atención debemos poner.

Quienes me conocen, saben que casi siempre hablo "fraseando"; me gusta usar frases y refranes para "encapsular" conceptos que, de otra manera, son complejos o tardan en entenderse.

"Una cosa es una cosa, y  otra cosa es otra"

Esta frase la uso mucho para denotar -dentro de un contexto- que un mismo evento puede tener dos significados distintos, o que, dentro de un mismo contexto, debemos aprender a diferenciar el alcance de los evento que ocurren dentro de él....... me explico con una historia y una anécdota.

La historia.
Esta historia no recuerdo bien en que libro la leí, sólo sé que, la he visto ya en varios libros y narrada de distinta manera, así que: aquí va mi propia versión.


Un barco de guerra recién partía de puerto hacia una misión de combate, muchos de los marinos realizaban su primer viaje, a algunos de ellos se les encargo amarran bien los cañones, pues, se pronosticaba una tormenta.


Uno de ellos en particular, realizó mal los amarres y no se lo reportó a su superior.


A medio viaje se desata una tormenta, era de tal magnitud que, las malas amarras del cañón comenzaron a ceder hasta que el cañón salió disparado causando destrozos en cubierta.
La tormenta no paraba y el cañón tampoco paraba de moverse causando ya daños severos, incluso provocando la muerte de un tripulante.


El marino al ver lo que su error estaba generando, se armó de valor, tomo unas cuerdas y entre un montón de suerte y destreza  logró detener el cañón,  amarrándolo fijamente.


Terminada la tormenta, el capitán ordenó llevar la  nave lo más pronto posible a puerto seguro; después de rendir honores al marino caído;  pidió que se investigara que había ocurrido, y por qué se había liberado un cañón; nuestro marino, no pudo más con su conciencia y confesó al capitán que había sido descuido de él al amarrar mal el cañón.


El capitán, sin dar lugar a gestos de confusión pidió que se organizaran dos ceremonias.


La 1a, una para entregar una medalla al mérito -por su valor- a nuestro marino de la historia.
La 2a para fusilarlo por cometer tal error en misión de guerra y por los resultados de dicho error.


Cuando un oficial cuestionó al capitán, porque había decidido tal cosa, el capitán le argumento que el marino se había ganado a pulso ambas; su valentía era digna de premio, su error merecía tal castigo.

La anécdota.

El hecho de trabajar en consultoría me ha hecho conocer a mucha gente, muchas veces pasados los años, es común toparme con personas que no veía en años ocupando nuevos puestos en distintas empresas.

En una ocasión tiene ya varios años, me tocó ir a presentar una propuesta que quería realizar una integración de dos sistemas, su departamento interno tenía ya una propuesta pero, querían compararla con una externa.

Al iniciar las primeras juntas (y quienes hayan trabajando en proyectos de integración en el DF, MX sabrán que las primeras juntas pueden tardar horas y horas), reconocí a una antiguo colega; eso casi siempre me da tranquilidad...... casi siempre.

En un pequeño break, el conocido se me acercó, me pidió que le contara cual era mi rol en esta propuesta y , en general, que tanto estaría involucrado en la ejecución de la solución en caso de aceptarla.

Le comente que, mi labor era en primera instancia, presentar la propuesta y, en caso de ser aceptada seguramente correría a mi cargo la 1a parte de la integración.

Recordamos un par de anécdotas del proyecto en que nos conocimos, reímos un rato y, sin cambiar para nada lo amena de la conversación, me dijo : "Lo siento, no puedo permitir que gane tu propuesta.
Ahora represento los intereses de todo mi equipo de trabajo y debo defenderlos".
Me pidió que entendiera que a pesar de que existía ya una antigua relación de compañerismo, ahora las cosas eran distintas y que, como se suele decir en estos casos, no debía tomarlo de manera personal.

Afortunadamente, algo que he aprendido en todos estos años  es que.... una cosa es una cosa y otra cosa es otra cosa.

Buen inicio de semana a todos...
----
RuGI

-----------------------------------------------
Créditos:
La foto es de: Antonio Costa.
http://www.flickr.com/photos/a_costa/401217788/
-----------------------------------------------





HTML 5. Canvas. Un ejemplo sencillo.


A estas alturas, si nos dedicamos al desarrollo web, tratar de negar la relevancia de HTML 5 puede ser un tanto contraproducente.

Yo aún me refugio en la seguridad del back-end, en la tranquilidad de los lenguajes compilados, pero, sé bien que, es menester comenzar a conocer "lo que vendrá".

Del estándar en particular, el tema de canvas me tiene muy emocionado. Creo que será un antes y un después en esto de la interactividad basada en estándares.

De entre todas las opciones que existen ya hoy en día (puedes revisar mis bookmarks en delicious), hay una en particular que me ha llamado mucho la atención - y no sólo por el nombre -, me refiero a : Raphaël

El hecho de permitir generar gráficas basadas en elementos PATH permite poder sacarle provecho a imágenes: SVG.

(si quieres generar tus propias imágenes SVG te recomiendo: SVG-Edit)

Y, aquí un ejemplo usando Raphaël  y un poco de javascript:


 


Puedes ver el ejemplo aquí.

El código, por supuesto, en mi recién inaugurada (fueron meses de resistencia debo aceptarlo) cuenta en github.

Saludos!!!
---
RuGI

Silencio en el centro.


 “Nuestros Silencios” 
del escultor mexicano Rivelino.
Zócalo de la Ciudad de México.
Noviembre 2011,
México, DF.

sábado, 29 de octubre de 2011

97 cosas. Primera entrega. 2009-2011

Entre Mayo del 2009 y Enero del 2011 hice una serie de post mientras revisaba el libro:




Debido a que pienso continuar la serie - no prometo fechas-, he decido migrarlos a este nuevo blog manteniendo los comentarios como parte de cada post en la parte inferior, para que, si gustan, podamos seguir con la retroalimentación.
Lo más interesante de los posts, fueron los comentarios que se dieron. El intercambio respetuoso de puntos de vista (no siempre similares) enriquecieron cada entrega, así que sugiero: ¡¡¡ no se los pierdan !!! ;)

Sea pues, esta la primera entrega sobre esas 97 cosas que todo arquitecto de software debe conocer.

Espero les sea de utilidad.
Que sea un buen cierre de año para todos.

---
RuGI
Isaac Ruiz Guerra

domingo, 23 de octubre de 2011

Servicios JSON con Grails y BBDD existente. Parte I.


Como bien saben, Jarhalla es un sideproject que tengo desde hace unos meses.
El slogan (patrocinio de  dargorshadow) es: Here, all {brave} jar's REST, y la intención es que el REST no sea sólo una parte alegórica del slogan.  ;)

La idea a media plazo es proporcionar un API  (no necesariamente full-rest debo aclarar) para poder ofrecer la información ya indexada.

¿Que ventajas tiene ofrecer servicios y devolver JSON? pues, la principal creo es que pueden llegar a  utilizarla como mejor se les facilite.
Además por supuesto, y como comenta ecamacho,  quiero utilizarlo para probar ciertos frameworks.

Por ello inicio esta serie de post's, en ellos iré describiendo los pasos para crear la versión inicia de esta API.
He decidio utilizar grails por muchas razones, pero, la principal, es la facilidad con la que se crean servicios  JSON.

Escenario inicial.
En este primer post, veremos como utilizar grails para operar sobre una base de datos ya existente (legacy para los letrados) y, como generar  de manera sencilla un servicio que devuelva información en formato JSON.

Básicamente haremos lo siguiente:

  • Conocer los detalles de la base de datos/tabla a utilizar
  • Crear el proyecto.
  • Modificar el archivo BuildConfig.groovy
  • Modificar el archivo DataSource.groovy
  • Crear nuestra: Clase de dominio
  • Hacer magia con grails: generate-all
  • Crear un Controller propio
  • Listo.

¡¡¡ Manos a la obra !!!

Conocer los detalles de la base de datos/tabla a utilizar.

Jarhalla actualmente utiliza MySql para almacenar la información.
Iniciaremos con una tabla sencilla Repos:
mysql> desc REPOS;
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| ID_REPO | int(11) | NO | PRI | NULL | auto_increment | 
| NAME_SHORT | varchar(45) | YES | | NULL | | 
| URL_HOME | varchar(255) | YES | | NULL | | 
| URL_ICON | varchar(255) | YES | | NULL | | 
| LAST_MODIF | varchar(8) | YES | | NULL | | 
+------------+--------------+------+-----+---------+----------------+
El campo id es ID_REPO, y es autoincremental.

------------------------------------  3 registros con los que pueden iniciar su tabla ----------------
insert into repos (NAME_SHORT, URL_HOME, URL_ICON, LAST_MODIF) values ('Maven Repo 1','http://repo1.maven.org/maven2/','http://maven.apache.org/images/apache-maven-project-2.png','01/12/10');
insert into repos (NAME_SHORT, URL_HOME, URL_ICON, LAST_MODIF) values ('Spring Source','http://www.springsource.com/repository/app/','http://www.springsource.org/sites/all/themes/dotorg09/images/dotorg09_logo.png','01/12/10');
insert into repos (NAME_SHORT, URL_HOME, URL_ICON, LAST_MODIF) values ('CodeHaus','http://repository.codehaus.org/','http://media.codehaus.org/images/unity-codehaus-logo-only.png','01/12/10');    
----------------------------------------------------------------------------------------------------

Crear el proyecto.
Ya conocemos la tabla que vamos a manipular, entonces, lo que sigue según nuestro plan es crear la aplicación,

Si aún no tienes instalado grails. aquí puedes conocer a detalle los pasos.

En nuestro idioma si requieres documentación para iniciar con grails y groovy:
Para crear la aplicación, únicamente ejecutamos:

rugi$ grails create-app api-jarhalla

Al finalizar de la ejecución de  los scripts de grails obtenemos algo como lo siguiente en los mensajes de la consola:
------------------------------------------

grails tomcat

Created Grails Application at
/path/al/directorio/donde/ejecutaste/laCreacion/api-jarhalla
Ya tenemos lista la aplicación.
La estructura que crea es algo ya conocido, pero, recordemosla:
 |-api-jarhalla
 |---grails-app
 |-----conf
 |-------hibernate
 |-------spring
 |-----controllers
 |-----domain
 |-----i18n
 |-----services
 |-----taglib
 |-----utils
 |-----views
 |-------layouts
 |---lib
 |---scripts
 |---src
 |-----groovy
 |-----java
 |---test
 |-----integration
 |-----unit
 |---web-app
 |-----META-INF
 |-----WEB-INF
 |-------tld
 |-----css
 |-----images
 |-------skin
 |-----js
 |-------prototype
Todas las instrucciones que ejecutemos serán dentro de la carpeta creada.
/path/al/directorio/donde/ejecutaste/laCreacion/api-jarhalla

Modificar el archivo BuildConfig.groovy
Antes de ejecutar nuestro proyecto por primera vez vamos a agregar las dependencias extras que requerimos, en este caso, necesitamos el driver para MySQL.

Si abrimos el archivo BuildConfig.groovy, ubicado en: /grails-app/conf/ veremos algo así:
--------------------------------------------------------------------------------------------
grails.project.class.dir = "target/classes"
grails.project.test.class.dir = "target/test-classes"
grails.project.test.reports.dir = "target/test-reports"
//grails.project.war.file = "target/${appName}-${appVersion}.war"
grails.project.dependency.resolution = {
 // inherit Grails' default dependencies
 inherits("global") {
 // uncomment to disable ehcache
 // excludes 'ehcache'
 }
 log "warn" // log level of Ivy resolver, either 'error', 'warn', 'info', 'debug' or 'verbose'
 repositories {
 grailsPlugins()
 grailsHome()
 grailsCentral()
 // uncomment the below to enable remote dependency resolution
 // from public Maven repositories
 //mavenLocal()
 //mavenCentral()
 //mavenRepo "http://snapshots.repository.codehaus.org"
 //mavenRepo "http://repository.codehaus.org"
 //mavenRepo "http://download.java.net/maven/2/"
 //mavenRepo "http://repository.jboss.com/maven2/"
 }
 dependencies {
 // specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes eg.
 // runtime 'mysql:mysql-connector-java:5.1.13'
 }
}

--------------------------------------------------------------------------------------------
Básicamente aquí se definen las dependencias del proyecto y los respectivos repositorios dónde debe encontrarlas.
Vamos a descomentar 3 líneas, dos que activan la búsqueda en repositorios maven , y una última que activa la dependendia que, precisamente, necesitamos:  el conector de mysql!.

Así, nuestro BuildConfig.groovy queda:
--------------------------------------------------------------------------------------------------------------------------------------

grails.project.class.dir = "target/classes"
grails.project.test.class.dir = "target/test-classes"
grails.project.test.reports.dir = "target/test-reports"
//grails.project.war.file = "target/${appName}-${appVersion}.war"
grails.project.dependency.resolution = {
 // inherit Grails' default dependencies
 inherits("global") {
 // uncomment to disable ehcache
 // excludes 'ehcache'
 }
 log "warn" // log level of Ivy resolver, either 'error', 'warn', 'info', 'debug' or 'verbose'
 repositories {
 grailsPlugins()
 grailsHome()
 grailsCentral()

 // uncomment the below to enable remote dependency resolution
 // from public Maven repositories
 mavenLocal()
 mavenCentral()
 //mavenRepo "http://snapshots.repository.codehaus.org"
 //mavenRepo "http://repository.codehaus.org"
 //mavenRepo "http://download.java.net/maven/2/"
 //mavenRepo "http://repository.jboss.com/maven2/"
 }
 dependencies {
 // specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes eg.

 runtime 'mysql:mysql-connector-java:5.1.13'
 }
}
--------------------------------------------------------------------------------------------------------------------------------------
Modificar el archivo DataSource.groovy
El siguiente paso es, modificar el archivo DataSource.groovy ubicado también en: /grails-app/conf/ 
Aquí indicaremos los datos requeridos para acceder a la base de datos que contiene la tabla que queremos utilizar.

Si abrimos el archivo veremos algo así:
--------------------------------------------------------------------------------------------------------------------------------------
dataSource {
 pooled = true
 driverClassName = "org.hsqldb.jdbcDriver"
 username = "sa"
 password = ""
}
hibernate {
 cache.use_second_level_cache = true
 cache.use_query_cache = true
 cache.provider_class = 'net.sf.ehcache.hibernate.EhCacheProvider'
}
// environment specific settings
environments {
 development {
 dataSource {
 dbCreate = "create-drop" // one of 'create', 'create-drop','update'
 url = "jdbc:hsqldb:mem:devDB"
 }
 }
 test {
 dataSource {
 dbCreate = "update"
 url = "jdbc:hsqldb:mem:testDb"
 }
 }
 production {
 dataSource {
 dbCreate = "update"
 url = "jdbc:hsqldb:file:prodDb;shutdown=true"
 }
 }
}
--------------------------------------------------------------------------------------------------------------------------------------
¿Qué modificamos?
Pues, únicamente requerimos indicar los datos de nuestra origen de datos (data source), el driver que debemos invocar, y, de momento, la configuración para el ambiente de desarrollo (development enviroment), aquí, para fines prácticos (no queremos hacer CREATE y mucho menos DELETE ¿cierto?), elegimos dbCreate = 'update'

Nuestro archivo DataSource.groovy queda así:
--------------------------------------------------------------------------------------------------------------------------------------
dataSource {
 pooled = true
 driverClassName = "com.mysql.jdbc.Driver"
 username = "miUsuario"
 password = "miContrasenya"
}
hibernate {
 cache.use_second_level_cache = true
 cache.use_query_cache = true
 cache.provider_class = 'net.sf.ehcache.hibernate.EhCacheProvider'
}
// environment specific settings
environments {
 development {
 dataSource {
 dbCreate = "update" // one of 'create', 'create-drop','update'
 url = "jdbc:mysql://localhost/JARHALLA"
 }
 }
 test {
 dataSource {
 dbCreate = "update"
 url = "jdbc:hsqldb:mem:testDb"
 }
 }
 production {
 dataSource {
 dbCreate = "update"
 url = "jdbc:hsqldb:file:prodDb;shutdown=true"
 }
 }
}
--------------------------------------------------------------------------------------------------------------------------------------
Ahora, ya que hemos configurado todo lo requerido, pasamos a:

Crear nuestra: Clase de dominio

Sólo necesitamos ejecutar:
grails create-domain-class org.jarhalla.Repository
Al hacer esto  agregamos un par de carpetas al proyecto:
api-jarhalla/grails-app/domain/org/jarhalla
api-jarhalla/test/unit/org/jarhalla

El archivo que nos interesa, como bien estas imaginando se encuentra en:
api-jarhalla/grails-app/domain/org/jarhalla/Repository.groovy

Si lo abrimos veremos esto:
--------------------------------------------------------------------------------------------------------------------------------------
package org.jarhalla

class Repository {
    static constraints = {

    }

}
--------------------------------------------------------------------------------------------------------------------------------------
Ahora, únicamente nos hace falta hacer el mapeo de nuestra tabla, adicionalmente, grails requiere (para poder hacer parte de su mágia) que indiquemos cual es el campo id

Veamos cómo queda nuestro objeto de dominio:
--------------------------------------------------------------------------------------------------------------------------------------
package org.jarhalla

class Repository {

 String nameShort;
 String urlHome
 String urlIcon;
 String lastModif
 static constraints = {
  nameShort(maxSize:45)
 urlHome(maxSize:255) 
 urlIcon(maxSize:255)
 lastModif(maxSize:8) 
 }

 static mapping = {
  table 'REPOS'
 version false 
 columns {
 id column:'ID_REPO'
  nameShort column:'NAME_SHORT'
 urlHome column:'URL_HOME'
 urlIcon column:'URL_ICON'
 lastModif column:'LAST_MODIF'
 }
 }
}
--------------------------------------------------------------------------------------------------------------------------------------
Comentemos un poco el código, en el bloque: static constraints , aqui se definen las validaciones de nuestro objeto de dominio.
Algunas de estas validaciones son:
  • blank
  • creditCard
  • display
  • email
  • inList
  • matches
  • max
  • maxSize
  • min
  • minSize
El siguiente bloque es él que nos interesa, en static mapping se define el mapeo con la tabla que será representada por nuestro objeto de dominio.

Definimos la tabla con: table 'REPOS',  la siguiente línea: versión false sirve para indicarle a grails que: no tenemos un campo version en nuestra tabla.
Grails utiliza este campo para realizar parte de su mágia (ver Pessimistic and optimistic locking).

Dentro del mapeo la primera línea en especial, sirve para indicarle a grails cual será nuestro campo id:

id column:'ID_REPO'

El resto de los campos no tiene mayor problema.

Ahora (parafrasando a un maestro de la comedia en MX), ha llegado la hora "cuchicuchesca",  el momento "chimenguenchón", básicamente... el por qué de este post!!

Hacer magia con grails: generate-all

Aplicamos esta instrucción sobre nuestra clase de dominio.
rugi$ grails generate-all org.jarhalla.Repository

Esta operación, ha creado por nosotros un controlador (RepositoryController) en la carpeta:
grails-app/controllers/org/jarhalla/
Ademas, ha creado los archivos gsp necesarios para operar sobre nuestro objeto de dominio.
grails-app/views/repository/
Los archivos creados son: create.gsp, edit.gsp, list.gsp, show.gsp

Ahora, probemos.. es la hora.

rugi$ grails run-app

Si todo sale bien,  veremos el siguiente mensaje indicandonos que la aplicación está ya en ejecución. 

Server running. Browse to http://localhost:8080/api-jarhalla

Y si abrimos esa URL, debemos ver algo como lo siguiente:
app en ejecucion. index

Veamos el listado
http://localhost:8080/api-jarhalla/repository/list
List

¿Recuerdan que dentro de la configuración indicamos cual es el campo id?
Pues, ese parámetro sirve justamente para que podamos hacer algo como lo siguiente:
http://localhost:8080/api-jarhalla/repository/show/1

show

En este punto, por lo general, las tablas legacy, únicamente se utilizan para lectura de datos, así que,
después de ver la mágia funcionar (si es que es tu caso), elimina los gsp's: create.gsp y edit.gsp

Parte de la mágia la realiza el controlador RepositoryController.groovy

Si abrimos el archivo veremos algo así:
--------------------------------------------------------------------------------------------------------------------------------------
package org.jarhalla

class RepositoryController {

 static allowedMethods = [save: "POST", update: "POST", delete: "POST"]

 def index = {
 redirect(action: "list", params: params)
 }

 def list = {
 params.max = Math.min(params.max ? params.int('max') : 10, 100)
 [repositoryInstanceList: Repository.list(params), repositoryInstanceTotal: Repository.count()]
 }

  def create = {
 def repositoryInstance = new Repository()
 repositoryInstance.properties = params
 return [repositoryInstance: repositoryInstance]
 }

 def save = {
 def repositoryInstance = new Repository(params)
 if (repositoryInstance.save(flush: true)) {
 flash.message = "${message(code: 'default.created.message', args: [message(code: 'repository.label', default: 'Repository'), repositoryInstance.id])}"
 redirect(action: "show", id: repositoryInstance.id)
 }
 else {
 render(view: "create", model: [repositoryInstance: repositoryInstance])
 }
 }

 def show = {
 def repositoryInstance = Repository.get(params.id)
 if (!repositoryInstance) {
 flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'repository.label', default: 'Repository'), params.id])}"
 redirect(action: "list")
 }
 else {
 [repositoryInstance: repositoryInstance]
 }
 }

 def edit = {
 def repositoryInstance = Repository.get(params.id)
 if (!repositoryInstance) {
 flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'repository.label', default: 'Repository'), params.id])}"
 redirect(action: "list")
 }
 else {
 return [repositoryInstance: repositoryInstance]
 }
 }

  def update = {
 def repositoryInstance = Repository.get(params.id)
 if (repositoryInstance) {
 if (params.version) {
 def version = params.version.toLong()
 if (repositoryInstance.version > version) {
 
 repositoryInstance.errors.rejectValue("version", "default.optimistic.locking.failure", [message(code: 'repository.label', default: 'Repository')] as Object[], "Another user has updated this Repository while you were editing")
 render(view: "edit", model: [repositoryInstance: repositoryInstance])
 return
 }
 }
 repositoryInstance.properties = params
 if (!repositoryInstance.hasErrors() && repositoryInstance.save(flush: true)) {
 flash.message = "${message(code: 'default.updated.message', args: [message(code: 'repository.label', default: 'Repository'), repositoryInstance.id])}"
 redirect(action: "show", id: repositoryInstance.id)
 }
 else {
 render(view: "edit", model: [repositoryInstance: repositoryInstance])
 }
 }
 else {
 flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'repository.label', default: 'Repository'), params.id])}"
 redirect(action: "list")
 }
 }

 def delete = {
 def repositoryInstance = Repository.get(params.id)
 if (repositoryInstance) {
 try {
 repositoryInstance.delete(flush: true)
 flash.message = "${message(code: 'default.deleted.message', args: [message(code: 'repository.label', default: 'Repository'), params.id])}"
 redirect(action: "list")
 }
 catch (org.springframework.dao.DataIntegrityViolationException e) {
 flash.message = "${message(code: 'default.not.deleted.message', args: [message(code: 'repository.label', default: 'Repository'), params.id])}"
 redirect(action: "show", id: params.id)
 }
 }
 else {
 flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'repository.label', default: 'Repository'), params.id])}"
 redirect(action: "list")
 }
 }
}
--------------------------------------------------------------------------------------------------------------------------------------
Nuevamente, si es tu caso y requieres únicamente exponer el contenido de tu tabla, elimina las operaciones : create, edit, update, delete.

Analizar a detalle cada operación, va más allá de las intenciones de este primer post.
Como cualquier otra tecnología/framework, es mejor ir comenzando a familiarizarnos con ella realizando operaciones sencillas y de ahi, ir subiendo el nivel de complejidad.

Así pues,  nuestro penúltimo paso es:
Crear un Controller propio

Vamos a crear un controlador que concentre los servicios que queremos exponer, para este ejercicio de momento serán:
  • Obtener la información de los repositorios.
  • Obtener un listado de jars segun ciertos criterios de busqueda.
  • Obtener un listado de clases segun ciertos criterios de busqueda.
  • Obtener el detalle de un jar
En este primer post, iniciamos con el primer servicio.
Para crear el controlador, sólo necesitamos hacer lo siguiente:
rugi$ grails create-controller org.jarhalla.RepoV1
Esto nos crea un archivo llamado: RepoV1Controller.groovy

Y, se encuentra en la carpeta:
    api-jarhalla/grails-app/controllers/org/jarhalla

Si lo abrimos veremos algo como lo siguiente:
--------------------------------------------------------------------------------------------------------------------------------------
package org.jarhalla

class RepoV1Controller {

 def index = { }
}
--------------------------------------------------------------------------------------------------------------------------------------
Para lograr nuestro objetivo, devolver JSON, debemos indicarle a grails que requerimos de los convertes.
Para ello, únicamente agregamos esta línea:
import grails.converters.*

El siguiente paso es definir nuestra operacion, le llamaremos repos

Recordemos que nuestra clase de dominio se llama: Repository, recordemos tambien que grails genera cierta cantidad de operaciones para realizar querys (Querying with GORM)

Veremos dos en este ejemplo:  list() y findAll('something_query')
def repos={
 def l = Repository.list()
 render l as JSON
}

Nuestro controller  queda:
--------------------------------------------------------------------------------------------------------------------------------------
package org.jarhalla

class RepoV1Controller {

 def index = {
 render (contentType:"text/json"){
 success = "ok" 
 } 
 }

 def repos={
 def l = Repository.list()
 render l as JSON
 }
}
--------------------------------------------------------------------------------------------------------------------------------------
(He agregado una sencilla respuesta para la operación index)
Ahora, asegurate de que la aplicación está en ejecución y puedes probarlo sin problema:

http://localhost:8080/api-jarhalla/repoV1/repos
Esto ya debe devolver información en formato json:
json

Ahora,  Repository.list() devuelve tal cual el contenido del nuestra tabla, pero,  muy probablemente existirán registros en nuestra tabla legacy que no debemos devolver, ya sea por que son obsoletos o por alguna otra razón.

En este tipo de escenarios, es mejor utilizar el método findAll.

Aqui un ejemplo:

   def list = {
     def l = Repository.findAll("from Repository as r where r.id in (1,2)")
     render l as JSON
   }
Observen que, la sintaxis del query es sobre nuestra clase de dominio, no sobre los nombres de los campos de la tabla.

Aquí mas información para  conocer más sobre findAll.

Y, voilá!!

Con esto terminamos nuestro ejemplo de utilización de tablas legacy para ofrecer un servicio JSON utilizando grails.

Si haz llegado hasta aquí (que paciente eres!!) seguramente tienes algunas preguntas en la mente:
  • ¿Que pasa si nuestra llave es compuesta?
  • ¿Que significado tienen los parámetros que no configuramos?
  • ¿Que ocurrío cuando ejecutamos generate-all?
  • ¿Es correcto dejar todo en un Controller?
  • ¿Quién ganará el próximo mundial?
Si no puedes esperar a los siguientes post's, te comparto los siguientes enlaces:

Mastering Grails: Grails and legacy databases
Manual de desarrollo web con grails.
Presentan al sucesor del Pulpo Paul

Espero pronto continuar con el siguiente post.

Saludos!!!

---
RuGI
Isaac Ruiz Guerra

sábado, 8 de octubre de 2011

Eufemismos de rugi

Eufemismos de RuGI Ver0.8

Apache Tika 1.0. Un ejemplo de uso.

Apache tika 1.0



Apache tika por fin llega a su versión 1.0.
Es uno de los tantos proyectos surgidos a partir de lucene, en su caso particular, su objetivo es extraer metadatos de los archivos.

La lista de archivos soportados es muy amplia y variada.
En lo personal actualmente lo utilizo (entre otras cosas) para implementar un pequeño filtro para los archivos recibidos en una aplicación web.

Es común que, cuando nuestra aplicación requiera recibir archivos por un upload, tengamos que agregar mecanismos de validación ya sea por tamaño o tipo de archivo (¿no queremos que nos suban un sh o algo similar cierto?).

Una primera validación puede basarse en validar la extención del archivo, pero, seamos sinceros, todos hemos cambiado la extención de un archivo para brincarlos validaciones ;)
-Durante mucho tiempo (y a causa de un sistema dónde supe que ya casi nadie esa esa extención) mis archivos de música y video obscuros tenían extención .tiff -

Entonces, debemos usar algo mucho más fiable, una segunda propuesta es validar la cabecera del archivo y así ir descifrando que tipo de archivo es.

Pero, los libros lo dicen claramente: no debemos re-inventar la rueda.

Apache tika, entre otras cosas permite obtener esa información.

A continuación implementaremos un sencillo método para validar que tipo de archivo estamos recibiendo.

El primer paso es agregar los imports requeridos:

import java.io.*;
import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.Parser;
import org.apache.tika.sax.BodyContentHandler;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
Nuestro método principal es uno que recibe un byte[] y un String con el mime-type sobre el cual queremos comparar (el trim es el sello de la casa):

public static boolean isMimeType(byte[] b, String mime) throws
IOException,
SAXException,
TikaException {
boolean res = false;
InputStream is = new ByteArrayInputStream(b);

ContentHandler contenthandler = new BodyContentHandler();
Metadata metadata = new Metadata();
metadata.set(Metadata.RESOURCE_NAME_KEY, "temporal");
Parser parser = new AutoDetectParser();
// OOXMLParser parser = new OOXMLParser();
parser.parse(is, contenthandler, metadata);
if (mime.trim().toLowerCase().equals(
metadata.get(Metadata.CONTENT_TYPE).trim().toLowerCase()))
{
res = true;
}
return res;
}
Nuestro método requiere un arreglo de bytes, asumiendo que así es como recibimos el archivo, pero, con este otro método podemos tomarlo desde de un File:
private static byte[] getBytesFromFile(File file) throws IOException {
InputStream is = new FileInputStream(file);
long length = file.length();
if (length > Integer.MAX_VALUE) {
// File is too large
}
byte[] bytes = new byte[(int) length];
int offset = 0;
int numRead = 0;
while (offset < bytes.length &&
(numRead = is.read(bytes, offset, bytes.length - offset)) >= 0)
{
offset += numRead;
}
if (offset < bytes.length) {
throw new IOException("Could not completely read file "
+ file.getName());
}
// Close the input stream and return bytes
is.close();
return bytes;
}
Con esos método, es fácil poder hacer lo siguiente:
// ¿Será un pdf?
System.out.println(
MimeUtil.isMimeType(
MimeUtil.getBytesFromFile(
new
File("/Users/aldebaran/carpeta1/documentos/archivo1.pdf")
),
"application/pdf")
);
Si nuestro archivo es un pdf, así tenga otra extención, el resultado será:
true

Y listo.... tenemos un sencillo filtro de archivos conociendo su mime-type a través de apache-tika.

Saludos...
---
RuGI

Magia y Esperanza


Este post fue publicado originalmente en Noviembre del 2004 en mi anterior blog.
Dado un intercambio de comentarios con @hguerreroo y @hidelink he decido migrarlo a este nuevo blog.



Zocalo, Mexico City

N:

Cuando llegué a esta ciudad, en la terminal de autobuses, escuche a un turista decir "Esta Ciudad es Mágica", y en el poco o mucho tiempo, según se vea , que llevo en ella me he dado cuenta de que quizá tiene razón.

  • Puedes pasar de un estado depresivo a la mayor de las esperanzas en sólo un instante.
  • puedes haber tenido uno de los días más pesados de tu vida;
  • puedes haber extraviado el último boleto del metro que te quedaba, y ahora necesitas comprar otro;
  • puedes haber sufrido un percance en la taquilla al comprar tu boleto;
  • puedes haber perdido el vagón por haber comprado el boleto;
  • puedes sentir que pasan horas y el siguiente tren no llega;
  • puedes recibir un fuerte empujón al intentar subir, pues alguien tiene más prisa que tú.

Una vez dentro, tus ojos buscan desesperadamente un lugar que logre librar a la torrente de personas que bajan y/o suben al mismo tiempo; paradójicamente, detrás de la puerta es el mejor lugar... pero si esta ocupado, una esquina del fondo es tu única opción.

Y de repente, entre todo el caos de esta ciudad, provocado, soportado y permitido por sus mismos habitantes (me incluyo), surge una chispa que te hace recobrar la esperanza.
Ahí estaba, era un niño (menor de 10 años ), acompañado de su hermana ( supongo ), una niña aún más pequeña, quizá 5 o 6 años; viajando en metro solos, entran al vagón y él la toma de la mano. En ese momento, no hay brazo más fuerte que el del niño, parecía que aunque la tierra se abriera, él por ningún motivo soltaría lo que su pequeño brazo apretaba.
Se colocan a tu lado, buscando, igual que tú, un lugar más o menos cómodo y seguro.

Como si supieran todo lo que te ha ocurrido, voltean, te ven a los ojos y esbozan una sonrisa, te sonríen a ti, y no puedes creer que sea a ti a quien sonríen.

Todavía incrédulo, intentas voltear para ver si le han sonreído a otra persona, pero recuerdas que estas en una esquina, y, a excepción que le hayan sonreído a las paredes del vagón, concluyes que te han sonreído a ti.

Te pones a pensar que has visto sonreír a muchas personas, pero la risa de estos niños te han llenado de una tranquilidad y una esperanza inmensa, jamas experimentada,
  • quizá tu estado depresivo te lo hace parecer así;
  • quizá la inocencia de los niños ha creado ese efecto;
  • quizá tú mismo lo has creado pues lo necesitas ó
Quizá realmente -como dijo un turista- "Esta ciudad es mágica"

---
RuGI


Comentarios:
Mira, no sé qué ciudad será, pero por lo que cuentas parece Madrid... :)
Enviado por emillan en noviembre 18, 2004 a las 03:56 AM CST

-----------------------------
Créditos:
La foto es de: noctambulator
-----------------------------

domingo, 2 de octubre de 2011

El no lugar.


Este post apareció originalmente el 09 de Octubre del 2005 en mi anterior blog.
Curiosamente es uno de los post's más leídos.




El Metro



Hace algunos años conocí este concepto en alguna exposición de arquitectura: "no lugar".

Un "no-lugar" es un término arquitectónico utilizado para designar esos lugares en donde no hay identidad, ni vínculos directos entre el que lo ocupa y el lugar mismo. Un espacio donde eres anónimo, donde nada te afecta.... generalmente se vincula mucho con los centros comerciales(funcionalmente hablando, te da igual estar en Perisur que en Santa Fé o en Interlomas )

En términos del autor del término Marc Augé:

"Si un lugar puede definirse como lugar de identidad, relacional e histórico, un espacio que no puede definirse ni como espacio de identidad ni como relacional ni como histórico, definirá un [no lugar]."

"La sobremodernidad es productora de no lugares, es decir, de espacios que no son lugares antropológicos y que, no integran los lugares antiguos: [lugares de memoria]."

En su escrito: [PDF] Sobremodernidad. Del mundo de hoy al mundo de mañana. Marc Augé menciona algunos ejemplos de estos lugares:
  • Los espacios de circulación: autopistas, áreas de servicios en las gasolineras, aeropuertos, vías aéreas...
  • Los espacios de consumo: super e hypermercados, cadenas hoteleras
  • Los espacios de la comunicación: pantallas, cables, ondas con apariencia a veces inmateriales.

Todo este comentario viene por lo siguiente, últimamente me he puesto a pensar si el metro entra en éste concepto; cada día que pasa me doy cuenta que el metro es una especie de limbo inter/intra personal.

Es un espacio público que por momentos alcanza el delicado grado de íntimo.

De repente un vagón cualquiera se convierte en:
  • La sala familiar donde los padres educan a sus hijos ( "le voy a decir a la maestra que no me dijiste que tenias tarea", "y no llores", "calla y obedece")
  • La recámara prenupcial de algunos enamorados que se empeñan en hacer difícil la vida de los pocos célibes que quedan en el planeta.
  • El tocador de la jovencita que revela todos sus secretos de belleza entre estación y estación.
  • El confesionario de un ex-convicto que parece enaltecerse de haber superado ya su gusto por lo ajeno, haciendo ahora labor de asesor de seguridad de cuanta persona se deje ("reina, puedo ver tu monedero, deberías guardarlo más.... ya te lo hubiera robado.").
  • El comedor central del adulto que olvida que no todos tenemos un olfato inmune a especias hindúes
  • Etc, etc.

Este conjunto de acciones, realizadas por lo general en un espacio íntimo/personal/único/nuestro, realizadas ahora en este "no-lugar", hacen que uno piense hacia donde va todo esto que llamamos "modernidad".

¿O la habremos rebasado ya y estaremos viendo los frutos de la llamada sobre-modernidad?
¿O será sólo mi paranoia desbordada?

Lo que más curioso me resulta es que, cada día nos vamos haciendo participes y cómplices de este estilo de vida (sic), un día cualquiera ayudaré a reprimir a un niño, le daré consejos a un enamorado, sugeriré tonos de colores de rubor a alguna jovencita, escucharé con más atención los consejos de seguridad de algún ex-presidiario, agregaré más especias a mis comidas y por que no, dejare de pensar en "no-lugares" y cosas raras y me dedicaré a disfrutar del viaje.... que 20 estaciones son un buen tramo ¿ no creen? ;)

Saludos
---
RuGI
Isaac Ruiz Guerra
------------------------------------------------------------
Créditos:
la foto es de:
------------------------------------------------------------



Eureka


Este post fue publicado originalmente hace un par de años en mi anterior blog y dada la fecha (2 de Octubre del 2011) he decido replicarlo este día.

Eureka.

En lo personal, la referencia principal que tengo de esta palabra es la anécdota que nos cuentan en las primeras clases de matemáticas. Cuando Arquímedes (sumergido en su bañera hace la deducción que permitiría medir volúmenes de cuerpos de formas irregulares) salta desnudo y grita: !!Eureka, Eureka¡¡... !! lo encontré!

El comentario viene a propósito del premio al Mérito Ciudadano que fue entregado por la asamblea legislativa del Distrito Federal a la Sra. Rosario Ibarra de Piedra (precursora de la lucha por los desaparecidos políticos en los años 70's).

La Sra. Ibarra es actualmente presidenta de un comité ciudadano que lleva como nombre precisamente la palabra que titula este mensaje.

100_3382

El comité Eureka esta dedicado a exigir la libertad y presentación de desaparecidos/presos políticos en México.

El premio me parece adecuado, creo que después de 25 años de estar esperando y luchando con la esperanza como única arma, cualquier incentivo a esa espera es bienvenido.

Quizá para los que no estén familiarizados con este tema, les parezca demasiado inapropiado tocarlo.

Pero para los que vivimos, sino en el instante, los ecos que esos acontecimientos que ocurrieron hace más de 25 años, cuando hablar o pensar algo en contra del gobierno era motivo de encarcelamiento y un enfrentamiento directo era sinónimo de suicidio, el tema aún esta fresco en la memoria.

De repente me pongo a pensar que (como siempre he pensado, lo que une a dos entes es lo que comparten), uno de los lazos que une a toda Iberoamérica no sólo es el idioma, sino que en todas nuestras historias hay un capítulo amargo coloreado con demasiadas ausencias.

Argentina, España, Colombia, México,Chile y en todos los demás países hay aún hoy personas que tienen la más grande de las esperanzas para encontrar o por lo menos saber que les ocurrió a sus hijos, hermanos o amigos.

Aún en estos días hay quienes pasan el mayor tiempo en sus casas esperando alguna noticia sobre ellos.

Aún en estos días, después de tantos años, siguen firmes exigiendo a sus respectivos gobiernos respuestas a sus preguntas.

Aún en estos días hay personas que se levantan cada mañana esperando poder gritar (con todas sus fuerzas), al igual que Arquímedes .... ¡¡¡ Eureka !!! lo he encontrado.

Saludos.
---
RuGI
Isaac Ruiz Guerra.
------------------------------------------------------------
Créditos:
la foto es de:
------------------------------------------------------------

sábado, 1 de octubre de 2011

mx.urbi.df.rugi.reflexion.Obviedad

La suposición es la madre de todos los errores.
Ley de Whithern


Tomar decisiones pensando o asumiendo cosas, es creo, uno de los problemas más comunes de nuestros tiempos.

Cuantas veces no hemos dicho, en tono de reclamo o de asombro, "Pero, ¡es obvio!"

El detalle con lo obvio es que, nos genera una sensación de falsa seguridad la cual puede llevarnos a tomar decisiones inadecuadas.

A veces requerimos de ciertos eventos un tanto cuanto cotidianos que nos puedan ayudar a dejar claro que, no todo lo obvio es obvio.

Aquí uno de ellos.

Hay tamales.
El tamal (del náhuatl tamalli, que significa envuelto) es un alimento básico en México, particularmente en la ciudad de México, se pueden encontrar cientos de puestos ambulantes que, durante las mañanas alimentan a buena parte de la fuerza laboral que se desplaza en promedio 1hr 15min para llegar a sus respectivos lugares de trabajo (incluso los que viajan en sus propios autos realizan en promedio 53 minutos)

El package: tamal, junto con un atole, es el desayuno diario para miles de personas en esta ciudad.

Un día, de entre tantos días, tuve que desplazarme a una zona poco conocida de la ciudad, me encuentro un puesto, y lo primero que dije al acercarme fue:
- "¿Me da un tamal?"
La persona a cargo del puesto me dice con voz amable:
- "No vendemos tamales"

Lo primero que pensé fue:
- "¡¡¡ Debe ser tardísimo, ya se les acabaron los tamales!!!"

Tomé aire y entonces dije imperiosamente:
-"Bueno, déme un atole de chocolate entonces"

Nuevamente la persona me queda viendo, un poco igual de imperiosa me dice:
-"Disculpe, aquí sólo vendemos café y tortas."

Agradecí la atención, dí media vuelta y camine a la siguiente cuadra en busca de otro puesto, al encontrar uno lo primero que dije (después de saludar con un empático -buenos días- para orgullo de mis maestros de primaria), fue:
"¿Tienen tamales y atole?"

El dueño del puesto contestó un tanto asombrado:
-"Sí, ¿Qué más podríamos vender?. ¿No es obvio?"


Tamales de la esquina


¡¡¡ Saludos !!!
---
RuGI
Isaac Ruiz Guerra.
------------------------------------------------------------------
Créditos:
La foto de los tamales es de :
Francisco Enrique Camacho Mezquita
------------------------------------------------------------------