Está en la página 1de 92

Traducido del inglés al español - www.onlinedoctranslator.

com

Lectura de documentos XML 519

if (qName != 'tarea') return def title =


Interesado solo en
atts.getValue('title') def total =
elementos de la tarea
atts.getValue('total') switch
(atts.getValue('done')) {
caso '0' : próximo << título ; descanso
case { it != total } : en proceso << title ; descanso
}
}
}

def handler = nuevo PlanHandler()


def factory = SAXParserFactory.newInstance() def reader = declara nuestro
Lector de SAXO
factory.newSAXParser().XMLReader reader.contentHandler =
controlador
nuevo archivo ('datos/plan.xml'). withInputStream { es ->
lector.parse(nuevo InputSource(es))
}

aseverar controlador.en camino == [


'usar en el proyecto actual'
]
aseverar controlador.próximo == [
'volver a leer DB capítulo',
'usar base de datos/XML combinación'

Tenga en cuenta que con este estilo de procesamiento, tenemos más trabajo por hacer. Cuando nuestro
elemento de iniciose llama al método, se nos proporciona información del evento SAX, incluido el nombre
del elemento (junto con un espacio de nombres, si se proporciona) y todos los atributos. Depende de
nosotros determinar si necesitamos esta información y procesarla o almacenarla según sea necesario
durante esta llamada de método. El analizador no hará más almacenamiento por nosotros. Esto minimiza
la sobrecarga de memoria del analizador, pero la implicación es que no podremos realizar un
procesamiento de estilo GPath y no estamos en condiciones de manipular una estructura de datos en
forma de árbol. Tendremos más que decir sobre la información del evento SAX cuando exploremos
XmlSlurpercon más detalle en la sección 14.2.

14.1.4 Lectura con un analizador StAX


Además de los analizadores SAX de estilo push admitidos por Java, una tendencia reciente en el procesamiento
de XML con Java es utilizar analizadores basados en eventos de estilo pull. Los más comunes de estos se llaman
Analizadores basados en StAX4(API de transmisión para XML). Con un analizador de este tipo, todavía está
interesado en los eventos, pero le pide eventos al analizador (extrae eventos según sea necesario) durante el
procesamiento,5en lugar de esperar a ser informado por los métodos que se llaman.
La siguiente lista muestra cómo puede usar StAX con Groovy. Si está utilizando una versión
anterior de Java, deberá agregar un analizador StAX a su classpath para ejecutar este ejemplo.

4
Elliotte Rusty Harold, “Una introducción a StAX”, O'Reilly XML.com, 2003, www.xml.com/pub/a/2003/09/17/
stax.html.
5
Este es el principal estilo basado en eventos compatible con .NET e incluido con Java 6.

www.it-ebooks.info
520 CPASADO14Trabajando con XML y JSON

Listado 14.7 Usando un analizador StAX con Groovy

importar javax.xml.stream.*

def entrada = 'archivo:datos/plan.xml'.toURL() def en


curso = []
def próximo = []

def eachStartElement(inputStream, rendimiento de cierre) {


token def = XMLInputFactory.newInstance() declara
analizador
. crearXMLStreamReader(entradaStream)
probar {
while (token.hasNext()) {
Bucles a través
if (token.startElement) produce token eventos de
token.next() interés
}
} finalmente {
¿token?.cerrar()
flujo de entrada?.cerrar()
}
}

clase XMLStreamCategory { define


Objeto estático obtener (XMLStreamReader self, clave de cadena) { categoría
devolver self.getAttributeValue(nulo, clave) por simple
} atributo
} acceso

uso (XMLStreamCategory) {
eachStartElement(entrada.openStream()) { elemento ->
if (elemento.nombre.toString() != 'tarea') return switch
(elemento.hecho) {
caso '0':
próximo << elemento.rotura de Usos
título categoría
case { it != elemento.total } :
en marcha << elemento.título
}
}
}

afirmar en marcha == [
'usar en el proyecto actual'
]
afirmar próximo == [
'volver a leer DB capítulo',
'usar base de datos/XML combinación'
]

Tenga en cuenta que este estilo de análisis es similar al análisis de estilo SAX, excepto que estamos
ejecutando el ciclo de control principal nosotros mismos en lugar de que el analizador lo haga. Este estilo
tiene ventajas para ciertos tipos de procesamiento donde el código se vuelve más simple de escribir y
comprender.
Suponga que tiene que responder a muchas partes del documento de manera diferente. Con los
modelos push, su código debe mantener un estado adicional para saber dónde se encuentra y cómo

www.it-ebooks.info
Procesamiento de XML 521

reaccionar. Con un modelo de extracción, puede decidir qué partes del documento procesar en
cualquier punto dentro de su lógica comercial. El flujo a través del documento es más fácil de
seguir y el código se siente más natural.
Ahora hemos explorado la variedad de opciones de análisis disponibles en Groovy. A continuación,
exploramos las ventajas de las opciones de análisis específicas de Groovy con más detalle.

14.2 Procesamiento de XML


Muchas situaciones que involucran XML requieren más que solo leer los datos y luego navegar a
un elemento o nodo específico. Los documentos XML a menudo requieren transformación,
modificación o consultas complejas. Cuando nos fijamos en las características de
XmlParseryXmlSlurpercuandoProcesandodatos XML de esta manera, vemos las mayores
diferencias entre los dos. Comencemos con una analogía simple pero quizás sorprendente:
calentar agua.
Hay esencialmente dos formas de hervir el agua, como se ilustra en la figura 14.2. Puede verter
agua en un tanque (llamadocaldera), caliéntelo y obtenga el agua caliente de la salida. O puede
usar un calentador de flujo continuo, que calienta el agua mientras fluye desde la entrada de agua
fría a través del serpentín de calentamiento hasta que llega a la salida. El calentamiento ocurre solo
cuando se solicita, como se indica al abrir el grifo de salida.
¿Cómo se relaciona el procesamiento XML con el agua hirviendo? Procesar XML significa que no solo
está utilizando fragmentos de la información almacenada, sino que también los está recuperando,
agregándoles una nueva calidad (haciéndoloscalienteen nuestra analogía), y dando salida a todo. Al igual
que hervir agua, esto se puede hacer de dos maneras: almacenando la información en la memoria y
procesándola en el lugar, o recuperando información de un flujo de entrada, procesándola sobre la
marcha y transmitiéndola a un dispositivo de salida. .
En general, procesar XML conXmlParser (yGroovy.util.nodo)es más como usar una
caldera;XmlSlurperpuede servir como fuente en un escenario de flujo análogo al
calentamiento de flujo continuo.

Caldera Calentador de flujo continuo

Figura 14.2 Comparación de las estrategias de ebullición versus calentamiento de flujo continuo

www.it-ebooks.info
522 CPASADO14Trabajando con XML y JSON

Comenzaremos observando la estrategia de "ebullición" de


modificación y procesamiento en el lugar y luego
procederemos a explorar el procesamiento transmitido y
las combinaciones con XPath.

14.2.1 Procesamiento in situ


El procesamiento in situ es el medio convencional de
procesamiento de XML. utiliza elXmlParserpara recuperar un
árbol de nodos. Estos nodos residen en la memoria y se
pueden reorganizar, copiar o eliminar, y se pueden cambiar
sus atributos. Usaremos este enfoque para generar un informe
HTML para realizar un seguimiento de nuestras actividades de
aprendizaje de Groovy. Figura 14.3 Un progreso HTML
Suponga que el informe debería parecerse a la figura 14.3. informe de actividades de aprendizaje Groovy

Puede ver que la nueva información se deriva de los datos


existentes: las tareas y las semanas tienen una nueva propiedad
que llamaremosestadocon los posibles valores deprogramado, en curso,yacabado.
Para las tareas, el valor de laestadoLa propiedad se determina observando lahecho
ytotalatributos Sihechoes cero, el estado se consideraprogramado;sihechoes igual o
superiortotal,el estado esacabado;de lo contrario, el estado esen progreso.
Las semanas sonacabadocuando todas las tareas contenidas sonacabado.Ellos sonen progreso
cuando al menos una tarea contenida esen progreso.
Parece que vamos a hacer muchas comparaciones de números con elhechoy
totalatributos Desafortunadamente, estos atributos se almacenan como cadenas, no como números. Estas
consideraciones conducen a un proceso de “calentamiento” de tres pasos:

1 Convierta todos los valores de atributo de cadena en números cuando sea adecuado.

2 Agregue un nuevo atributo llamadoestadoa todas las tareas y determinar el valor.


3 Agregue un nuevo atributo llamadoestadoa todas las semanas, y determine el valor.

Con una representación de datos tan mejorada, finalmente es fácil de usarCreador de


marcaspara producir el informe HTML.
Tenemos que producir una fuente HTML como

<html>
<cabeza>
<título>Actual maravilloso progreso</título>

<enlace href='estilo.css' type='texto/css' rel='hoja de estilo' /> </head>

<cuerpo>
<h1>Semana nº 0: en curso</h1> <dl>

<clase dt='acabado'>leer capítulo XML</dt> <dd>(2/2):


terminado</dd>

www.it-ebooks.info
Procesamiento de XML 523

</dl>
</cuerpo>
</html>

donde la hoja de estilo style.css contiene la decisión de cómo se muestra finalmente una
tarea según su estado. Puede, por ejemplo, usar las siguientes líneas para ese propósito:

dt {fuente-peso:negrita}
dt.finished { font-weight:normal; decoración de texto: línea a través }

El listado 14.8 contiene la solución completa. losnumerarEl método implementa la conversión de cadena a
número para aquellos atributos que esperamos que sean de contenido entero. También muestra cómo
trabajar recursivamente a través del árbol de nodos.
Los métodosestado de la semanayestado de la tareahacer lo nuevoestadoatributo disponible en el nodo
correspondiente, dondeestado de la semanallamadasestado de la tareapara todas sus tareas contenidas
para asegurarse de que pueda trabajar en su estado dentro de las expresiones GPath.
El finalhtmlReportarEl método es la forma convencional de construir HTML. Gracias al
trabajo previo de "calentamiento", no se necesita lógica en el informe. El informe utiliza la
estadoatributo para asignar una hoja de estiloclasedel mismo valor.

Listado 14.8 Generando un reporte HTML con preparación de datos en memoria

importar Groovy.xml.MarkupBuilder
Convierte cadenas
a números
void numberfy(nodo nodo) {
def atts = node.attributes() atts.keySet().grep(['capacidad', 'total',
'hecho']).each {
atts[it] = atts[it].toInteger()
}
node.each { if (instancia de Nodo) numberfy(it) }
}

void taskStatus(tarea) {
Calcula y
def atts = task.attributes() switch asigna el estado de la tarea
(atts.done) {
caso 0: atts.status = 'programado'; descanso
caso 1..<atts.total: atts.status = 'en progreso'; romper por defecto:
atts.status = 'terminado';
}
}
Calcula y
estado de semana vacío (semana) { asigna semana

semana.tarea.cada { taskStatus(it) } def atts = estado

semana.atributos() atts.status =
'programado'
if (semana.tarea.cada { it.@status == 'terminado'})
atts.status = 'terminado'
if (semana.tarea.cualquiera { it.@status == 'en progreso'})
atts.status = 'en curso'
}

www.it-ebooks.info
524 CPASADO14Trabajando con XML y JSON

void htmlReport(constructor, plan) {


Informes
constructor.html { lógica de construcción
cabeza {
titulo('Actual Progreso maravilloso')
enlace (rel: 'hoja de estilo',
escribe: 'texto/css',
referencia: 'estilo.css')

}
cuerpo {
plan.week.eachWithIndex { semana, i ->
h1("Número de semana $i: ${ semana.@estado }")
dl {
semana.tarea.cada { tarea ->
dt(clase: tarea.@estado , tarea.@título ) dd("(${ tarea.@hecho }/$
{ tarea.@total }): ${ tarea.@estado }")
}}}}}}

def nodo = new XmlParser().parse(nuevo archivo('data/plan.xml'))


numberfy(nodo) Prepara datos
nodo.semana.cada { estadodesemana(es) } para informar

nuevo archivo ('data/GroovyPlans.html').withWriter { escritor ->


def builder = new MarkupBuilder(escritor)
htmlReport(constructor, nodo)
}

Después del cuidadoso trabajo previo, el código no sorprende. Lo que es un poco poco
convencional es tener muchas llaves de cierre en una línea al final dehtmlInforme.Esto no es solo
para la composición tipográfica compacta en el libro. A veces también usamos este estilo en
nuestro código cotidiano. Encontramos que revela muy bien qué niveles de sangría se deben cerrar
y aún nos permite verificar la coincidencia de llaves por columna. Sería genial tener soporte IDE
para alternar entre este y el diseño de código convencional.
Ahora que ha visto cómo usar la "caldera" en memoria, investiguemos el escenario
de transmisión.

14.2.2 Procesamiento de transmisión

Para demostrar el uso de la transmisión, comencemos con el tipo de procesamiento más simple
que se nos ocurra: bombear lo que entra sin ninguna modificación. Incluso este simple ejemplo
puede ser difícil de entender mientras el enfoque no sea familiar. Te recomendamos que si te
resulta confuso, sigas leyendo, pero no te preocupes demasiado por los detalles. Definitivamente
vale la pena volver más tarde para un segundo intento, aunque, en muchas situaciones, los
beneficios del procesamiento basado en secuencias bien valen el modelo conceptual más difícil.

www.it-ebooks.info
Procesamiento de XML 525

tuTUBERÍAS NO MODIFICADAS

Tu usasXmlSlurperpara analizar el XML original. Debido a que el formato de salida final es XML
nuevamente, necesita algún dispositivo que pueda generar XML en forma de transmisión. los
Groovy.xml.StreamingMarkupBuilderLa clase está especializada para generar marcado bajo
demanda, en otras palabras, cuando unsumidero de informaciónlo solicita Tal sumidero es una
operación que solicita unEscribible (por ejemplo, la llamada del operador de desplazamiento a la
izquierda en flujos o la evaluación de GStrings). el truco queStreamingMarkupBuilderutiliza para
lograr este efecto es similar al enfoque de los motores de plantilla.StreamingMarkupBuilder
provee ununirmétodo que devuelve unCierre Escribible.Este objeto es unescribible
y un cierre al mismo tiempo. porque es unescribible,puede usarlo donde se solicite el marcado final.
Debido a que es un cierre, la generación de este marcado se puede realizar de forma perezosa
sobre la marcha, sin almacenar resultados intermedios.
El listado 14.9 muestra esto en acción. losunirEl método también necesita la información
sobre qué lógica se va a aplicar para producir el marcado final. Donde sea que se necesite
lógica, los cierres son el primer candidato, y así es conunir.Pasamos un cierre a la
unirmétodo que describe la lógica de marcado.
Para nuestro ejemplo inicial de bombear elsenderoa través de, utilizamos una función especial de
StreamingMarkupBuilderque nos permite ceder la lógica de generación de marcado a un
construible,un objeto que sabe construirse a sí mismo. Sucede que unGPathResult
(y por lo tantosendero)es edificable. Para darle la lógica de construcción, usamos elrendir
método. Pero no podemos usarlo sin calificar porque produciríamos un <rendimiento/>marcado si lo hicimos. El
símbolo especialmkpmarca nuestra llamada de método como perteneciente al espacio de nombres de las
palabras clave de marcado.

Listado 14.9 Bombeo de un flujo XML sin modificación

importar Groovy.xml.StreamingMarkupBuilder

def ruta = nuevo XmlSlurper().parse(nuevo archivo('datos/plan.xml'))

def builder = new StreamingMarkupBuilder() def copier =


builder.bind{ mkp.yield(path) } def result = "$copier"

afirmar result.startsWith('<plan><week ')


afirmar result.endsWith('</week></plan>')

Están sucediendo muchas cosas aquí en solo unas pocas líneas de código. losresultadovariable, por
ejemplo, se refiere a un GString con un valor: una referencia acopiador.Tenga en cuenta que no lo
llamamos "copia" porque no es una cosa sino un actor.
Cuando llamamos a lacomienza conmétodo enresultado,se solicita la representación
de cadena de GString, y debido a que el valor de GStringcopiadores unescribible,su
escribir ase llama el método. loscopiadorfue construido por elconstructorde modo queescribir a
relés aruta.construir().

www.it-ebooks.info
526 CPASADO14Trabajando con XML y JSON

La figura 14.4 resume este comportamiento de transmisión.


Observe cómo en la figura 14.4, el procesamiento no comienza antes de que se soliciten
los valores. Solo después de los GStringEncadenarse llama el métodocopiadorempezar a
correr y es elsenderoiterado sobre. Hasta entonces, elsenderono se toca! No se ha creado
ninguna representación de memoria con fines de marcado o iteración. Esta es una
simplificación de lo que está pasando.XmlSlurpertiene requisitos de memoria. Almacena la
información del evento SAX que vio en la sección 14.1.3, pero no la procesa ni la almacena en
el procesamiento amigable.Nodoobjetos.
Vocacióncomienza cones como abrir el grifo de salida para sacar el marcado del
copiador,que a su vez extrae su fuente de información de lasenderoentrada. Cualquier código
antes de ese punto es solo la plomería.
Como variante del listado 14.9, también puede escribir directamente el marcado en la consola.
Usa lo siguiente:

System.out << copiadora

RecuérdaloSistema.fueraes unSalida de corrienteque entiende el operador de desplazamiento a la izquierda


con unescribibleargumento.

:Guion

XMLSlurper.parse(1) <<Construible>>
sendero:
GPathResult
crear constructor:
StreamingMarkup
Constructor

unir
crear <<Escribible>>
copiador
copiadora: Cierre

crear
resultado:GString

comienza con

escribir a
construir

Figura 14.4 Diagrama de secuencia UML para construcción transmitida

www.it-ebooks.info
Procesamiento de XML 527

Para este ejemplo simple, podríamos haber usado los enfoques SAX o StAX que vio
anteriormente. Serían soluciones aún más simplificadas. No solo no necesitarían procesar y
almacenar las estructuras de datos en forma de árbol queXmlParsercrea para usted, pero
tampoco necesitarían almacenar la información del evento SAX. No ocurre lo mismo con los
escenarios más complicados que siguen. Como es común en muchos escenarios de
procesamiento de XML, los ejemplos restantes tienen requisitos de procesamiento que
abarcan varios elementos. Dichos escenarios se benefician enormemente de la capacidad de
utilizar expresiones GPathstyle.

HCOMER HASTAHTML
Hasta ahora, copiamos solo la entrada "fría". Es hora de encender nuestro calefactor. El objetivo es
producir la misma GUI que en la figura 14.3.
Comenzamos con los conceptos básicos del listado 14.9, pero mejoramos el cierre de marcado
que se une al constructor. En el listado 14.10, el edificio se ve casi igual que en el ejemplo
“hirviendo” del listado 14.7; sólo la evaluación de lasemanaytareahay que adaptar el estado. No
calculamos el estado por adelantado y lo almacenamos para referencia posterior, sino que
hacemos la clasificación sobre la marcha cuando el constructor lo solicita con pereza.

Listado 14.10 Calentamiento transmitido de XML a HTML

importar Groovy.xml.StreamingMarkupBuilder
Calcula
def taskStatus(tarea) { estado de la tarea
switch ( tarea.@done.toInteger ()) {
caso 0: devuelve 'programado'
caso 1..< tarea.@total.toInteger (): devuelve 'en progreso'
predeterminado: devuelve
'acabado'
}
}
Calcula
{
estado de la semana
semanaEstado(semana)
definitivamente

if (week.task.every { taskStatus(it) == 'finished' })


volver 'terminado'
if (week.task.any { taskStatus(it) == 'en progreso' })
volver 'en progreso'
volver 'programado'
}
"Sorbe" en
def plan = nuevo XmlSlurper().parse(nuevo archivo('datos/plan.xml'))
el XML

Marcado de cierre = {
Expresa el
html { procesamiento como
cabeza { un cierre
titulo('Actual maravilloso Progreso')

enlace (rel: 'hoja de estilo',


escribe: 'texto/css',
referencia: 'estilo.css')

}
cuerpo {
plan.week.eachWithIndex { semana, i ->
h1("Semana No. $i: ${propietario.weekStatus(week)}")

www.it-ebooks.info
528 CPASADO14Trabajando con XML y JSON

dl{
semana.tarea.cada { tarea ->
def status = propietario.taskStatus(tarea)
Enlaces analizados
dt(clase: status, task.@title ) dd("($
XML a
{ task.@done }/${ task.@total }): $estado")
lógica de procesamiento
}}}}}}

def calentador = new StreamingMarkupBuilder().bind(markup) def outfile =


new File('data/StreamedGroovyPlans.html') outfile.withWriter{ it << escribe
resultado a un archivo
calentador }

Lo bueno aquí es que a primera vista parece similar a la lista 14.8, pero funciona de manera
muy diferente:
- Toda evaluación se hace con pereza.

- Se minimiza el consumo de memoria para las operaciones de GPath.


- No se crea ningún ensamblaje en memoria de la representación HTML antes de la salida.

Esto le permite producir una gran cantidad de resultados, ya que no se ensamblan en la memoria, sino
que se transmiten directamente a la salida según lo exige la lógica de construcción. Pero debido al
almacenamiento de información de eventos SAX en la entrada, este enfoque no permitirá documentos de
entrada tan grandes como sería posible con SAX o StAX.
La figura 14.5 esboza las diferencias entre ambos enfoques de procesamiento con
respecto a los requisitos de procesamiento y el uso de la memoria. El proceso va de izquierda
a derecha, ya sea en la fila superior (para "hervir") o en la fila inferior (para transmisión).
Ambos procesos abarcananalizando,evaluando,edificio, yserializandoa HTML, donde
evaluandoyedificiono están necesariamente en secuencia estricta. Aquí también es donde
están las diferencias: trabajar sobre estructuras de datos intermedias (árboles de listas y
nodos) o sobre objetos ligeros que encapsulan lógica (iteradores y cierres).

análisis Evaluación de la ruta G Edificio HTML

Liza Nodos

Hirviendo

iteradores

Lógica

Transmisión

Figura 14.5 Características del uso de la memoria para las estrategias de "ebullición" versus transmisión

www.it-ebooks.info
Procesamiento de XML 529

Eso es todo lo básico para leer XML y transformar XML en estructuras de datos
completamente nuevas. Pero a veces desea actualizar solo una parte de un documento XML.
Groovy también tiene soporte para ese tipo de operación.

14.2.3 Actualización de XML

Suponga que ahora desea actualizar su almacén de datos XML mediante programación. Ha
llegado al final de la primera semana en sus actividades planificadas. Desea actualizar la
primera semana para registrar su progreso total en la tercera tarea y proporcionar un
pequeño comentario. Para la segunda semana has revisado tus prioridades. Ya no desea
realizar la segunda tarea actual y, en su lugar, desea realizar dos alternativas.
Consideremos esto como una oportunidad para ejercitar el desarrollo de prueba primero y
escribir un asistente de prueba primero para reflejar cómo espera que se vea el XML al finalizar la
actualización.
Durante la primera semana (recuerde que es índice0)esperas que las horas hechas sean
iguales a las horas en total (es decir, siete horas) y esperas que el comentario 'ahorrador de
tiempo'aparecerá para una de las tareas (la última, pero no prescribiremos en exceso el orden
entre las tareas).
Para la segunda semana (índice1)esperas que la semana tenga tres tareas. Espera que la
tarea anterior se haya ido (sin @títulocoincidirá con el valor anterior) y se deben encontrar
dos tareas nuevas: verificaremos el @títulode uno y el @totaldel otro. Nuestro código de
prueba podría verse así:

clase UpdateChecker {
control estático (texto) {
def actualizado = nuevo XmlParser().parseText(texto)
actualizado.semana[0].with { w0 ->
afirmar w0.tarea.@hecho *.toInteger().sum() == 7 afirmar
w0.find{ it.text() == 'ahorro de tiempo' }
}
actualizado.semana[1].con { w1 ->
afirmar w1.niños().tamaño() == 3 afirmar
w1.buscar{ it.@total == "4" }
afirmar w1.find{ it.@title == "construir cliente de servicio web" } afirmar !
w1.find{ it.@title == "usar combinación DB/XML" }
}
}
}

estas usandoXmlParseren su cheque, pero como ha visto anteriormente, podría haber elegido
XmlSlurpero usadoDOMCategoríasi lo prefieres Algo más que vale la pena señalar aquí es que su
verificación supone que su XML actualizado se vuelve a escribir como texto listo para ser analizado
por su código de prueba. ParaXmlParseryDOMCategoría,podría haber hecho fácilmente algunas
afirmaciones en el árbol de nodos en memoria, pero siga con su enfoque anterior, ya que también
funciona paraXmlSlurper (hace actualizaciones perezosas) y también muestra el proceso de extremo
a extremo para escribir XML después de una actualización.
Ahora considere la siguiente lista, que muestra laDOMCategoríacódigo necesario para
actualizar el XML.

www.it-ebooks.info
530 CPASADO14Trabajando con XML y JSON

Listado 14.11 Actualización de XML conDOMCategoría

importar maravilloso.xml.DOMBuilder
importar maravilloso.xml.XmlUtil
importar Groovy.xml.dom.DOMCategory

def doc = DOMBuilder.parse(nuevo FileReader('data/plan.xml')) def plan =


doc.documentElement bActualizaciones hechas
atributo con
uso (categoría DOMC) {
nuevo valor
plan.semana[0].tarea[2]['@hecho'] = '2'
plan.semana[0].tarea[2].valor = 'ahorro de tiempo'
Establece la tarea
plan.semana[1].tarea[1] .replaceNode {
Cvalor de texto
tarea (hecho: '0', total: '4', título: 'construir servicio web')
}
plan.semana[1].tarea[1] + {
tarea (hecho: '0', total: '1', título: 'construir cliente de servicio web')
}
}

UpdateChecker.check(XmlUtil.serialize(plan))

Establecemos el nuevo valor del atributo usando Groovy'sponlo enAtajo de sintaxis de GPathB. El valor de
texto del nodo se establece mediante elvalorpropiedadC. Entonces usamosreemplazarNodoy
más (usando la abreviatura +) para modificar los nodos de la tarea.

Hay varias otras formas en que podría haber actualizado el XML. Elegimos un
enfoque que ilustraba dos de las operaciones más comunes que se usan típicamente
(reemplazarNodoymás)pero, si hubiéramos querido, podríamos tener:
- UsóreemplazarNodouna vez pero con dos entradas de nodo de tarea dentro
- UsóappendNodeen lugar de más, proporcionándole el nuevo nombre de nodo y atributos
como un mapa
- Eliminó el nodo original usando elremoveChildmétodo y luego tuvo unmás
cierre con dos nudos o dosappendNodellamadas a métodos

Ahora vuelve a hacer lo mismo con elXmlParsercomo se muestra en el siguiente listado.

Listado 14.12 Actualización de XML conXmlParser

importar Groovy.xml.XmlUtil

def plan = nuevo XmlParser().parse(nuevo archivo('datos/plan.xml'))

plan.semana[0].tarea[2] .@hecho = '2'


plan.semana[0].tarea[2].valor = 'ahorro de tiempo'

plan.semana[1].tarea[1].replaceNode {
tarea (hecho: '0', total: '4', título: 'construir servicio web')
}
plan.semana[1].tarea[1] + {
tarea (hecho: '0', total: '1', título: 'construir cliente de servicio web')
}
UpdateChecker.check(XmlUtil.serialize(plan))

www.it-ebooks.info
Procesamiento de XML 531

Se requieren operaciones de GPath idénticas para realizar la actualización. Y opciones similares


también existen como viste paraCategoría DOMC.Por ejemplo, podrías haber usadoappendNode
o varias otras opciones. Ahora lo haremos una vez más conXmlSlurpercomo se muestra en el
siguiente listado.

Listado 14.13 Actualización por streaming de XML conXmlSlurper

importar Groovy.xml.XmlUtil

def plan = nuevo XmlSlurper().parse(nuevo archivo('datos/plan.xml'))

plan.semana[0].tarea[2] .@hecho = '2'


plan.semana[0].tarea[2] = 'ahorro de tiempo'

plan.semana[1].tarea[1].replaceNode {
tarea (hecho: '0', total: '4', título: 'construir servicio web')
}
plan.semana[1].tarea[1] + {
tarea (hecho: '0', total: '1', título: 'construir cliente de servicio web')
}

UpdateChecker.check(XmlUtil.serialize(plan))

Lo primero que debe tener en cuenta es que se requieren nuevamente expresiones GPath idénticas para
las operaciones de actualización. También podrías haber usadoagregar nodo (aunqueXmlSlurper's
sintaxis paraappendNodevaría ligeramente, tomando unCierreparámetro comomáslo hace). También
podrías haber usado elShift izquierdo (atajo de sintaxis <<) para agregar nodos al nodo principal,
ahorrando solo un poco de escritura.
Otra cosa a tener en cuenta es que con elXmlSlurper,el comportamiento de transmisión
adicional está en juego. Cuando llamas alreemplazarNodoymásmétodos,XmlSlurperen realidad
no altera las estructuras de datos subyacentes que representan el árbol original; en su lugar,
guarda los cambios deseados que se aplicarán cuando envíe el documento XML a alguna
secuencia. En este caso, es cuando serializas el plan para su posterior verificación con tu
verificador.
En la sección 14.1.1, vio que los analizadores DOM clásicos de Java devuelven objetos de tipo
org.w3c.dom.Nodo,que difiere de lo que devuelven los analizadores Groovy. Al usar Java para
procesar tales nodos, las API de bajo nivel que ha visto hasta ahora pueden ser un poco
engorrosas. Si bien Java no tiene nada parecido a las expresiones GPath que ha visto para
Groovy, permite usar un enfoque de nivel ligeramente superior con la ayuda de XPath. La
siguiente sección muestra cómo se puede usar el procesamiento Java XPath y Groovy XML en
combinación.

14.2.4 Combinando con XPath


XPath es para XML lo que SQLSeleccionelas declaraciones son para bases de datos relacionales o las
expresiones regulares para texto sin formato. Es un medio para seleccionar partes de un documento
completo y hacerlo de manera descriptiva.

www.it-ebooks.info
532 CPASADO14Trabajando con XML y JSON

tuENTENDERXPATH
Un XPath es una expresión que aparece en Java o Groovy como una cadena (exactamente como lo hacen los
patrones regex o las declaraciones SQL). Una introducción completa a XPath está más allá del alcance de este
libro, pero aquí hay una breve introducción desde el punto de vista de un programador de Groovy.6
Al igual que un GPath, un XPath selecciona nodos. Donde GPath usa puntos, XPath usa barras.
Por ejemplo,

/plan/semana/tarea

selecciona todotareanodos de todossemanas abajoplan.La barra diagonal inicial indica que la selección
comienza en el elemento raíz. En esta expresión,plan de semana,ytareacada uno se llama unprueba de
nodo. Cada prueba de nodo puede ir precedida de unaespecificador de ejede la tabla 14.7 y dos puntos
dobles.

Tabla 14.7 Especificadores del eje XPath

Eje Selecciona nodos Atajo

niño Directamente debajo nada o *

padre Directamente arriba ..

uno mismo El nodo en sí (usar para más referencias) .

antepasado Todo lo de arriba

antepasado o yo Todo lo anterior incluido uno mismo

descendiente Todo a continuación

descendiente o propio Todo a continuación, incluido uno mismo //

siguiendo Todo en el mismo nivel final en el documento XML

siguiente-hermano Todo con el mismo padre al final del documento XML

anterior Todo en el mismo nivel anterior en el documento XML

hermano anterior Todo con el mismo padre anterior en el documento XML

atributo El nodo de atributo @

espacio de nombres El nodo del espacio de nombres

Con estos especificadores, puede seleccionar todostareaelementos vía

/descendiente-o-yo::tarea

Con la sintaxis de acceso directo, puede seleccionar todototalnodos de atributos de todostareas vía

//tarea/@total

6
Para obtener una descripción completa del estándar, consulte www.w3.org/TR/xpath; y para un tutorial, visite
www.w3schools.com/xpath/. Para un buen libro, verXSLT 2.0 y XPath 2.0, 4ª edición, por Michael Kay (Wiley, 2008).

www.it-ebooks.info
Procesamiento de XML 533

Una prueba de nodo puede tener un finalpredicadoentre corchetes para restringir el resultado. Un
predicado es una expresión formada por expresiones de ruta, funciones y operadores para los
tipos de datosconjunto de nodos, cadena, número,ybooleanoLa tabla 14.8 enumera lo que es posible.
7 La tabla 14.9 muestra ejemplos.

Tabla 14.8 Hoja de trucos de expresión de predicado XPath

Categoría Apariencia Nota

Operadores de ruta /, //, @, [], *, .., . Ver tabla 14.7

operador sindical | Unión de dos conjuntos de nodos

operadores booleanos y, o, no() no()es una funcion

Aritmética + , - , * , div, mod, idiv (XPath 2.0)


operadores

Comparación =, !=, <, >, <=, >=


operadores

Funciones de cadena concat(), substring(), contains(), substring- Consulte los documentos para conocer

before(), substringafter(), translate(), los significados y parámetros exactos;

normalizespace(), string-length() por ejemplo,


www.w3schools.com/xpath/
xpath_functions.asp#string

Funciones numéricas suma(), ronda(), suelo(), techo()

Funciones de nodo nombre(), nombre-local(), espacio de nombres-uri()

Funciones de contexto posición(), último() [norte]es corto para


[posición()=n]

Conversión cadena(), número(), booleano()


funciones

Comparación de valores eq, ne, lt, le, gt, ge


hijos (XPath 2.0)

Tabla 14.9 Ejemplos de XPath

XPath significado y notas Nota

/plan/semana[1] Primero*semananodo La indexación comienza en uno

//tarea[@done<@total] Todas las tareas sin terminar Conversión automática a un número

//tarea[@hecho<@total][@hecho>0] Todas las tareas en curso Implícitoyentre paréntesis

sum(//semana[1]/tarea/@total) Horas totales en la primera Devuelve un número


semana

* Más específicamente, elsemananodo en la posición1abajoplan.

7Estos operadores están disponibles en XPath 1.0 y XPath 2.0 a menos que se indique lo contrario.

www.it-ebooks.info
534 CPASADO14Trabajando con XML y JSON

La siguiente pregunta obvia es cómo usar tales expresiones XPath en código Groovy.

tuCANTA ELXPATHAPI
Groovy viene con todo el soporte que necesita para usar expresiones XPath en su código,
basándose en Java.fábricamétodo para acceder a la biblioteca XPath de forma independiente de la
plataforma. Utilizar eljavax.xml.xpath.XPathFactoryclase para crear una instancia de unxpathobjeto.
Luego, este objeto tiene métodos disponibles para evaluar expresiones XPath en su XML analizado
(o cuando la eficiencia es una preocupación importante, podemos compilar expresiones XPath para
una evaluación posterior).
En la práctica, es posible que desee hacer algo con todossemanas. Usted seleccionará la apro-
lista privada de nodos a través dexpath.evaluate('//week', plan, NODESET).El último parámetro
ter indica el tipo de retorno esperado. En su caso, desea un agregado de nodos. Porque esto
devuelve unlista de nodos,puede usar los métodos de iteración de objetos para obtener cada
semana:

xpath.evaluate('//week', plan, NODESET).eachWithIndex{ semana, i ->


// hacer algo con la semana
}

Para cada semana, imprima la suma de lostotalyhechoatributos con la ayuda de XPath. Cada
semananode se convierte en el nuevo nodo de contexto para la evaluación de XPath y el tipo de
retorno esperado esNÚMERO:

xpath.evaluate('//week', plan, NODESET).eachWithIndex{ semana, i ->


out << "\nNúmero de semana $i\n"
int total = xpath.evaluate('sum(tarea/@total)', semana, NÚMERO) int hecho =
xpath.evaluate('sum(tarea/@done)', semana, NÚMERO) out << " planeado $total
de ${semana.'@capacidad'}\n"
out << " hecho $hecho de $total"
}

La siguiente lista reúne todo esto con una pequeña funcionalidad de informes que produce un
informe de texto para cada semana, indicando la capacidad, el total de horas planificadas y el
progreso en horas realizadas.

Listado 14.14 Informes de XPath a texto

importar maravilloso.xml.DOMBuilder
importar Groovy.xml.dom.DOMCategory

importar javax.xml.xpath.XPathFactory

importar estático javax.xml.xpath.XPathConstants.NODESET


importar estático javax.xml.xpath.XPathConstants.NUMBER

def doc = DOMBuilder.parse(nuevo FileReader('data/plan.xml')) def plan =


doc.documentElement
def xpath = XPathFactory.nuevaInstancia().nuevaXPath()
bUsar categoría DOMC
def out = new StringBuilder() por simple
acceso de atributos
use(DOMCategory) {

www.it-ebooks.info
Procesamiento de XML 535

xpath.evaluate('//week', plan, NODESET).eachWithIndex {


semana, yo ->
Selección
out << "\nNúmero de semana $i\n"
dEvaluación
a través de XPath,

recuperando
int total = xpath.evaluate('sum(tarea/@total)', wk, NUMBER) int done =
índice y xpath.evaluate('sum(tarea/@done)', wk, NUMBER) out << " planeado $total de $ utilizando XPath

valorC {sem.'@capacidad'}\n"
out << " hecho $hecho de $total"
Evaluación usando
}
Atributos DOM
}
directamente
afirmar out.toString() == ''' Semana
No. 0
planeado 7 de 8
hecho 6 de 7
Semana No. 1
planeado 4 de 8
hecho 0 de 4''''

XPath se usa de dos maneras aquí: la capacidad de consulta se usa para seleccionar todos lossemana
elementosBy luego los atributostotalyhechose extraen con laevaluar
métodoC. Mezclará y combinará formas de acceder a los atributos, utilizandoDOMCategoría
para acceder a lacapacidadatributo con elnodo.@nombreAtributo sintaxisd.
Un informe de texto de este tipo está bien para empezar, pero sin duda sería mejor mostrar el
progreso en un gráfico. La figura 14.6 sugiere una solución HTML. En una situación normal,
usaríamos colores en dicho informe, pero no serían visibles en la impresión de este libro. Por lo
tanto, usamos solo una representación de caja simple de los números.
Cada cuadro está hecho del borde de un estilodivisiónelemento. El estilo también
determina el ancho de cada cuadro.
Este tipo de tarea de producción de HTML
requiere un enfoque de plantillas, porque hay varios
patrones recurrentes para los fragmentos de HTML:
para los cuadros, para cada fila de atributos y para
cada semana. Usaremos motores de plantilla, GPath
y XPath en combinación para que esto suceda.
El listado 14.15 presenta la plantilla que
vamos a utilizar. Es una plantilla simple como
se presentó en la sección 12.4.2. Asume la
presencia de dos variables en el enlace: una
escala,que se necesita para hacer visibles los tamaños
de las cajas a partir de los valores de los atributos, y
semanas, que es una lista de mapas semanales. Cada
semana el mapa contiene las claves 'capacidad', 'total',y
'hecho'con valores enteros.
La plantilla reside en un archivo separado. Nos
gusta nombrar estos archivos con la palabramodeloen
el nombre y terminando en la extensión de archivo Figura 14.6 Captura de pantalla de un informe
habitual para el formato que producen. Por ejemplo, basado en HTML

www.it-ebooks.info
536 CPASADO14Trabajando con XML y JSON

el nombre GroovyPlans.template.html revela la naturaleza del archivo y aún podemos usarlo


con un editor de HTML.

Listado 14.15 Diseño de informes HTML en data/GroovyPlans.template.html

<html>
<cabeza>
<título>Actual maravilloso progreso</título>
</cabeza>
<cuerpo>
<% semanas.eachWithIndex{ semana, i -> %>
<h1>Semana No.psi</h1> <espacio entre celdas de la
tabla="5" >
<tbody>
<% ['capacidad','total','hecho'].each{ attr -> %> <tr>

<td>$atributo</td>
<td>${semana[atributo]}</td>
<td>
<estilo div=
"borde: delgado sólido #000000; ancho:pssemana[atributo]*escala}píxeles">
</div>
</td>
</tr>
<% } // fin del atributo %> </tbody>

</tabla>
<% } // fin de semana %> </
body>
</html>

Esta plantilla parece un archivo JSP, pero no lo es. La lógica contenida se expresa en Groovy,
no en Java simple. En lugar de ser procesado por un motor JSP, será evaluado por Groovy's
SimpleTemplateEnginecomo se muestra en el listado 14.16. Usamos expresiones XPath para
preparar los valores para el enlace. Una aplicación especial de GPath entra en juego al
calcular el factor de escala.
Se requiere escalar para que la barra de capacidad más larga tenga una longitud de 200, por lo que
debemos encontrar la capacidad máxima para el cálculo. Debido a que ya hemos puesto estos valores en
el enlace, podemos usar un GPath para obtener una lista de ellos y jugar nuestros trucos GDK con él
(llamandomáx.).

Listado 14.16 Uso de XPath, GPath y plantillas en combinación para informes HTML

importar maravilloso.xml.DOMBuilder
importar Groovy.xml.dom.DOMCategory
importar groovy.text.SimpleTemplateEngine como STE

importar javax.xml.xpath.XPathFactory estático


importar javax.xml.xpath.XPathConstants.NODESET
importar estático javax.xml.xpath.XPathConstants.NUMBER

www.it-ebooks.info
Procesamiento de XML 537

def doc = DOMBuilder.parse(nuevo FileReader('data/plan.xml')) def plan =


doc.documentElement
def xpath = XPathFactory.nuevaInstancia().nuevaXPath()

enlace def = [ escala: 1, semanas: [] ] use


XPath en
(DOMCategory) {
nodos DOM
xpath.evaluate('//week', plan, NODESET).each{ semana ->
encuadernación.semanas << [
total: (int) xpath.evaluate('sum(tarea/@total)', semana, NÚMERO), hecho: (int)
xpath.evaluate('sum(tarea/@done)', semana, NÚMERO), capacidad:
semana.'@capacidad'.toInteger()
]
}
} Ruta en
def max = vinculante.semanas.capacidad.max() if (max > 0)
Unión
binding.scale = 200.intdiv(max)

def templateFile = nuevo archivo ('datos/GroovyPlans.template.html') def plantilla


= nuevo STE().createTemplate(archivoplantilla) Plantillas

nuevo Archivo('datos/XPathGroovyPlans.html').withWriter { <<


esotemplate.make(binding)
}

El código no cambió drásticamente entre el informe de texto del listado 14.14 y el informe
HTML del listado 14.16. Pero el listado 14.16 proporciona una solución más general, porque
también podemos obtener un informe de texto simplemente cambiando la plantilla.
El tipo de transformación de XML a HTML que logramos con el listado 14.16 se aborda
clásicamente con XMLTransformación de hoja de estilo(XSLT), que es una tecnología
poderosa. Utiliza hojas de estilo en formato XML para describir un mapeo de transformación,
también usando XPath y plantillas. Sus medios lógicos son equivalentes a los de un lenguaje
de programación funcional.
Aunque XSLT es adecuado para mapear estructuras de árbol, a menudo nos resulta más fácil
usar el enfoque Groovy cuando la lógica es mínimamente compleja. XPath, plantillas, constructores
y el lenguaje Groovy forman una combinación única que permite soluciones elegantes y concisas.
Puede haber personas que puedan mirar cantidades significativas de XSLT durante más de unos
pocos minutos a la vez sin arriesgar su estabilidad mental, pero son pocos y distantes entre sí.
Usando las tecnologías que ha encontrado, puede aprovechar sus fortalezas para comprender
Groovy en lugar de usar un lenguaje diferente con un paradigma fundamentalmente diferente.

LADICIONAL DE ENVEJECIMIENTOjAVAXMLTECNOLOGÍAS DE PROCESAMIENTO


Antes de concluir nuestra introducción al procesamiento de XML con Groovy, debemos mencionar
que, aunque creemos que encontrará que las funciones XML integradas de Groovy son adecuadas
para muchas de sus necesidades de procesamiento, no está obligado a usar solo esas API. Debido
a la herencia de Java de Groovy, muchas bibliotecas y tecnologías están disponibles

www.it-ebooks.info
538 CPASADO14Trabajando con XML y JSON

para que lo consideres. Ya hemos mencionado StAX y Jaxen. Aquí hay algunos más de
nuestros favoritos:8
- A pesar de queXmlParser, XmlSlurper,y, por supuesto, Java DOM y SAX deberían satisfacer la
mayoría de sus necesidades, siempre puede considerar JDOM, dom4j o XOM.
- Si necesita comparar dos fragmentos XML en busca de diferencias, considere XMLUnit.
- Si desea procesar XML usando XQuery, considere Saxon.
- Si necesita conservar su XML, considere JAXB o Stream.
- Si necesita hacer una transmisión de alto rendimiento, considere Nux.

Nuestra introducción a Groovy XML podría terminar en este punto, porque ha visto todos los
conceptos básicos de la manipulación de XML. Ahora debería poder escribir programas Groovy que
lean, procesen y escriban XML de forma básica. Necesitará documentación más detallada cuando
surja la necesidad de tratar problemas más avanzados, como espacios de nombres, entidades de
resolución y manejo de DTD de forma personalizada.
La sección final de este capítulo trata sobre una de las alternativas más
extendidas a XML: JSON.

14.3 Análisis y creación de JSON


JSON, la notación de objetos de JavaScript, se derivó originalmente de un subconjunto del
lenguaje JavaScript, pero el formato de datos en sí es independiente del lenguaje y se usa
ampliamente con muchos lenguajes de programación. JSON fue diseñado para representar
estructuras de datos simples. Echemos un vistazo a lo que implica analizar y luego crear
contenido JSON.

14.3.1 Análisis de JSON


Revisemos nuestro ejemplo de plan de antes en el capítulo. Supongamos que hubiéramos almacenado
nuestra pequeña base de datos de información en un archivo JSON en lugar del archivo XML discutido
anteriormente. El archivo JSON se vería así:

{ "semanas": [
{
"capacidad": 8,
"Tareas": [
{"hecho": 2,"total": 2,
"título":"leer capítulo XML", {" "estado":"fácil"},
hecho": 3,"total": 3,
"título":"intente algunos informes","estado":"divertida"}, {"
hecho": 1,"total": 2,
"título":"usar en el proyecto actual"}
]
},

8
Hay más información disponible en http://xmlbeans.apache.org, http://saxon.sourceforge.net, http://dsd.lbl.gov,
http://xmlunit.sourceforge.net, http://xstream .codehaus.org y https://jaxb.java.net.

www.it-ebooks.info
Analizando y construyendo JSON 539

{
"capacidad": 8,
"Tareas": [
{"hecho": 0,"total": 1,"título":"volver a leer el capítulo de DB"}, {"hecho": 0,"
total": 3,"título":"usar combinación DB/XML"}
]
}
]}

Para este ejemplo, supondremos que nuestro archivo se llama plan.json en una carpeta de datos. Ahora echemos
un vistazo a la siguiente lista para ver cómo podemos analizarla.

Listado 14.17 Analizando JSON

importar groovy.json.JsonSlurper

def plan = new JsonSlurper().parse(new File('data/plan.json')) asertar


plan.weeks[0].tasks[0].status == 'fácil'
afirmar plan.semanas[1].capacidad == 8
afirmar plan.weeks[1].tasks[0].title == 'volver a leer el capítulo DB'

Eso es tan fácil como podríamos esperar y sigue de cerca (pero no exactamente) lo que vimos
para XML. Notará que falta el concepto de atributos para JSON. Almacenamos dicha
información como una lista de propiedades.
La buena noticia es que, si bien los ejemplos fáciles como los que acabamos de ver son realmente fáciles, los casos

complicados también se manejan, principalmente cambiando entre implementaciones de análisis con características

ligeramente diferentes. La Tabla 14.10 muestra las implementaciones del analizador proporcionadas.

Tabla 14.10JsonSlurperimplementaciones del analizador

Implementación Descripción

JsonParserCharArray (defecto) Copia subarreglos de caracteres ("cortar") durante el análisis

JsonFastParser Un analizador de superposición de índices que evita o difiere la creación


de nuevoscarbonizarsearreglos oCuerdainstancias y mantiene
punteros a la matriz de caracteres original subyacente

JsonParserLax Admite comentarios, cadenas sin comillas y otras


construcciones que no se admiten oficialmente en la
gramática ECMA-404 JSON

JsonParserUsingCharacterSource Un analizador especial para archivos muy grandes.

Puede cambiar a uno de los otros analizadores al llamar al constructor de esta manera:

nuevo JsonSlurper (tipo: JsonParserType.LAX)

Consulte la documentación en línea de Groovy para obtener más detalles.9Eso es todo para analizar. ¿Qué pasa si
quiere ir por el otro lado y realmente crear algo de contenido JSON? Veamos eso a continuación.

9"Análisis y producción de JSON", http://groovy-lang.org/json.html.

www.it-ebooks.info
540 CPASADO14Trabajando con XML y JSON

14.3.2 Creación de JSON


Veamos cómo construir algo de JSON. El JSON producido es similar al que producimos cuando
actualizamos nuestro plan con XML, pero para simplificar mostraremos la creación directa del
contenido JSON en lugar de actualizar el archivo original. La siguiente lista ilustra lo que se
requiere: simplemente cree unJsonBuildery utilícelo de la misma manera que ha visto para otros
constructores.

Listado 14.18 Construyendo JSON

importar groovy.json.JsonBuilder
def constructor = new JsonBuilder()
constructor.semanas {
capacidad '8'
Tareas(
[{
hecho '0'
total '4'
title 'construir servicio web' }, {

hecho '0'
suma '1'
título 'construir cliente de servicio web'
}]
)
}
afirmar constructor.toString() == '{"semanas":{"capacidad":"8","tareas":[' +
'{"hecho":"0","total":"4","title":"construir servicio web"},' + '{"hecho":"0","total":"1","
title":"construir cliente de servicio web"}' + ']}}'

Como ha visto antes con otros constructores, también podemos usar la lógica de codificación
entremezclada con nuestros métodos sintéticos cuando usamosJsonBuilder.Veamos de nuevo nuestro
ejemplo de factura de capítulos anteriores.10Puede generar JSON correspondiente a nuestra información
de factura anidada como se muestra en la siguiente lista.

Listado 14.19 Ejemplo de factura conJSONBuilder

importar groovy.json.JsonBuilder
bmétodo fingido
def constructor = nuevo JsonBuilder() puede tomar colección
constructor { y cierre
facturas(1..3) { día ->
fecha de la factura: "2015-01-0$día") {
recuento de elementos: día) { CEl cierre define
producto (nombre: 'ULC', dólar: 1499) JSON para cada
} artículo en colección
}
}
}

10Consulte la sección 7.5.1 para ver ejemplos de GPath y la sección 11.4 para ver ejemplos de constructores.

www.it-ebooks.info
Analizando y construyendo JSON 541

afirmar constructor.toPrettyString().startsWith( '''{


Comprueba el inicio
dde bonita salida
"facturas": [
{
"factura": [
{
"fecha": "2015-01-01"
''')

Hay soporte especial para el manejo de listas de estructuras similares a mapas. por ejemplo, nuestro
facturascontendrá una lista de tresfacturamapas, por lo que pasamos una colección (en este
caso, el rango1..3)afacturasBy también proporcionar un cierre para procesar cada artículo en
la colecciónC. En lugar de usar el normalEncadenar()método como el que usamos en el
listado 14.18, usaremos untoPrettyString()variantedque realiza sangrías y saltos de línea
apropiados para aclarar las relaciones de anidamiento en el resultado.
También hay un generador JSON de transmisión llamado (como puede suponer)StreamingJSON-
Builder.Consulte la documentación de la API de GroovyDoc para obtener más detalles.
La última clase JSON útil que veremos esSalida Json.Se utiliza para serializar objetos Groovy en
JSON. Maneja los tipos de datos más comunes y, lo que es más importante, también estructuras
anidadas de objetos y sus propias clases de dominio. Es una clase auxiliar con métodos de utilidad
estáticos. Contiene numerosostoJsonmétodos correspondientes a los diversos tipos de datos que
convierte y unbonitaImprimirmétodo. Usarlos es bastante sencillo, como se muestra para una
estructura de datos de atleta simple en la siguiente lista.

Listado 14.20 Ejemplo de atleta conJsonSalida

importar groovy.json.JsonOutput.* estático

def json = toJson([fecha: '2015-01-01', hora: '6 am']) assert json ==


'{"fecha":"2015-01-01","hora":"6 am" }'

atleta de clase { Cadena primero, último }

def mj = nuevo Atleta(primero: 'Michael', último: 'Jordan') afirmar toJson(mj) ==


'{"primero":"Michael","último":"Jordan"}'

def pt = nuevo atleta (primero: 'Paul', último: 'Tergat') def atletas =


[baloncesto: mj, maratón: pt]

json = toJson(atletas) afirmar


prettyPrint(json) == ''' {

"baloncesto": {
"primero": "Miguel",
"ultimo": "Jordán"
},
"maratón": {
"primero": "Pablo",
"último": "Tergat"
}
}
'''.recortar()

www.it-ebooks.info
542 CPASADO14Trabajando con XML y JSON

La buena noticia es que la mayoría de las veces, no necesita usarJsonSalidadirectamente. El


ejemplo final en el listado 14.19 podría haber usadoJsonBuildercomo

nuevo JsonBuilder(atletas).toPrettyString()

yJsonBuilderLlamaríaJsonSalidadebajo de las sábanas. Sin embargo, puede encontrar usando


JsonSalidadirectamente es útil para casos complicados como tratar de serializar estructuras de
datos recursivas; simplemente no espere que ninguna de las clases maneje automáticamente tal
caso.
Eso concluye nuestro recorrido por JSON. Echemos un vistazo a lo que cubrimos en este capítulo.

14.4 Resumen
XML y JSON son temas tan importantes que no podemos tocar todas las bases en un libro
sobre Groovy. Hemos cubierto los aspectos más importantes con suficiente detalle para
proporcionar una buena base para la experimentación y la lectura adicional. Al superar los
límites con Groovy XML y JSON, probablemente encontrará temas que no se tratan en este
capítulo. No dude en consultar los recursos en línea.
En este punto, tiene una base sólida para comprender las diferentes formas de trabajar
con XML y JSON en Groovy.
El uso de los analizadores Java DOM familiares en Groovy le permite trabajar en el estándar
org.w3c.com.Nodoobjetos siempre que la situación lo requiera. Dichos nodos se pueden recuperar
de laDOMBuilder,convenientemente accesible con la ayuda deDOMCategoría, e investigado con
expresiones XPath. Groovy hace la vida más fácil con el DOM, pero no puede rectificar algunas de
las decisiones de diseño que dan sorpresas o implican trabajo extra sin ningún beneficio.

interno de GroovyXmlParseryXmlSlurperproporcionar acceso a documentos XML de una manera


compatible con Groovy que admita expresiones GPath para trabajar en el documento.
XmlParserproporciona una representación en memoria para la manipulación de nodos en el lugar,
mientras queXmlSlurperes capaz de trabajar de una manera más parecida a una corriente. Para reducir
aún más la memoria, también puede utilizar SAX y StAX.
Finalmente, es fácil analizar y compilar JSON para situaciones en las que no se requiere XML. Los
analizadores XML en Java se han optimizado durante muchos años y Groovy se apoya en los hombros de
los gigantes al hacer que XML sea más accesible. Para JSON, la situación es bastante diferente. Aquí,
Groovy está actualmente a la cabeza del grupo en términos de velocidad de análisis.
Sea cual sea su actividad basada en XML o JSON, es probable que Groovy tengaalguna cosaque
facilitará su trabajo. Por ahora, eso no debería ser una sorpresa.

www.it-ebooks.info
interactuando
con servicios web

Este capítulo cubre


- Consumir fuentes RSS y ATOM
- Usando REST y JAX-RS
- Operaciones remotas con XML-RPC
- Servicios web SOAP

El servicio a los demás es el alquiler que pagas por tu habitación aquí en la tierra.

— Muhammad Alí

Desde los primeros días de las redes informáticas, hemos utilizado una gran cantidad de protocolos y
formatos de datos para permitir que las computadoras interactúen e intercambien información. Con la
popularidad y la ubicuidad de la World Wide Web, la gama de protocolos y formatos de datos se ha
consolidado para centrarse principalmente en HTTP, que implementa el modelo de solicitud y
respuesta que conoce de sus actividades de navegación diarias, y una pequeña cantidad de
notaciones de marcado (principalmente HTML, XML y JSON) como formato de intercambio de datos.
Estos son los comúnmente llamadosservicio webtecnologías y son el tema de este capítulo.

En un nivel simple, el intercambio de datos ocurre cada vez que navega por la web. Con la ayuda
de su navegador, Ud.solicituduna dirección URL El servidorrespondecon un documento HTML

543

www.it-ebooks.info
544 CPASADO15Interactuar con servicios web

que su navegador sabe mostrar. El servidor y el navegador están interconectados a


través de HTTP que implementa el modelo de solicitud-respuesta, y utilizan HTML como
formato de intercambio de datos.
Ahora imagine un programa que navegue por la web en su nombre. Un programa de este tipo podría visitar
una lista de direcciones URL para buscar actualizaciones, explorar una lista de proveedores de noticias para
obtener nueva información sobre sus temas favoritos (sugerimos "Groovy"), acceder a un indicador de acciones
para ver si sus acciones han excedido algún precio objetivo, y verifique el servicio meteorológico local para ver si
emitió una advertencia de tormenta.
Tal programa tendría importantes dificultades para superar si tuviera que encontrar la
información solicitada en el HTML de cada sitio web. El HTML describe no solo qué son los
datos, sino también cómo deben presentarse en términos generales. Un cambio en el
aspecto de presentación del HTML fácilmente podría romper el programa que intentaba
comprender los datos.
¿Qué pasa si en lugar de utilizar un formato destinado a presentar contenido a lectores humanos, elegimos
una notación más amigable para las computadoras? ¿Quizás podríamos usar una notación binaria que coincida
directamente con la forma en que nuestra computadora representa los datos? Desafortunadamente, los sistemas
interconectados pueden ser heterogéneos. Pueden estar escritos en diferentes idiomas, ejecutarse en diferentes
plataformas (piense en .NET versus Java), usar diferentes sistemas operativos y ejecutarse en diferentes
arquitecturas de hardware.
En lugar de tratar los dos aspectos del contenido y la presentación juntos, sería
más confiable si hubiera una descripción XML o JSON del contenido puro. De esto
se tratan los Servicios Web.
XML y JSON describen datos de forma independiente del sistema. Esto los convierte en
candidatos obvios para el intercambio de datos a través de una red. No importa cuán diferentes
sean estos sistemas, pueden intercambiar datos a través de XML o JSON, siempre que ambas
partes tengan alguna idea de cómo interpretar o serializar los datos que reciben.
Una descripción completa de todos los formatos y protocolos de servicios web está más allá del alcance de
este libro, pero le mostraremos cómo puede usar algunos de ellos con Groovy. Nuestro enfoque estará en
escribir clientes de servicios web en Groovy, pero veremos algunos fragmentos de tecnología y técnicas de
servidor en el camino. Cubrimos la lectura de recursos XML a través de RSS y ATOM, seguido de una mirada más
detallada sobre el uso del estilo REST. Luego cubrimos el soporte especial de XML-RPC de Groovy en el lado del
cliente y, aunque sea brevemente, del lado del servidor. Finalmente, veremos todas las formas de usar Groovy
para escribir solicitudes a los servicios SOAP.
En caso de que REST y SOAP suenen como si estuviéramos hablando de tomar un baño
en lugar de acceder a servicios web, le complacerá saber que comenzamos con una breve
descripción de algunos de estos protocolos y convenciones.

15.1 Una descripción general de los servicios web

Las soluciones de servicios web cubren un espectro de enfoques que van desde lo simple hasta lo que
algunos consideran extremadamente complejo. Quizás el enfoque más simple es usar el protocolo HTTP
sin estado para solicitar un recurso a través de una URL. Esta es la base de laTransferencia de estado
representacional(RESTO) arquitectura. Los términos REST y RESTful se utilizan a veces en

www.it-ebooks.info
Lectura de RSS y ATOM 545

un sentido muy estricto para los servicios web que siguen todos los principios propugnados en el Ph.D.
original. tesis1por Roy Fielding sobre el tema, pero los términos también se han utilizado más
ampliamente para referirse a cualquier mecanismo para exponer contenido en la web a través de simples
XML o JSON.
Uno de los primeros usos populares de la arquitectura REST en su forma más básica fue hacer que el
contenido de los blogs web estuviera disponible. Dos de los formatos más utilizados en esta área son
Sindicación Realmente Simple2(RSS) y ÁTOMO (RFC-4287).Comenzaremos nuestra exploración de los
servicios web observando estos formatos.
La siguiente extensión lógica del uso de una URL para solicitar un recurso es usar XML simple
incorporado dentro de una solicitud HTTP POST normal. Esto también se puede considerar como
una solución REST. Examinaremos varias API XML y JSON de esta naturaleza como parte de nuestro
recorrido REST.
Cuando el foco no está en el recurso remoto sino en desencadenar una operación en el sistema
remoto, elLlamada de procedimiento remoto XML(XML-RPC) se puede utilizar. XML-RPC usa HTTP pero
agrega contexto, lo que lo convierte en un protocolo con estado (a diferencia de REST).
El jabon3El protocolo amplía el concepto de XML-RPC para admitir no solo operaciones remotas, sino
incluso operaciones remotas.objetos. Las funciones empresariales de servicios web que se basan en SOAP
brindan otras funciones, como seguridad, transacciones y mensajería confiable, por nombrar algunas de
las muchas funciones avanzadas disponibles.
Ahora que se ha orientado, veamos cómo Groovy puede acceder a dos de los formatos de servicios
web más populares que se utilizan en la actualidad.

15.2 Lectura de RSS y ATOM


Comencemos nuestro día leyendo las noticias. La BBC difunde sus últimas noticias en un
canal RSS. Como somos programadores ocupados, solo nos interesan los tres titulares
principales. Un pequeño programa Groovy los busca y los imprime en la consola. Lo que nos
gustaría ver es el título, una breve descripción y una URL que apunte al artículo completo en
caso de que un título capte nuestro interés.
Aquí hay algunos resultados de muestra (editados ligeramente por brevedad):

Las tres noticias principales de hoy:


Cameron desafió la financiación de los alumnos...
http://www.bbc.co.uk/...
David Cameron promete no recortar los presupuestos escolares...
----
Litvinenko 'trabajó como consultor del MI6' http://
www.bbc.co.uk/...
El exespía ruso Alexander Litvinenko...
----

1
“Estilos arquitectónicos y diseño de arquitecturas de software basadas en red”, disponible en www.ics.uci.edu/
~fielding/pubs/dissertation/top.htm.
2
También llamadoResumen del sitio enriquecido(RSS 0.9x) oMarco de descripción de recursos(CDR)Resumen del sitio(RSS 1.0).
3
SOAP solía significarSimple Object Access Protocol, pero este significado se eliminó desde la versión 1.2, porque SOAP
hace más que acceder a objetos, y la palabrasimplefue cuestionable desde el principio.

www.it-ebooks.info
546 CPASADO15Interactuar con servicios web

Greste 'angustia' por colegas encarcelados http://


www.bbc.co.uk/...
El periodista australiano de Al-Jazeera Peter Greste...
----

La siguiente lista implementa este lector de noticias. Solicita el recurso web que contiene la noticia
en formato XML. Encuentra el recurso por su URL. Pasar la URL a laanalizar gramaticalmente
El método lo obtiene implícitamente de la web. El resto del código puede funcionar directamente
en el árbol de nodos usando expresiones GPath.

Listado 15.1 Un sencillo lector de noticias RSS

def base = 'http://news.bbc.co.uk/rss/newsonline_uk_edition/' def url = base


+'front_page/rss091.xml'

println 'Las tres noticias principales de hoy:'


def items = new XmlParser().parse(url).channel[0].item for (item in
items[0..2]) {
imprimir artículo.título.texto()
imprimir elemento.enlace.texto()
imprimir artículo.descripción.text() '----'
imprimir
}

Por supuesto, para escribir dicho código, necesitamos saber qué elementos y atributos están
disponibles en el formato RSS. En el listado 15.1, asumimos que al menos la siguiente estructura
está disponible:

<rss...>
<canal>
...
<elemento>

<título>…</título>
<descripción>...</descripción>
<enlace>...</enlace>
...

Esto es solo un pequeño subconjunto de la información completa. Puede encontrar una


descripción completa de los formatos RSS y ATOM y sus diversas versiones enRSS y ATOM en
acciónpor Dave Johnson (Manning, 2006).
Leer un feed ATOM es igualmente fácil, como se muestra en el listado 15.2. Lee la fuente ATOM
de la fuente de temas de IBM DeveloperWorks Java Technology. Al momento de escribir este
capítulo, se imprime

22 de enero de 2015 Integración de FindBugs, CheckStyle y Cobertura con el sistema de


compilación Rational Team Concert
21 de enero de 2015 Incruste informes enriquecidos en sus aplicaciones
17 de diciembre de 2014 Cree una aplicación de búsqueda de cupones combinando Yelp, Google Maps,
Servicios de Twitter y Klout
...

www.it-ebooks.info
Uso de una API basada en REST 547

Una cosa nueva en el listado 15.2 es el uso de espacios de nombres XML. El formato ATOM hace uso de
espacios de nombres como este:

<feed xmlns="http://www.w3.org/2005/Átomo">
...
<entrada>
<title>Java.next: Los lenguajes de Java.next</title> . . .

Para atravesar nodos que están vinculados a espacios de nombres con expresiones GPath, nombres
calificados (QNombreobjetos) se utilizan.4AQNombreEl objeto se puede recuperar de unespacio de nombres
objeto solicitando la propiedad del nombre del elemento correspondiente. Para recopilar las
entradas que nos interesan utilizamosátomo.entrada.Para cada entrada buscamos posteriormente
suátomo.publicadofecha (truncando la información de tiempo), suátomo.resumen (imprimiendo una
estrella para cualquiera que mencione Groovy), y suátomo.título.

Listado 15.2 Lectura de un feed ATOM

importar Groovy.xml.Espacio de nombres

def url = 'http://www.ibm.com/developerworks/views/java/rss/' + espectáculos

'libraryview.jsp?feed_by=átomo' más reciente

def atom = new Namespace('http://www.w3.org/2005/Atom') def Tres


entradas
numEntries = 3
def entradas = new XmlParser().parse(url)[atom.entry][0..<numEntries] def len = "dd mmm
aaaa ".size()
Labios
def resúmenes = entradas.recoger { publicado
it[átomo.publicado].texto()[0..<largo] + fecha posterior
(it[atom.summary].text().contains('Groovy') ? '*' : ' ') + it[atom.title].text() tantos
caracteres

}
println resúmenes.join("\n")

Todo eso fue bastante fácil, ¿verdad? El próximo tema, REST, será más elaborado pero cubre un
área más amplia de aplicabilidad, porque es un enfoque más general.

15.3 Uso de una API basada en REST


Aunque muchos servicios web están sujetos a un estándar, REST es un concepto abierto más que
un estándar. Los denominadores comunes de los servicios REST son los siguientes:5
- Los datos formateados se intercambian entre el cliente y el servidor (típicamente usando XML
o JSON).
- La comunicación se realiza en un modelo de solicitud-respuesta sin estado a través de HTTP (S) utilizando
verbos comoOBTENER, PUBLICAR,Etcétera.
- Los recursos o servicios están dirigidos por una URL.

4
XmlParseryXmlSlurperson conscientes del espacio de nombres de forma predeterminada, pero se pueden configurar para que no manejen
espacios de nombres si lo prefiere, aunque normalmente tendría más trabajo que hacer manualmente en ese caso.
5
Podríamos profundizar aquí y hablar sobre restricciones adicionales, pero lo mantendremos simple por ahora.

www.it-ebooks.info
548 CPASADO15Interactuar con servicios web

Ningún estándar vinculante describe la estructura del XML o JSON que se envía. Debe
consultar la documentación de cada servicio REST para averiguar qué información se
solicita y se proporciona. La documentación describirá los recursos disponibles, los
verbos admitidos, la estructura XML o JSON que esperan recibir (para operaciones que
consumen una carga útil) y el resultado que devuelven.
Como primer ejemplo, examinaremos los servicios REST para interactuar con los servicios de
The Apache Software Foundation.6JIRA7utilizado por Groovy para el seguimiento de problemas. La
documentación de estos servicios se puede encontrar en el sitio web de Atlassian en http://
docs.atlassian.com/jira/REST/latest/. Tiene alrededor de 40 recursos diferentes enumerados en la
API (algunos con subrecursos). Para cada recurso, se proporcionan los métodos admitidos. Para
nuestros propósitos, estamos interesados en el siguiente recurso (que nos permitirá consultar los
detalles sobre un tema de interés particular de JIRA8):

/rest/api/latest/issue/{issueIdOrKey}

es compatibleOBTENER, PONER,yELIMINARpero solo estamos interesados enOBTENER.Mirando la


documentación deOBTENER,tiene algunas opciones para personalizar nuestra consulta (por
ejemplo, para seleccionar qué campos se devuelven). Estos se pasarían como parámetros de URL
(en general, las API REST podrían usar mensajes con formato JSON o XML, por ejemplo, en un
CORREOmétodo en lugar de parámetros de URL). Para nuestros propósitos, no personalizaremos nuestra

consulta, por lo que no se requieren parámetros adicionales.


Invocar la consulta es simplemente una cuestión de realizar unaOBTENERsolicitud en la URL
anterior pero sustituyendo la clave por el tema de interés. Desde unOBTENERrequest es el tipo de
solicitud predeterminado, abrimos una conexión HTTP a la URL y devolverá JSON que contiene los
detalles que buscamos. Podemos usar GroovyJsonSlurperpara consumir el JSON devuelto y
confirmar los detalles sobre el problema de interés. También nos gustaría comprobar que
obtenemos un código de respuesta válido:

def httpConnection = nueva URL (base + clave).openConnection() afirmar


httpConnection.responseCode == httpConnection.HTTP_OK def result =
slurper.parse(httpConnection.inputStream.newReader()) // hacer algo con el resultado...

Proporcionaremos una clase contenedora compatible con Groovy (clasejiraen el listado 15.3) en
torno a estas llamadas. De esa forma, si necesitamos cambiar el analizador JSON por uno XML, o
los detalles cambian sobre qué métodos y recursos debemos llamar, no afectará el código que usa
nuestra clase contenedora. Poner esto junto se puede ver en la siguiente lista.

6
El proyecto Groovy actualmente está alojado en la incubadora de Apache como parte del traslado a Apache Software Foundation.
Ver www.apache.org para más detalles.
7
El sistema de seguimiento de proyectos de Atlassian. Consulte www.atlassian.com/software/jira/overview para obtener más
8
información. Un buen aspecto de este recurso en particular es que para consultas simples, no se requiere autenticación.

www.it-ebooks.info
Uso de una API basada en REST 549

Listado 15.3 Consultando JIRA a través de su API REST

importar groovy.json.JsonSlurper

clase Jira {
def base = 'https://issues.apache.org/jira/rest/api/latest/issue/' def slurper = new
JsonSlurper()

def consulta(clave) {
def httpConnection = nueva URL (base + clave).openConnection() afirmar
httpConnection.responseCode == httpConnection.HTTP_OK
slurper.parse(httpConnection.inputStream.newReader())
}
}

def jira = nuevo Jira()


def respuesta = jira.query("GROOVY-5999")
respuesta.campos.con {
afirmar resumen == "Hacer que @Delegate funcione con @DelegatesTo"
afirmar fixVersions.name == ['2.1.1']
afirmar fecha de resolución.startsWith('2013-02-14')
}

Para este ejemplo simple, usando el JDKURLLa clase era relativamente simple e indolora, pero a medida
que crece la complejidad, este enfoque tiende a involucrar un poco de lógica repetitiva en la solución.
Podemos simplificar las cosas mediante el uso de una API de cliente REST dedicada. Usaremos
HTTPBuilder,9que proporciona unRESTClienteclase. Esta clase hace que elOBTENER, PUBLICAR,
y otros métodos HTTP fácilmente disponibles para nosotros y oculta los detalles subyacentes del
manejo de la conexión. Puede determinar automáticamente si se devuelve una respuesta JSON o
XML utilizando información MIME-TYPE en la respuesta y nos proporciona el slurper necesario sin
que tengamos que preocuparnos por los detalles. El resultado final es un código más limpio, como
se muestra en la siguiente lista.

Listado 15.4 Consultando JIRA usando HTTPBuilder

@Grab('org.codehaus.groovy.modules.http-builder:http-builder:0.7.2') import
groovyx.net.http.RESTClient

def base = 'https://issues.apache.org/jira/rest/api/latest/' def jira = nuevo


RESTClient(base)
jira.get(ruta: 'problema/GROOVY-5999') { resp, json ->
afirmar resp.estado == 200
json.fields.with {
afirmar resumen == "Hacer que @Delegate funcione con @DelegatesTo"
afirmar fixVersions.name == ['2.1.1']
afirmar fecha de resolución.startsWith('2013-02-14')
}
}

9 Consulte https://github.com/jgritman/httpbuilder/wiki.

www.it-ebooks.info
550 CPASADO15Interactuar con servicios web

Veamos otro servicio web utilizado para encontrar la tasa de conversión entre dos monedas
monetarias. Está alojado en el popular portal de servicios web en www.webservicex.net/. Es muy
similar a nuestro ejemplo anterior, pero requiere parámetros de consulta y devuelve XML en lugar
de JSON. La URL base para este servicio es:

http://www.webservicex.net/CurrencyConvertor.asmx

La ruta y los parámetros de consulta para la conversión de dólares estadounidenses a euros son:

<url base>/ConversionRate?FromCurrency=USD&ToCurrency=EUR

Cuando se invoque, se generará contenido XML con un tipo de cambio apropiado:

<doble xmlns="http://www.webserviceX.NET/">0,882</doble>

El código HTTPBuilder para acceder a este servicio web se muestra en el siguiente listado.

Listado 15.5 Usando HTTPBuilder con parámetros de consulta

@Grab('org.codehaus.groovy.modules.http-builder:http-builder:0.7.2') import
groovyx.net.http.RESTClient

def url = 'http://www.webservicex.net/CurrencyConvertor.asmx/' def convertidor =


nuevo RESTClient(url)
def params = [FromCurrency: 'USD', ToCurrency: 'EUR'] convertidor.get(ruta:
'ConversionRate', consulta: params) { resp, datos ->
afirmar resp.estado == 200 afirmar
datos.nombre() == 'doble' imprimir
datos.texto()
}

Cuando se ejecutó, se produjo el siguiente resultado en el momento de la escritura:

0.882

El ejemplo ilustra una buena característica de la biblioteca HTTPBuilder. La biblioteca examina el


tipo MIME de la respuesta del servicio web y elige correctamente un slurper apropiado para
manejar el contenido entrante. Mientras que eligió un slurper JSON para nuestro ejemplo de JIRA
en el listado 15.4, ahora ha elegido un slurper XML. Cuando combinamos esa característica con el
tipeo pato de Groovy, terminamos con un código que es más resistente en caso de que el formato
de la respuesta cambie en el futuro. Al igual que nuestro ejemplo de JIRA en el listado 15.4, este
ejemplo también usó un HTTPOBTENERpero el servicio web de moneda webservicex también es
compatibleCORREOsolicitudes de método como se muestra en la siguiente lista.

Listado 15.6 Usando HTTPBuilder con unCORREOmétodo

@Grab('org.codehaus.groovy.modules.http-builder:http-builder:0.7.2') importar
groovyx.net.http.RESTClient
importar estático groovyx.net.http.ContentType.URLENC

def url = 'http://www.webservicex.net/CurrencyConvertor.asmx/' def convertidor =


nuevo RESTClient(url)

www.it-ebooks.info
Uso de una API basada en REST 551

def postBody = [FromCurrency: 'USD', ToCurrency: 'EUR']


convertidor.post(ruta: 'ConversionRate', body: postBody,
requestContentType: URLENC) { resp, datos -> afirmar
resp.status == 200
afirmar datos.nombre() == 'doble' println
datos.texto()
}

Los ejemplos hasta ahora son muy simples, pero un ejemplo más típico podría diferir en una de dos
formas:

1 Puede implicar una mayor complejidad. Puede tener muchos más parámetros de URL, involucrar
autenticación, involucrar JSON o XMLCORREOcargas útiles, etc. La biblioteca HTTP-Builder
proporciona generadores de marcado para admitir estos escenarios más complejos. Consulte la
documentación de la API para obtener más detalles.10
2 Puede ser una combinación que combine muchos otros servicios para brindar un
servicio más atractivo.

Habiendo dicho eso, ahora debería tener el conocimiento para abordar aplicaciones REST tan complejas,
incluso con solo las pocas herramientas pequeñas que le mostramos en esta sección.
Antes de dejar esta sección, debemos analizar brevemente el último estándar JAX-RS 2.0
(también conocido como JSR-339).11Las versiones anteriores de este estándar han sido parte del
mundo Java EE durante algún tiempo. Anotaciones JAX-RS como @OBTENER, @PONER, @Ruta,y
@QueryParamse agregan al código de implementación del lado del servidor. La biblioteca JAX-
RS se encarga de la asignación entre el punto final del protocolo HTTP y el código de
implementación. Parte del mapeo puede implicar la serialización entre los objetos de dominio
utilizados en el código de implementación y su representación como parámetros o como una
carga JSON o XML.
Obviamente, esta API libera al desarrollador de muchos detalles de bajo nivel, por lo que
definitivamente es de interés para los escritores de servicios de punto final, pero este es un capítulo que
se enfoca en escribir código de cliente de servicio web. ¿Por qué JAX-RS debería ser de interés? La buena
noticia es que a partir de la versión 2.0 de este estándar, ahora hay unAPI de cliente unificado. Además,
algunas de las implementaciones no requieren un contenedor Java EE y se pueden usar de forma
independiente con Java SE. Eso lo hace ideal para nuestras necesidades. Usaremos el cliente RESTEasy
(www.jboss.org/resteasy) de JBoss.
Cuando usa JAX-RS como cliente, usa una API fluida proporcionada por unConstructor de clientes
class para definir los detalles necesarios para que el constructor realice la solicitud HTTP adecuada
a su punto final de interés y comprenda la respuesta de retorno. La API tiene numerosos puntos de
extensión que le permiten atender tipos MIME personalizados o necesidades especiales de
serialización. Evitaremos usar esas partes de la API. Para nuestro ejemplo, es suficiente especificar
la URL de destino y nuestros parámetros de consulta y luego declarar que

10Consulte https://github.com/jgritman/httpbuilder/wiki.
11Consulte www.jcp.org/en/jsr/detail?id=339 para ver la especificación final.

www.it-ebooks.info
552 CPASADO15Interactuar con servicios web

quiere una cadena sin formato como tipo de respuesta. Procesaremos la respuesta nosotros
mismos con Groovy'sXmlSlurper.El código resultante se puede ver en el siguiente listado.

Listado 15.7 Usando JAX-RS

@Grab('org.jboss.resteasy:resteasy-client:3.0.10.Final') importar
javax.ws.rs.client.ClientBuilder

def cliente = ClientBuilder.nuevoCliente()


def base = "http://www.webservicex.net/CurrencyConvertor.asmx" def respuesta =
client.target(base + '/ConversionRate')
. queryParam("DeDivisa", "USD") bespecifica
. queryParam("ADivisa", "EUR") una cuerda

respuesta
. solicitud().get(Cadena)
def rate = new XmlSlurper().parseText(respuesta) asertar
rate.name() == 'doble'
println rate.text()

Especificar que queríamos una cadena sin procesar fue tan fácil como proporcionar laCuerdaclase al
método getB. Alternativamente, podríamos recuperar el objeto de respuesta HTTP o un objeto de
dominio que hayamos definido previamente usando anotaciones JAX-RS. Dicha clase estaría disponible si
hubiéramos utilizado JAX-RS para definir nuestro servicio.
También debemos señalar una buena característica de RESTEasy que actualmente no forma
parte del estándar JSR-339. Proporciona un método proxy que toma una clase de interfaz. La clase
de interfaz se anota de la misma manera que lo haría un punto final de servicio con las
convenciones JAX-RS normales, pero con esta técnica solo se atiende el mapeo del lado del cliente.
Esto es exactamente lo que queremos y el código resultante se muestra en la siguiente lista.

Listado 15.8 Usando un proxy de cliente RESTEasy

@Grab('org.jboss.resteasy:resteasy-client:3.0.10.Final') importar
javax.ws.rs.GET
importar javax.ws.rs.Path
importar javax.ws.rs.Produce
importar javax.ws.rs.QueryParam
importar javax.ws.rs.client.ClientBuilder

interfaz Convertidor de divisas {


@OBTENER

@Ruta("Tasa de conversión")
@Produce("aplicación/xml")
Cadena convert(@QueryParam("FromCurrency") Cadena de,
@QueryParam("ADivisa") Cadena a)
}
def cliente = ClientBuilder.nuevoCliente()
def base = "http://www.webservicex.net/CurrencyConvertor.asmx" def proxy =
client.target(base).proxy(CurrencyConvertor)
def respuesta = proxy.convert("USD", "EUR") def root = new
XmlSlurper().parseText(respuesta) afirmar root.name() == 'doble'

println root.text()

www.it-ebooks.info
Usando XML-RPC 553

Aquí elConvertidor de monedainterfaz es nuestra descripción declarativa de los detalles


del servicio web. Anotaciones en elconvertirmétodo y sus parámetros dan suficiente
información para elapoderadométodo para proporcionarnos un objeto que satisfaga el
Convertidor de monedapero realiza la llamada HTTP correcta al servicio web cuando se le
llama.
No tenemos espacio en este capítulo para profundizar en otros detalles de JAX-RS. Para
algunos detalles más con sabor a Groovy, sugerimos leerHacer Java Groovypor Kenneth
Kousen (Manning, 2013) o visitando el sitio web de JAX-RS (https://jax-rs-spec.java.net/) para
conocer todos los detalles oficiales.
Las API REST proporcionan un mecanismo muy simple pero poderoso para la interacción
cliente-servidor. La desventaja es que cada API es diferente; necesita comprender los detalles
de cada uno antes de usarlo. Verá un enfoque para superar esta falta de estandarización
cuando analicemos XML-RPC en la siguiente sección.

15.4 Usando XML-RPC


La especificación XML-RPC es casi tan antigua como XML. Es extremadamente simple y conciso. Visite
www.xmlrpc.com para obtener todos los detalles.
Gracias a esta especificación, Groovy puede proporcionar una implementación general para
muchos de los detalles de la infraestructura que debe escribir para REST. Un módulo separado
(groovy-xmlrpc) contiene la implementación general y se puede obtener fácilmente con a @Tomar
anotación en su clase o secuencia de comandos o simplemente descargando el archivo .jar.
Quizás la mejor manera de convencerlo de sus méritos sea con el ejemplo. Suponga que tiene un
servidor XML-RPC simple ejecutándose en su máquina local en el puerto 8080 que expone un
ecooperación que devuelve lo que recibe. Usar este servicio desde un cliente Groovy es tan
simple como

import groovy.net.xmlrpc.XMLRPCServerProxy como Proxy def remote


= new Proxy('http://localhost:8080/') afirmar '¡Hola mundo!' ==
remoto.echo('¡Hola mundo!')

Instalar un servidor que implemente elecola operación es igualmente fácil. Cree una instancia
de servidor y asigne un cierre a suecopropiedad:

importar groovy.net.xmlrpc.XMLRPCServer como servidor

servidor def = nuevo servidor()

servidor.eco= {devolverlo }

El servidor debe iniciarse en unServerSocketantes de que el cliente pueda llamarlo, y


debe detenerse después. La siguiente lista instala elecoservidor, lo inicia, solicita el
ecooperación y la detiene al final.

Listado 15.9 Servidor y cliente XML-RPC autónomo para elecooperación

@Grab('org.codehaus.groovy:groovy-xmlrpc:0.8') importar
groovy.net.xmlrpc.XMLRPCServerProxy como proxy importar
groovy.net.xmlrpc.XMLRPCServer como servidor

www.it-ebooks.info
554 CPASADO15Interactuar con servicios web

def servidor = nuevo Servidor()


servidor.echo = { devolverlo }

def socket = nuevo ServerSocket(8080)


server.startServer(socket)

remoto = nuevo Proxy("http://localhost:8080/") afirmar '¡Hola mundo!'


Codigo del cliente
== remoto.echo('¡Hola mundo!')

servidor.pararServidor()

Tener el cliente y el servidor juntos es útil para fines de prueba, pero en producción, estas dos
partes generalmente se ejecutan en diferentes sistemas.
XML-RPC también define el manejo de errores, que en Groovy XML-RPC está disponible a través de
laXMLRPCCallFailureExceptioncon las propiedadescadena de fallasycódigo de fallo.
Las áreas de aplicación de XML-RPC son tan amplias que cualquier lista que pudiéramos hacer
estaría necesariamente incompleta. Se utiliza para leer y publicar en blogs, conectarse a sistemas
de mensajería instantánea (a través del protocolo Jabber para sistemas como GoogleTalk12),
fuentes de noticias, motores de búsqueda, servidores de integración continua, sistemas de
seguimiento de errores, etc.
Es atractivo porque es poderoso y simple al mismo tiempo. Veamos, por ejemplo,
información sobre los proyectos gestionados en Apache Software Foundation (ASF).13La ASF
proporciona la JIRA14sistema de seguimiento de errores para sus proyectos alojados.
La impresión de todos los nombres de proyectos se puede hacer fácilmente con el siguiente código:

importar groovy.net.xmlrpc.XMLRPCServerProxy como proxy

def remoto = nuevo Proxy('http://jira.codehaus.org/rpc/xmlrpc')

def loginToken = remoto.jira1.login('usuario','***') def proyectos =


remoto.jira1.getProjects(loginToken) proyectos.cada { println it.name }

Es convencional que las operaciones expuestas a través de XML-RPC tengan una notación de puntos como
jira1.login.El soporte XML-RPC de Groovy puede solucionarlo.
Pero si llama a muchos métodos, usando elremoto.jira1.prefijo se interpone en el camino de la
legibilidad. Sería mejor evitar eso. El listado 15.10 tiene una solución. Las llamadas a los métodos de proxy
siempre pueden cerrarse opcionalmente. Dentro de ese cierre, los nombres de los métodos se resuelven
contra el proxy. Extendemos este comportamiento con un especialistaJiraProxyque prefija las llamadas a
métodos conjira1.

12Vea el excelente artículo de Guillaume sobre cómo usar GoogleTalk a través de Groovy en http://glaforge.free.fr/
blog/index.php?itemid=142.
13El proyecto Groovy está alojado actualmente en la incubadora de Apache como parte del cambio al software Apache.
Base.
14Encuentre información sobre los métodos JIRA XML-RPC en http://confluence.atlassian.com/display/JIRA/
JIRA+XML-RPC+Resumen.

www.it-ebooks.info
Aplicación de SOAP 555

Para hacer las cosas un poco más interesantes esta vez, publicamos información sobre el
proyecto Groovy en el JIRA de ASF.

Listado 15.10 Usando la API XML-RPC de JIRA en el proyecto Groovy

@Grab('org.codehaus.groovy:groovy-xmlrpc:0.8') importar
groovy.net.xmlrpc.XMLRPCServerProxy como proxy

clase JiraProxy extiende Proxy {


JiraProxy(url) { super(url) }

Object invocarMetodo(String nombre de metodo, args) {


super.invokeMethod('jira1.' + nombre de método, argumentos)
}
}

def jira = nuevo JiraProxy('https://issues.apache.org/jira/rpc/xmlrpc')

// inserte su nombre de usuario y contraseña de codehaus debajo de


jira.login('username', '****') { loginToken ->
def proyectos = getProjectsNoSchemes(loginToken)
println "${projects.size()} proyectos encontrados en Apache jira" def groovy =
projects.find { it.name == 'Groovy' }
si (genial) {
println "Se encontró el proyecto $groovy.name con la clave $groovy.key" println
"Descripción: $groovy.description"
println "Dirigido por $groovy.lead y alojado en $groovy.projectUrl"
}
}

esto imprime

519 proyectos encontrados en Apache jira Encontré


el proyecto groovy con la clave GROOVY
Descripción: lenguaje de programación Groovy: un lenguaje dinámico moderno para JVM Dirigido por
guillaume y alojado en https://groovy.incubator.apache.org

Tenga en cuenta la simplicidad del código. A diferencia de REST, no necesita trabajar en nodos XML
o JSON, ni en la solicitud ni en la respuesta. Simplemente puede usar tipos de datos Groovy como
cadenas (usuario), listas (proyectos) y mapas (maravilloso). ¿Quién puede pedir más?
Valdría más la pena escribir un libro sobre XML-RPC y su módulo Groovy, especialmente
sobre la implementación del lado del servidor. Pero este libro tiene pocas páginas y debe
consultar la documentación en línea para obtener más detalles y escenarios de uso.
Ahora tiene la información básica para comenzar a trabajar con XML-RPC. ¡Por qué no
intentarlo ahora mismo! O bien, continúe con nuestro recorrido por todas las opciones de
procesamiento distribuido con una solución integral: SOAP.

15.5 Aplicación de SOAP


SOAP es el sucesor de XML-RPC y sigue el enfoque de proporcionar un estándar
vinculante. Este estándar es mantenido por el W3C; consulte www.w3.org/TR/soap/.
El estándar SOAP amplía el estándar XML-RPC en múltiples dimensiones. Una extensión es
tipos de datos. Donde XML-RPC permite solo un pequeño conjunto fijo de tipos de datos, SOAP

www.it-ebooks.info
556 CPASADO15Interactuar con servicios web

proporciona medios para definir nuevos tipos de datos específicos del servicio. Otros marcos,
incluidos CORBA, DCOM y Java RMI, brindan una funcionalidad similar a la de SOAP, pero los
mensajes SOAP están escritos completamente en XML y, por lo tanto, son independientes de la
plataforma y el idioma. El enfoque general de SOAP es permitir que un servicio web describa su API
pública: dónde se encuentra, qué operaciones están disponibles y los formatos de solicitud y
respuesta (llamados mensajes). Un servicio SOAP hace que esta información esté disponible a
través del Lenguaje de definición de servicios web (WSDL).
SOAP ha sido ampliamente adoptado por la industria y hay numerosos servicios gratuitos disponibles, que
van desde tiendas en línea hasta datos financieros, mapas, música, sistemas de pago, subastas en línea,
seguimiento de pedidos, blogs, noticias, galerías de imágenes, servicios meteorológicos, validación de tarjetas de
crédito. -la lista es interminable.
Numerosos lenguajes de programación y plataformas brindan un excelente soporte para
SOAP. Las implementaciones populares de la pila SOAP en la plataforma Java incluyen Metro
(http://metro.java.net) y Apache CXF (http://cxf.apache.org/). El soporte SOAP incorporado
para Groovy es bastante básico, pero se usa con éxito para proyectos de producción.
Primero, exploraremos cómo puede usar SOAP con Groovy puro de una manera efectiva pero
concisa.

15.5.1 Hacer SOAP con Groovy simple


Nuestro ejemplo utiliza el servicio web de conversión de divisas que vimos anteriormente ubicado en
www.webservicex.net, que proporciona una gran cantidad de servicios web públicos interesantes.
Primero, buscamos la descripción del servicio para su convertidor de moneda como se muestra en la
siguiente lista.

Listado 15.11 Listado de las operaciones de un servicio SOAP

importar Groovy.xml.Espacio de nombres

def url = 'http://www.webservicex.net/CurrencyConvertor.asmx?WSDL' def wsdl = nuevo


espacio de nombres('http://schemas.xmlsoap.org/wsdl/','wsdl') def doc = nuevo XmlParser
().parse (url)

println doc[wsdl.portType][wsdl.operación].'@nombre'

Esto imprime las operaciones disponibles:

["Tasa de conversión", "Tasa de conversión", "Tasa de conversión"]

El servicio expone tres operaciones denominadasTasa de conversióncon diferentes


caracteristicas.15Nos interesa uno que lleveDesdeMonedayamonedacomo parámetros de
entrada y devuelve la tasa de conversión actual. Las monedas se pueden expresar usando un
formato comoDólar estadounidenseoEUR.

15Para obtener consejos sobre cómo leer la descripción de un servicio WSDL, consulte www.w3.org/TR/wsdl.

www.it-ebooks.info
Aplicación de SOAP 557

SOAP usa algo llamado formato de sobre para la solicitud. Los detalles están más allá del
alcance de este capítulo. Vea las especificaciones para más detalles. Nuestro sobre se ve así:

<?xml version="1.0" encoding="utf-8"?> <soap:Sobre

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://
esquemas .xmlsoap.org/soap/envelope/"> <soap:Cuerpo>

<ConversionRate xmlns="http://www.webserviceX.NET/">
<FromCurrency>${from}</FromCurrency>
<ToCurrency>${to}</ToCurrency> </
ConversionRate>
</jabon:Cuerpo>
</jabon:Sobre>

Como puede ver en la notación ${}, este sobre es una plantilla que podemos usar con un
motor de plantillas Groovy.
El código del listado 15.12 lee esta plantilla, la llena con parámetros para la conversión de dólares
estadounidenses a euros y la agrega a unaCORREOsolicitud a la URL del servicio. La solicitud necesita
encabezados de solicitud adicionales, como elSOAPActionpara que el servidor lo entienda. Usamos
explícitamenteUTF-8codificación de caracteres para evitar problemas de codificación multiplataforma.
El servicio responde con un sobre de resultados SOAP. Sabemos que contiene un nodo llamado
Resultado de la tasa de conversiónperteneciente al espacio de nombres del servicio. Ubicamos el primer
nodo de este tipo en la respuesta y obtenemos la tasa de conversión como sutextovalor.

Listado 15.12 Usando elTasa de conversiónservicio SOAP

importar groovy.text.SimpleTemplateEngine como STE


importar groovy.xml.Namespace
Sobre con plantilla
de solicitud SOAP
= nuevo archivo ('datos/conv.templ.xml')
archivo de definición

def plantilla = nuevo STE().createTemplate(archivo) def


parámetros = [desde: 'USD', hasta: 'EUR']
solicitud
definitivamente = template.make(parámetros).toString().getBytes('UTF-8')

def url = 'http://www.webservicex.net/CurrencyConvertor.asmx' def conn = nueva


URL (url).openConnection()
def reqProps = [
'Tipo de contenido': 'texto/xml; charset=UTF-8', 'http://www.webserviceX.NET/ Peticiones
'SOAPAction' : ConversionRate', 'aplicación/soap+xml, texto/*' encabezados a usar
'Aceptar' : cada vez
]
reqProps.each { clave, valor -> conn.addRequestProperty (clave, valor) }

conn.requestMethod = 'CORREO'
conn.doSalida = verdadero
conn.outputStream << new ByteArrayInputStream(solicitud) if
envía el
(conn.responseCode != conn.HTTP_OK) { solicitud
println "Error - HTTP:${conn.responseCode}" devuelve

www.it-ebooks.info
558 CPASADO15Interactuar con servicios web

resp.
definitivamente =
XmlParser().parse(conn.inputStream) Espacio de
nuevo
Analiza el
servir
definitivamente =
nombres('http://www.webserviceX.NET/')
nuevo
respuesta
def resultado = serv.ConversionRateResult

print "Tasa de conversión actual de USD a EUR: " Extractos


println resp.profundidadPrimero().find{resultado == it.name()}.text() el resultado

Al momento de escribir, se imprime

Tasa de conversión actual de USD a EUR: 0.882

Esto es sencillo en términos de cada paso individual, pero tomado como un todo, el código es
bastante engorroso. Un punto a tener en cuenta sobre la implementación está oculto al ubicar el
resultado en el sobre de respuesta. usamos elservirespacio de nombres y pídale su
Resultado de la tasa de conversiónpropiedad, que devuelve unQNombre.Lo asignamos a laresultado
variable y hacer uso del hecho de queQNombreimplementa eles igualmétodo con cadenas
para que encontremos el nodo adecuado.
SOAP es detallado en comparación con otros enfoques. Es detallado en el código que exige para su
ejecución y, lo que es más importante, es detallado en el formato de su mensaje. No es inusual que los
mensajes SOAP tengan 10 veces más marcado XML que el tamaño de la carga útil.
Pero el estándar SOAP permite brindar herramientas generales para lidiar con su
complejidad.

15.5.2 Simplificación del acceso SOAP mediante HTTPBuilder


La biblioteca HTTPBuilder utilizada anteriormente para acceder a los servicios web REST también
puede acceder a los servicios SOAP. HTTPBuilder admite una notación de estilo constructor para
crear su sobre de solicitud SOAP. Esto le evita tener esos detalles en un archivo de plantilla
separado. El código resultante se puede ver en el siguiente listado.

Listado 15.13 Accediendo a un servicio SOAP usando HTTPBuilder

@Grab('org.codehaus.groovy.modules.http-builder:http-builder:0.7.2') importar
groovyx.net.http.RESTClient
importar estático groovyx.net.http.ContentType.XML

def base = 'http://www.webserviceX.NET/CurrencyConvertor.asmx' def soapEnv =


'http://www.w3.org/2003/05/soap-envelope'
def contentType = 'aplicación/soap+xml; charset=UTF-8' nuevo bregistra un
Respuesta SOAP
RESTClient(base).with {
analizador.'aplicación/soap+xml' = analizador.'aplicación/xml' encabezados = analizador

['Tipo de contenido': tipo de contenido]


post(requestContentType: XML, cuerpo: { Tipo de contenido

'jabón:Sobre'('xmlns:jabón': jabónEnv) { esperado por

'jabón:Cuerpo' { Cservidor SOAP


Tasa de conversión (xmlns: 'http://www.webserviceX.NET/') {
DesdeDivisa('USD')
ADivisa('EUR')
}
}
}
}) { respectivamente, datos ->

www.it-ebooks.info
Aplicación de SOAP 559

afirmar resp.status == 200 println


data.text()
}
}

La mayor parte de esto debería parecer familiar a los ejemplos anteriores de análisis y compilación. HTTP-
Builder sabe cómo crear solicitudes XML y analizar respuestas XML. Utilizará esas capacidades y
proporcionará los elementos, atributos y espacios de nombres correctos que se esperan para una
interacción SOAP.
Quizás la primera parte complicada es que necesita registrar un analizador apropiadoB
(utilizando el 'aplicación/jabón+xml'tipo MIME) para la respuesta devuelta. Dado que la
respuesta SOAP se puede tratar como XML simple y antiguo, puede aprovechar ese
analizador. Además, aunque la solicitud SOAP es simplemente XML antiguo, el servidor SOAP
esperará un tipo de contenido SOAP, por lo que debe establecer un encabezado con el valor
que espera el servidor SOAP.C.
Nuestra implementación de HTTPBuilder es un gran paso adelante con respecto a la lista 15.12, pero
aún contiene bastante código repetitivo. Solo como un ejemplo, el sobre SOAP y los elementos del cuerpo
existirían para cada servicio SOAP. Nuestro próximo paso a considerar es una biblioteca dedicada
compatible con SOAP. Ese es el tema de la siguiente sección.

15.5.3 Simplificación del acceso SOAP mediante groovy-wslite

La biblioteca groovy-wslite conoce SOAP (y REST también si desea considerar una alternativa a
HTTPBuilder para REST). Utiliza un enfoque de estilo constructor que nos permite especificar la
carga útil de nuestro cuerpo SOAP, pero oculta muchos de los detalles asociados con la creación de
las solicitudes SOAP reales o el análisis de las respuestas SOAP. La siguiente lista muestra el código
resultante.

Listado 15.14 Tasas de conversión usando groovy-wslite

@Grab('com.github.groovy-wslite:groovy-wslite:1.1.0') importar
wslite.soap.SOAPClient

def url = 'http://www.webservicex.net/CurrencyConvertor.asmx?WSDL' def cliente = nuevo


SOAPClient(url)
def acción = "http://www.webserviceX.NET/ConversionRate" def respuesta =
cliente.send(SOAPAction: acción) {
cuerpo {
Tasa de conversión (xmlns: 'http://www.webserviceX.NET/') {
DesdeDivisa('USD')
ADivisa('EUR')
}
}
}
afirmar respuesta.httpResponse.statusCode == 200
println respuesta.ConversionRateResponse.ConversionRateResult

Ahora, eso es mucho más maravilloso! Esta solicitud SOAP en particular utilizará la versión predeterminada de
SOAP 1.1, pero podemos cambiar a SOAP 1.2 tan fácilmente como se puede ver en la siguiente lista.

www.it-ebooks.info
560 CPASADO15Interactuar con servicios web

Listado 15.15 Tasas de conversión usando groovy-wslite y SOAP 1.2

@Grab('com.github.groovy-wslite:groovy-wslite:1.1.0') importar
wslite.soap.*

def url = 'http://www.webserviceX.NET/CurrencyConvertor.asmx?WSDL' def cliente =


nuevo SOAPClient(url)
def respuesta = cliente.enviar {
versión SOAPVersion.V1_2
cuerpo {
Tasa de conversión (xmlns: 'http://www.webserviceX.NET/') {
DesdeDivisa('USD')
ADivisa('EUR')
}
}
}
afirmar respuesta.httpResponse.statusCode == 200
println respuesta.ConversionRateResponse.ConversionRateResult

Aquí necesitamos establecer explícitamente la versión que requerimos, pero podemos eliminar elSOAPAction
información ya que no es necesaria para SOAP 1.2.
Esperamos que haya disfrutado de nuestro viaje a través de la variedad de tecnologías de clientes de
servicios web. Debe tener suficiente información para crear una amplia variedad de clientes de servicios
web.

15.6 Resumen
Los servicios web son un tema tan amplio que posiblemente no podamos tocar todas las bases en un libro
introductorio sobre Groovy, pero ha visto los aspectos más importantes con suficiente detalle para tener
una buena base para la experimentación y la lectura adicional.
Se ha vuelto cada vez más popular construir arquitecturas de aplicaciones completas en torno a
servicios pequeños, dedicados y débilmente acoplados que cooperan mediante el uso de la mecánica que
hemos mencionado en este capítulo. Se llaman microservicios.
Pueden vivir en su propio pequeño contenedor que incluso puede contener su sistema
operativo privado. Cada uno puede tener su propia forma de exponer su servicio siempre y cuando
cumpla con los estándares web. Pueden ejecutarse en servidores Java Enterprise completos, Spring
Boot, Grails, Groovy + Jetty como en la sección 12.5, o algo tan pequeño y liviano como Ratpack
(www.ratpack.io).
No está claro si esta tendencia continuará, pero de cualquier manera, su conocimiento del
soporte de servicios web de Groovy le permite implementar arquitecturas convencionales tan bien
como las más recientes.caderaarquitecturas
Como ha visto, es fácil enviar XML y JSON alrededor del mundo para hacer que las
computadoras en red trabajen juntas, compartiendo información y poder de cómputo. XML-RPC y
SOAP tienen soporte en las bibliotecas de Groovy, aunque es probable que ese soporte cambie
significativamente con el tiempo. REST no puede beneficiarse de este soporte tan fácilmente (ni
siquiera en el mundo dinámico de Groovy) debido a la falta de estandarización, pero ha visto cómo
el uso de constructores puede simplificar el desarrollo de una API para un servicio REST específico. .

www.it-ebooks.info
Integrando Groovy

Este capítulo cubre


- Incrustando Groovy en proyectos Java
- Protección de scripts definidos por el usuario

- Personalización del contexto de tiempo de ejecución

En el mundo de los lenguajes de programación, una regla de supervivencia es


simple: bailar o morir. No es suficiente hacer un lenguaje hermoso. También debe
facilitar que los programas escritos en su hermoso idioma interactúen con
programas escritos en otros idiomas.

—Simón Peyton Jones

Una de las mayores ventajas de Groovy (incluso una de las razones de su creación) es que se
integra de forma nativa con Java porque ambos lenguajes se ejecutan en la misma plataforma y,
en su mayor parte, comparten las mismas estructuras de datos de uso común, como listas y
mapas. Es importante comprender qué hace que Groovy sea una opción tan atractiva cuando
necesita incrustar un lenguaje de secuencias de comandos en su aplicación.
Desde una perspectiva corporativa, tiene sentido construir sobre la misma plataforma en la que ya
se están ejecutando la mayoría de sus proyectos. Esto protege la inversión en habilidades, experiencia
y tecnología, mitigando el riesgo y, por lo tanto, los costos.

561

www.it-ebooks.info
562 CPASADOdieciséisIntegrando Groovy

Donde Java no encaja perfectamente como lenguaje, las características de expresividad, brevedad y potencia
de Groovy pueden ser más apropiadas. Por el contrario, cuando Groovy se queda corto debido a la inevitable
compensación entre agilidad y velocidad, el código crítico para el rendimiento se puede reemplazar con Java sin
procesar.1Estas decisiones de equilibrio pueden tomarse temprano o tarde con pocas repercusiones debido a los
estrechos vínculos entre los dos idiomas. Groovy proporciona un mecanismo de integración transparente que
permite una mezcla y combinación uno a uno de las clases de Java y Groovy. Este no siempre es el caso con otras
soluciones de secuencias de comandos, algunas de las cuales solo proporcionan contenedores o proxies que
rompen el contrato de jerarquía de objetos.

16.1 Preludio a la integración


Si Groovy y Java se integran tan perfectamente, ¿por qué se dedica todo un capítulo a describir las
posibilidades de integración? Para responder, tenemos que dar un paso atrás.
Si ya tiene un proyecto de Java que se compila con Gradle, entonces "incorporar" Groovy en su
proyecto puede ser tan simple como agregar una línea en la parte superior de su archivo de
compilación de Gradle para habilitar el complemento de Groovy y agregar una dependencia al
archivo JAR de Groovy. . O, si está compilando y empaquetando sus artefactos de Groovy en
archivos de clase, entonces podría agregarlos a su classpath de Java. De manera similar, si utiliza
principalmente Groovy y quiere "incorporar" un poco de Java, podría significar agregar un archivo
JAR a su classpath o usar el indicador de compilación conjunta al llamar al compilador Groovy.
Pero estos escenarios simples no permiten ninguna provisión de código justo a tiempo, ya sea a
través de usuarios que ingresen expresiones como lo harían en una calculadora gráfica o desarrolladores
que proporcionen scripts de reemplazo solo para los bits de código que requieren cambios frecuentes
dentro de un programa en vivo. sistema. Como idea de cuán ampliamente utilizado puede ser este tipo de
instalación, considere Visual Basic (VB). No estamos en el negocio de juzgar sus pros y sus contras, pero
sería difícil negar que VB es popular y lo ha sido durante mucho tiempo. Aunque muchos desarrolladores
escriben aplicaciones completas en VB desde cero, muchos más usan la capacidad de varios productos
paraempotrarpiezas de código VB para personalizar el comportamiento de formas que los
desarrolladores originales nunca habrían considerado.
Ahora considere permitir ese tipo de flexibilidad en su aplicación. En lugar de
escuchar a la gente hablando de escribir VB en Microsoft Office, imagine a esas mismas
personas hablando de escribir Groovy ensusolicitud. Imagínelos usando su producto de
formas que nunca pensó, haciéndolo cada vez más valioso para ellos.
Por lo tanto, hay una enorme ventaja en la integración, pero potencialmente también puede
agregar una complejidad significativa según el camino que tome. Entonces, veamos algunos de los
puntos que debe tener en cuenta al evaluar una oportunidad de integración y, luego, qué debe
hacer para comenzar.

1
Pero asegúrese de leer sobre @CompileStaticen el capítulo 10, que le da a gran parte de su código Groovy una velocidad similar a la
de Java.

www.it-ebooks.info
Preludio a la integración 563

16.1.1 Integración apropiada


Nadie puede decirle cuáles son las necesidades de su aplicación o qué será adecuado para su
situación particular. Debe analizar detenidamente sus requisitos y considerar si se beneficiará
en absoluto de la integración de Groovy. No podemos tomar esa decisión por usted, pero
esperamos poder darle algunas ideas para guiarlo.
Vale la pena reconocer explícitamente que no todas las aplicaciones se benefician de la integración de un
lenguaje de secuencias de comandos como Groovy. Podemos ir tan lejos como para decir que muchos no lo
requieren. Si está escribiendo un sitio web de comercio electrónico, un reproductor multimedia o un cliente FTP,
lo más probable es que no necesite un lenguaje de secuencias de comandos. Pero no permita que le impidamos
usar Groovy como (uno de) sus lenguajes de implementación para su aplicación. Sin embargo, esperamos que lo
compile todo de una sola vez en un proceso de compilación tradicional con un solo ciclo de vida.

Por otro lado, suponga que está creando un procesador de textos avanzado, una aplicación de hoja
de cálculo o un módulo complejo de cálculo de riesgos para un paquete de software bancario aún más
complicado que tuvo que evolucionar rápidamente para seguir los rápidos cambios del mercado, la
legislación o nuevas reglas de negocio. Estas aplicaciones pueden necesitar un punto de extensión donde
los usuarios finales puedan personalizarlas para satisfacer sus necesidades. La figura 16.1 muestra un
ejemplo de dónde podría integrar Groovy.
Por ejemplo, la aplicación bancaria puede requerir la definición de reglas comerciales en un
script que podría definirse en tiempo de ejecución sin requerir una nueva y tediosa fase de
desarrollo/prueba/calificación, lo que reduce el tiempo de comercialización y aumenta la capacidad
de respuesta a los cambios en las prácticas financieras. Otro ejemplo podría ser un conjunto de
aplicaciones ofimáticas que ofrece un sistema de macros para crear funciones reutilizables que se
pueden invocar con una pulsación de tecla. Se vuelve obvio que una dicotomía del mundo del
software diferencia las aplicaciones monolíticas, que no necesitan evolucionar con el tiempo y
tienen un alcance funcional fijo, de las aplicaciones más fluidas, cuya lógica puede extenderse o
modificarse durante su vida útil para adaptarse a los cambios de contexto.
Antes de considerar usar Groovy en su aplicación, analice si necesita personalizarlo y
vea si desea personalizar, ampliar o modificar la lógica, y no solo parámetros simples. Si
la parametrización satisface sus necesidades, es mejor que utilice mecanismos de
configuración clásicos, como una interfaz web de administración a través de un
conjunto de servicios web; o, para un seguimiento y una acción más avanzados

Captura
Capa de interfaz de usuario
maravilloso

código
Evaluar
capa empresarial Figura 16.1 Un ejemplo de un
solución de integración. El usuario ingresa
GroovyShell el código Groovy en la capa de la interfaz
capa de datos de usuario y luego lo ejecuta en la capa
empresarial.

www.it-ebooks.info
564 CPASADOdieciséisIntegrando Groovy

administración, también puede considerar exponer JMX2Frijoles MB. A veces, incluso si la


lógica tiene que cambiar, si la elección está entre un conjunto pequeño y bien definido de
reglas comerciales que se conocen desde el principio, también puede integrar todas esas
reglas dentro de la aplicación y decidir mediante la parametrización cuál es. ser usado.
Una vez que haya examinado sus necesidades y llegado a la conclusión de que lo que necesita su
aplicación es un entorno de secuencias de comandos, este capítulo le proporcionará toda la información
que necesita para hacer que su aplicación sea ampliable en tiempo de ejecución con lógica escrita en
Groovy.3
En las siguientes secciones, aprenderá a usar elGroovyShellclase para evaluar
expresiones simples y guiones, así como laGroovyScriptEnginey elGroovy-ClassLoader
para cargar más clases de Groovy. Además de las técnicas proporcionadas por
Groovy para integrar sus scripts en Java, descubrirá alternativas para aprovechar
Spring Framework y la API de scripting disponible desde Java 6, también conocida
como JSR-223.

16.1.2 Configuración de dependencias


Para usar Groovy dentro de su proyecto, deberá configurarlo para usar las bibliotecas de Groovy. Esta
sección cubre las dependencias requeridas para la integración de Groovy. El hecho de que sea tan corto
debería ser una fuente de consuelo: hay poco trabajo que hacer para ponerse en marcha.
La distribución de Groovy viene con un directorio que contiene todas las bibliotecas principales
que forman el tiempo de ejecución de Groovy. El mínimo para incrustar Groovy es el archivo
groovy-2.4.0.jar.4Contiene todas las clases principales de Groovy más versiones integradas de Antlr,
ASM y Commons-CLI. Si usa una función en uno de los módulos de Groovy, como XML, SQL o JSON,
también querrá incorporar el archivo JAR de ese módulo, llamado groovy-xml.jar, por ejemplo, para
el módulo groovy-xml. . Consulte el apéndice B para obtener una lista completa de módulos.
Alternativamente, si desea todos los módulos, puede utilizar elempotrable
. jar, llamado groovy-all-2.4.0.jar, que reside en el directorio integrable de su
distribución. O puede obtener estas dependencias de su repositorio en línea favorito.

NOTALas versiones de Groovy anteriores a la 2.3.0 no incorporaban los archivos JAR


Antlr, ASM y Commons-CLI, excepto en el archivo JAR integrable groovy-all. Esto
significaba que a veces podía salirse con la suya con una huella más pequeña
porque esos archivos JAR de terceros pueden haber sido una dependencia existente
para alguna otra parte de su proyecto y, por lo tanto, ya están en el classpath. Pero
si tenía una versión no compatible de esos archivos JAR en su classpath, existía el
riesgo de que Groovy no funcionara. Ahora están integrados para evitar tales
conflictos y puede usar libremente una versión diferente de esas bibliotecas dentro
de su aplicación si tiene sentido hacerlo.

2
Extensiones de gestión de Java; consulte www.oracle.com/technetwork/java/javase/tech/javamanagement-140525. html

3
Por supuesto, no deseamos desanimarlo de leer el capítulo incluso si no tiene ninguna necesidad de integración en este
momento. Obtener conocimiento es una búsqueda digna en sí misma.
4
El número puede ser diferente si está utilizando una versión diferente.

www.it-ebooks.info
Evaluación de expresiones y scripts con GroovyShell 565

No son solo las aplicaciones Java las que pueden beneficiarse de la disponibilidad de un motor de
secuencias de comandos: ¡incluso puede integrar secuencias de comandos y expresiones Groovy
personalizadas desde una aplicación escrita en Groovy! Mientras explicamos los diversos mecanismos de
incrustación, le mostraremos cómo se pueden explotar los intérpretes y cargadores de clases de Groovy
desde ambos lados de la valla lingüística. Ahora que hemos configurado nuestro entorno, podemos ver la
primera de nuestras tres formas de integración directa con Groovy:GroovyShell.

16.2 Evaluación de expresiones y scripts con GroovyShell


La primera API de Groovy que examinaremos esGroovyShell.Esta es, en muchos sentidos, la más simple de
las técnicas de integración y, si cubre su situación, es posible que sea todo lo que necesita. Con todas las
bibliotecas en su lugar, comenzaremos a evaluar expresiones dinámicamente en unas pocas líneas de
código simples. Luego, avanzaremos gradualmente hacia escenarios más complejos, pasando datos entre
el código de llamada y el script de ejecución dinámica, y luego creando clases en el script para usar en el
exterior. Examinamos diferentes formas de ejecutar secuencias de comandos (precompilarlas o
ejecutarlas solo una vez) y los diferentes tipos de secuencias de comandos que se pueden ejecutar. Por
último, analizamos las formas en que puede modificarGroovyShellpara usos más avanzados. No se
preocupe si parece que hay mucho que aprender; en situaciones simples, las soluciones simples a
menudo son suficientes. Además, gran parte de la información presentada aquí es relevante cuando se
analizan las otras API que proporciona Groovy.

16.2.1 Comenzar simplemente

El requisito de integración más simple imaginable evalúa una expresión. Por ejemplo, algunas
aplicaciones matemáticas pueden requerir que los usuarios ingresen expresiones arbitrarias en un
campo de entrada de formulario que no se puede configurar en el momento del desarrollo en una
función o un cierre (por ejemplo, una aplicación de hoja de cálculo donde las fórmulas son
expresiones Groovy). Esas aplicaciones luego le piden al tiempo de ejecución que calcule la fórmula
ingresada. En tales situaciones, la herramienta elegida para evaluar expresiones y guiones es el
GroovyShellclase. El uso de esta clase es sencillo y es similar si la usa desde Java o desde Groovy. Se
puede implementar un evaluador de expresiones simple usandoGroovyShellcomo se muestra en el
siguiente listado.5

Listado 16.1 Un ejemplo trivial de evaluación de expresiones en Groovy

def shell = nuevo GroovyShell()


resultado def = shell.evaluar("12 + 23") afirmar
resultado == 35

5
Quizás se pregunte por qué elegimos integrar Groovy a Groovy. Bueno, es más probable que lo hagamos desde Java, pero usar Groovy simplifica nuestros
ejemplos. Hacerlo puede ser útil incluso desde Groovy, de modo que pueda organizar el código de la utilidad en secuencias de comandos externas,
ejecutar secuencias de comandos con ciertas políticas de seguridad o ejecutar entradas proporcionadas por el usuario en tiempo de ejecución.

www.it-ebooks.info
566 CPASADOdieciséisIntegrando Groovy

El programa Java completo equivalente es naturalmente un poco más largo debido al código de
andamiaje y las importaciones requeridas, pero la lógica central es exactamente la misma. La siguiente
lista proporciona el código Java completo requerido para realizar la evaluación, aunque sin manejo de
errores. Los ejemplos de Java más adelante en el capítulo se han reducido a solo el código involucrado en
la integración. Las importaciones generalmente se muestran solo cuando no están claras en el contexto.

Listado 16.2 Ejemplo trivial del listado 16.1, esta vez en Java

//Java
importar Groovy.lang.GroovyShell;

clase pública HolaIntegraciónMundo {


public static void main(String[] args) {
shell GroovyShell = nuevo GroovyShell(); Resultado del
objeto = shell.evaluate("12+23"); afirmar
resultado.equals(35);
}
}

En ambos casos, primero creamos una instancia deGroovy.lang.GroovyShelly llamamos al


evaluaren él, que toma una cadena como parámetro que contiene la expresión a
evaluar. losevaluarEl método devuelve un objeto que contiene el valor de la expresión.
No mostraremos el equivalente de Java para todos los ejemplos de este capítulo, pero a
veces proporcionamos uno, más que nada para recordarle lo fácil que es.6

Entre laevaluarmétodos sobrecargados presentes enGroovyShell,aquí están los


más utilizados:

Objeto evaluar (archivo archivo)


Objeto evaluar (Lector en)
Objeto evaluar(Cadena guiónTexto)
Objeto evaluar (URI uri)

Puede evaluar expresiones provenientes de un archivo, un lector, una cadena o un URI. También
hay algunas variantes que no mostramos que toman un parámetro de nombre de archivo adicional
que se usa para especificar el nombre de la clase que se creará al evaluar el script, porque Groovy
siempre genera clases para los scripts también.

6
rara vezbastantetan fácil como el equivalente de Groovy, pero a estas alturas deberías darte cuenta de que esto no tiene nada que ver con las
características que se muestran y todo que ver con que Groovy haga la vida más fácil en general.

www.it-ebooks.info
Evaluación de expresiones y scripts con GroovyShell 567

Desde los scripts Groovy, se puede usar un atajo: los scripts son clases que extienden el
Guionclase, que ya tiene unaevaluarmétodo. En el contexto de un script, nuestro
ejemplo anterior se puede acortar a lo siguiente:

afirmar evaluar ("12 + 23") == 35

El parámetro de cadena pasado aevaluarpuede ser un script completo con varias líneas de código,
no solo una expresión simple, como puede ver en la siguiente lista.

Listado 16.3 Evaluación de un script multilínea conGroovyShell

def shell = nuevo GroovyShell()


def energíacinética = shell.evaluate('''
masa def = 22.3
def velocidad = 10.6 masa *
velocidad**2 / 2
''')
afirmar energía cinética == 1252.814

Sobre la base deGroovyShell,laGroovy.util.Evalclass puede ahorrarle el código repetitivo


de instanciarGroovyShellpara evaluar expresiones simples con cero a tres parámetros.
La siguiente lista muestra cómo usarevaluarpara cada caso de Groovy (lo mismo aplica
para Java, por supuesto).

Listado 16.4evaluarguarda explícitamente la creación de unGroovyShellpara casos simples

afirmar "Hola" == Eval.yo("'Hola'") afirmar 1 == Eval.


X(1, "x") afirmar 3 == Eval.xy(1, 2, "x+y") afirmar 6 ==
Eval.xyz(1, 2, 3, "x+y+z")

losyoEl método se utiliza cuando no se requieren parámetros. Los otros métodos se utilizan
para uno, dos y tres parámetros, donde el primer, segundo y tercer parámetro están
disponibles comox, y,yz,respectivamente. Esto es útil cuando su única necesidad es evaluar
expresiones simples o incluso funciones matemáticas. A continuación, verá cómo puede ir
más allá con la parametrización de la evaluación de scripts conGroovyShell.

16.2.2 Pasar parámetros dentro de un enlace


En el listado 16.3, usamos un script de varias líneas que define dos variables de masa y velocidad para
calcular la energía cinética de un objeto de 22,3 kilogramos de masa con una velocidad de 10,6 km/h.
Pero tenga en cuenta que esto tiene un interés limitado si no podemos reutilizar el evaluador de
expresiones. Afortunadamente, es posible pasar variables al evaluador con unmaravilloso
lang.Encuadernaciónobjeto, como se muestra en el siguiente listado.

www.it-ebooks.info
568 CPASADOdieciséis Integrando Groovy

Listado 16.5 Hacer que los datos estén disponibles para unGroovyShellusando unUnión

vinculación def = nueva vinculación()


vinculación.mass = 22.3
bCrea y
rellena el enlace
vinculante.velocidad = 10.6

def shell = new GroovyShell(enlace) def expresión = "masa *


velocidad ** 2/2" asertar shell.evaluar(expresión) == 1252.814 CEvalúa la expresión
usando enlace

vinculante.setVariable("masa", 25.4) dCambios vinculantes


afirmar shell.evaluar (expresión) == 1426.972 datos y vuelve a evaluar

Para empezar, unUniónse instancia el objeto. PorqueUniónextiendeGroovy-ObjectSupport,


podemos establecer variables directamente en él como si estuviéramos manipulando
propiedades: elmasayvelocidadlas variables se han definido en el enlaceB. los
GroovyShellconstructor toma el enlace como parámetro y, más adelante, todas las
evaluaciones usan variables de ese enlace como si fueran variables globales del scriptC.
Cuando cambiamos el valor de lamasavariable, vemos que el resultado de la ecuación es
diferented. Esta línea es particularmente interesante porque hemos redefinido lamasa
variable gracias a laestablecerVariablemétodo enUnión.Así es como podríamos establecer o
modificar variables desde Java; Java no reconoceríamasa vinculante,porque este es un atajo
introducido en Groovy porUniónextensiónSoporte GroovyObject.
Es posible que ya hayas adivinado que si hay unestablecerVariablemétodo disponible, entonces
obtenerVariabletambién existe Donde el primero le permite crear o redefinir variables desde el
enlace, el último se usa para recuperar el valor de una variable del enlace. losevaluarEl método
puede devolver solo un valor: el valor de la última expresión del script evaluado. Cuando se
necesitan varios valores en el resultado, la secuencia de comandos puede usar el enlace para que
estén disponibles para el contexto de llamada. La siguiente lista muestra cómo un script puede
modificar los valores de las variables existentes o cómo puede crear nuevas variables en el enlace
que se pueden recuperar más tarde.

Listado 16.6 Los datos pueden salir del enlace y entrar en él

def enlace = nuevo enlace (x: 6, y: 4) def shell =


prepoblado
nuevo GroovyShell (enlace) shell.evaluate ('''
bdatos vinculantes
Métodod xCuadrado = x * x CConfiguración de datos vinculantes
el acceso a los
yCubo = y * y * y dentro del script evaluado
Unión ''')
datos
afirmar enlace.getVariable("xCuadrado") == 36 afirmar mipropiedad maravillosa
acceso a datos vinculantes
enlace.yCube == 64

En este ejemplo, creamos una instancia de enlace a la que agregamos dos parámetros,Xy
y,pasando un mapa a laUniónconstructorB. Nuestro script evaluado crea dos nuevas
variables en el enlace asignando un valor a variables no definidas:xCuadrado
yyCuboC. Podemos recuperar los valores de estas variables conobtenerVariablede Java y
Groovyd, o podemos usar el acceso similar a una propiedad de Groovymi.

www.it-ebooks.info
Evaluación de expresiones y scripts con GroovyShell 569

No se puede acceder a todas las variables conobtenerVariableporque Groovy hace una


distinción en los scripts entre variables definidas y variables indefinidas: si una variable se
define con eldefinitivamentepalabra clave o con un tipo, será una variable local, pero si no la
estás definiendo y le estás asignando un valor sin definición previa, se creará o asignará una
variable en el enlace. Aquí,variable localno está en la encuadernación, y el
llamar aobtenerVariablelanzaría unExcepción de propiedad faltante:

enlace def = nuevo enlace ()


def shell = new GroovyShell(binding)
shell.evaluate('''
definitivamentelocalVariable = "variable local"
bindingVariable = "variable de enlace"
''')

afirmar enlace.getVariable("bindingVariable") == "variable de enlace"

Se puede poner o recuperar cualquier cosa del enlace, y solo se puede devolver un valor de
retorno como evaluación de la última instrucción del script. El enlace es la mejor manera de
pasar sus objetos de dominio o instancias de sesiones o transacciones predefinidas o
rellenadas previamente a sus scripts. Examinemos una forma más creativa de devolver un
valor de la evaluación de su script.

16.2.3 Generación de clases dinámicas en tiempo de ejecución

Usandoevaluartambién puede ser útil para generar nuevas clases dinámicas sobre la marcha. Por
ejemplo, es posible que necesite generar clases para un servicio web en tiempo de ejecución,
basándose en elementos XML del WSDL para el servicio. En la siguiente lista se muestra un ejemplo
artificial para evaluar y devolver una clase ficticia.

Listado 16.7 Definiendo una clase en un script evaluado

def shell = new GroovyShell() def clazz =


shell.evaluate('''
clase MiClase {
método def() { "valor" } Define un
} Nueva clase
Crea un
devolver mi clase
en vez de
la clase
''')
afirmar clazz.nombre == "MiClase" def instancia
utiliza el objeto
= clazz.nuevaInstancia() afirmar
como normal
instancia.método() == "valor"

En todos los ejemplos que has visto hasta ahora, hemos usado elevaluarmétodo, que compila y
ejecuta un script de una sola vez. Eso está bien para evaluaciones únicas, pero otras situaciones se
benefician al separar la compilación (análisis) de la ejecución, como verá a continuación.

16.2.4 Análisis de secuencias de comandos

losanalizar gramaticalmentemétodos deGroovyShelldevolver instancias deGuionpara que


pueda reutilizar los scripts a voluntad sin reevaluarlos cada vez, sin compilarlos por completo

www.it-ebooks.info
570 CPASADOdieciséisIntegrando Groovy

otra vez. (Recuerde nuestroConstructor de columpiotrazador del capítulo 11.) Este método es similar
aevaluar,tomando el mismo conjunto de argumentos; pero en lugar de ejecutar el código, genera
una instancia delGuionclase. Todos los scripts que puede escribir son siempre instancias
deGuion.
Tomemos un ejemplo concreto. Supongamos que dirigimos un banco y tenemos clientes que
piden un préstamo para comprar una casa. Necesitamos calcular el monto mensual que tendrán
que pagar, sabiendo el monto total del préstamo, la tasa de interés y la cantidad de meses para
pagar el préstamo. Pero, por supuesto, queremos reutilizar esta fórmula y la almacenaremos en
una base de datos o en otro lugar del sistema de archivos en caso de que la fórmula evolucione en
el futuro.
Supongamos que las variables del algoritmo son las siguientes:

- Monto-El monto total del préstamo (el principio)


- Velocidad-La tasa de interés anual
- número de meses-El número de meses para reembolsar el préstamo

Con estas variables, queremos calcular el pago mensual. La secuencia de comandos en la siguiente
lista muestra cómo podemos reutilizar la fórmula para calcular esta cifra importante.

Listado 16.8 Múltiples usos de una calculadora de pago mensual

def mensual = "cantidad*(tasa/12) / (1-(1+tasa/12)**-numeroDeMeses)"

Analiza la fórmula en
def shell = nuevo GroovyShell() def script =
guión reutilizable Accesos
shell.parse(mensual)
Unión
variable
script.enlace.cantidad = 154000
guión.tasa = 3.75/100
Enlace de accesos
script.númeroDeMeses = 240 variable usando
taquigrafía
afirmar script.run() == 913.0480050387338

script.binding = nuevo enlace (cantidad: 185000,


Crea nuevo
tasa: 3.50/100,
Unión
número de meses: 300)

afirmar script.run() == 926.1536089487843

Después de definir nuestra fórmula, la analizamos conGroovyShell.parsepara recuperar una instancia de


Guion.Luego establecemos las variables del enlace del script para nuestras tres variantes.
capaces. Tenga en cuenta cómo podemos acortarscript.binding.someVariableascript.alguna-
VariableporqueGuionimplementosGroovyObjetoy anula suestablecer propiedad
método. Una vez establecidas las variables, llamamos alcorrermétodo, que ejecuta el
script y devuelve el valor de la última declaración: el pago mensual que queríamos
calcular en primer lugar.
Para reutilizar esta fórmula sin tener que volver a compilarla, podemos reutilizar la instancia
del script y llamarlo con otro conjunto de valores definiendo un nuevo enlace, en lugar de
modificar el enlace original como en la primera ejecución.

www.it-ebooks.info
Evaluación de expresiones y scripts con GroovyShell 571

16.2.5 Ejecutar scripts o clases


loscorrermétodos deGroovyShellpuede ejecutar scripts y clases. Cuando se analiza una clase y se
reconoce que se extiendeGroovyTestCase,un corredor de prueba de texto ejecutará el caso de
prueba.
Tres de las variantes comúnmente utilizadas delcorrerlas firmas de métodos pueden tomar un
Cuerda,aExpediente,o unLectorpara leer y ejecutar el script o la clase, un nombre para el script
y una matriz deCuerdas para los argumentos:

ejecutar(String scriptText, String scriptName, String[] args) run(File scriptFile,


String[] args)
ejecutar (Lector de entrada, String scriptName, String [] args)

Pero existen otras variantes que leen el script desde una URI o toman los parámetros como una
lista en lugar de una matriz. Consulte el GroovyDoc paraGroovyShellpara más detalles.
la ejecución decorreres un poco diferente a la deevaluar. evaluarevalúa solo scripts, pero
corrertambién puede ejecutar clases con unprincipalmétodos y pruebas unitarias. Se aplican
las siguientes reglas:
- Si la clase a ejecutar tiene unmain(Objeto[] argumentos)oprincipal(Cadena[] argumentos)método,
se ejecutará. Tenga en cuenta que un script es una clase Java normal que implementaEjecutable
y escorrermétodo es llamado por un implícitoprincipalmétodo.
- Si la clase se extiendeGroovyTestCasoo es una prueba JUnit, entonces un corredor de prueba
JUnit la ejecuta.
- Si la clase implementaejecutable,se instancia con un constructor tomando un
Cuerdamatriz, o un constructor predeterminado, y la clase se ejecuta con sucorrermétodo.

El mecanismo del corredor también es extensible, por lo que módulos como el módulo groovy-testng
definen su propio corredor.

16.2.6 Parametrización adicional de GroovyShell


usamos elUniónclass para pasar variables a los scripts y recuperar variables modificadas o
nuevas definidas durante la evaluación del script. Podemos configurar aún más nuestro
GroovyShellinstancia pasando otros dos objetos en el constructor: un padreClase-
Cargadory/o unConfiguración del compilador.
Como referencia, aquí están las firmas de constructores disponibles enGroovyShell:

GroovyShell()
GroovyShell(Binding vinculante)
GroovyShell(CompilerConfiguration configuración)

GroovyShell(Binding binding, CompilerConfiguration config)


GroovyShell(ClassLoader padre)
GroovyShell(padre de ClassLoader, enlace vinculante) GroovyShell(padre de
ClassLoader, configuración de CompilerConfiguration) GroovyShell(padre de
ClassLoader,
Unión vinculante,
CompilerConfiguration config)

www.it-ebooks.info
572 CPASADOdieciséisIntegrando Groovy

Cargador de clases A
Padre Padre

Cargador de clases B Cargador de clases C


Figura 16.2 Árbol
estructura del cargador de clases

Mientras usa elUniónes fácil y autodescriptivo, entendiendo cómo Groovy maneja los
cargadores de clases, en particular cuando crea una instanciaGroovyShellcomo aquí, es muy
importante. Podría terminar creando una pérdida de memoria sin una comprensión
adecuada de la mecánica.

CELEGIR UN CARGADOR DE CLASES PARA PADRES


Groovy usa cargadores de clases para cargar clases de Groovy. La consecuencia es que debe tener una
comprensión mínima de cómo funcionan los cargadores de clases al integrar Groovy. Por desgracia,
dominar los cargadores de clases no es la tarea más trivial en el viaje de un desarrollador de Java. Cuando
trabaja con bibliotecas que generan clases o proxies dinámicos en tiempo de ejecución con
instrumentación de código de bytes, o con una jerarquía compleja de cargadores de clases para hacer
que el código crítico se ejecute de forma aislada en un espacio aislado seguro, la tarea se vuelve aún más
complicada. Es importante comprender cómo se estructura la jerarquía de los cargadores de clases.
Un caso de uso común se representa en la figura 16.2.
Una clase cargada por el cargador de clases B no puede ser vista por el cargador de clases C. La forma
estándar en que los cargadores de clases cargan clases es preguntando primero al cargador de clases principal si
conoce la clase, antes de intentar cargar la clase. Las clases se buscan navegando hacia arriba en la jerarquía del
cargador de clases, pero una clase cargada por C no podrá ver una clase cargada por B, porque B no es un padre
de C. Afortunadamente, al establecer inteligentemente el cargador de clases padre de C para ser B, el problema
está resuelto, como se muestra en la figura 16.3. Esto se puede hacer usandoGroovy-Shell's constructores, que le
permiten definir un cargador de clases principal para los scripts que se evalúan.

Cargador de clases A
Padre

Cargador de clases B
Padre

Cargador de clases C
Figura 16.3 Lineal
estructura del cargador de clases

www.it-ebooks.info
Evaluación de expresiones y scripts con GroovyShell 573

Para especificarGroovyShell's classloader, especifique el classloader principal para aplanar su jerarquía de la


siguiente manera:

def parentClassLoader = objectFromB.classloader def shellForC =


new GroovyShell(parentClassLoader)

Si tiene problemas con el cargador de clases, obtendrá unClassNotFoundExceptiono, peor aún, un


No Error Clase Def Encontrado.Para depurar estos problemas, lo mejor que puede hacer es imprimir el cargador de

clases para todas las clases afectadas e imprimir el cargador de clases principal de cada cargador de clases, y así
sucesivamente hasta la raíz de todos los cargadores de clases. Entonces tendrá una buena imagen de toda la
jerarquía de cargadores de clases en su aplicación, y el paso final será configurar los cargadores de clases
principales en consecuencia para aplanar la jerarquía. Aún mejor, intente hacer que las clases sean cargadas por
los mismos cargadores de clases si es posible.

CCONFIGURAR LA COMPILACIÓN
En la lista de constructores de laGroovyShellclase, habrás notado laConfiguración del
compiladorparámetro. Una instancia de esta clase se puede pasar aGroovyShellpara
personalizar las opciones del proceso de compilación.
Sin estudiar todas las opciones disponibles, repasemos algunas de las más útiles, como
se muestra en la tabla 16.1. Algunas funciones más se tratan en la sección 16.7.

Tabla 16.1 Algunos métodos útiles enCompiladorConfiguración

Firma del método Descripción

establecerClasspath Defina su propia ruta de clase utilizada para buscar clases, lo


(ruta de la cadena) que le permite restringir la ruta de clase de la aplicación y/o
mejorarla con otras bibliotecas

establecerDepurar Ajustado averdaderopara obtener seguimientos de pila completos y sin filtrar

(depuración booleana) cuando se escriben excepciones en el flujo de error

establecerSalida Establecer el escritor en el que se imprimirán los errores de

(Escritor de impresión escritor) compilación

setScriptBaseClass Definir una subclase deGuioncomo clase base para


(Estruendo de cuerdas) instancias de script

setSourceEncoding Establezca la codificación de los scripts para evaluar, lo cual es


(Cadena codificada) importante cuando se analizan scripts de archivos o flujos de entrada
que usan una codificación diferente a la predeterminada de la
plataforma.

setRecompileGroovySource Ajustado averdaderopara volver a cargar las fuentes de Groovy que han

(booleano b) cambiado después de haber sido compiladas; de forma predeterminada, esta

marca está configurada enfalso

setMinimumRecompilationInterval (int Establezca la cantidad mínima de tiempo de espera


milisegundos) antes de verificar si las fuentes son más recientes que
las clases compiladas

www.it-ebooks.info
574 CPASADOdieciséisIntegrando Groovy

De estos métodos,setScriptBaseClasses particularmente digno de mención. Si desea que todos sus


scripts compartan un conjunto común de métodos, puede especificar una clase base que se
extiendaGroovy.lang.Scriptque alojará estos métodos y luego estará disponible dentro de los scripts.
Compartir métodos entre scripts es una buena técnica para inyectar enlaces a sus propios servicios
de marco. Consideremos una clase de script base que se extiendeGuiony su función será la de
inyectar una función de multiplicación global7en todos los guiones evaluados por
GroovyShell:
clase abstracta BaseScript extiende Script {
def multiplicar(a, b) { a * b }
}

guion baseextiendeGuion,que es una clase abstracta, por lo que la clase debe declararse
abstracta, porque elcorrerEl método es abstracto. Al compilar o interpretar scripts,
Groovy ampliará este script base e inyectará las declaraciones del script en elcorrer
método.
Para hacer de esta clase la clase base de sus scripts, ahora necesita pasar unorg.code-
haus.groovy.control.CompilerConfigurationinstancia aGroovyShell's construcción-
tor, como se explica en el siguiente ejemplo de Groovy:

definitivamenteconferencia=nueva configuración del


compilador () conferencia
.setScriptBaseClass("BaseScript") def shell = new
GroovyShell(conferencia) valor def = shell.evaluate('''
multiplicar(5, 6)
''')
afirmar valor == 30

Esta no es la única forma de inyectar funciones en todos sus scripts. Otro truco para
compartir funciones entre scripts es almacenar cierres en el enlace deGroovyShellsin
necesidad de usarConfiguración del compilador.Esto se puede ver en el siguiente listado.

Listado 16.9 Usando elUniónpara compartir funciones entre scripts

def enlace = nuevo enlace (multiplicar: { a, b -> a * b }) def shell = nuevo


Crea cierre
GroovyShell (enlace) dentro de enlace
valor def = shell.evaluate('''
multiplicar(5, 6)
Cierre de llamadas como
''') un método normal
afirmar valor == 30

También debe poder escribir el mismo código en Java, por lo que debemos poder crear
cierres y ponerlos en el enlace. Desde Java, crear un cierre no es tan sencillo como en
Groovy. Debe crear una clase que derive degroovy.lang.Cierree implementar unObjeto
doCall (argumentos de objeto)método. Una técnica alternativa es

7
La multiplicación es fácil de demostrar en un libro, pero los ejemplos del mundo real pueden incluir el manejo de recursos
transaccionales, la configuración y el registro.

www.it-ebooks.info
Uso del motor de secuencias de comandos Groovy 575

crear una instancia deorg.codehaus.groovy.runtime.MethodClosure,que eli-


abre la llamada a un método de multiplicación en un personalizadomultiplicadorinstancia de clase:

//Java
MétodoCierremclos=new MethodClosure(multiplicador, "multiplicar"); Vinculación
vinculación = nueva vinculación ();
vinculante.setVariable("multiplicar",mclos); shell GroovyShell
= nuevo GroovyShell (enlace); shell.evaluar("multiplicar(5, 6)");

Ahora hemos cubierto completamente cómoGroovyShellse puede operar tanto desde Java como desde Groovy
para extender su aplicación.GroovyShelles una buena clase de utilidad para crear puntos de extensión en su
propio código y ejecutar lógica que se puede externalizar en scripts almacenados como cadenas, en el sistema de
archivos o en una base de datos. Esta clase es excelente para evaluar, analizar o ejecutar secuencias de
comandos que representan una unidad de trabajo única e independiente, pero es menos fácil de usar cuando la
lógica se distribuye entre secuencias de comandos dependientes. Aquí es donde
laGroovyScriptEngineyGroovyClassLoaderpoder ayudar. Estos son los temas de
las siguientes dos secciones.

16.3 Uso del motor de secuencias de comandos Groovy

losGroovyShellLa clase es ideal para secuencias de comandos independientes y aisladas, pero puede ser
menos fácil de usar cuando las secuencias de comandos dependen unas de otras. La solución más simple
en ese punto es usarGroovyScriptEngine.Esta clase también brinda la capacidad de recargar scripts a
medida que cambian, lo que permite que su aplicación admita modificaciones en vivo de su lógica
comercial. Cubriremos los usos básicos del motor de secuencias de comandos y le mostraremos cómo
decirle al motor dónde encontrar secuencias de comandos.

16.3.1 Puesta a punto del motor

El motor de secuencias de comandos tiene varios constructores para elegir cuando lo instancia. Puede
pasar diferentes argumentos a estos constructores, como una serie de rutas o direcciones URL en las que
el motor intentará encontrar los scripts de Groovy, un cargador de clases que se usará como cargador de
clases principal o un programa especial.conector de recursosque proporcionaURLConexións. En nuestros
ejemplos, asumiremos que estamos cargando y ejecutando scripts desde el sistema de archivos:

motor def = nuevo GroovyScriptEngine(".")

o con una matriz de URL o cadenas que representan URL:

motor def = nuevo GroovyScriptEngine([".", "../carpeta "])

El motor asume que las cadenas representan ubicaciones del sistema de archivos. Si sus scripts se van a
cargar desde un lugar que no sea el sistema de archivos, debe usar URL en su lugar:

motor def = nuevo GroovyScriptEngine(


["archivo://.", "http://algunUrl"]*.toURL() como URL[])

www.it-ebooks.info
576 CPASADOdieciséisIntegrando Groovy

El motor buscará el recurso siguiendo cada URL secuencialmente hasta que encuentre
el script.
Los constructores también pueden tomar un cargador de clases, que luego será utilizado por el motor para
el cargador de clases principal de las clases compiladas:

motor def = nuevo GroovyScriptEngine("., parentCL)

El cargador de clases principal también se puede definir con elsetParentClassLoadermétodo.


Una vez que haya creado una instancia del motor, eventualmente puede ejecutar sus scripts.

16.3.2 Ejecución de secuencias de comandos

Para ejecutar un script, el mecanismo principal es elcorrermétodo deGroovyScriptEngine. Este método


toma dos argumentos: el nombre de la secuencia de comandos para ejecutar como la ruta relativa del
archivo y el enlace para almacenar las variables que la secuencia de comandos necesitará para operar. El
método también devuelve el valor de la última expresión evaluada por el script, como
GroovyShelllo hace.
Si tiene la intención de ejecutar un archivo llamado MyScript.groovy situado en elpruebacarpeta
relativa al directorio actual, puede ejecutarlo como se muestra aquí:

motor def = nuevo GroovyScriptEngine(".")


valor def = motor.correr("prueba/MyScript.groovy", nueva vinculación())

El motor almacena en caché automáticamente los scripts cargados y se actualizan cada vez que se
actualiza el recurso. El motor también puede cargar clases de script directamente con elcargar-
ScriptByNamemétodo; devuelve unClaseobjeto que representa la clase del script, que es una clase
derivada demaravilloso.lang.Script.Sin embargo, hay una trampa a tener en cuenta con este método.
Toma una secuencia de comandos con una notación de nombre de clase completamente calificada en
lugar de la ruta relativa del archivo:

motor def = nuevo GroovyScriptEngine(".")


def clazz = motor.cargarScriptPorNombre("prueba.MyScript")

Este ejemplo devuelve la clase del script myScript.groovy situado en la carpeta de prueba. Si
no está usando el sistema de archivos, estará usando URL en lugar de archivos, y en ese caso
es obligatorio usar un conector de recursos especial que es responsable de cargar los
recursos.

16.3.3 Definición de un conector de recursos diferente

Si desea cargar secuencias de comandos desde una ubicación en particular, es posible que desee
proporcionar su propio conector de recursos. Esto se hace pasándolo como argumento al
constructor deGroovyScript Engine,ya sea con o sin la especificación de un cargador de clases
principal. El siguiente ejemplo muestra ambos métodos sobrecargados:

def myResourceConnector = getResourceConnector()


motor def = nuevo GroovyScriptEngine(myResourceConnector) def engine2 = nuevo
GroovyScriptEngine(myResourceConnector, principal)

www.it-ebooks.info
Trabajando con GroovyClassLoader 577

Para implementar su propio conector, debe crear una clase que implemente el
Groovy.util.ResourceConnectorinterfaz, que contiene un solo método:

URLConnection pública getResourceConnection(nombre de la cadena)


lanza ResourceException;

losgetResourceConnectionEl método toma un parámetro de cadena que representa el


nombre del recurso a cargar y devuelve una instancia deURLConexión.Si también está
creando su propiaURLConexión,al menos tres métodos deben implementarse
correctamente (potencialmente podría dejar de lado los otros y lanzar unno soportado-
OperationExceptionoexcepción de servicio desconocido,como algunas clases JDK del
java.netpaquete hacer):

público largo getLastModified()


público URL obtenerURL()
InputStream público getInputStream() lanza IOException

Aunque generalmente almacenará su script en el sistema de archivos o dentro de una base de datos,
implementando su propioconector de recursosyURLConexiónle permite controlar los scripts que provienen
de cualquier ubicación: desde una base de datos, un sistema de archivos remoto, un documento XML o
un almacén de datos de objetos.
GroovyScriptEnginees perfecto para manejar scripts, pero se queda corto para la
manipulación más compleja de clases. De hecho, ambosGroovyShellyGroovyScriptEngine
confiar en un único mecanismo para cargar scripts o clases: elGroovyClassLoader.Este cargador de clases
especial es lo que discutiremos a continuación.

16.4 Trabajar con GroovyClassLoader


losGroovyClassLoaderes la navaja suiza con todas las herramientas posibles para integrar Groovy
en una aplicación, ya sea explícitamente o mediante clases comoGroovyShell.Esta clase es un
cargador de clases personalizado, que puede definir y analizar clases y scripts de Groovy como
clases normales que se pueden usar desde Groovy o desde Java. También es capaz de compilar
todas las clases requeridas y dependientes.
Esta sección lo guiará a través de cómo usar elGroovyClassLoader,desde los usos más simples
hasta las situaciones más complicadas. Examinamos cómo sortear los problemas de dependencia
circular, cómo cargar scripts que están almacenados fuera del sistema de archivos local y cómo
hacer que su entorno de integración sea seguro y aislado, permitiendo que los scripts realicen solo
las operaciones que desea permitir.

16.4.1 Análisis y carga de clases de Groovy


Digamos que tienes una clase Groovy simpleHolacomo el siguiente:

clase hola {
def saludo() { "¡Hola!" }
}

www.it-ebooks.info
578 CPASADOdieciséisIntegrando Groovy

Desea analizar y cargar esta clase con elGroovyClassLoader.En Groovy, puedes


hacerlo así:

gcl = nuevo GroovyClassLoader()


definitivamente

ClaseclaseSaludo = gcl.parseClass(archivo nuevo("Hola.groovy")) afirmar "¡Hola!" ==


claseSaludo.nuevaInstancia().saludo()

Instanciando GroovyClassLoader
En el ejemplo, usamos el constructor predeterminado. Pero esta clase ofrece más
constructores.GroovyClassLoader (cargador de ClassLoader)le permite definir un cargador
de clases principal para evitar problemas con una jerarquía compleja, como se explica en la
sección sobreGroovyShell.el constructorGroovyClassLoader (cargador de ClassLoader,
configuración de configuración del compilador)le da más control sobre el comportamiento
del cargador de clases, como se explica en la sección sobreGroovyShell,gracias a la
parametrización deConfiguración del compilador.

una instancia deGroovyClassLoaderse crea, y suparseClassSe llama al método y se pasa el


archivo Hello.groovy. El método devuelve unClaseobjeto que luego puede ser instanciado
usandoClase'snueva instanciamétodo, que invoca el constructor por defecto deHola.Una vez
Holaestá instanciado, porque Groovy admite la tipificación pato (sección 3.2.4), puede llamar
directamente alsaludométodo definido enHola.Pero en un lenguaje fuertemente tipado, no
podría llamar directamente al método. Entonces, desde Java, para invocar un método, debe
usar la reflexión explícitamente, lo que generalmente es bastante feo, o confiar en el hecho
de que todas las clases de Groovy implementan automáticamente el método.maravilloso
. lang.GroovyObjectinterfaz, exponiendo elmétodo de invocación, obtener propiedad,yconjunto-
Propiedadmétodos.
DóndeobtenerPropiedadyestablecer propiedadson responsables de acceder a las propiedades de su
clase Groovy desde Java,método de invocaciónle permite llamar a cualquier método en las clases de Groovy
fácilmente desde Java:

//Java
GroovyClassLoader gcl = nuevo GroovyClassLoader();
Class greetingClass = gcl.parseClass(nuevo archivo("Hola.groovy")); GroovyObjeto
hola = (GroovyObject) greetingClass.newInstance();
Objeto[] argumentos = {};
afirmar "¡Hola!".equals(hola.método de invocación("saludo", argumentos));

losmétodo de invocaciónEl método toma dos parámetros: el nombre del método a llamar y
uno que corresponde a los parámetros para pasar al método que está intentando llamar. Si el
método toma solo un parámetro, páselo directamente como argumento; si se esperan varios
parámetros, deben envolverse dentro de una matriz deObjetos, que se convierte en el
argumento. Si desea llamar a un método que agrega dos objetos junto con una firma como
agregar (a, b),lo llamas así:

a.invokeMethod("agregar", nuevo Objeto[] {obj1, obj2}); //Java

www.it-ebooks.info
Trabajando con GroovyClassLoader 579

Pero si un método al que desea llamar requiere una matriz como parámetro único, también debe
envolverlo dentro de una matriz:

a.invokeMethod("takesAnArray", new Object[] {anArray}); //Java

A pesar de que es posible llamar a cualquier método en una clase Groovy desde Java con
método de invocación,hacerlo no es compatible con Java porque el compilador de Java no sabrá que
existen estas clases y no le permitirá usar elsaludométodo directamente, a menos que haya
precompilado sus clases de Groovy y las haya empaquetado dentro de un archivo JAR.
Afortunadamente, hay una solución para eludir esta deficiencia dejavac.Para que Java comprenda
sus clases de Groovy, tanto Groovy como Java tienen que encontrar un terreno común de acuerdo.
Esto es lo que llamamos el problema del huevo y la gallina.

16.4.2 El problema de la dependencia del huevo y la gallina

Las versiones anteriores de Groovy hacían bastante difícil resolver el problema del huevo y la
gallina. Si tenía una clase de Java que usaba una clase de Groovy, que a su vez usaba una clase de
Java definida en el mismo proyecto, tenía un problema. Arreglar esto implicó la refactorización del
código, como la introducción de interfaces para eliminar las dependencias cíclicas. La buena noticia
es que esto ya no es un problema, gracias acompilación conjunta. La compilación conjunta es el
proceso de compilar clases de Java y Groovy en un solo paso aparente, solo usando elmaravilloso
dominio.
Para ilustrar, consideremos la aplicación Java en la siguiente lista.

Listado 16.10 Una aplicación mixta Java/Groovy

//Java
clase pública ShapeInfoMain {
public static void main(String[] args) {
Cuadrado s = nuevo Cuadrado(7);
Circulo c = nuevo Circulo(4); nuevo
MaxAreaInfo().displayInfo(s, c);
nuevo MaxPerimeterInfo().displayInfo(s, C);
}
}

Supongamos que elCuadradoyMaxPerimeterInfoLas clases están escritas en Java y el


CirculoyMaxAreaInfolas clases están escritas en Groovy. Podríamos tener la tentación de intentar
usarjavacen todos los archivos fuente *.java seguido demaravillosoen todos los archivos *.groovy.
Pero esto no funcionará porque elmostrarInfométodo enMaxPerimeterInforequiere
Circulopara ser compilado primero. Tampoco podemos cambiar el orden porque tendremos
el problema inverso conMaxAreaInfosiCuadradono se compila primero.
La solución es bastante simple. en lugar de llamarjavacpor ti mismo, deja que Groovy lo haga
por ti, usando el –jopción:

maravilloso -j *.java *.groovy

www.it-ebooks.info
580 CPASADOdieciséisIntegrando Groovy

Internamente, el compilador Groovy:


- Genere stubs de Java para los archivos Groovy
- Compile las fuentes de Java y los stubs usandojavac
- Compilar las clases de Groovy
La generación de stubs, que son ficheros fuente Java correspondientes a la “API pública” de
las fuentes Groovy, permite lajavaccompilador para compilar correctamente las clases, y el
compilador Groovy reemplazará esos stubs con los archivos Groovy reales en el segundo
paso. Tenga en cuenta que el uso de -jLa opción es muy importante: si la olvida, los archivos
fuente de Java se compilarán como si estuvieran escritos en Groovy.

16.4.3 Proporcionar un cargador de recursos personalizado

losGroovyClassLoadertiene varios métodos que le permiten analizar y cargar clases de Groovy desde diferentes
orígenes: desde un archivo, desde un flujo de entrada o desde una cadena. Estos son algunos de los métodos
que se pueden usar cuando se le pide explícitamente al cargador de clases que cargue una clase determinada:

clase pública parseClass (archivo de archivo)


lanza CompilationFailedException
clase pública parseClass (texto de cadena, nombre de archivo de cadena)
lanza CompilationFailedException
Clase pública parseClass (InputStream in, String fileName)
lanza CompilationFailedException

Si está almacenando sus fuentes en una base de datos, una posible solución es recuperarlas como un
Cuerdao como unFlujo de entrada.Luego, puede usar el cargador de clasesparseClassmétodos
para analizar y cargar sus clases. Pero en lugar de implementar explícitamente la plomería y
la búsqueda y analizarlo usted mismo, Groovy proporciona una mejor solución, en forma de
unGroovy.lang.GroovyResourceLoader.El cargador de recursos es una interfaz que debe
implementar para especificar dónde se encuentran sus fuentes: déle un nombre de un
recurso y se devolverá una URL que apunta a la ubicación del recurso. Esto se hace mediante
un único método desde esa interfaz:

URL loadGroovySource (nombre de archivo de cadena) lanza MalformedURLException

Una implementación del cargador de recursos en Java se parecerá a la siguiente


clase:
clase pública MyResourceLoader extiende GroovyResourceLoader {
URL pública loadGroovySource (nombre de archivo de cadena final)
lanza MalformedURLException {
URL url = ... // crea la URL que apunta a la url de retorno del recurso;

}
}
PROPINAComo fue el caso conGroovyScript Engine,si estás creando el tuyo propio
URLyURLConexiónclases derivadas, asegúrese de que suURLanula su
conexión abiertamétodo, que devuelve una instancia deURLConexión;y
asegúrese de anular también elobtenerÚltimaModificación, obtenerURL,y
getInput-Streammétodos de la devoluciónURLConexión.

www.it-ebooks.info
Trabajando con GroovyClassLoader 581

Una vez que haya definido esta clase, debe registrarla en su cargador de clases antes de usarla de esta
manera:

GroovyClassLoader gcl = nuevo GroovyClassLoader();


gcl.setResourceLoader(nuevo MiResourceLoader());

¡Su cargador de clases ahora usará su cargador de recursos para encontrar los recursos que
necesita desde donde quiera! En este punto, puede encontrar que tiene menos control del que le
gustaría sobre qué código se ejecuta. Es posible que deba bloquear cuánto acceso tiene el código
al resto del sistema, dependiendo de cuánto sepa sobre los orígenes del código. Aquí es donde
entra en juego el modelo de seguridad de Java y Groovy, como verá en la siguiente sección.

16.4.4 Jugando a lo seguro en una caja de arena protegida

Al empaquetar una aplicación, sabe que todo su código fuente es de confianza. Cuando abre las puertas a
un código dinámico que puede evolucionar con el tiempo, como cambiar las reglas comerciales debido a
un cambio en la legislación, debe asegurarse de que también se pueda confiar en este código. Solo los
usuarios de confianza deberían poder cambiar el código dinámico iniciando sesión y proporcionando las
credenciales pertinentes. Pero incluso con la autenticación y la autorización vigentes, nunca estará
protegido contra los errores humanos. Es por eso que Groovy proporciona un segundo nivel de confianza
en el código dinámico en forma de un espacio aislado seguro que puede configurar para cargar este
código externo.
Modificar, cargar y ejecutar código dinámico en tiempo de ejecución es una buena manera de
extender su aplicación de manera ágil, reduciendo el tiempo requerido para adaptarla según sea
necesario. Los largos y tediosos escenarios de reempaquetado, recalificación y redistribución pueden
desaparecer en poco tiempo. Este no es un tema para tomar a la ligera y, por supuesto, siempre tendrás
que entregar tu aplicación al equipo de aceptación y pasar las pruebas de integración pertinentes; pero
incrustar código de un lenguaje de secuencias de comandos en su aplicación puede ayudarlo a ser más
versátil cuando los requisitos cambian.

TÉLjMODELO DE SEGURIDAD AVA


Por muy interesante que pueda ser incorporar un lenguaje de secuencias de comandos o dinámico, y por
muy bien diseñado que esté su sistema en términos de seguridad, puede agregar potencialmente otra
capa de confianza al permitir que este código se ejecute en un espacio aislado seguro. Java proporciona la
infraestructura para asegurar el código fuente a través de su modelo de seguridad con la ayuda de un
gerente de seguridady los asociadospolíticaque dicta qué permisos se otorgan al código. Para ver un
ejemplo simple del daño que puede ocurrirle a su aplicación, imagine que un usuario carga un script que
contieneSistema.salir(1):¡Todo su sistema podría colapsar en un segundo si no está asegurado
correctamente! Afortunadamente, con alguna configuración, es posible protegerse de ese código
malicioso.

NOTACubrir todo el modelo de seguridad de Java con sus administradores de seguridad, permisos y

archivos de políticas está más allá del alcance de este capítulo. Suponemos que está familiarizado con
estos conceptos. De lo contrario, recomendamos los recursos en línea proporcionados en el sitio web
de Oracle para obtener una visión detallada de cómo funciona la seguridad en la plataforma Java.

www.it-ebooks.info
582 CPASADOdieciséisIntegrando Groovy

En el modelo de seguridad de Java, se otorgan permisos a las fuentes de código de acuerdo con su código
fuente. Una fuente de código se compone de una base de código en forma de URL desde la cual el
cargador de clases cargó el código fuente y, potencialmente, un certificado que se usa para verificar el
código cuando se obtiene de un archivo JAR firmado.
Hay dos casos que tienes que considerar. Si todas sus fuentes de Groovy se compilan primero en
archivos .class y finalmente se agrupan en un archivo JAR, se aplican los mecanismos de seguridad
estándar. Esas clases son como fuentes normales compiladas de Java, por lo que siempre puede usar los
mismos administradores de seguridad que de costumbre. Pero cuando estás compilando fuentes de
Groovy sobre la marcha, a través de la integración significa que has estudiado hasta ahora, se deben
seguir pasos adicionales.

GRAMOROOVYCODASOURCE Y EL RESPONSABLE DE SEGURIDAD


Cuando los scripts y las clases se cargan desde el sistema de archivos, se cargan mediante un
Groovy-ClassLoader,que busca archivos Groovy en el classpath y les proporciona un código fuente
construido a partir de un código base creado a partir de la URL del archivo fuente. Cuando las
fuentes de Groovy se cargan desde un flujo de entrada o desde una cadena, no se les asocia
ninguna URL en particular. Es posible asociar un código base con fuentes de Groovy para
compilarlo especificando unGroovyCodeSource—siempre que las fuentes de carga de la persona
que llama tengan permiso para especificar el código base. No es necesario que el código base que
asocie con las fuentes se refiera a una ubicación física real. Su importancia es para el administrador
y la política de seguridad, que asignan permisos en función de las URL.
Un ejemplo concreto siempre es mejor que largas explicaciones. Digamos que estamos ejecutando
una aplicación en un servidor, y esta aplicación carga secuencias de comandos de Groovy que deben estar
en un espacio aislado y solo se les debe permitir acceder a laarchivo.codificaciónpropiedad del sistema. La
aplicación del servidor debe tener todos los permisos posibles, pero tenemos que restringir el script
Groovy cargado que lee la propiedad. Escribimos un archivo de políticas indicando explícitamente esas
reglas:

conceder codeBase "archivo:${server.home}/classes/-" {


permiso java.security.AllPermission;
};
otorgar codeBase "archivo:/restringido" {
permiso java.util.PropertyPermission "archivo.codificación", "leer";
};

La primera parte otorga todos los permisos a nuestra aplicación de servidor, la segunda parte solo
permite los scripts delarchivo:/restringidobase de código para acceder a laarchivo.codificación
propiedad en modo de sólo lectura. Este archivo de política debe estar disponible en el classpath
de la aplicación y la propiedad del sistemajava.politica.de.seguridadla definición del archivo de
política que se utilizará debe especificarse en la línea de comandos que inicia la JVM o en el código.

Un script que solicite leer la propiedad del sistema incluiría un código como:

codificación def = System.getProperty("archivo.codificación")

www.it-ebooks.info
Trabajando con GroovyClassLoader 583

Su aplicación de servidor cargará y evaluará el script usandoGroovyShell,usando los métodos


que toman unGroovyCodeFuentepara envolver el script y definir su fuente de código:

guión def = '''


System.getProperty("archivo.codificación")
'''
definitivamentegcs=nuevo GroovyCodeSource(secuencia de comandos, "Nombre de la secuencia de comandos","/
restringido") def caparazón = nuevo GroovyShell()
println shell.evaluate(gcs)

AGroovyCodeFuentese puede construir de varias maneras dependiendo de cómo recupere el código


fuente: desde una cadena, un archivo, un lector o un URI. Estos son los cuatro constructores que le
permiten construir unGroovyCodeFuente:

public GroovyCodeSource(String script, String name, String codeBase) public


GroovyCodeSource(Reader in, String name, String codeBase) public GroovyCodeSource(File
file) lanza FileNotFoundException public GroovyCodeSource(URI uri) lanza IOException

Para que la aplicación que llama pueda crear unGroovyCodeFuentecon un código base
específico, la política debe otorgarle permiso. El permiso específico requerido
es unGroovy.security.GroovyCodeSourcePermission,cuál es la aplicación de llamada
tiene implícitamente porque el archivo de política le otorgó lajava.seguridad.Todos los permisos, que otorga
todos los derechos posibles.

GRAMOROOVYSINFIERNO YGRAMOROOVYCMUCHACHALODER CONGRAMOROOVYCODASFUENTE


Ambas cosasGroovyShellyGroovyClassLoaderte permite especificarGroovyCodeFuentes
para envolver scripts o clases que deben estar protegidos, peroGroovyScriptEngineno en el momento de
escribir. Si el código fuente de Groovy no está envuelto dentro de unfuente de código Groovy, la política no
se aplicará, lo que permitirá que se ejecute código no confiable dentro de la aplicación.
En las secciones relacionadas conGroovyShellyGroovyClassLoader,enumeramos varios métodos
que le permiten evaluar, analizar o ejecutar scripts y clases de Groovy. Mencionemos ahora los
métodos que toman unfuente de código Groovy,que puede usar para hacer que la integración de
código dinámico sea más segura.
GroovyShelltiene dos métodos que toman unfuente de código Groovy,uno para
evaluar scripts y el otro para analizar scripts:

evaluar objeto público (GroovyCodeSource codeSource)


lanza CompilationFailedException análisis de script público
(GroovyCodeSource codeSource)
lanza CompilationFailedException

GroovyClassLoadertambién tiene dos métodos; ambas clases de análisis, pero el último también proporciona una
opción para controlar si la clase analizada debe colocarse en la memoria caché del cargador de clases:

Clase pública parseClass (GroovyCodeSource codeSource)


lanza CompilationFailedException
Clase pública parseClass(GroovyCodeSource codeSource,
booleano shouldCache) lanza CompilationFailedException

www.it-ebooks.info
584 CPASADOdieciséisIntegrando Groovy

Armado con diferentes medios para integrar Groovy de forma segura en su aplicación, puede crear
aplicaciones extremadamente flexibles. Por supuesto, esos mecanismos son específicos de Groovy.
Sin embargo, estos no son los únicos medios disponibles. Si está utilizando Spring Framework
como base común para su aplicación, o si está utilizando la compatibilidad con secuencias de
comandos agregada en Java 6 (JSR-223), puede utilizar los mecanismos proporcionados en estas
plataformas para cargar su código dinámico en una manera que facilitaría alejarse de Groovy, si
alguna vez lo desea.8

16.5 Integración de resorte


Como dice en la lata, Spring es un innovador marco de aplicaciones Java Enterprise en capas
y un contenedor liviano inventado por Rod Johnson, que maduró mientras Rod estaba
escribiendo el libro.Experto en diseño y desarrollo J2EE uno a uno(Wiley, 2004). Spring
generalizó los conceptos y patrones deInversión de control(COI) y Inyección de dependencia(
DI) y está construido a partir de dos bloques de construcción principales: su contenedor IoC y
su sistema AOP. El marco trae una capa de abstracción adicional que envuelve API comunes
como transacciones, JDBC o Hibernate para ayudar al desarrollador a enfocarse en las tareas
comerciales centrales; da acceso a AOP; e incluso proporciona su propia Modelo-Vista-
Controlador(MVC) tecnología. El marco Spring se puede utilizar como un todo o pieza por
pieza según las necesidades.
Spring le permite conectar los componentes de su aplicación a través de DI instanciando,
configurando y definiendo las relaciones entre sus objetos en un archivo de configuración XML
central (y desde hace poco Groovy), a través de anotaciones o llamadas API. Sus objetos suelen ser
objetos Java simples (POJO), pero también pueden ser objetos Groovy (POGO) porque los objetos
Groovy también son JavaBeans estándar. Esta sección explora cómo puede inyectar dependencias
de Groovy en el modelo de objetos de su aplicación, con opciones para permitir que los beans se
actualicen automáticamente y especificar los cuerpos de los scripts directamente en el archivo de
configuración.
Desde la versión 2.0, Spring Framework admite la integración de beans escritos en varios lenguajes
de secuencias de comandos. Spring es compatible con Groovy y otros lenguajes de secuencias de
comandos probados para JVM. Con este soporte, cualquier cantidad de clases escritas en estos lenguajes
se pueden conectar e inyectar en su aplicación de forma tan transparente como si fueran objetos Java
normales.

NOTAEstá más allá del alcance de esta sección explicar cómo se puede instalar,
usar o configurar Spring. Suponemos que el lector interesado ya está
familiarizado con el marco. Si este no es el caso, los creadores de Spring tienen
documentación en línea completa y detallada en http://spring.io/ docs que
debería ser ideal para descubrir de qué se trata.

Explicaremos cómo puede conectar POGO en Spring, analizaremos la recarga del código fuente de Groovy sobre
la marcha y cubriremos cómo el código fuente de Groovy se puede especificar directamente en el

8
No es que podamos pensar en ninguna razón por la que desee hacerlo, pero nos gusta el principio de evitar el bloqueo de proveedores siempre que sea
posible.

www.it-ebooks.info
Integración de primavera 585

XML/> archivo de configuración


BeanFactory
Producción
plan

especifica Crea

Figura 16.4 PrimaveraBeanFactorylee un archivo de configuración XML y crea instancias de


JavaBeans y GroovyBeans especificados en él.

archivo de configuración, en su caso. Comencemos con la situación más simple antes de


avanzar hacia escenarios más complicados.

16.5.1 Cableado de GroovyBeans

Tomemos como ejemplo las clases de información de forma de la sección 16.4.


Vamos a usar la fábrica de frijoles de Spring para crear los objetos Groovy que necesita nuestro programa
principal. Todas las definiciones de nuestra clase se capturan declarativamente en un archivo de configuración de
Spring, a veces denominado comoarchivo XML de cableado. Esto se ilustra en la figura 16.4.
Normalmente conectamos las clases Java y Groovy en el archivo de conexión e indicamos las
dependencias entre las diferentes partes de nuestro sistema en este archivo. En este caso, sin
embargo, vamos a mantenerlo simple. Vamos a especificar definiciones simples en el archivo para
ilustrar la integración entre Spring y Groovy. Por ahora, asumimos que todos nuestros archivos
Groovy están precompilados.
Así es como se ve el archivo de definición de Spring, llamado beans.xml en nuestro caso:

<?xml versión="1.0" codificación="UTF-8"?>


<frijoles>
<frijol identificación = "círculo" class="primavera.groovy.círculo">
<constructor-arg valor="4"/>
<propiedad nombre="color" valor="Negro"/>
</frijol>
<bean id="maxareainfo" class="spring.groovy.MaxAreaInfo"/> </beans>

www.it-ebooks.info
586 CPASADOdieciséisIntegrando Groovy

En nuestro archivo fuente de Groovy, tenemos el mismo constructor que teníamos anteriormente,
y hemos agregado uncolorpropiedad a nuestroCirculoclase. En el archivo de definición de Spring, el
anidadoconstructorindica el valor a pasar al constructor durante la creación de nuestroCirculo.los
propiedadelemento indica que elcolorLa propiedad también debe establecerse como parte de la
inicialización. Para hacer uso de estas definiciones, necesitamos cambiar nuestraprincipalmétodo
enShapeInfoPrincipalen el listado 16.10 para convertirse

//Java
probar {
ApplicationContext ctx =
nuevo ClassPathXmlApplicationContext("beans.xml"); Forma s =
nuevo Cuadrado (7);
Forma c = (Forma) ctx.getBean("círculo");
información de ShapeInfo = (ShapeInfo) ctx.getBean("maxareainfo");
info.displayInfo(s, c);
nuevo MaxPerimeterInfo().displayInfo(s, c); } captura
(Excepción e) {
e.printStackTrace();
}

Spring proporciona una serie de mecanismos para crear beans para usted. En este caso, usamos lo
que se llama elcontexto de aplicación. Tiene unobtenerBeanmétodo que nos permite pedir un bean
por su nombre.
Como mencionamos anteriormente, asumimos aquí que todas nuestras clases de Groovy están
precompiladas. Entonces, ¿qué hemos ganado? Hemos comenzado el proceso de eliminación de
dependencias explícitas de nuestro código base. Con el tiempo, podríamos comenzar a mover más
información de dependencia al archivo de cableado y permitir que nuestro sistema se configure más
fácilmente. Como consecuencia, nuestro diseño también se vuelve más flexible, porque podemos cambiar
fácilmente nuestras implementaciones concretas. Esto es particularmente importante para las pruebas
unitarias, donde podríamos reemplazar implementaciones concretas con implementaciones simuladas.
Sin embargo, podemos hacer más: Spring admite la compilación dinámica de nuestros scripts Groovy
a través de una clase especial de fábrica Groovy. Así es como lo usaríamos. Ampliaríamos nuestro archivo
de configuración de bean de la siguiente manera:


<lang:id maravilloso="maxareainfo2"
script-source="classpath:MaxAreaInfo.groovy"> <lang:property
name="prefix" value="Live Groovy dice" /> </lang:groovy>

Spring 2.0 y versiones posteriores admiten una serie de lenguajes de secuencias de comandos dinámicos a través
de fábricas especiales específicas del lenguaje. el espacio de nombresidioma: maravillosoaccede
automáticamente a la fábrica Groovy especial. Ahora podemos usarmaxareainfo2como el nombre que le
pasamos a la fábrica de beans cuando creamos nuestro bean, y Spring compilará automáticamente los archivos
fuente de Groovy necesarios.

www.it-ebooks.info
Integración de primavera 587

16.5.2 Frijoles actualizables


Otra característica que proporciona Spring es la capacidad de detectar dinámicamente cuándo cambian los
archivos fuente de Groovy y compilar y cargar automáticamente la última versión de cualquier archivo Groovy
durante el tiempo de ejecución. El concepto se conoce comofrijoles refrescantesy está habilitado en nuestro
archivo de definición usando elactualización-verificación-retrasoatributo de la siguiente manera (en este caso,
configurando el retraso en cinco segundos):


<lang:id maravilloso="maxareainfo2"
refresh-check-delay="5000" script-
source="classpath:MaxAreaInfo.groovy"> <lang:property
name="prefix" value="Live Groovy dice" /> </lang:groovy>

Actualizar beans sobre la marcha puede hacer que el desarrollo sea más rápido, pero debe considerar
deshabilitarlo nuevamente para los sistemas de producción: reiniciar el sistema después de que se haya
realizado un cambio tiende a evitar situaciones confusas en las que durante un período de tiempo (por
breve que sea) solopartedel sistema ha visto la actualización.

16.5.3 Guiones en línea

Aunque podría decirse que es una mala idea poner código dentro del archivo de configuración de Spring, Spring
ofrece otra forma de definir beans con secuencias de comandos alen líneaellos, incluida la fuente directamente
en el archivo de configuración. La documentación de Spring menciona escenarios para tal caso, como dibujar y
definir validadores para controladores Spring MVC o controladores de secuencias de comandos para creación
rápida de prototipos o definición de flujo lógico.
En el siguiente listado, alineamos una variación deMaxAreaInfo (tenemos que cambiar nuestra
fábricaobtenerBeanllamar para usarmaxareainfo3).

Listado 16.11 Configuración de resorte con clase Groovy en línea

<lang:id maravilloso="maxareainfo3">
Le dice a Spring que somos
<lang:inline-script> usando Groovy
importar resorte.forma.común
importar spring.common.ShapeInfo Define el
clase que queremos
clase SufijoMaxAreaInfo implementa ShapeInfo { una instancia de
sufijo de cadena
void displayInfo(Forma s1, Forma s2) {
print "La forma con el área más grande es: "
if (s1.area() > s2.area()) println s1 + ":" + sufijo else println s2 + ":" +
sufijo
}
}
</lang:inline-script>
<lang:propiedad nombre="sufijo" Especifica un
value="¿Lo has adivinado correctamente?"/> propiedad de frijol
</lang:genial>

www.it-ebooks.info
588 CPASADOdieciséisIntegrando Groovy

En este caso, debido a que el contenido está codificado, la configuración del atributo actualizable de la fábrica de
secuencias de comandos no se aplica a esos beans con secuencias de comandos en línea. Una última
observación: si su secuencia de comandos contiene un signo menor que (<), la configuración XML Spring no será
válida, porque el analizador XML pensará que es el comienzo de una nueva etiqueta. Para eludir este problema,
debe envolver todo el bean con script en una sección CDATA.
Esta ha sido una breve introducción a las capacidades del bean de secuencias de comandos del
marco Spring. Para obtener más detalles y explicaciones más detalladas, consulte la
documentación del proyecto en http://spring.io/docs.
Spring no es la única tecnología reciente que adopta las secuencias de comandos. La
siguiente sección anticipa la próxima versión de la plataforma Java y explora qué soporte se
brindará para la integración de Groovy.

16.6 Montando Mustang y JSR-223


Las secuencias de comandos y los lenguajes dinámicos vuelven a estar de moda gracias a Groovy y al
omnipresente JavaScript en todas sus variantes. Este frenesí originalmente llevó a Sun y luego a Oracle a
reconocer que, para ciertas tareas, los lenguajes de secuencias de comandos pueden ayudar a simplificar
el desarrollo de aplicaciones. El Java Community Process ha aceptado nuevos JSR para estandarizar
lenguajes como Groovy, JavaScript y otros para crear una API común que permita el acceso a varios
motores de secuencias de comandos desde sus aplicaciones Java.
Esta sección lo guía a través de la ejecución de scripts de Groovy en la nueva forma de "estándar de
Java", destacando las características de la nueva API, así como algunas formas en las que es
inevitablemente torpe.

16.6.1 Introducción a JSR-223


JSR-223, titulado "Secuencias de comandos para la plataforma Java", proporciona un conjunto de clases e
interfaces que se utilizan para contener y registrar motores de secuencias de comandos y para representar
secuencias de comandos, espacios de nombres de pares clave-valor disponibles para secuencias de comandos o
contextos de ejecución. Ofrece una API elegante y simple que admite algunos lenguajes de secuencias de
comandos, siendo Groovy uno de ellos. Desde Mustang (Java SE 6), el núcleo JSR-223 (javax.script.*)Las clases y un
motor de ejecución para JavaScript se han incluido con el JDK. Esto hace que las secuencias de comandos sean un
ciudadano de primera clase en la JVM. El motor de ejecución de Groovy JSR-223 es uno de los módulos de Groovy
y ya estará en su instalación de Groovy, por lo que debería estar listo para comenzar a usar JSR-223.

Además de incorporar lajavax.script.*interfaces y clases, el JDK distribuye una nueva


herramienta de línea de comandos llamadajrunscriptpara ejecutar scripts, que es un poco como el
propio Groovymaravillosoymaravillosocomandos Así es como se utiliza esta nueva herramienta:

Uso: jrunscript [opciones] [argumentos...] donde


[opciones] incluyen:
- classpath, -cp <ruta> Especificar dónde encontrar archivos de clase de usuario
- D<nombre>=<valor> Establecer una propiedad del sistema
- J<bandera> Pase <flag> directamente al sistema de tiempo de ejecución. Use el
- l <idioma> lenguaje de secuencias de comandos especificado.
- e <script> Evaluar guión dado

www.it-ebooks.info
Montando Mustang y JSR-223 589

- codificación <codificación> Especificar la codificación de caracteres utilizada por los archivos de script.
- f <archivo de script> Evaluar el archivo de script dado.
-f- Modo interactivo, lea el script desde la entrada
estándar
- ayuda, -? Imprime este mensaje de uso y sal
-q Enumere todos los motores de secuencias de comandos disponibles y salga.

Aunque la línea de comando le permite ejecutar Groovy a través de la nueva API sin escribir
ningún código para hacerlo, si su aplicación va a integrar Groovy, usará la API directamente
en lugar de depender de la herramienta. Conozcamos las clases principales involucradas en
la ejecución de scripts a través de JSR-223.

16.6.2 El administrador del motor de secuencias de comandos y sus motores de secuencias de comandos

El principal punto de entrada de la API JSR-223 esjavax.script.ScriptEngineManager.A


comience, cree una instancia de esta clase desde su aplicación Java:

Administrador de ScriptEngineManager = nuevo ScriptEngineManager();

El administrador puede recuperar motores de secuencias de comandos a través de diferentes mecanismos de


búsqueda: por extensión de archivo, por tipo mime o por nombre, con tres métodos dedicados:

ScriptEngine getEngineByExtension(String extension) ScriptEngine


getEngineByMimeType (String mimeType) ScriptEngine
getEngineByName (Cadena nombre corto)

Por lo tanto, si desea recuperar el motor de secuencias de comandos Groovy proporcionado con la
implementación de referencia, puede buscarlo por su nombre:

ScriptEngine gEngine = manager.getEngineByName("groovy");

Con unmotor de secuencias de comandos,puede evaluar expresiones y scripts de Groovy


proporcionados a través de una instancia deLectoro de unCuerdacon el conjunto deevaluarmétodos
que devuelven unObjetocomo resultado de la evaluación. Puede evaluar una expresión simple de la
siguiente manera:

Administrador de ScriptEngineManager = nuevo ScriptEngineManager();


ScriptEngine gEngine = manager.getEngineByName("groovy"); Resultado de
cadena = (String)gEngine.eval("'+-----' * 3 + '+'");

Aquí están los otrosevaluarmétodos disponibles:

Evaluación de objetos (lector lector)


Object eval(Reader reader, Bindings b) Object eval(Reader reader,
ScriptContext context) Object eval(String script)

Evaluación de objeto (secuencia de comandos de cadena, enlaces b) Evaluación de objeto


(secuencia de comandos de cadena, contexto de ScriptContext)

pueden lanzar unexcepción de secuencia de comandos,que puede contener una causa de excepción raíz, un
mensaje, un nombre de archivo e incluso un número de línea y un número de columna donde ocurrió un
error, particularmente cuando el error es un error de compilación. el opcionalScriptContext

www.it-ebooks.info
590 CPASADOdieciséisIntegrando Groovy

El parámetro corresponde al entorno dentro del cual se evalúa un script, y un


Encuadernacioneses un mapa especial que contiene una asociación entre una clave y un objeto que desea
pasar a sus scripts. Estos afectan qué información está disponible para sus scripts y cómo diferentes
scripts pueden pasarse datos entre sí. Consulte la documentación detallada de JSR-223 para obtener más
información sobre este tema.

16.6.3 Motores de secuencias de comandos compilables e invocables

Más allá de las capacidades básicas de evaluación de guiones, el motor Groovy implementa dos
otras interfaces:javax.script.Compilableyjavax.script.Invocable.El primero
le permite precompilar y reutilizar scripts, y este último le permite ejecutar un método, una unidad
de ejecución, en lugar de ejecutar un script completo como lo hace con elevaluarmétodo. La
implementación de estas interfaces no es obligatoria, pero el motor Groovy proporciona esta
función:

//Java
Administrador de ScriptEngineManager = nuevo ScriptEngineManager();
ScriptEngine gEngine = manager.getEngineByName("groovy"); compilable
compilable= (Compilable)gEngine; compilable.put("nombre", "Dierk");

Script compiladoguion=compilable.compile("nombre de retorno"); String


dierksNombre =guion.eval();
compilable.put("nombre", "Guillaume");
String guillaumesName =guion.eval();

Una vez que tenga un control sobre elcompilablemotor (echando el motor a la


compilableinterfaz), puede llamar a doscompilarmétodos que toman un lector o una cadena
que contiene el script para precompilar. Estos métodos devuelven una instancia de
Guión compilado,que contiene un script precompilado que puede ejecutar varias veces a
voluntad sin necesidad de volver a analizarlo o compilarlo. Entonces elScript compilado
se puede evaluar con tresevaluarmétodos: uno sin ningún parámetro, uno tomando un
espacio de nombres,y el último tomando unScriptContext.
Incluso después de precompilar una secuencia de comandos, aún no puede llamar directamente a los
métodos declarados en esa secuencia de comandos. losjavax.script.InvocableLa interfaz lo hace posible de una
manera que recuerda a llamar a los métodos normales de Java con reflexión.
Imagine que tenemos un script cuya función es cambiar un parámetro de cadena a su
representación en mayúsculas:

//Java
Administrador de ScriptEngineManager = nuevo ScriptEngineManager();
ScriptEngine gEngine = manager.getEngineByName("groovy");

invocableinvocable= (Invocable)gEngine; invocable.eval("def upper(s)


{ s.toUpperCase() }"); Objeto s = invocable.invokeFunction("upper",
"Groovy");

invocable.eval("def add(a, b) { a + b }"); invocable.invokeFunction("añadir", nuevo


entero(1), nuevo entero(2));

assertTrue(invocable.invokeMethod(s, "termina con", "Y"));

www.it-ebooks.info
Montando Mustang y JSR-223 591

El script se evalúa y retiene en el contexto de ejecución del script; entonces, la función


definida se puede llamar con elfunción de invocaciónmétodo, que toma el nombre de la
función a llamar y unVararglista de objetos para pasar a la función con script subyacente
como parámetros. Sin embargo, tenga cuidado porque solo puede invocar funciones
definidas en el último script evaluado. Unmétodo de invocaciónEl método va más allá y le
permite llamar a métodos arbitrarios en objetos resultantes de la ejecución de scripts. Así es
como llamamos a latermina conmétodo en la cadena devuelta por la primera función
invocada y pasarle la letraYcomo argumento.
Por supuesto, en el último caso, podríamos haber emitido el valor de retorno desuperioraCuerda
directamente. Aunque esto puede parecer obvio, es posible porque Groovy funciona muy bien con Java,
devolviendo clases reales y normales. Otros lenguajes de secuencias de comandos devolverían algún tipo
de proxy o contenedor, lo que dificultaría la integración con Java.
A pesar de la conveniencia de poder llamar a cualquier función definida en un script, aún no es
tan compatible con Java como podríamos esperar. Sin embargo, elinvocableinterfaz le brinda otro
método útil para su caja de herramientas: elobtenerInterfazmétodo. Con este método, puede crear
un proxy de una interfaz determinada que delegará todas las invocaciones de métodos a los
métodos definidos en el script.
Digamos que tenemos una interfaz Java que representa un servicio comercial como el siguiente:

//Java
interfaz Servicio empresarial {
vacío en eso();
Objeto ejecutar(Objeto[] parámetros);
vacío liberar();
}

Creamos un script que contiene funciones mapeando las mismas firmas que las
provistas en elServicio empresarialinterfaz:
// genial
void init() { println "init" }
Objeto ejecutar(Objeto[] objs) { println "ejecutar" } anular liberación()
{ println "liberar" }

Podemos hacer que un script de este tipo parezca implementar elServicio empresarialinterfaz
llamando alobtenerInterfazmétodo del motor de script invocable:

//Java
Administrador de ScriptEngineManager = nuevo ScriptEngineManager();
ScriptEngine gEngine = manager.getEngineByName("groovy"); invocable
invocable= (Invocable)gEngine; invocable.evaluar(scriptAsAString); Servicio
empresarialServicio=

invocable.obtenerInterfaz(BusinessService.clase);

Servicio.en eso();
Resultado del objeto =Servicio.execute(nuevo Objeto[] {});
Servicio.liberar();

Primero, evaluamos el script que se mostró anteriormente, luego llamamos alobtenerInterfazmétodo con
la clase de la implementación que queremos que nuestro script implemente, y luego recuperamos

www.it-ebooks.info
592 CPASADOdieciséisIntegrando Groovy

una instancia que implementa esa interfaz. Nuestro script ni siquiera tiene que implementar
explícitamente elServicio empresarialinterfaz, pero a través del mecanismo de proxy, parece
como si fuera el caso. Con dicho mecanismo, puede manipular scripts como si fueran beans
Java normales, sin tener que llamar a algún tipo deinvocarmétodo.

16.6.4 Programación políglota


Hasta ahora nos hemos centrado en llamar a Groovy desde Java, pero puede usar
JSR-223 dentro de su Groovy para llamar a otros idiomas. El siguiente código llamará a
JavaScript desde Groovy:

def mgr = nuevo ScriptEngineManager()


afirmar mgr.getEngineByName("javascript").eval(''' función
factorial(n) {
si (n == 0) { devuelve 1; } devuelve n *
factorial(n - 1);
}
factoriales(4)
''') == 24.0

El motor de JavaScript se incluye con los JDK de Oracle. Para otros idiomas, es posible que también
deba obtener el motor de ejecución JSR-223 respectivo de un idioma.
Eso concluye nuestra discusión sobre las opciones de integración independientes del lenguaje
usando Spring y JSR-223. En breve, discutiremos todos los pros y los contras de las opciones de
integración de Groovy nativas y neutrales en cuanto al idioma, y uno de los puntos de discusión será qué
tan profundo necesita acceder a las partes internas de Groovy. Antes de hacerlo, ampliemos una de las
clases clave para una integración nativa profunda con Groovy, elCompiladorConfiguración
clase. Mencionamos la clase anteriormente en este capítulo, pero tiene habilidades avanzadas que cubriremos a
continuación.

16.7 Dominar la configuración del compilador


En las secciones anteriores, hemos mostrado cómo puede aprovechar laGroovyShell,la
GroovyScript Engine,o elGroovyClassLoaderpara integrar código Groovy con diferentes sabores. Todas
esas clases tienen en común la capacidad de crear clases en tiempo de ejecución. Incluso los scripts,
como hemos mostrado, se compilan en clases que se cargan, eventualmente, mediante el cargador de
clases especializado Groovy.
Crear clases en tiempo de ejecución, en este caso, no difiere mucho de llamar al
maravillosoherramienta de línea de comandos. El proceso de compilación es el mismo si llamasmaravilloso
desde la línea de comando o use unGroovyShell.La diferencia radica en la forma en que se proporciona el
código fuente (archivos, cadenas o URI) y la forma en que se cargan (el cargador de clases).
¿Qué hace que elGroovyShelltan poderoso es que implícitamente creas unGuion
clase que se puede usar, por ejemplo, como la implementación central de un DSL:

def conf = new CompilerConfiguration()


conf.setScriptBaseClass("BaseScript") def shell =
new GroovyShell(conf)

www.it-ebooks.info
Mastering CompilerConfiguración 593

valor def = shell.evaluate('''


multiplicar(5, 6)
''')
afirmar valor == 30

En este ejemplo, estamos cambiando explícitamente la clase de script base deGuionaGuión


base.Esto hace que todos los métodos de laguion baseclase directamente invocable desde el
texto del script cuando llamamosevaluar. setScriptBaseClasses solo un ejemplo de lo que
CompiladorConfiguraciónclass tiene para ofrecer en términos de personalización del proceso
de compilación.
¿No sería bueno, por ejemplo, si pudiera agregar importaciones predeterminadas a sus scripts?
Es decir, haga que algunas clases estén disponibles para su script sin que el usuario necesite
agregar un explícitoimportar¿declaración? Groovy, por ejemplo, importa por defecto clases de
maravilloso.langojava.util.Tal vez quieras que tus clases también se importen de forma
predeterminada. Esto es particularmente importante si piensa en DSL: tener que agregarimportar
declaraciones en un DSL no hace que se vea tan bien.
En el capítulo 9, mostramos cómo puede aprovechar la metaprogramación en tiempo de compilación para
agregar comportamiento a sus clases en tiempo de compilación. Usando transformaciones AST como
@Encadenar,lo cual genera unEncadenar()método para usted, es fácil reducir drásticamente el
código repetitivo. Las transformaciones AST tienen un precio, que es el uso de una anotación,
que no es necesariamente fácil de usar. ¿Qué sucedería si pudiera aplicar transformaciones
AST de manera transparente a los scripts que evalúa elGroovyShell?
Esos dos ejemplos, agregar importaciones de forma predeterminada y aplicar transformaciones AST de
manera transparente, son lo que llamamos personalizadores de compilación. Groovy viene con un conjunto de
personalizadores de compilación predefinidos que le permiten conectarse al proceso de compilación en sí de una
manera elegante. Por supuesto, los personalizadores de compilación no se limitan a lo que ofrece la distribución
Groovy, por lo que puede escribir los suyos propios.
Aplicar un personalizador de compilación es fácil. Veamos cómo puedes agregar unImportación-
Personalizador,la clase que le permite agregar importaciones por defecto:

definitivamenteconferencia=nuevo CompilerConfiguration() def personalizador = nuevo


ImportCustomizer()
personalizador.addImports('java.util.concurrent.atomic.AtomicInteger',
'java.util.concurrent.atomic.AtomicLong')
conf.addCompilationCustomizers(personalizador) def shell =
new GroovyShell(conferencia) valor def = shell.evaluate('''

def myInt = new AtomicInteger(1) def


myLong = new AtomicLong(2)
''')

Este ejemplo consta de dos partes bien diferenciadas: configurar unConfiguración del compilador, que
incluye el personalizador de compilación y la evaluación de un script. Debido a que el personalizador de
compilación le dice al compilador que agregue importaciones de manera transparente, cuando se ejecuta
el script ya no es necesario agregarlos para que se compile el script.
Hagamos un breve recorrido por los personalizadores de compilación que proporciona Groovy.

www.it-ebooks.info
594 CPASADOdieciséisIntegrando Groovy

16.7.1 El personalizador de importación

Groovy es ideal para construir DSL, pero hay algunos puntos a considerar. Ningúninterno DSL
basado en Groovy es en realidad código Groovy. Sin embargo, el objetivo de un DSL es que los
expertos del dominio puedan utilizarlo. No hay ninguna razón por la que los usuarios de su DSL
sean programadores. Teniendo eso en cuenta, sería muy desafortunado que los usuarios tuvieran
que agregar importaciones a sus scripts para que funcionen.
Imaginemos un DSL que permita la evaluación de expresiones matemáticas. La
idea natural sería aprovechar las funciones y constantes disponibles en el
java.lang.matemáticasclase. Por supuesto, un usuario podría escribir esto:

importar java.lang.Math.* estático


cos(PI/2)

Pero obviamente, la única parte importante del guión es la propia expresión matemática. los
importardeclaración está aquí para que sea más agradable de escribir o leer. El problema es que le
pedimos al usuario que agregue elimportardeclaración, aunque como sabemos que nuestro DSL
está destinado a evaluar expresiones matemáticas, lo normal sería hacer las importaciones por
defecto y dejar que el usuario llame alporquefunción así como laPiconstante sin problemas. La
primera solución obvia al problema es agregar importaciones al script del usuario:

definitivamentecaparazón=nuevo GroovyShell()
shell.evaluate('importar java.lang.Math.*\n'+expresión estática)

Si bien esto funciona, la solución no es muy elegante y presenta un problema importante: los
scripts, incluso si son "evaluados" por un Groovy Shell, al final son compilados y ejecutados por una
JVM. Esto significa que si el script contiene un error (y sucederá en algún momento), se lanzará una
excepción. En ese caso, el seguimiento de la pila no coincidirá con el código fuente: con el ejemplo
anterior, estamos introduciendo una línea de código adicional, lo que significa que cuando la
excepción muestre un error en la línea 145, el error será, para el usuario, estar realmente en la
línea 144 (debido a la importación adicional). Por supuesto, podría filtrar el seguimiento de la pila y
arreglar los números de línea/columna usted mismo, pero no resolvería el problema de
depuración: si el script está compilado, hay algunas formas de depurarlo (por ejemplo, establecer
puntos de interrupción en el IDE), y una vez más, los números de línea en el código de bytes no
coincidirían con los del código fuente. Esto es definitivamente un impedimento para la depuración.

Para solucionar este problema, el personalizador de importaciones te permitirá conectarte al


proceso de compilación y dar a conocer las importaciones al compilador sin necesidad de tenerlas
en forma de código fuente:

definitivamenteconferencia=new CompilerConfiguration()
def personalizador = nuevo ImportCustomizer()
personalizador.addStarImports 'java.lang.Math'
conf.addCompilationCustomizers(personalizador) def shell =
nuevo GroovyShell(conferencia) valor def =
shell.evaluate('cos(PI/2)')

www.it-ebooks.info
Mastering CompilerConfiguración 595

El personalizador de importaciones le permite agregar varios tipos de importaciones, desde "regulares" hasta
importaciones de estrellas estáticas, incluida la capacidad de usar alias:

personalizador.addStaticImport '-','java.lang.Math','PI'
personalizador.addStaticImport 'coseno','java.lang.Math','cos'
conf.addCompilationCustomizers(personalizador) def
shell = new GroovyShell(conferencia) valor def =
shell.evaluate('coseno(-/2)')

La Tabla 16.2 resume los métodos disponibles a través del personalizador de importación.

Tabla 16.2 Métodos ofrecidos por el personalizador de importación

Método Descripción

addImport(String nombre de clase) Agrega una importación regular

addImport(String alias, Agrega una importación con un alias explícito

Cadena nombre de clase)

addImports(String… importaciones) Agrega múltiples importaciones regulares a la vez

addStarImport(String nombre del paquete) Agrega una importación de estrellas para un paquete específico

addStarImports(String… nombres de paquetes) Agrega importaciones estrella de múltiples paquetes a la vez

addStaticImport(String className, Agrega una importación estática de un miembro

miembro de la cadena)

addStaticImport(String alias, StringAgrega una importación estática de un miembro usando un alias


className, miembro de cadena)

addStaticStar(String clasName) Agrega una importación de estrella estática de todos los miembros de

una clase

addStaticStars(String… nombres de clase) Agrega importaciones de estrellas estáticas de múltiples clases a


la vez

Usar el personalizador de importación es realmente fácil y debería ayudarlo a reducir la cantidad


de código de configuración que los usuarios deben escribir para poner en funcionamiento un DSL.
Debido a que a menudo los usuarios de DSL no son programadores, ya no es necesario ponerlos
frente a un código tan hostil y puede concentrarse en el DSL en sí. Este es, en general, el objetivo
de los personalizadores de compilación, y el siguiente que vamos a analizar está nuevamente
orientado a la facilidad de uso.

16.7.2 El personalizador consciente de la fuente

Una aplicación típica de Groovy consta de archivos fuente compilados por el compilador de Groovy, ya sea
mediante la herramienta de línea de comandos, Gradle, Ant o Maven. Pero, ¿qué sucede si sus archivos
fuente son, de hecho, scripts de usuario? ¿Qué sucede si se supone que esos scripts deben compilarse
utilizando una configuración de compilador específica porque, por ejemplo, corresponden a un

www.it-ebooks.info
596 CPASADOdieciséisIntegrando Groovy

ADSL? En ese caso, una opción que tiene es omitir esos archivos de la compilación y tener un contenedor
(normalmente unGroovyShell)que compilará los archivos en tiempo de ejecución utilizando una
configuración de compilador específica. Un problema con esto es que los scripts se compilan en tiempo
de ejecución, lo que significa que paga el costo de la compilación al inicio de la aplicación o durante su
ciclo de vida. En algunas circunstancias esto no es aceptable.
Otro requisito típico del usuario es tener diferentes opciones de compilación según la extensión del
archivo. De manera predeterminada, Groovy usa la extensión de archivo .groovy, pero algunos usuarios
quieren poder usar diferentes extensiones con diferentes significados. Por ejemplo, un archivo .spec
podría corresponder a un archivo de especificación ejecutable que implica transformaciones AST que no
son necesarias para los archivos .groovy normales.
Para esos casos de uso, el personalizador consciente de la fuente proporciona un poderoso
mecanismo de configuración que básicamente aplica diferentes opciones de compilación basadas
en la fuente real. En la práctica, este personalizador actúa como un protector para otro
personalizador, según el archivo de origen. La siguiente lista crea un personalizador de origen que
aplica elEncadenarTransformación AST a clases cuyos nombres terminan conFrijol.

Listado 16.12 Aplicar@Encadenara las clases cuyos nombres terminan conFrijol

importar groovy.transform.ToString
importar org.codehaus.groovy.control.CompilerConfiguration
importar org.codehaus.groovy.control.customizers.* bCrea
se envuelve enC def conf = nueva configuración del compilador() ToString AST
personalizador
una fuente- def ASTPersonalizador = new ASTTransformationPersonalizador(ToString) def
consciente
sourceAwareCustomizer =
personalizador
nuevo SourceAwareCustomizer(astCustomizer)
sourceAwareCustomizer.baseNameValidator = {
nombre -> nombre.termina con 'Bean'
Crea una base-
}
conf.addCompilationCustomizers(sourceAwareCustomizer)
dfiltro de nombre

def gcl = new GroovyClassLoader(getClass().classLoader, conf) def clazz =


Usos
gcl.parseClass '''
configuración
clase MrBean { Cadena primero, último } ''', con Groovy-
'MrBean.groovy' micargador de clases
def resultado = clazz.nuevaInstancia()
resultado.primero= 'Serbal'
resultado.último = 'Atkinson'
afirmar resultado.toString() == 'MrBean(Rowan, Atkinson)'

Creamos el personalizador de transformación ASTB, que se habría aplicado a todas las clases
compiladas si lo hubiéramos agregado directamente a la configuración del compilador. En su
lugar, creamos un personalizador consciente de la fuenteCque envuelve el personalizador de
transformación AST, luego establecemos un predicado en el nombre basedque determina en qué
condición se aplicará el personalizador de transformación AST. Como último paso, hacemos uso de
la configuración en conjunto con unGroovyClassLoaderpara compilar una clasemi.

www.it-ebooks.info
Mastering CompilerConfiguración 597

Tal como están, los personalizadores conscientes de la fuente ofrecen tres formas de proteger a otro personalizador.

- El validador de nombre base: funciona en el nombre de archivo sin la extensión.


- El validador de extensiones (extensionValidator): le permite trabajar específicamente en la
extensión del archivo.
- losfuenteUnitValidator—Trabaja directamente en una estructura de compilación interna llamada
Unidad de origen.A diferencia de los anteriores, es capaz de manejar más que solo el nombre del
archivo, lo que implica que puede funcionar en fuentes que no son archivos.
Unidad de origenle da acceso al árbol de sintaxis concreta (CST) o al AST, lo que significa que puede
trabajar en las clases reales que se encuentran en el código fuente.

Usar el personalizador consciente de la fuente es muy interesante, pero debemos advertirle que es una
herramienta muy poderosa que puede conducir a resultados difíciles de analizar (porque el código fuente
no cumple con las expectativas dependiendo de la extensión del archivo, por ejemplo) .
Groovy viene con muchas opciones de personalización que presentamos aquí desde un
integraciónperspectiva. En el capítulo 19, los revisaremos en parte con el propósito de cómo crear
un DSL. Allí, también discutiremos elPersonalizador de transformación ASTy elpersonalizador AST
seguro,que también sería pertinente en el contexto de la integración.
Pero no tienes que vivir con un conjunto fijo de personalizadores que te ofrece Groovy. Puedes
escribir el tuyo con la misma facilidad.

16.7.3 Escribiendo su propio personalizador


La última opción que te ofrece Groovy con respecto a la personalización de la compilación es escribir la tuya
propia. En términos de complejidad, es de alguna manera más fácil que las transformaciones AST pero casi tan
poderosa. Todos los personalizadores que hemos descrito hasta ahora están extendiendo un
clase base llamadaCompilaciónPersonalizadorencontrado en elorg.codehaus.groovy.control
. personalizadorespaquete. Esto le da una pista de las increíbles posibilidades que ahora
se le abren.
Como ejemplo, crearemos un personalizador de compilación que fallará en la compilación si
una clase no contiene un campo de tipoCuerdaeso se llamanombre.Lo primero que debe hacer es
crear la clase de personalizador:

clase HasNameFieldCustomizer extiende CompilationCustomizer {


HasNameFieldPersonalizador() {
super(CompilePhase.CANONICALIZACIÓN)
Establece el

}
} bfase

Este es el código mínimo que tendrás que escribir. AB, estamos configurando la fase de
compilación donde funciona el personalizador de compilación. Al igual que con las
transformaciones AST, un personalizador de compilación debe elegir la fase de compilación
adecuada y, a menudo, cuanto antes, mejor. Ahora tenemos que escribir el código, que realmente
verificará si el campo existe:

llamada vacía (fuente SourceUnit, contexto GeneratorContext, ClassNode classNode) {


def campo = classNode.getDeclaredField('nombre')

www.it-ebooks.info
598 CPASADOdieciséisIntegrando Groovy

si (campo) {
if (!field.type.equals(ClassHelper.STRING_TYPE)) {
source.addError(new SyntaxException("Clase ${classNode.name} " +
"define el campo 'nombre' pero usando el tipo incorrecto"),
field.lineNumber, field.columnNumber)
}
} más {
source.addError(new SyntaxException("Clase ${classNode.name} " +
"no define un campo llamado 'nombre' de tipo 'Cadena'",
classNode.lineNumber, classNode.columnNumber))
}
}

Vocaciónfuente.addErrorcon unSyntaxExceptionhará que el compilador detenga la


compilación, mostrando un mensaje de error localizado. En este caso, si existe un campo
pero usa el tipo incorrecto, entonces mostraría el error en el campo, pero si la clase no define
un campo llamadonombre,entonces el error estaría ubicado en la clase.
Este es un ejemplo muy simple que muestra que un personalizador de compilación puede
hacer básicamente lo que puede hacer una transformación AST, pero tiene más control sobre
cómo se ejecuta. La única restricción detrás de un personalizador de compilación es que funciona
exclusivamente en nodos de clase, pero no le impide visitar métodos dentro del personalizador de
compilación.
Hasta ahora, le hemos mostrado cómo puede usar los personalizadores existentes, crear los suyos
propios y vincularlos a unConfiguración del compilador;Ahora veamos cómo puedes usar las mismas
herramientas con elmaravillosoherramienta en sí.

16.7.4 La opción de compilación configscript


Los personalizadores de compilación son muy potentes para scripts incrustados, como DSL de
usuario ejecutados a través de un espacio aislado.GroovyShell.Hay algunas situaciones en las que
desearía el mismo nivel de personalización desde la propia línea de comandos; es decir, usando
maravillosotambién. Esto es posible usando el --guión de configuraciónopción de compilación:

groovyc --configscript config.groovy MiClase.groovy

Como puede ver, esta opción requiere un archivo de configuración que es en sí mismo un script de
Groovy. Este archivo de configuración le dará acceso a laCompiladorConfiguracióninstancia que el
maravillosoEl comando crea internamente, lo que le brinda la oportunidad de conectar sus
personalizadores de compilación. Esta instancia de configuración del compilador se expone en el script de
configuración usando elconfiguraciónvariable. Esto significa que puede escribir esto en el archivo de
configuración:

importar groovy.transform.Log
importar org.codehaus.groovy.control.customizers.ASTTransformationCustomizer

config.addCompilationCustomizers(nuevo ASTTransformationCustomizer(Registro))

Esta opción de configuración también está disponible en elmaravillosocomando, así como en la tarea Ant.
Por lo tanto, todos los archivos compilados utilizarán la configuración modificada por el script.

www.it-ebooks.info
Mastering CompilerConfiguración 599

Pero lo que es realmente bueno es que este script de configuración también expone un buen DSL para
personalizar la configuración, lo que reduce drásticamente la cantidad de código que se requiere para
modificar elConfiguración del compilador.Este DSL es un generador de personalizadores de compilación,
que se vincula automáticamente alconConfigmétodo:

withConfig(config) {
ast(Registro)
}

Este código es semánticamente equivalente al anterior, a pesar de ser mucho más conciso (ya no es
necesario importar), pero usa la sintaxis del constructor en su lugar, lo que significa que
ast(Registro)crea un personalizador de transformación AST para elTroncoTransformación AST.
La Tabla 16.3 resume el mapeo entre la sintaxis del constructor y la sintaxis
tradicional.

Tabla 16.3 Mapeo entre la creación de personalizadores normales y de estilo constructor

Constructor Tradicional Ejemplo

rápido ASTTransformación ast(Registro)


- personalizador ast(nombre:'registrador', Registro)

importaciones ImportCustomizer importaciones {


normal 'com.ejemplo.Foo'
estrella 'com.ejemplo.Foo'
staticStar 'java.lang.Math' alias 'Bar',
'com.example.Foo' staticMember
'com.Foo.bar'
}

seguro SecureASTPersonalizador seguro {


importsWhiteList=[]
}

fuente SourceAwarePersonalizador fuente(extensión:'sgroovy') {


ast(CompilarEstático)
}

en línea Compilación personalizada en línea (fase: 'CONVERSIÓN') {


personalizador fuente, contexto, classNode ->
println "Clase $classNode.name"
}

Ajustando la configuración del compilador usando elguión de configuraciónLa opción de línea de


comandos es muy poderosa, pero siempre debe tener cuidado de no poner demasiada magia allí. Los
programadores a menudo esperan que un código de apariencia idéntica produzca clases de apariencia
idéntica, pero al usar una herramienta como el personalizador consciente de la fuente, podrá producir
salidas muy diferentes, como solo dependiendo del nombre del paquete. Si alguna vez quieres

www.it-ebooks.info
600 CPASADOdieciséisIntegrando Groovy

Para hacer esto, siempre debe tomarse el tiempo para documentar la configuración, explicar por qué se hizo de
la manera en que se hizo y asegurarse de que su archivo de compilación use la configuración del compilador. Al
final, nunca use un archivo de configuración local y considere los scripts de configuración del compilador como
parte del código fuente. ¡Si no lo hace, es demasiado fácil crear construcciones irreproducibles!

Ahora conoce las técnicas nativas de Groovy para integrar Groovy en su aplicación
Java y las soluciones más independientes del lenguaje que usan Spring o JSR-223. Lo
bueno de esto es que te presenta una opción. La desventaja es que debe tomar una
decisión, por lo que brindamos orientación en la última sección de este capítulo.

16.8 Elección de un mecanismo de integración


Esta sección es similar a la primera del capítulo, en que no podemos tomar ninguna decisión por
usted. Buena orientacióntiendeacertar más que equivocarse, pero siempre habrá casos que
parezcan ajustarse a un patrón pero que se beneficien más de otro después de un examen
minucioso. No sabemos cuáles son sus necesidades, por lo que no podemos hacer ese examen
detallado. Todo lo que podemos hacer es dar sugerencias y razones para ellas.
Para dar una buena regla general, si su aplicación se basa en Spring, debería preferir
usar la integración de Spring. Si desea poder cambiar o mezclar lenguajes de secuencias de
comandos al mismo tiempo, o si tiene la libertad de cambiar a voluntad, usar la integración
de secuencias de comandos de JSR-223 tiene mucho sentido. Pero si quiere hacer cosas más
avanzadas o si le preocupa el posible agujero de seguridad abierto por el código dinámico,
probablemente debería elegir algunos de los mecanismos Groovy estándar para incrustar y
ejecutar código Groovy conGroovyShell, GroovyScriptEngine, o el todopoderoso
GroovyClassLoader.La Tabla 16.4 muestra un resumen de las ventajas y desventajas de cada
mecanismo de integración.

Cuadro 16.4 Puntos dulces y limitaciones de los diferentes mecanismos de integración

Mecanismo Punto justo Limitaciones

Eval.me Para expresiones muy simples No apto para evaluaciones frecuentes

GroovyShell Perfecto para entrada de usuario de una No escalará a scripts


sola línea, expresiones pequeñas y DLS dependientes

GroovyScriptEngine Soporta recarga No admite clases No


Sólida seguridad disponible admite seguridad
Agradable para scripts
dependientes Admite recarga

GroovyClassLoader El mecanismo de integración más Más complicado de manejar en el caso de una


poderoso Soporta recarga jerarquía de cargador de clases compleja
Seguridad robusta disponible

Soporte de secuencias de comandos de Spring Se integra bien con Spring Requiere Primavera
Puede cambiar de idioma
fácilmente Admite la recarga

www.it-ebooks.info
Resumen 601

Cuadro 16.4 Puntos dulces y limitaciones de los diferentes mecanismos de integración

Mecanismo Punto justo Limitaciones

JSR-223 Puede cambiar de idioma fácilmente Requiere Java 6


No es compatible con la seguridad No es
compatible con la recarga No es
compatible con la configuración específica
de Groovy

Secuencias de comandos de frijol Puede cambiar de idioma No es compatible con la seguridad No es


Estructura fácilmente No requiere Java 6 compatible con la recarga No es
compatible con la configuración específica
de Groovy
Capacidades más limitadas que
JSR-223

La base de la integración de Groovy es su excelente compatibilidad con Java. Hemos enumerado las
formas más comunes de integrar Groovy con Java, pero en cualquier lugar donde se pueda integrar Java,
Groovy también puede funcionar. Algunas bases de datos permiten que los procedimientos almacenados
se escriban en Java, por ejemplo, por lo que Groovy se puede usar de la misma manera. Es posible que
con el tiempo aparezcan mecanismos de integración adicionales de diversas formas. (Vea Grengine,
http://grengine.ch/.) ¡No asuma que las opciones dadas aquí son exhaustivas!

16.9 Resumen
Este capítulo le ha dado una idea de cómo podría permitir que sus aplicaciones se vuelvan más
flexibles, brindando a los usuarios apropiados la capacidad de personalizar el comportamiento de
una manera que les permita resolver los problemas.exactoproblema al que se enfrentan, en lugar
del que estaba más cerca de lo que podía imaginar al diseñar la aplicación.
Los medios para integrar Groovy en su aplicación se dividen en dos campos: los
proporcionados directamente por las bibliotecas de Groovy y los proporcionados de manera
neutral por Spring y, desde Java 6, a través de JSR-223. Como suele ser el caso, las soluciones
más específicas resultan ser las más poderosas, pero a costa de la neutralidad lingüística.
Como sujetalibros del capítulo, discutimos los tipos de aplicaciones que se benefician de
este tipo de integración y ofrecemos orientación sobre qué mecanismo de integración podría
ser mejor para su situación.
Hay una buena razón por la cual el equipo de Spring hizo de Groovy un ciudadano de primera clase y
su lenguaje de secuencias de comandos preferido en la versión 4.0: la integración de Groovy con su
plataforma subyacente es tan profunda que es la elección natural para cualquier actividad dinámica en la
JVM.
Puede beneficiarse de las ventajas de ambos mundos: puede crear aplicaciones empresariales grandes y
escalables sin dejar de utilizar Groovy para una configuración inteligente, reglas empresariales adaptables, lógica
definida por el usuario e inspecciones espontáneas en tiempo de ejecución.

www.it-ebooks.info
www.it-ebooks.info
parte 3

Groovy aplicado

Construimos demasiados muros y no suficientes puentes


—Isaac Newton

yo En el transcurso de este libro, ha visto una gran parte de Groovyland. La Parte 1 le


presentó el lenguaje Groovy, los tipos de datos, los operadores, las estructuras de control e
incluso el Protocolo de metaobjetos. La Parte 2 lo guió a través de la biblioteca Groovy,
mostrando constructores, plantillas, numerosas mejoras de JDK, trabajo con bases de datos
y compatibilidad con XML. Tu mochila está llena de valiosos conocimientos que esperan ser
llevados a nuevos horizontes.
La Parte 3 le brinda orientación sobre cómo aplicar mejor su conocimiento con otras
herramientas, marcos y bibliotecas que adoptan Groovy.
Comienza con las pruebas unitarias en el capítulo 17, una actividad sin la cual ningún
desarrollador profesional que se precie puede trabajar. Con una combinación inteligente de la
sabiduría de Groovy que ya ha adquirido y un poco de orientación a través del excelente soporte
de pruebas de Groovy, apreciará las pruebas unitarias como otro punto fuerte de Groovy.
El Capítulo 18 lo prepara para la esperada era multinúcleo y le permite aprovechar
al máximo todos los muchos núcleos de procesamiento que probablemente tendrá su
máquina.
El Capítulo 19 le brinda el poder de diseñar su propio lenguaje para que los aspectos
comerciales puedan expresarse en la jerga de su dominio. Te convertirás en el maestro de
los lenguajes específicos de dominio (DSL).
El capítulo 20 viene como una bonificación para todos los lectores diligentes que
aguantaron hasta el final. Se le reembolsará con un adelanto de una serie de herramientas,

www.it-ebooks.info
604 PAGSARTE3Groovy aplicado

bibliotecas y marcos que lo ayudan con tareas tan diversas como escribir aplicaciones web,
aplicaciones de escritorio, automatizar Windows, usar herramientas de análisis de calidad, diseñar
por contrato y mucho más. Es una descripción general rápida pero amplia para despertar su
interés en aprender más sobre estos proyectos. Esperamos que lo tomes como tu trampolín para
sumergirte en el océano Groovy.

www.it-ebooks.info
Pruebas unitarias con Groovy

Este capítulo cubre


- Pruebas unitarias de código Groovy y Java
- Incorporación de herramientas de cobertura de código

- Integración de IDE
- Probando con Spock
- Automatización del proceso de construcción

La principal diferencia entre algo que podría salir mal y algo que no
puede salir mal es que cuando algo que no puede salir mal sale mal,
por lo general resulta imposible repararlo.
—Douglas Adams

Las pruebas de unidad de desarrollador se han convertido en un estándar de facto en la


comunidad de Java.1La confianza y estructura que JUnit2y otros marcos de prueba que aportan
al proceso de desarrollo son casi revolucionarios. A los que estuvimos activamente

1
Véase Kevin Tate,Desarrollo de software sostenible: una perspectiva ágil(Addison Wesley Professional, 2005) y
Greg Smith y Ahmed Sidky,Volverse ágil(Manning, 2009).
2
Véase Petar Tahchiev, et al.,JUnit en Acción, 2Dakota del Norteed.(Manning, 2010); JB Rainsberger,Recetas JUnit(Manning,
2004) y www.junit.org para obtener más información.

605

www.it-ebooks.info
606 CPASADO17Pruebas unitarias con Groovy

Al desarrollar aplicaciones Java en los últimos años del siglo XX, las pruebas unitarias
automatizadas eran casi desconocidas. Sí, escribimos pruebas, ¡pero no estaban automatizadas ni
formaban parte de una compilación estándar!
Avance rápido hasta el presente, y muchas personas no pensarían en escribir, y mucho menos
liberando, código sin pruebas unitarias correspondientes. Escribimos pruebas todo el tiempo y
esperamos que todos los demás en nuestros equipos hagan lo mismo. Además, está creciendo el
impulso detrás de la idea de escribir código escribiendo siempre primero las pruebas. Aunque esto
no es universal, es otro indicador de que continuará el crecimiento reciente en la importancia de
las pruebas.
Realizamos pruebas en todos los niveles, desde pruebas unitarias hasta pruebas de integración y
pruebas de sistemas. A veces es más divertido escribir las pruebas que el tema bajo prueba, porque
hacerlo mejora no solo el código en sí, sino también el diseño del código. Cuando las pruebas se escriben
con frecuencia y de manera continua, el código tiene la ventaja de ser altamente extensible, además de
estar obviamente más libre de defectos y más fácil de reparar cuando sea necesario.
Combine este mayor conocimiento de las pruebas de desarrollador con Groovy, y tendrá una
combinación perfecta. Con Groovy, las pruebas se pueden escribir de forma más rápida y sencilla. Se
vuelve aún mejor cuando combina la simplicidad de las pruebas unitarias en Groovy con Java normal.
Puede escribir pruebas de Groovy para sus sistemas basados en Groovy y aprovechar las muchas
bibliotecas de Java y paquetes de extensión de prueba. Puede escribir pruebas de Groovy para sus
sistemas basados en Java y aprovechar los beneficios de sintaxis mejorada y la funcionalidad de prueba
extendida de Groovy.
Groovy hace que las pruebas unitarias sean muy sencillas, independientemente de la forma en que lo use, principalmente debido a cuatro

aspectos clave:

- Groovy incorpora JUnit, por lo que no tiene que configurar una nueva dependencia.
- Groovy tiene una clase de caso de prueba mejorada, que agrega una plétora de nuevos métodos
de aserción.
- Groovy tiene funciones integradas de simulación, código auxiliar y otras funciones dinámicas de creación de clases que

simplifican el aislamiento de una clase de prueba de sus colaboradores.

- Las pruebas escritas en Groovy se pueden ejecutar fácilmente desde Gradle, Maven o su IDE favorito.

Nuestro enfoque en este capítulo es la prueba unitaria; sin embargo, muchas de las ideas también se
pueden extender a otros tipos de pruebas. Mencionaremos ejemplos específicos a lo largo del capítulo.

17.1 Primeros pasos


El encabezado de la sección implica que debe prepararse antes de poder comenzar sus actividades de
prueba. Pero no lo haces. No hay soporte externo para descargar o instalar. Groovy trata las pruebas
unitarias como un deber de desarrollador de primera clase y se envía con todo lo que necesita para ese
propósito.
Aún más importante, simplifica las pruebas al hacer que las afirmaciones sean parte del lenguaje,3
ejecutar automáticamente casos de prueba invocando de forma transparente su TestRunner

3Java también admite aserciones a nivel de idioma, pero las desactiva de forma predeterminada.

www.it-ebooks.info
Empezando 607

cuando sea necesario, y brindando los medios para ejecutar conjuntos de casos de prueba fácilmente,
tanto desde la línea de comandos como a través de la integración con su IDE o entorno de compilación.
Esta sección le muestra lo simple que puede ser y le presentaGroovyTestCase,la clase base utilizada para la
mayoría de las pruebas unitarias en Groovy.

17.1.1 Escribir pruebas es fácil


Suponga que tiene un código Groovy que convierte las temperaturas medidas en Fahrenheit
(F) a Celsius (C). Para ello, se define unCelsiusmétodo en unConvertidorclase así:

convertidor de clase {
estático celsius (fahrenheit) { (fahrenheit - 32) * 5 / 9 }
}

¿Es correcta esta implementación? Probablemente, pero no puedes estar seguro. Debe ganar más
confianza en este método antes de que el próximo viajero fuera de los EE. UU. use su método para
comprender el pronóstico del tiempo de los EE. UU.
Un enfoque común con las pruebas unitarias es llamar al sujeto bajo prueba con datos de
muestra estáticos que producen resultados bien conocidos. De esa manera, puede comparar los
resultados calculados con sus expectativas.
Elegir un buen conjunto de muestras es clave. Como regla general, tener algunos casos
típicos y todos los casos de esquina que se te ocurran es una buena opción.4Los casos típicos
serían 68° F = 20° C para tener una fiesta en el jardín o 95° F = 35° C para ir a la playa. Los
casos de esquina serían 0° F, que está entre -17° C y -18° C, la temperatura más fría que
Gabriel Daniel Fahrenheit pudo crear con una mezcla de hielo y sal común en 1714. Otro caso
de esquina es cuando el agua se congela a 32 °F = 0°C.
¿Suena complicado? no lo es La siguiente lista importa el método de forma estática y luego agrega pruebas
unitarias con secuencias de comandos utilizando afirmaciones simples que están integradas en el propio
lenguaje.

Listado 17.1 Pruebas unitarias con guión para el método de conversión de Fahrenheit a Celsius

importar estático Convertidor.celsius

afirmar 20 == centígrados (68)


afirmar 35 == centígrados (95) Redondea a
número entero
afirmar -17 == celsius(0).toInteger() afirmar 0 ==
celsius(32)

Las pruebas con guión de este tipo son útiles. Como ejemplo, mire este libro: la mayoría de los listados
contienen afirmaciones de autocomprobación para garantizar que el código funcione y ayudar a revelar
sus expectativas del código al mismo tiempo. La mayoría incluso trabaja comopruebas en líneadonde las
aserciones viven dentro del código bajo prueba.

4
Encontrar buenos datos de prueba es una ciencia en sí misma e implica actividades como el análisis estructural del dominio de parámetros.
Para nuestros propósitos, lo mantenemos simple. Consulte la literatura de antecedentes para obtener más información.

www.it-ebooks.info
608 CPASADO17Pruebas unitarias con Groovy

Pero, ¿qué pasa si una prueba falla debido a un error de implementación? Groovy informará de
esto de forma visual. Digamos que en lugar de convertir 95° F a 35° C, asumimos incorrectamente
que el resultado debería ser 34° C. Si ejecutamos lo siguiente:

afirmar 34 == celsius(95)

Groovy nos informará el siguiente error de afirmación:

afirmar 34 == centígrados (95)


| |
| 35
falso

Aquí puedes ver el Power Assert de Groovy en funcionamiento. Power Assert, introducido
originalmente en Spock Testing Framework, proporciona una forma muy concisa y clara de
informar errores al enviar el resultado de cada invocación a la consola. Esto hace que sea más
fácil entender qué partes salieron bien y cuáles mal.
Cada vez que cambia el entorno del código de autoevaluación, las pruebas en línea
afirman que todavía está funcionando. Los cambios ambientales pueden ocurrir por varias
razones: evaluar el script en una máquina diferente, usar una versión actualizada de JDK o
Groovy, o ejecutar diferentes versiones de paquetes de los que depende el script.
Hay circunstancias en las que las pruebas no se puedenen línea, como debido a los requisitos de
rendimiento. Algunas vecescon guionlas pruebas no son lo suficientemente convenientes porque no se
autoorganizan en árboles de conjuntos de pruebas. En tales casos, es convencional empaquetar todas las
pruebas de una secuencia de comandos o clase dada en una clase separada que resida en un archivo separado.
Aquí es dondeGroovyTestCasoaparece en el escenario.

17.1.2 GroovyTestCase: una introducción


Groovy agrupa una clase JUnit extendida denominadaGroovyTestCase,lo que facilita las pruebas unitarias
de varias maneras. Incluye una gran cantidad de nuevos métodos de aserción y también facilita la
ejecución de scripts de Groovy disfrazados de casos de prueba.
Las afirmaciones añadidas se enumeran en la tabla 17.1. No entraremos en los detalles de cada
método, principalmente porque tienen nombres descriptivos. Cuando no sea absolutamente obvio
cuál es el significado, la descripción proporcionada en la tabla debería ser suficiente. Aunque no los
discutiremos explícitamente, los usaremos en las afirmaciones en otras partes de este capítulo, así
que verá lo útiles que son.

Tabla 17.1 Aserciones mejoradas disponibles en GroovyTestCase

Método Descripción

void assertArrayEquals(Objeto[] esperado, Compara el contenido y la longitud de


Objeto[] valor) cada matriz.

void assertContains(char esperado, char[] Verifica que una matriz dada decarbonizarses contiene un
array) valor esperado

www.it-ebooks.info
Empezando 609

Tabla 17.1 Aserciones mejoradas disponibles en GroovyTestCase

Método Descripción

void assertContains(int esperado, Verifica que una matriz dada deEn ts contiene un
matriz int[]) valor esperado

void assertInspect(Valor del objeto, Similar aafirmarToStringmétodo, excepto que


Cadena esperada) llama al método de inspección

void asertLength(longitud int, Método de conveniencia para afirmar la longitud de una


matriz char[]) matriz

void asertLength(longitud int, Método de conveniencia para afirmar la longitud de una


matriz int[]) matriz

void asertLength(longitud int, Método de conveniencia para afirmar la longitud de una


Objeto[] matriz) matriz

void asertScript(cadena final Intentos de ejecutar el script proporcionado

guion)

void assertToString(Valor del objeto, invoca elEncadenaren el objeto


Cadena esperada) proporcionado y compara el resultado
con la cadena esperada

void shouldFail (Código de cierre) Verifica que el cierre provisto falla

void shouldFail(Clase clazz, Verifica que el cierre proporcionado arroja una


código de cierre) excepción de tipoestruendo

void shouldFail(String scriptText) Verifica que el script provisto falla cuando se ejecuta

void shouldFail(Clase clazz, Verifica que el script provisto arroja una excepción de tipo
Cadena scriptText) estruendocuando se ejecuta

void debe fallar con causa (clase Verifica que el cierre provisto falla y que una
clazz, código de cierre) excepción particular es la causa de la falla

Además de los métodos enumerados en la tabla anterior, considere el convenienteaun no


implementadométodo, que puede utilizar para marcar un método de prueba como aún no
implementado. Aquí hay un ejemplo:

public void testNotImplementedYet() {


if (GroovyTestCase.notYetImplemented(this)) fail("se devolver
implementará mañana")
}

En el ejemplo anterior, el caso de prueba se marca como aún no implementado. Si la prueba pasa de
alguna manera, lo que aún no se esperaba, la prueba fallará con un mensaje de error descriptivo.
Groovy no te obliga a extenderGroovyTestCase,y eres libre de continuar extendiendo el
tradicionalCaso de pruebaclase proporcionada por JUnit.5Habiendo dicho eso, a menos que

5 Estos métodos amplían la versión 4.12 de JUnit, que se incluye con Groovy.

www.it-ebooks.info
610 CPASADO17Pruebas unitarias con Groovy

necesita la funcionalidad de una subclase diferente deCaso de prueba,tienes muchas razones para
usarGroovyTestCasoy no hay razones para evitarlo específicamente. Junto con las afirmaciones
enumeradas en la tabla 17.1, es más fácil trabajar conGroovyTestCasoqueCaso de prueba, como verá
en la siguiente sección.

17.1.3 Trabajar con GroovyTestCase


Para usar la función mejorada de GroovyCaso de pruebaclase, extiéndala de la siguiente manera:6

clase SimpleUnitTest extiende GroovyTestCase {


anular pruebaSimple() {
afirmarEquals("Groovy debe agregar correctamente", 2, 1 + 1)
}
}

También puedes usar el JUnit 4@Pruebaanotación o el equivalente de TestNG para marcar su


prueba. En ese caso, no tiene que extenderse desdeGroovyTestCasoa menos que quieras usar
GroovyTestCase's métodos de conveniencia, ni tiene que comenzar su método de prueba con el
prefijo "prueba":

importar org.junit.Prueba
importar estático org.junit.Assert.assertEquals
clase SimpleUnitTest {
@Prueba
vacío debe agregar () {
afirmarEquals("Groovy debe agregar correctamente", 2, 1 + 1)
}
}

Recuerde, usted es libre de extender cualquierCaso de pruebaclase que elija, siempre que esté en su
classpath. Puede extender fácilmente JUnitCaso de pruebacomo sigue:

importar junit.framework.TestCase

class AnotherSimpleUnitTest extiende TestCase {


void testSimpleOtraVez() {
afirmarEquals("Debería restar correctamente también", 2, 3 - 1)
}
}

Los casos de prueba se pueden ejecutar a través demaravillosocomando que ha utilizado anteriormente para
scripts y aplicaciones. Por ejemplo, puede ejecutar elPruebaUnitariaSimplescript visto anteriormente, por tip-
ing el comandomaravilloso SimpleUnitTest:

> maravilloso SimpleUnitTest


.
Tiempo: 0

Bien (1 prueba)

6No tiene que importarlo: reside en uno de los paquetes importados de forma predeterminada.

www.it-ebooks.info

También podría gustarte