Table of ContentsIntroducción. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Consideraciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Organización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Localhost . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Entorno de trabajo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Organización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Preparación del entorno. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Carga de información y creación de los productos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Carga de información . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Creación de cada producto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Construcción del catálogo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
PDF. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Personalización del pdf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Json a Pdf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Publicación y Schedule . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Convertir PowerPoint to HTML. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Preparación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Parsear el PowerPoint. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
RevealJS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Conversión con Asciidoctor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Conceptos básicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Punto y coma, paréntesis y demás . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Declaración de tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Comillas simples y dobles. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
String vs GString. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Closure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Listas, mapas y otros seres. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Bucles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
CliBuilder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Ejecutar comandos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Comando simple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Esperar finalización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Llamadas entre scripts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
WithBatch. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
De Properties a YML. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Consumo de recursos con DSLs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Primer intento. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Segundo intento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Base de datos de DSLs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Empaquetado Scripts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Directorio compartido. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Alojado en SVN/Git . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Servidor HTTP. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Jar. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Publicando Jar. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Estructura directorios. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Gradle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Publicando. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
"Grapeando" . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
Extraer una Table HTML a CSV . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Instalación y primeros pasos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Usando los binarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Windows. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Maven (y similares) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
SdkMan. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Comprobación. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
GroovyConsole . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Consola . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Groovy Web Console . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Métodos últiles para trabajar con listas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Contar registros de base de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
GORM en tus scripts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Dependencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Domain . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Configuración . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Crear una Biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Añadir Libros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Consultar la biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Test. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Docker y Groovy (básico) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
GroovyShell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Hello Docker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Montando volumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Consumir JSON . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Ejecución programada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
Gitlab. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Planificación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Resultado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Mantenimiento de un Registry de Docker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Dependencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Configuración y argumentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Autorización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Iterar repositorios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
Obtener Tags . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
Report . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
Asciidoctor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
Limpieza. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
Biblioteca (docudocker) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Herramientas que vamos a emplear. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Preparación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
Descripción del script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
Dependencias y configuración necesarias. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Actualización de versión.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Subida de nuevos documentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Creación de la imagen docuDocker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Tostando imágenes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Dockerfile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Generando la imagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
Ejecutando la imagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
De Excel a i18n y viceversa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Dependencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
Argumentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
De i18n a Excel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
De Excel a i18n . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
Buscar el fichero de mayor tamaño recursivamente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
Buscar en fichero y volcar coincidentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
Maven Inventory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
Resultado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
Organizar fotografías por fecha . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
Eliminar lineas con error . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
Scan and Execute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
A qué dedicas tu tiempo libre (Google Calendar) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
Consola Google . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
Groogle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
Personalización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
Business . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
Vista. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
Import/Export de Google Sheet a Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
Consola Google . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
Groogle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
Dependencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
Autorización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
Database. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
Argumentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
Comparando Metadatos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
De Google a Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
De Database a Google . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
Gestionando permisos de Google Drive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
Dependencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
Opciones y login . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
dumpFile / dumpAll. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
removeFile / removeAll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
shareFile. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
Documentación adicional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
Raffle (Google Sheet + Meetups) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
Consola Google . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
Groogle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
Leyendo la hoja . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
Presentación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
Ejemplo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
Organizando eventos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
Consola Google . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
Groogle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
Identificadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
Sheet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
Calendar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
Crea tu propio DSL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
Caso de uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
Exp4j . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
Ejemplo DSL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
DSL. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
PlotDSL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
PlotsDSL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
De texto a Closure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
JavaFX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
Xml a Calendar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
Xml. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
Consola Google . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
Groogle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
Eliminando antiguos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
Consumiendo XML. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
Business . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
OpenDataMadrid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
Preparación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
Scripts vs Task. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
OpenDataMadrid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
Dependencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
Parse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
Telegram . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
Ejecución manual. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
Monitorea un servidor (o lo que quieras). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
Caso de Uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
Modelo-Vista-Controlador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
Transiciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
TwitterFX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
Caso de Uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
Twitter App . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
Dependencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
Customize. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
Obtener Top Sales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
Generamos el gráfico de tartas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
A Twitter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
Cerrar automáticamente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
VisualSheet (Mejora la interface de tus scripts) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
Caso de Uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
Dependencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
Business . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
Vista. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
Visualizar Extension de Ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
Calculo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
Vista. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
Ejemplo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
Configuración dinámica de fichero remoto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
Envío de eventos con RabbitMQ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
RabbitMQ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
Conexion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
Monitorizar fichero (producer) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
Recibir eventos (consumer). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
Resultado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
Docker. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
Crear un fichero de texto con todas las operaciones disponibles en el API del INE . . . . . 219
Servidor web en 1 fichero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
Leer un fichero desde un host remoto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
Contacts2QRCode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
Objetivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
Excel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
Dependencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228
Parseo del Excel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228
Dump de QRCodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
generateQRLink . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
GenerateVCard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
Desmenuzando un Pdf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
Objetivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
Dependencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
Script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
En una linea (más Grappe) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
Importar datos de un excel a una tabla . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
Volcar datos de una tabla a Excel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242
Mail . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
Mail con documentos adjuntos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
Memento en el TAE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
TAE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
Stress . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253
Memoize. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254
Comparativa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255
TweetReport (Persistencia con SQLite) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
Dependencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
Argumentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
Modelo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257
Prepare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
Update User . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
Report User . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
Persistencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259
Buscando claves ssh en BitBucket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262
Rest al rescate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262
OAuth Consumer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
Dependencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
Autorización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
BitBucket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
Listar repositorios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
Inspeccionar un repo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265
Report . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265
Valida tu NIF o SOAP hecho fácil . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
SOAP y Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
SOAP y Groovy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
Consulta de NIF válido según AEAT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
Consultar un NIF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
Consultar hasta 10K NIFs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
Ejecución y certificado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
IntroducciónJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2017-08-01
Puedes acceder a todo el contenido en formato HTML a través de esteenlace https://groovy-lang.gitlab.io/101-scripts/
Puedes acceder a todo el contenido en formato eBook a través de esteenlace 101-groovy-scripts-epub.epub
101 Scripts de Groovy pretende ser una serie de prácticos scripts desarrollados engroovy y que puedan servir como base para aplicarlos en su día a día. Tú puedes aportarel tuyo, simplemente tienes que seguir esta guía:
• Debes tener una cuenta en Gitlab (si sabes como hacer un fork desde Github o similarcuentanoslo)
• Puedes encontrar el repositorio en https://gitlab.com/groovy-lang/101-scripts
• Debes hacer un fork del proyecto a tu espacio (https://docs.gitlab.com/ce/workflow/forking_workflow.html)
• Cuando tengas tu script y su documentación lista deberás hacer un Merge Requestpara su revisión y aceptación
Consideraciones• Queremos añadir scripts que solucionen un problema específico (real o inventando).
No importa que tu script sea particular a una situación concreta pero seguro queaporta ideas de cómo atacar situaciones parecidas.
• Tampoco importa que sea fácil o simple. Lo que a tí te parece sencillo a otro le puederesolver un problema (y así llegamos a 101 antes)
• Debes adjuntar al menos un script y una documentación. Si quieres adjuntar imágenesperfecto pero no es vital.
• El script debe ser funcional y completo. La idea es que bajo las circunstanciasdocumentadas el script funcione tal como se explice.
• Hemos creado una serie de categorías pero no es una lista cerrada. Si quieres que tuscript pertenezca a una nueva categoría comentálo y se crea.
• Usamos Asciidoctor como herramienta para documentar. Es imperativo porque es laherramienta que nos permite publicar en Html, Pdf y ePub sin modificar ladocumentación. Sin embargo no tienes porqué usar todas sus funcionalidades.
• Para la parte web usamos el generador de contenidos estático JBake. Hay otros pero anosotros nos gusta este. Lo bueno es que ya está montado y no tendrás que peleartecon él.
101 Groovy Script
| 1
OrganizaciónTu script se debe de alojar en src/jbake/assets/script/categoria donde categoria es una en laque creas que mejor encaja. Actualmente tenemos basico,bbdd,docker,file ,google,office,etc
Tu documentación se debe de alojar en src/jbake/content/categoria (atención a losdirectorios)
Como normal general el script lo capitalizamos y la documentación no. Si tudocumentación está en ingles es preferible que le añadas el sufijo -en al nombre (porejemplo la-docu-en.adoc ). Si está en español no es necesario.
Toda documentación tiene que contener una cabecera similar a:
= TituloNombre <[email protected]>yyyy-mm-dd:jbake-type: post:jbake-status: published:jbake-tags: blog, asciidoc, los tags que quieras:jbake-category: la categoria a la que pertenece el script:jbake-script: /scripts/categoria/NombreScript.groovy:jbake-lang: es (o en si está en inglés. Por defecto se toma español):idprefix::imagesdir: ../images
Tu documentacion comienza aqui
jbake-category es el atributo que sirve al generador para ubicar tu script en el menujbake-script es el atributo que sirve al generador para enlazar tu scritp con la docu jbake-lang es el atributo que indica en que idioma está la documentación jbake-type indica quees un documento tipo post (el que usamos) jbake-status indica que el documento está listopara su publicación (si no deberías usar draft)
LocalhostSi cuentas con Java instalado en tu máquina es posible ejecutar el blog en local y así teserá más fácil revisarlo antes de hacer el Merge Request. Para ello ejecuta simplementedesde el raiz del proyecto
./gradlew serveApp
y tras bajarse media internet podrás acceder a http://localhost:8081/101-scripts
101 Groovy Script
2 |
Entorno de trabajoPuedes usar desde un simple editor a un IDE sofisticado. Nosotros trabajamos con Intellijpero probablemente Eclipse te sirva también. Simplemente recuerda que debes reiniciarla tarea serveApp para que te vuelva a generar el contenido
101 Groovy Script
| 3
Unresolved directive in 101-groovy-scripts-pdf.adoc -include::../../jbake/content/content/about_template.adoc[] <<<< :jbake-script:/scripts/asciidoctor/Catalogo.groovy = Catálogo de productos con Asciidoctor Miguel Rueda<[email protected] [mailto:[email protected]]> 2018-01-25
El supuesto que vamos a plantear en este documento es la creación de un catálogo deproductos de una tienda.
Dicho catálogo consistirá en un Pdf donde mostraremos, para cada artículo de interés, ladescripción, el precio y una imagen del producto.
Para la generación de este catálogo nos vamos a basar en Asciidoctor[http://asciidoctor.org/]. Para una consulta rápida sobre esta herramientapuedes consultar el siguiente tutorial [http://jorge-aguilera.gitlab.io/
tutoasciidoc/], el cual es una guía básica pero completa sobre esta materia.
En este caso vamos a utilizar imágenes en nuestro local pero podemosapuntar a links donde tengamos nuestras imágenes . En nuestro ejemploel id de cada item corresponderá con el nombre de imagen a usar para elartículo.
El schema de nuestra base de datos será algo muy simple, consistente enuna única tabla con el id, descripción, precio de cada artículo.
OrganizaciónNuestro script necesitará conectarse a una base de datos de donde obtendrá lainformación de cada artículo, y para cada uno de ellos generará una "página" del Pdf. Paraello usaremos un fichero plantilla común a todos, aunque utilizar uno diferente en base ala categoría, precio, etc de cada artículo es una modificación trivial.
product.tpl
.Ref: ${sku}----Descripcion: ${description}
Referencia ${sku}, ${price}----
image::${sku}.png[${description}]
101 Groovy Script
4 |
catalog.tpl
= Catalogo de Productos
Miguel Rueda <[email protected]>
:idprefix:
:icons: font
:imagesdir: ./images
Este es el catálogo de nuestros productos a día de hoy. En el puede encontrar las referencias,junto con precios e imágenes, de cada uno de ellos.
Preparación del entorno
Unresolved directive in /builds/groovy-lang/101-scripts/src/jbake/content/asciidoctor/catalogo.adoc -
include::{sourcedir}{jbake-script}[tags=prepararEntorno]
Para empezar a trabajar lo primero es cargar las plantillas anteriormente mencionadas(las del producto: product.tpl y la del catálogo general: catalog.tpl), al igual que la querypara la base de datos junto con la condición que requiera el cliente.
El siguiente paso será limpiar todos los posibles ficheros con extensión adoc que podamostener en la ruta de ejecución de nuestro script
Por último inicializaremos nuestra variable engine que nos servirá para tratar lasplantillas.
Carga de información y creación de los productosA continuación pasamos a explicar como cargamos de la base de datos los productos ycomo posteriormente generamos partiendo de un fichero base nuestros productos.
Carga de información
Para ello utilizaremos la función generateProducts() la cual se conecta a la base de datosque le indiquemos, ejecutará la query anteriormente definida y por cada producto llamaráal método createProductAdoc encargado del hacer el .adoc del producto que recibirá porparámetro:
generateProducts
Unresolved directive in /builds/groovy-lang/101-scripts/src/jbake/content/asciidoctor/catalogo.adoc -
include::{sourcedir}{jbake-script}[tags=generateProducts]
Creación de cada producto
Mediante el engine previamente inicializado con la plantilla correspondienteconseguiremos generar un fichero para cada artículo donde se sustituyan las variables de
101 Groovy Script
| 5
la plantilla con los valores del producto en cuestión;
createProductAdoc
Unresolved directive in /builds/groovy-lang/101-scripts/src/jbake/content/asciidoctor/catalogo.adoc -
include::{sourcedir}{jbake-script}[tags=create_adoc]
Construcción del catálogo.Para la construcción del catálogo en sí usaremos la misma técnica que para cadaproducto, utlizando esta vez una plantilla catalog.tpl De esta forma podremosparametrizar el aspecto inicial del catálogo.
Sobre el fichero generado iremos añadiendo directivas asciidoctor para incluir los adocgenerados para cada artículo tal como se detalla en el apartado anterior. El resultado finalserá el fichero catalogo.adoc con tantos includes como artículos hemos obtenido en laconsulta:
generateCatalog
Unresolved directive in /builds/groovy-lang/101-scripts/src/jbake/content/asciidoctor/catalogo.adoc -
include::{sourcedir}{jbake-script}[tags=create_catalog]
PDFPor último nos resta invocar a Asciidoctor para que tomando como base los ficherosgenerados (catalogo y sus includes) nos genere un Pdf catalogo.pdf
cretatePDF
Unresolved directive in /builds/groovy-lang/101-scripts/src/jbake/content/asciidoctor/catalogo.adoc -
include::{sourcedir}{jbake-script}[tags=create_pdf]
① Creamos los atributos que contendrá nuestro fichero asciidoctor. En los cualespodemos indicar el tipo de documento si tendrá o no tabla de contenidos…
② Indicamos que opciones para crear nuestro catálogo entre ellas backend('pdf') ya quees el formato que deseamos obtener
③ Conversión del adoc a pdf
Personalización del pdf
Si quisiéremos personalizar aún más nuestro pdf podemos crear un "tema" para nuestropdf y con ello aumentar las características del mismo. Para ello tenemos que crear unfichero con la extensión yml en el que podemos incluir el tipo de fuente, el tamaño,imágenes de fondo y un largo de etcétera de caraterísticas. Vamos a ver un ejemplo detema:
101 Groovy Script
6 |
title_page: ① align: leftbase: font_family: Times-Roman ② font_size: 12 ③
① Indicamos que el titulo de nuestra imagen está alineado a la izquierda
② La fuente de nuestro texto es Times-Roman
③ El tamaño de letra de es 12
Para que Asciidoctor utilize este tema simplemente hay que indicarlo en los atributos a lahora de su invocación:
asciidoctor = Factory.create(); attributes = AttributesBuilder.attributes().attribute("pdf-style", "tema.yml"). docType('book'). tableOfContents(true). sectionNumbers(true). get()
101 Groovy Script
| 7
Json a PdfJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2018-08-13
"Commit es la conferencia donde nos reunimos a discutir diferentesperspectivas en la forma de hacer y gestionar software. Ven connosotros para vivir dos días compartiendo y aprendiendo todo lo quetiene la tecnología, y por la oportunidad de romper con la rutina yexperimentar algo nuevo."
— CommitConf
Commit es una conferencia anual que cuenta con un agenda de charlas y talleres muyextensa. Este año (2018) son dos días con 12 tracks simulatáneos. Puedes consultar laagenda en https://www.koliseo.com/events/commit-2018/r4p/5630471824211968/agenda
Aunque la página es "responsibe" a veces se hace dificil elegir entre tantas charlas por loque vamos a desarrollar un pequeño script que consumirá la propia agenda en formatoJSON y lo transformará en un PDF agrupando las charlas por rangos horarios además demostrar un listado de speakers al final del documento con las charlas que dará cada uno
En resumen vamos a consumir una estructura de datos pensada para un tipo depresentación y la vamos a transformar en otro formato.
Para ello vamos a utilizar:
• http-builder-ng para consumir el JSON
• asciidoctor para generar el Pdf
ScriptEl script en sí es muy sencillo:
101 Groovy Script
8 |
Cargar dependencias
@Grab(group='io.github.http-builder-ng', module='http-builder-ng-apache', version='1.0.3')
@Grab(group='org.asciidoctor', module='asciidoctorj', version='1.5.6')
@Grab(group='org.asciidoctor', module='asciidoctorj-pdf', version='1.5.0-alpha.16')
@Grab(group='org.jruby', module='jruby-complete', version='9.1.15.0')
import groovyx.net.http.*
import static groovyx.net.http.HttpBuilder.configure
import static groovyx.net.http.ContentTypes.JSON
import static groovy.json.JsonOutput.prettyPrint
import static groovy.json.JsonOutput.toJson
import org.asciidoctor.OptionsBuilder
import org.asciidoctor.AttributesBuilder
import org.asciidoctor.SafeMode
import org.asciidoctor.Asciidoctor.Factory
Recuperar agenda
def http = configure { request.uri = 'https://www.koliseo.com/' request.contentType = JSON[0] request.accept = JSON[0]}.get{ request.uri.path='/events/commit-2018/r4p/5630471824211968/agenda'}
101 Groovy Script
| 9
Transformar
days =[:]speackers =[:]
http.days.each{ day -> def slots = [] day.tracks.each{ track -> track.slots.each{ slot -> if( ['TALK'].contains(slot.contents?.type) ){ def talk = [ title: slot.contents.title, day: "$day.name", when: "$slot.start $slot.end", slot: slot.id, authors: slot.contents.authors.collect{ it.name }.join(',') ] def d = days[day.name] ?: [:] def w = d[talk.when] ?: []
w.add talk d[talk.when]= w days[day.name]=d
slot.contents.authors.each{ author -> def speacker = speackers[author.name] ?: [] speacker.add talk speackers[author.name] = speacker } } } }}
La transformación simplemente itera entre los days del JSON y por cada uno itera por lostracks y de estos por los 'slots`. Para cada slot del tipo TALK crea un mapa con los datosque nos interesa de la charla, como puede ser el titulo y los ponentes.
Como cada charla la pueden dar varios ponentes, para cada una de ellas los buscamos ycreamos un elemento en el mapa de speakers con la información que nos interese delmismo.
101 Groovy Script
10 |
Asciidoctor
file = new File("commit2018.adoc")file.write "= Commit 2018\n"file << "Agenda\n\n\n"file << ":chapter-label:\n"file << "\n"
days.sort().each{ day -> file << "== $day.key\n" day.value.sort{it.key}.each{ file << "=== $it.key\n" it.value.each{ file << "- $it.title ($it.authors)\n\n" } }}
file << "== Speackers\n\n"
speackers.sort{it.key}.each{ file << "=== $it.key\n" it.value.each{ file << "- $it.title\n" file << " $it.day $it.when \n\n" } file << "\n"}
asciidoctor = Factory.create();attributes = AttributesBuilder.attributes(). docType('book'). tableOfContents(true). sectionNumbers(false). sourceHighlighter("coderay"). get()
options = OptionsBuilder.options(). backend('pdf'). attributes(attributes). safe(SafeMode.UNSAFE). get()
asciidoctor.convertFile(new File("commit2018.adoc"), options)
Para generar el PDF vamos a generar un fichero .adoc (texto plano) utilizando la sintáxisde Asciidoctor (título, subtitulo, etc) e invocaremos al conversor utilizando las APIs quenos proporciona.
101 Groovy Script
| 11
Publicación y ScheduleUna vez que tenemos nuestro conversor listo nos gustaría poder compartir el documentopor lo que vamos a utilizar la capacidad de Gitlab de servir contenido estático generadopor nuestro proyecto.
Con alguna pequeña modificación también podrías usar otros productoscomo Github, Bitbucket etc
Para ello crearemos un proyecto en nuestra cuenta de Gitlab y añadiremos además delscript un fichero .gitlab-ci.yml
..gitlab-ci.yml
commit2018: image: groovy stage: build script: - groovy commit2018.groovy - mkdir build - mv commit2018.pdf build artifacts: paths: - build
pages: stage: deploy script: - mkdir public - cp -R build/* public artifacts: paths: - public
Mediante este fichero Gitlab será capaz de ejecutar tu script en sus servidores (utilizará laimagen Docker groovy que especificamos en el job commit2018) y si todo va bien lopublicará en un servidor de contenido estático
Puedes ver el resultado final en este enlace https://jorge-aguilera.gitlab.io/commit2018.pdf
Como la agenda puede sufrir modificaciones utilizaremos la funcion Schedule de Gitlabque nos permite programar de forma recurrente nuestros jobs de tal forma que podremosactualizar diariamente (por ejemplo) el documento.
101 Groovy Script
12 |
Script
//tag::dependencies[]
@Grab(group='io.github.http-builder-ng', module='http-builder-ng-apache', version='1.0.3')
@Grab(group='org.asciidoctor', module='asciidoctorj', version='1.5.6')
@Grab(group='org.asciidoctor', module='asciidoctorj-pdf', version='1.5.0-alpha.16')
@Grab(group='org.jruby', module='jruby-complete', version='9.1.15.0')
import groovyx.net.http.*
import static groovyx.net.http.HttpBuilder.configure
import static groovyx.net.http.ContentTypes.JSON
import static groovy.json.JsonOutput.prettyPrint
import static groovy.json.JsonOutput.toJson
import org.asciidoctor.OptionsBuilder
import org.asciidoctor.AttributesBuilder
import org.asciidoctor.SafeMode
import org.asciidoctor.Asciidoctor.Factory
//end::dependencies[]
//tag::consumir[]
def http = configure {
request.uri = 'https://www.koliseo.com/'
request.contentType = JSON[0]
request.accept = JSON[0]
}.get{
request.uri.path='/events/commit-2018/r4p/5630471824211968/agenda'
}
//end::consumir[]
//tag::transformar[]
days =[:]
speackers =[:]
http.days.each{ day ->
def slots = []
day.tracks.each{ track ->
track.slots.each{ slot ->
if( ['TALK'].contains(slot.contents?.type) ){
def talk = [
title: slot.contents.title,
day: "$day.name",
when: "$slot.start $slot.end",
slot: slot.id,
authors: slot.contents.authors.collect{ it.name }.join(',')
]
def d = days[day.name] ?: [:]
def w = d[talk.when] ?: []
w.add talk
d[talk.when]= w
days[day.name]=d
slot.contents.authors.each{ author ->
101 Groovy Script
| 13
def speacker = speackers[author.name] ?: []
speacker.add talk
speackers[author.name] = speacker
}
}
}
}
}
//end::transformar[]
println "talks:"
days.each{ d->
d.each{
println it
}
}
println "speackers:"
speackers.each{
println it
}
//tag::asciidoctor[]
file = new File("commit2018.adoc")
file.write "= Commit 2018\n"
file << "Agenda\n\n\n"
file << ":chapter-label:\n"
file << "\n"
days.sort().each{ day ->
file << "== $day.key\n"
day.value.sort{it.key}.each{
file << "=== $it.key\n"
it.value.each{
file << "- $it.title ($it.authors)\n\n"
}
}
}
file << "== Speackers\n\n"
speackers.sort{it.key}.each{
file << "=== $it.key\n"
it.value.each{
file << "- $it.title\n"
file << " $it.day $it.when \n\n"
}
file << "\n"
}
asciidoctor = Factory.create();
attributes = AttributesBuilder.attributes().
docType('book').
tableOfContents(true).
sectionNumbers(false).
sourceHighlighter("coderay").
get()
101 Groovy Script
14 |
options = OptionsBuilder.options().
backend('pdf').
attributes(attributes).
safe(SafeMode.UNSAFE).
get()
asciidoctor.convertFile(new File("commit2018.adoc"), options)
//end::asciidoctor[]
101 Groovy Script
| 15
Convertir PowerPoint to HTMLMiguel Rueda <[email protected] [mailto:[email protected]]>2018-04-29
Supongamos que por causas del destino hasta ahora has tenido que trabajar con elformato .pptx (PowerPoint) ya sea para la creación de una presentación propia, para laempresa donde trabajas, etc pero ahora quieres compartirla en internet y todos sabemosque el formato powerpoint NO es el indicado, sino que te gustaría usar HTML para que sepudiera ver en cualquier navedor. Para ello vamos a convertir tu presentación a HTMLusando el framework de presentaciones RevealJS.
En este script vamos a poder leer cada una de las diapositivas del documento y con laayuda de una plantilla defininida crear un fichero .adoc que nos permitirá crear la nuevapresentación utilizando el backend RevealJs de Asciidoctor.
PreparaciónEstos son los plugins que vamos a necesitar:
Grapes
@Grapes([ @Grab(group='org.apache.poi', module='poi', version='3.17' ), @Grab(group='org.apache.poi', module='poi-ooxml', version='3.17' ), @Grab(group='org.asciidoctor', module='asciidoctorj', version='1.5.6' ), @Grab(group='org.jruby', module='jruby-complete', version='9.1.15.0')])
Parsear el PowerPointXMLSlideShow nos va permitir acceder a la presentación pasando como argumento unobjeto tipo FileInputStream que contiene nuestra presentación en formato .pptx.
El siguiente paso es recorrer cada una de las diapositivas extrayendo el texto y la imagen(si la tiene) de cada una de las diapositivas. Si nuestro script encuentra un objeto de tipoXSLFPictureShape será una imagen y creará un png con ella.
Una vez ha terminado de parsear la diapositiva incrementará el fichero slide.adoc con lainformación obtenida de la misma creando un nuevo apartado asciidoctor
101 Groovy Script
16 |
File file = new File("slide.adoc")
XMLSlideShow ppt = new XMLSlideShow(new FileInputStream('titulo.pptx'))
ppt.getSlides().each{slide->
String text = ""
String image=""
slide.getShapes().each{shape->
if (shape instanceof XSLFPictureShape ){
if (shape.getPictureData()){
new File(shape.getPictureData().getFileName()) << shape.getPictureData().getData()
image = "image:${shape.getPictureData().getFileName()}[]"
}
}else{
if (shape.text)
text = shape.text
}
}
file << """
== ${text}
${slide_c.image}
"""
}
RevealJSUna vez creado el fichero .adoc necesitamos convertirlo en formato html con la ayuda delbackend reveal.js y de asciidoctor-reveal.js
Como ambos deben encontrarse descargados usaremos el método dumpRevealJS que seencargará de descargar estos proyectos en los subdiretorios adecuados:
void dumpRevealJS(){
["git", "clone", "https://github.com/hakimel/reveal.js.git"].execute()
["git", "clone", "https://github.com/asciidoctor/asciidoctor-reveal.js.git"].execute()
}
Conversión con AsciidoctorPor último sólo queda la conversión con la ayuda de Asciidoctor y Reveal.js para lo cualusaremos un OptionsBuilder al que vamos a indicar donde se encuentran las "templates"que nos hemos bajado previamente. Así mismo vamos a indicar que usaremos comobackend el revealjs.
Si todo ha ido bien, el script nos habrá creado un slide.html con nuestra presentación.
101 Groovy Script
| 17
void createSlides(){ asciidoctor = Factory.create() options = OptionsBuilder.options(). templateDirs(new File('./asciidoctor-reveal.js/','templates')). backend('revealjs'). inPlace(true). safe(SafeMode.UNSAFE). get()
asciidoctor.convertFile(new File("slide.adoc"), options)}
101 Groovy Script
18 |
Script
//tag::grapes[]
@Grapes([
@Grab(group='org.apache.poi', module='poi', version='3.17' ),
@Grab(group='org.apache.poi', module='poi-ooxml', version='3.17' ),
@Grab(group='org.asciidoctor', module='asciidoctorj', version='1.5.6' ),
@Grab(group='org.jruby', module='jruby-complete', version='9.1.15.0')
])
//end::grapes[]
import org.apache.poi.xslf.usermodel.*
import org.apache.poi.xslf.usermodel.XMLSlideShow
import org.asciidoctor.SafeMode
import org.asciidoctor.OptionsBuilder
import org.asciidoctor.Asciidoctor.Factory
//tag::create[]
void createSlides(){
asciidoctor = Factory.create()
options = OptionsBuilder.options().
templateDirs(new File('./asciidoctor-reveal.js/','templates')).
backend('revealjs').
inPlace(true).
safe(SafeMode.UNSAFE).
get()
asciidoctor.convertFile(new File("slide.adoc"), options)
}
//end::create[]
//tag::load[]
void dumpRevealJS(){
["git", "clone", "https://github.com/hakimel/reveal.js.git"].execute()
["git", "clone", "https://github.com/asciidoctor/asciidoctor-reveal.js.git"].execute()
}
//end::load[]
// limpiamos si hubiera restos anteriores
new File('.').eachFileMatch(~/slide.adoc/) { file ->
file.delete()
}
//tag::each[]
File file = new File("slide.adoc")
XMLSlideShow ppt = new XMLSlideShow(new FileInputStream('titulo.pptx'))
ppt.getSlides().each{slide->
String text = ""
String image=""
slide.getShapes().each{shape->
if (shape instanceof XSLFPictureShape ){
if (shape.getPictureData()){
new File(shape.getPictureData().getFileName()) << shape.getPictureData().getData()
image = "image:${shape.getPictureData().getFileName()}[]"
}
}else{
101 Groovy Script
| 19
if (shape.text)
text = shape.text
}
}
file << """
== ${text}
${slide_c.image}
"""
}
//end::each[]
dumpRevealJS()
createSlides()
println "Creado!!!"
101 Groovy Script
20 |
Conceptos básicosJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2017-08-20
Groovy es muy parecido a Java, de hecho puedes usar código Java y prácticamente seráválido en Groovy. Al igual que en Java, puedes declarar clases, definir herencia, interfaces,etc pero además Groovy aporta algunas funcionalidades y clases muy útiles que le hacenmenos "verbose" que Java.
Una de estas funcionalidades es el poder crear ficheros Scripts que pueden ser ejecutadosdirectamente por Groovy sin necesidad de pasar por todo el proceso de compilación locual le hace muy útil por ejemplo para crear utilidades orientadas a los administradoresde sistemas.
En este post vamos a crear un script básico donde poder ver alguna de las característicasque le hacen especial.
Punto y coma, paréntesis y demásSí, el famoso asunto del punto y coma es lo primero que se comenta cuando empiezas conGroovy. Efectivamente Groovy no necesita que termines las sentencias en ";" pues escapaz de deducirlo por la sintaxis y el contexto. De todas formas si quieres ponerlotampoco le molesta.
Cuando usamos una única función en una línea tampoco es necesario el uso de paréntesiscon lo que ganamos en legibilidad
println("no necesito un punto y coma al final")
println "Yo tampoco, ni los parentesis"
Declaración de tiposLa siguiente batalla que afrontarás será la declaración de tipos o no (tipado estático vstipado dinámico).
Groovy admite ambos tipos de declaración por lo que puedes declarar tus variables yfunciones indicando el tipo de dato, pero disponer de una declaración dinámica de tipospuede hacer tu código mucho más versatil y legible.
Cuando no nos importe el tipo de dato de una variable (o retorno de una función)usaremos def de tal forma que Groovy buscará en tiempo de ejecución si ese objetoimplementa los métodos que indiquemos
101 Groovy Script
| 21
String myString
def unObjeto
unObjeto = "soy un string"
println unObjeto
unObjeto = 10
println unObjeto
Comillas simples y doblesPodemos declarar cadenas usando tanto la comilla simple como la comilla doble, de talforma que podemos incluir una de ellas en la otra.
Además si queremos usar un String multilinea podemos hacerlo mediante el uso de trescomillas dobles evitando el uso de caracteres de escape
println "Necesito una comilla simple ' "println 'Necesito una comilla doble " 'println """Necesito muchas lineasdonde poder ver los retornos de carrosin tanto lio como concatenando String """
String vs GStringGroovy añade la clase GString que es muy similar a String. De hecho en los ejemplosanteriores cuando usabamos la cadena doble en realidad estabamos instanciando unGString.
La utilidad que tiene esta clase es que nos permite insertar código en la cadena que seráevaluado en el momento de utilizar la variable evitando las tipicas concatenaciones decadenas de java:
println "Mira lo que puedo hacer ${2+2} simplemente con el dolar y las llaves"
ClosureMucho antes de que Java implementará las famosas Lambdas, Groovy proporciona elconcepto de closure
101 Groovy Script
22 |
Una closure es un pedazo de código anónimo, en el sentido de que no se declara como unafunción sino que se asigna a una variable. Por lo demás puede recibir parámetros ydevolver un resultado como cualquier función. Podemos invocarla mediante el métodoimplícito call(argumentos) o incluso pasarla como parámetro
def unaClosure = { param -> "${param}".reverse()}
println unaClosure.call("una cadena")println unaClosure.call(10)
Listas, mapas y otros seresDeclarar listas y mapas, asi como asignarlos es trivial
def lista = [1, 3, 4, "Una cadena", new Date() ]List otraLista = [1, 3, 4, "Una cadena", new Date() ]
def mapa = [ clave : "valor", otra_clave: "otro valor"]
BuclesAdemás del típico for(int i=0; i<10;i++){ } y sus derivados, Groovy proporciona algunaforma más de realizar bucles siendo "each", para recorrer todos los elementos de unalista/map, uno de los más usados
lista.each{ println "it es un objeto de la lista $it"}mapa.each{ println "it es un Map.Entry $it.key=$it.value" }
Si necesitamos conocer en cada iteración además del elemento, la posición que ocupa enla lista usaremos "eachWithIndex"
lista.eachWithIndex{ ele, idx -> ① println "posicion $idx, elmenento $ele"}
① La closure recibe dos parámetros
101 Groovy Script
| 23
Script
println("no necesito un punto y coma al final")
println "Yo tampoco, ni los parentesis"
String myString
def unObjeto
unObjeto = "soy un string"
println unObjeto
unObjeto = 10
println unObjeto
println "Necesito una comilla simple ' "println 'Necesito una comilla doble " 'println """Necesito muchas lineasdonde poder ver los retornos de carrosin tanto lio como concatenando String """
println "Mira lo que puedo hacer ${2+2} simplemente con el dolar y las llaves"
def unaClosure = { param -> "${param}".reverse()}
println unaClosure.call("una cadena")println unaClosure.call(10)
def lista = [1, 3, 4, "Una cadena", new Date() ]List otraLista = [1, 3, 4, "Una cadena", new Date() ]
def mapa = [ clave : "valor", otra_clave: "otro valor"]
lista.each{ println "it es un objeto de la lista $it"}mapa.each{ println "it es un Map.Entry $it.key=$it.value" }
lista.eachWithIndex{ ele, idx -> ① println "posicion $idx, elmenento $ele"}
101 Groovy Script
24 |
CliBuilderMiguel Rueda <[email protected] [mailto:[email protected]]>2017-08-30
Se puede dar la casuística de tener un script que realiza unas determinadas funciones quequeremos parametrizar.
La opción más fácil es usar la variable args ímplicita en el script y que es un array deString que contiene los parámetros que se pasan tras el nombre del fichero.
Sin embargo cuentas también con CliBuilder, una utilidad de Groovy que te permitehacer que los argumentos que se le pueden pasar a un script sean más explícitos.
Un script muy básico utilizando esta herramienta podría ser el siguiente:
101 Groovy Script
| 25
def cli = new CliBuilder(usage: 'groovy CliBuilder.groovy -[habcd]')
cli.with { ①
h(longOpt: 'help', 'Usage Information \n', required: false)
a(longOpt: 'Option a','Al seleccionar "a" pinta seleccionada -> a ', required: false)
b(longOpt: 'Option b','Al seleccionar "b" pinta seleccionada -> b ', required: false)
c(longOpt: 'Option c','Al seleccionar "c" pinta seleccionada -> c ', required: false)
d(longOpt: 'Option d','Al seleccionar "d" pinta seleccionada -> d ', required: false)
}
def options = cli.parse(args)
if (!options) {
return
}
if (options.h) { ②
cli.usage()
return
}
if (options.a) { ③
println "------------------------------------------------------------------"
println "Seleccionada ha sido la 'a'"
println "------------------------------------------------------------------"
}
if (options.b) {
println "------------------------------------------------------------------"
println "Seleccionada ha sido la 'b'"
println "------------------------------------------------------------------"
}
if (options.c) {
println "------------------------------------------------------------------"
println "Seleccionada ha sido la 'c'"
println "------------------------------------------------------------------"
}
if (options.d) {
println "------------------------------------------------------------------"
println "Seleccionada ha sido la 'd'"
println "------------------------------------------------------------------"
}
① Definimos los parámetros que vamos a necesitar.
② Mostrará la leyenda de nuestro comando.
③ Esta parte se ejecutará al mandar como parametro -a.
Si por ejemplo llamamos a nuestro script pasando el parámtro -h obtendremos la leyendade nuestro comando:
101 Groovy Script
26 |
groovy clibuilder_ebook.groovy -husage: clibuilder_ebook.groovy -[habcd] -a,--Option a Al seleccionar "a" pinta seleccionada -> a -b,--Option b Al seleccionar "b" pinta seleccionada -> b -c,--Option c Al seleccionar "c" pinta seleccionada -> c -d,--Option d Al seleccionar "d" pinta seleccionada -> d -h,--help Usage Information
101 Groovy Script
| 27
Script
def cli = new CliBuilder(usage: 'groovy CliBuilder.groovy -[habcd]')
cli.with { ①
h(longOpt: 'help', 'Usage Information \n', required: false)
a(longOpt: 'Option a','Al seleccionar "a" pinta seleccionada -> a ', required: false)
b(longOpt: 'Option b','Al seleccionar "b" pinta seleccionada -> b ', required: false)
c(longOpt: 'Option c','Al seleccionar "c" pinta seleccionada -> c ', required: false)
d(longOpt: 'Option d','Al seleccionar "d" pinta seleccionada -> d ', required: false)
}
def options = cli.parse(args)
if (!options) {
return
}
if (options.h) { ②
cli.usage()
return
}
if (options.a) { ③
println "------------------------------------------------------------------"
println "Seleccionada ha sido la 'a'"
println "------------------------------------------------------------------"
}
if (options.b) {
println "------------------------------------------------------------------"
println "Seleccionada ha sido la 'b'"
println "------------------------------------------------------------------"
}
if (options.c) {
println "------------------------------------------------------------------"
println "Seleccionada ha sido la 'c'"
println "------------------------------------------------------------------"
}
if (options.d) {
println "------------------------------------------------------------------"
println "Seleccionada ha sido la 'd'"
println "------------------------------------------------------------------"
}
101 Groovy Script
28 |
Ejecutar comandosMiguel Rueda <[email protected] [mailto:[email protected]]>2017-08-31
Es muy común en el día a día que tengamos que ejecutar comandos de shell de formarepetida y en función de la respuesta del mismo optar por realizar acciones, como porejemplo ejecutar un comando u otro, etc. Estos comandos van desde un simple listado deficheros ( dir ), copiar ficheros ( cp, copy ) a otros más elaborados.
Así mismo numerosas veces debemos ejecutar esos comandos de forma repetida (una vezpor cada directorio, por cada fichero, etc) e incluso condicional (si la invocación de estecomando ha ido bien realizar estas acciones y si no estas otras). Para ello solemos recurrira ficheros por lotes ( .bat en Windows, .sh en Linux ) donde podemos tratar los problemascomentados anteriormente.
En este entorno, Groovy nos ofrece poder ejecutar comandos con la ayuda del método.execute() de la clase String y tratar la salida de este como si fuera una cadena, utilizandotoda la potencia del lenguaje.
Comando simpleSupongamos que en un sistema *NIX quisieramos realizar un listado de los ficheros de undirectorio y mostrar la salida en mayúsculas. Nuestro script sería:
String resultado = "ls -lt ".execute().textprintln resultado.toUpperCase()
Como podemos observar, simplemente tenemos que construir un String con el comando yllamar a su método execute() Este método nos devuelve un objeto que entro otras cosasnos ofrece la salida del comando como una cadena mediante la property text quepodemos asignar a una variable
Esperar finalizaciónSi lo que necesitamos es lanzar un comando y esperar a que termine para lanzar de nuevootro comando u otra acción se puede realizar de la siguiente manera:
101 Groovy Script
| 29
def resultado = new StringBuilder() ①def error = new StringBuilder()
def comando = "ls -lt".execute() ②comando.consumeProcessOutput(resultado, error) ③comando.waitForOrKill(1000) ④
if (!error.toString().equals("")) ⑤ println "Error al ejecutar el comando"else{ println "Ejecutado correctamente" println resultado ⑥
}
① Definimos la variables donde volcaremos el resultado de nuestro execute.
② Ejecutamos el comando.
③ Obtenemos la salida del comando resultado de su ejecución.
④ Establecemos un time out a nuestro comando.
⑤ En error obtendremos la salida en caso de que falle nuestro comando y en caso defuncionar correctamente nuestro valor se guardará en resultado.
⑥ El resultado es correcto podemos continuar.
101 Groovy Script
30 |
Script
def resultado = new StringBuilder() ①def error = new StringBuilder()
def comando = "ls -lt".execute() ②comando.consumeProcessOutput(resultado, error) ③comando.waitForOrKill(1000) ④
if (!error.toString().equals("")) ⑤ println "Error al ejecutar el comando"else{ println "Ejecutado correctamente" println resultado ⑥
}
101 Groovy Script
| 31
Llamadas entre scriptsMiguel Rueda <[email protected] [mailto:[email protected]]>2017-08-31
El caso más fácil de explicar es cuando tenemos varios scripts en los cuales hay unafunción/acción que se repite en cada uno de ellos y cada vez que creamos un nuevo scriptdebemos incluir. Está acción repetitiva puede ir desde la conexión a una base de datos,exportar a excel o simplemente contener los datos de conexión a la base de datos.
A continuación vamos a crear un script llamado Configuration.groovy el cual contendrátoda la infomación necesaria para conectarnos a una base de datos de nuestro entorno.Esta configuración podrá ser llamada desde cualquier de nuestros scripts, por lo tanto deesta manera evitaremos tener que copiar y pegar esa parte de código para como en estecaso a una base de datos de nuestro contexto.
Configuration.groovy
import Sql
class Configuration {
String user = "user" ①
String passwd = "passwd" ②
String server = "localhost" ③
String database = "test" ④
String url = "jdbc:mysql://$server:3306/$database?jdbcCompliantTruncation=false" ⑤
def instanceMysql(){ ⑥
return Sql.newInstance( "jdbc:mysql://$server:3306/$database?jdbcCompliantTruncation=false",
"$user", "$passwd", "com.mysql.jdbc.Driver")
}
def instanceMysql(my_user,my_passwd,my_server,my_database){ ⑦
return Sql.newInstance(
"jdbc:mysql://$my_server:3306/$my_database?jdbcCompliantTruncation=false", "$my_user", "$my_passwd",
"com.mysql.jdbc.Driver")
}
}
① Método que nos devuelve usuario para conectarnos.
② Método que nos devuelve la contraseña.
③ Método que nos devuelve el servidor donde nos vamos conectar.
④ Método que nos devuelve la base de datos.
⑤ Método que nos devuelve la url para conectarnos.
⑥ Método que nos devuelve una instancia mysql con los datos ya definidos.
⑦ Método que nos devuelve una instancia mysql con los datos enviados por parámetro.
Para obtener una instancia podemos realizarlo de la siguiente manera:
101 Groovy Script
32 |
Script.groovy
def sql = Configuration.instanceMysql()
O con los datos de conexión enviados por parámetro:
OtroScript.groovy
def my_user = "user",def my_passwd = "passwd",def my_server ="localhost"def database = "test"def my_sql = Configuration.instanceMysql(my_user,my_passwd,my_server,my_database)
101 Groovy Script
| 33
Script
import Sql
class Configuration {
String user = "user" ①
String passwd = "passwd" ②
String server = "localhost" ③
String database = "test" ④
String url = "jdbc:mysql://$server:3306/$database?jdbcCompliantTruncation=false" ⑤
def instanceMysql(){ ⑥
return Sql.newInstance( "jdbc:mysql://$server:3306/$database?jdbcCompliantTruncation=false",
"$user", "$passwd", "com.mysql.jdbc.Driver")
}
def instanceMysql(my_user,my_passwd,my_server,my_database){ ⑦
return Sql.newInstance(
"jdbc:mysql://$my_server:3306/$my_database?jdbcCompliantTruncation=false", "$my_user", "$my_passwd",
"com.mysql.jdbc.Driver")
}
}
101 Groovy Script
34 |
WithBatchMiguel Rueda <[email protected] [mailto:[email protected]]>2017-08-31
A veces nos surge que tenemos que extraer los datos de una base de datos e insertarlas enotra base de datos (o tabla). Si simplemente fuera una extracción y una carga tu motor dedatos probablemente incluya alguna herramienta import/export, pero si tienes querealizar alguna transformación en los registros la cosa ya se complica.
Además realizar un insert por cada registro probablemente no sea la forma más óptimade cargar los datos pues cada insert realiza una transacción con su consiguiente coste.
Mediante este script vamos a leer los datos de una tabla, realizar una transformación a uncampo e insertar los registros en modo batch
Fijate que el número de registros a incluir en cada batch se puede indicar de una formasimple mediante un argumento en la llamada withBatch
@Grab('mysql:mysql-connector-java:5.1.6')①
@GrabConfig(systemClassLoader=true)
import groovy.sql.Sql
def sql_orig = Sql.newInstance( "jdbc:mysql://localhost:3306/origen?jdbcCompliantTruncation=false", "user",
"password", "com.mysql.jdbc.Driver")②
def sql_dest = Sql.newInstance( "jdbc:mysql://localhost:3306/destino?jdbcCompliantTruncation=false", "user",
"password", "com.mysql.jdbc.Driver")
batchSize=20
sql_dest.withBatch( batchSize, "insert into TABLE_DESTINO(a,b,c) values(?,?,?)"){ ps-> ③
sql_orig.eachRow"select a,b,c from TABLE_ORIGEN",{ row -> ④
row.a = row.a.toUpperCase().reverse() ⑤
ps.addBatch(row) ⑥
}
}
⑦
① Usamos Grape para cargar las dependencias
② Definimos la conexión con las bases de datos.
③ Preparamos un insert en modo batch de batchSize elementos
④ Leemos la tabla origen. Podemos usar sort, where, etc
⑤ Hacemos nuestra transformacion de negocio, por ejemplo, pasar a mayuscula yrevertir la cadena
⑥ Vamos insertando en el batch. Cada batchSize elementos la closure los volcara en labbdd
⑦ Al finalizar la lectura todas las closures realizan el cerrado de recursos
101 Groovy Script
| 35
Script
@Grab('mysql:mysql-connector-java:5.1.6')①
@GrabConfig(systemClassLoader=true)
import groovy.sql.Sql
def sql_orig = Sql.newInstance( "jdbc:mysql://localhost:3306/origen?jdbcCompliantTruncation=false", "user",
"password", "com.mysql.jdbc.Driver")②
def sql_dest = Sql.newInstance( "jdbc:mysql://localhost:3306/destino?jdbcCompliantTruncation=false", "user",
"password", "com.mysql.jdbc.Driver")
batchSize=20
sql_dest.withBatch( batchSize, "insert into TABLE_DESTINO(a,b,c) values(?,?,?)"){ ps-> ③
sql_orig.eachRow"select a,b,c from TABLE_ORIGEN",{ row -> ④
row.a = row.a.toUpperCase().reverse() ⑤
ps.addBatch(row) ⑥
}
}
⑦
101 Groovy Script
36 |
De Properties a YMLJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2018-03-07
Cuando tu script es sencillo y requiere unos pocos parámetros de configuraciónprobablemente te sea suficiente con interpretar la linea de comando, usando por ejemploCliBuilder . Sin embargo cuando la configuración se vuelve más compleja esta opción noresulte cómoda y la opción más obvia es utilizar un fichero properties de Java donde cadalínea es un clave=valor.
Aunque es una opción válida y muy usada resulta muy oscura y propensa al error. En estepost vamos a ver lo sencillo que es utilizar un formato más amigable para el humano yque permite expresar mucho mejor configuraciones "de niveles" llamado YAMLhttps://es.wikipedia.org/wiki/YAML
Mediante este formato podemos escribir no sólo pares de clave,valor sino expresar arrays,mapas, etc Por ejemplo una configuración típica para conexiones a base de datos puedeser como la siguiente:
# config jerarquizadadataSources: development: url: jdbc:mysql:localhost://desa username: user password: pwd production: url: jdbc:mysql:dataserver://prod username: sasdfoi123k password: asfd9.dslsd0
# array de [username,password]logins: - username: pp password: PP
- username: otro password: pazzz
En este ejemplo simplemente leemos un fichero de texto y si no está bien formado elparseador generará una excepción. Una vez cargado el fichero podemos navegar a travésdel mapa usando las características de groovy:
• comprobar si una clave existe con el operador '?'
• recorrer un array con each o eachWithIndex
101 Groovy Script
| 37
@Grab('org.yaml:snakeyaml:1.17')import org.yaml.snakeyaml.Yaml
Yaml parser = new Yaml()
config = parser.load( new File('config_script.yml').text )
println config.doesntExists ?: "doesnExists doesn't exists"
println config.dataSources?.development?.url
println config.dataSources?.production?.url
config.logins.eachWithIndex{ user, idx-> println "index $idx:" println "$user.username = $user.password"}
101 Groovy Script
38 |
Script
@Grab('org.yaml:snakeyaml:1.17')import org.yaml.snakeyaml.Yaml
Yaml parser = new Yaml()
config = parser.load( new File('config_script.yml').text )
println config.doesntExists ?: "doesnExists doesn't exists"
println config.dataSources?.development?.url
println config.dataSources?.production?.url
config.logins.eachWithIndex{ user, idx-> println "index $idx:" println "$user.username = $user.password"}
101 Groovy Script
| 39
Consumo de recursos con DSLsJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2018-08-26
Hace unos días surgió en un conversación si había comprobado el consumo de recursosen una aplicación que hiciera un uso intensivo de DSL (Domain Specific Language)porque mi interlocutor estaba seguro que había perdidas de memoria incluso reportadasy sin solución. Así que me puse manos a la obra para comprobarlo
Primer intentoEl primer intento de comprobarlo fue mediante este simple script
(0..100000).each{idx->
new GroovyShell().parse("println 0")
if( idx%100 == 0) { System.gc(); sleep 100}}
y utilizando jconsole comprobé alarmado que era verdad: El consumo de memoriacrecía sin parar.
Entonces me fijé en un pequeño detalle: el número de clases cargadas también crecía!!
Efectivamente: cada vez que invocamos a parse Groovy compila el texto y genera unaclase nueva que es cargada y no se libera porque … no es una instancia de un objeto, escódigo!!.
Segundo intentoEl segundo intento fue entonces parsear una sóla vez el script y mantener su referencia:
101 Groovy Script
40 |
dsl = new GroovyShell().parse("println 0")
void executeScript(){ dsl.run()}
(0..100000).each{idx->
executeScript()
if( idx%100 == 0) { System.gc(); sleep 100}}
Sin embargo, aunque en menor medida, seguía teniendo el mismo problema de no liberarrecursos … hasta que aumenté a 1 segundo el sleep y entonces empecé a comprobar queel consumo de recursos fluctuaba pero en un rango estable.
Base de datos de DSLsSi nuestra aplicación va a tener que ejecutar miles de veces diferentes scripts/dsls y notenemos en cuenta esta situación nos encontraremos con que al cabo del tiempo nuestraaplicación habrá consumido todos los recursos y tendremos problemas. Así pues unaposible solución es mantener un repositorio de scripts donde nuestra responsabilidad seabuscar si el código fuente ya ha sido compilado y utilizar el Script asociado
En este pequeño ejemplo implementamos esta idea:
Creamos al inicio una lista de posibles Scripts a ejecutar y en un Map asociamos cadaString con su Script de tal forma que cuando queremos ejecutar uno de ellos, lo buscamosen este Map.
101 Groovy Script
| 41
dsls = [
"println new Date()",
"println 1",
"""
println new Random().with {(1..9).collect {(('a'..'z')).join()[
nextInt((('a'..'z')).join().length())]}.join()}
"""
]
database = [:]
dsls.each{
database[it] = new GroovyShell().parse(it)
}
void executeDSL( int idx ){
database[ dsls[idx] ].run()
}
// wait to jconsole
sleep 1000*10
// run a lot of times
(0..100000).each{
executeDSL( (it % dsls.size()) )
if( (it % 1000) == 0) {
sleep 2000
println "liberando "
System.gc()
sleep 2000
}
}
Utilizando jconsole podemos comprobar que el consumo de recursos se mantiene estable:
101 Groovy Script
42 |
Lógicamente estos scripts son muy simples y no son parametrizables por lo que quedacomo ejercicio para el lector implementar una posible solución más completa
101 Groovy Script
| 43
Script
dsls = [
"println new Date()",
"println 1",
"""
println new Random().with {(1..9).collect {(('a'..'z')).join()[
nextInt((('a'..'z')).join().length())]}.join()}
"""
]
database = [:]
dsls.each{
database[it] = new GroovyShell().parse(it)
}
void executeDSL( int idx ){
database[ dsls[idx] ].run()
}
// wait to jconsole
sleep 1000*10
// run a lot of times
(0..100000).each{
executeDSL( (it % dsls.size()) )
if( (it % 1000) == 0) {
sleep 2000
println "liberando "
System.gc()
sleep 2000
}
}
101 Groovy Script
44 |
Empaquetado ScriptsJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2017-11-14
En este artículo vamos a tratar cómo podemos agrupar nuestros scripts en un jar de talforma que sean accesibles al resto del equipo (o del mundo) de una forma cómoda ysencilla.
Como puedes ver, en el resto de artículos tratamos scripts sueltos que cumplen unafunción (buscar ficheros, transformar datos, invocar servicios web, etc) pero puede darsela situación que todos, o parte de ellos, sean de utilidad para ciertos usuarios en diferentessituaciones y queremos que puedan utilizarlos bien sea ejecutándolos directamente obien como base para otros scripts.
Así por ejemplo podríamos tener las siguientes situaciones:
• Scripts de utilidad directa
• Scripts base destinados a ser personalizados (extendidos) por otros
• Scripts con lógica de negocio que nos interesa ocultar.
• Scripts que dependen de otros para su ejecución correcta
• etc
Directorio compartidoLa primera solución que se nos presenta para poder reutilizar nuestros scripts esubicarlos en una carpeta compartida, bien sea en una carpeta de red por ejemplo SAMBAo bien en una carpeta local de una máquina a la que podemos acceder.
La ventaja de esta solución obviamente es la sencillez con el añadido que si dicha carpetase encuentra versionada (SVN, Git, etc) es muy fácil tenerla actualizada
Obviamente una desventaja de estos scripts es que estarán restringuidos a ejecutarse enel entorno de esta máquina. Es decir si por ejemplo tenemos un script que escanea undirectorio buscando un fichero determinado para trabajar con él sólo tendría sentidobuscarlo en esta máquina y no en la nuestra por ejemplo.
Alojado en SVN/GitOtra opción es disponer de un SCM (source control manager) tipo subversion SVN, git osimilar donde los usuarios puedan clonarse el proyecto en sus máquinas y ejecutarlosdesde ellas.
Así simplemente mediante comandos de de actualización como por ejemplo git pullrefrescaríamos el directorio local con la última versión y podríamos ejecutar el comando.
101 Groovy Script
| 45
Sin embargo, a parte de que necesitamos instalar el control de versiones en la máquinadonde queramos ejecutar los scripts, tendríamos que revisar que hacemos el pull deforma habitual etc
Servidor HTTPGracias a la capacidad de Groovy de poder ejecutar scripts en URL remotas podemoshacer que los scripts se alojen en un servidor web (Apache, Nginx, o similar) y que losusuarios los invoquen vía http
groovy http://groovy-lang.gitlab.io/101-scripts/scripts/office/ExtractPdf.groovy
https://www.boe.es/boe/dias/2017/09/21/pdfs/BOE-B-2017-54046.pdf
Como podemos ver en este ejemplo sólo necesitamos tener Groovy instalado y ejecutarscripts que residen en un servidor web a la vez que le pasamos argumentos
JarUtilizando la capacidad de Groovy de poder ejecutar scripts contenidos en un zip/jarpodemos agrupar nuestros ficheros con un simple comando:
java cvf mis_scripts.jar *.groovy ①groovy jar:file:mis_scripts.jar'!'/FindFile.groovy ②
① Empaquetamos nuestros scripts en un jar (lo que viene siendo un zip)
② Mediante esa URI conseguimos ubicar nuestro script dentro del jar y así poderejecutarlo
Publicando JarSi disponemos de un servidor Maven (o una cuenta en MavenCentral, Bintray, etc)podremos así mismo publicar este jar para poder usarlo mediante @Grape en otrosscripts.
En este apartado vamos a explicar cómo preparar un proyecto Gradle que nos permitagenerar el Jar y publicarlo en Bintray para que el resto de nuestros scripts puedan usarlos(por lo que deberás tener una cuenta creada en Bintray)
Estructura directorios
Establece una estructura de directorios como el de la imagen. Nuestros scripts seubicarán en el directorio resources
101 Groovy Script
46 |
+-------------+| mis_scripts |+-------------+ | | +-----+ +>| src | +-----+ | +------+ +--->| main | +------+ | +-----------+ +--->| resources | +-----------+
Gradle
En el directorio raiz mis_scripts copia el siguiente fichero:
build.gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'
}
}
apply plugin: 'groovy'
apply plugin: 'java-library-distribution'
apply plugin: 'maven'
apply plugin: 'maven-publish'
apply plugin: 'com.jfrog.bintray'
repositories {
mavenCentral()
}
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.4.12'
testCompile 'junit:junit:4.12'
}
javadoc {
title = "$project.description $project.version API"
}
task sourceJar(type: Jar, dependsOn: classes) {
classifier = 'sources'
from sourceSets.main.allSource
}
task javadocJar(type: Jar) {
classifier = "javadoc"
from javadoc
101 Groovy Script
| 47
}
distZip.shouldRunAfter(build)
publishing {
publications {
maven(MavenPublication) {
artifactId 'mis-scripts' ①
from components.java
artifact sourceJar {
classifier "sources"
}
}
}
}
bintray {
user = System.getenv("BINTRAY_USER") ?: project.hasProperty("bintrayUser") ? project.bintrayUser : '' ②
key = System.getenv("BINTRAY_KEY") ?: project.hasProperty("bintrayKey") ? project.bintrayKey : ''
publications = ['maven']
publish = true
pkg {
repo = "mi-repositorio" ③
userOrg = project.hasProperty('userOrg') ? project.userOrg : 'mi-organization' ④
name = "mis-scripts"
desc = "Groovy Scripts"
websiteUrl = "https://tuwebsite.com"
licenses = ['Apache-2.0']
publicDownloadNumbers = true
version {
name = project.version
}
}
}
① Indica el nombre del artefacto que quieres publicar en Bintray
② Configuración de user/token por variable de entorno, gradle.properties o valor pordefecto
③ El nombre del repositorio donde quieres publicar en Bintray (tienes que haberlocreado antes en Bintray)
④ El nombre de tu organización (tienes que haberlo creado antes en Bintray)
Publicando
Para empaquetar y publicar tu artefacto simplemente ejecuta:
gradle bintrayUpload
Si todo va bien, se habrá creado tu jar con los scripts embebidos en él y se habrá subido aBintray
"Grapeando"
A partir de aqui nuestro artefacto es accesible mediante cualquier gestor de dependenciascomo puede ser Maven o Gradle y por supuesto Grape por lo que podremos crear nuevos
101 Groovy Script
48 |
scripts que dependan de nuestro artefacto simplemente poniendo las "coordenadas"(repositorio, organización, artefacto y versión) oportunas en el script
101 Groovy Script
| 49
Extraer una Table HTML a CSVJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2018-06-24
Este sencillo script parsea una página HTML de Internet que contenga una tabla (ennuestro caso identificada por un atributo id pero es fácil adaptarlo a otros casos de uso) yla volcará a un fichero en formato csv
@Grab('org.ccil.cowan.tagsoup:tagsoup:1.2.1')import org.ccil.cowan.tagsoup.Parser
new File(args[1]).withWriter('UTF-8') { writer -> def slurper = new XmlSlurper(new Parser()) def url = args[0] def htmlParser = slurper.parse(url) htmlParser.'**'.find {it.@id==args[3] }.tr.each{ writer.writeLine it.td.collect{"$it".trim()}.join(';') }}
Básicamente el script solicita tres argumentos:
• URL a parsear
• Nombre del fichero a generar
• Identificador de la tabla a descargar
Una vez descargada la página y parseada, buscará el elemento que coincida con el idproporcionado y recorrerá todos sus elementos tr
Para cada elemento tr que encuentre generará una única línea concatenando todos loscampos td que contenga
101 Groovy Script
| 51
Script
@Grab('org.ccil.cowan.tagsoup:tagsoup:1.2.1')import org.ccil.cowan.tagsoup.Parser
new File(args[1]).withWriter('UTF-8') { writer -> def slurper = new XmlSlurper(new Parser()) def url = args[0] def htmlParser = slurper.parse(url) htmlParser.'**'.find {it.@id==args[3] }.tr.each{ writer.writeLine it.td.collect{"$it".trim()}.join(';') }}
101 Groovy Script
52 |
Instalación y primeros pasosMiguel Rueda <[email protected] [mailto:[email protected]]>2017-08-02
Groovy es un lenguaje de programación dinámico para la máquina virtual de java (JavaVirtual Machine). Esto quiere decir que el código que genera puede ser ejecutado por elRuntime de Java y por ello se requiere tener instalado Java 6+ (recomendado Java 8 y muypronto Java 9)
Usando los binariosSimplemente hay que descargarse los binarios desde la página oficial, descomprimirlo enun directorio a nuestro antojo y ajustar las variables de entorno GROOVY_HOME y PATH
WindowsExiste un instalador de Windows basado en NSIS que nos permitirá instalar y desinstalarGroovy en nuestra máquina
Maven (y similares)Es posible también embeber Groovy en nuestros proyectos utilizando los artefactos:
<groupId>org.codehaus.groovy</groupId><artifactId>groovy</artifactId><version>2.4.12</version>
SdkManPara incorporar la herramienta groovy recomandamos instalar SdkMan (http://sdkman.io/). Este software es muy práctico tanto para instalar como para manejarlas diferentes versiones de las herramientas instaladas.
Con el sdkman en nuestro sistema realizar desde la consola.
sdk install groovy
ComprobaciónUna vez finalizado el comando de instalación pasaremos a comprobar que tenemos elgroovy preparado.
101 Groovy Script
| 53
groovy -version
GroovyConsole
Ya podemos lanzar nuestra primera consola de groovy
groovyConsole
En pocos segundos se abrirá nuestra consola de groovy donde podemos empezar atrabajar con los primeros scripts
Para realizar una prueba sencilla podemos ejecutar nuestro primer "Hola Mundo":
HolaMundo.groovy
println "Hola mundo"
Mediante el menú de GroovyConsole podemos ejecutar el script y visualizar el resultadoen el panel inferior.
101 Groovy Script
54 |
Consola
La consola de groovy es muy útil cuando estamos desarrollando nuestro script porque nospermite ejecutarlo, corregirlo y volver a ejecutarlo en un mismo sitio. Sin embargotambién podemos usar nuestro editor de texto preferido (que no es el Word) como puedeser Notepad, Notepad++, gedit, vi, emacs, etc y ejecutarlo desde consola (o embebido en unbash por ejemplo)
groovy HolaMundo.groovy
Groovy Web ConsolePor último, también puedes probar a utilizar la consola on-linehttps://groovyconsole.appspot.com/ la cual te permitirá ejecutar algunos scripts sencillos(además de compartirlos de forma pública)
101 Groovy Script
| 55
Métodos últiles para trabajar con listasMiguel Rueda <[email protected] [mailto:[email protected]]>2017-11-29
Tanto si son tus primero pasos con groovy como si llevas tiempo disfrutando con él seguroque has podido trabajar con arrays y has podido ver el gran potencial que poseen.
Estos objetos los puedes encontrar diariamente en tu desarrollo de diferentes maneraspueden ser: los datos que devuelve una consulta sql, un criteria, un JSON o un simplefichero de configuración fuera de tu aplicaciones que contiene una lista con parámetrosde configuración… Es decir, te va tocar sí o sí trabajar con ellas por lo tanto es siempre útilconocer métodos simples para trabajar con ellas de la manera más simple y eleganteposible.
A continuación vamos a ver algunos métodos prácticos y básicos para trabajar con listas:
Recorrer una lista con hilos.
Para ello debemos utilizar el plugin:
@Grab(group='org.codehaus.gpars', module='gpars', version='1.0.0')
Una vez realizado será tan sencillo como:
withPool(10) { lista.eachParallel{l-> println l } }
Con withPool indicamos que queremos que se ejecute en hilos, en este caso del 10 en 10 ycon eachParallel que los lance en paralelo.
De dos listas obtener los valores semejantes.
Imaginemos que tenemos dos listas y de las cuales solo no queremos quedar con losvalores iguales para ello tenemos:
lista_1.intersect(lista_2)
En este método lo que hacemos el simplemente recorrer una de las listas y para cadamapa buscar en la segunda lista si existe una mapa con el mismo key y de ser así losguardamos en otra lista donde al final quedarás los objetos con el mismo key.
De dos listas obtener los valores diferentes.
101 Groovy Script
| 57
En este caso queremos que además de diferente key el value del map también seadiferente. Podíamos emplear el método anterior utilizando simplemente un !row en el if yen método find incluir el value pero vamos a realizarlo de una manera diferente:
def commons = lista_1.intersect(lista_2) def diff_part1 = lista_1.plus(lista_2) diff_part1.removeAll(commons)
El método intersect nos proporciona los elementos iguales (tanto key como value), queestán en ambas listas. Este método no nos serviría para el caso anterior ya que en élpedíamos que tuvieran sólo la misma key.
Con el método plus lo que conseguimos en crear una nueva lista con los valores de laoriginal más la lista que pasamos por parámetro.
Y finalmente con removeAll eliminamos la lista de valores comunes entre las dos listas
Ordenar una lista
def list_sort = list.sort{it."${criterio}"}
En este caso groovy nos ofrece el método sort al cual le pasaremos por parámetro porcual criterio queremos ordenar y nos devolverá la lista con el formato indicado.
Agrupar una lista
def list_group = list.groupBy{it."${criterio}"}
Para realizar la agrupación por un criterio dentro de nuestra lista será tan simple comoutilizar el groupby indicando por cual criterio deseamos realizar esta acción.
Eliminar valores de una lista
def list_rm = list.removeAll {it."${criterio}" == 1}
Al igual que en los otros casos utilizaremos el método en cuestión en este caso removeAllpasando por parámetro el valor que queremos eliminar.
Obtener valores no repetidos
def list_unique = list.unique()
Con esta funcionalidad lo que obtenemos es una lista de valores sin duplicidadesobteniendo una array con valores únicos. Para emplearlo simpletemente utilizaremos elmétodo unique().
101 Groovy Script
58 |
Script
//tag::grab[]@Grab(group='org.codehaus.gpars', module='gpars', version='1.0.0')//end::grab[]@GrabConfig(systemClassLoader=true)
import static groovyx.gpars.GParsPool.withPool
def lista_1 = [ [id:1,value:5], [id:2,value:8], [id:3,value:4], [id:4,value:1], [id:5,value:2]]def lista_2 = [ [id:11,value:5], [id:12,value:18], [id:13,value:14], [id:14,value:11], [id:15,value:12]]def lista_3 = [ [id:1,value:15], [id:2,value:8], [id:3,value:4], [id:4,value:1], [id:5,value:2]]def lista_4 = [ [id:1,value:1], [id:1,value:1], [id:2,value:2], [id:2,value:2], [id:5,value:2]
]def lista_5 = [ [id:1,value:1], [id:1,value:11], [id:2,value:2], [id:2,value:21], [id:5,value:2]
]def recorrerListaHilos(lista){ //tag::withPool[] withPool(10) {
101 Groovy Script
| 59
lista.eachParallel{l-> println l } } //end::withPool[]}def unirValoresIgualesPorKey(lista_1,lista_2){ //tag::unir[] def list_all = [] lista_1.each{a-> def row = lista_2.find{it.key == a.key} if (row) list_all << row } //end::unir[] return list_all}def mostrarDiferencias(lista_1, lista_2){ //tag::diff[] def commons = lista_1.intersect(lista_2) def diff_part1 = lista_1.plus(lista_2) diff_part1.removeAll(commons) //end::diff[] return diff_part1}def unirValoresIguales(lista_1,lista_2){ //tag::iguales[] lista_1.intersect(lista_2) //end::iguales[] return lista_1.intersect(lista_2)}def ordenarPor(list,criterio){ //tag::sort[] def list_sort = list.sort{it."${criterio}"} //end::sort[] return list_sort}def agruparPor(list,criterio){ //tag::groupby[] def list_group = list.groupBy{it."${criterio}"} //end::groupby[] return list_group}def obtenerValoresUnicos(list){ //tag::unique[] def list_unique = list.unique() //end::unique[] return list_unique}def eliminarValores(list,criterio){
101 Groovy Script
60 |
//tag::remove[] def list_rm = list.removeAll {it."${criterio}" == 1} //end::remove[] return list_rm}
101 Groovy Script
| 61
Contar registros de base de datosJorge Aguilera, [email protected] [mailto:jorge.aguilera@puravida-
software.com] 2017-08-26
Supongamos que tenemos una tabla en una base de datos MySQL donde cada servidor denuestra red reporta su estado. Digamos que cada servidor realizar un insert/update en latabla actualizando un campo lastupdate con la fecha del sistema para poder saber asícuando fue la última actualización de ese servidor.
Como sysadmin te interesa saber en un momento determinado cúantos servidores seencuentran caídos desde un momento determinado (desde hace 1 minuto, 1 hora, 1 día …it’s up to you) para poder determinar si tienes problemas.
Con este script simplemente te conectas a la base de datos, realizas una consulta COUNT ymuestras el resultado por pantalla. Esta salida podrás concatenarla con algún otroprograma/script que te permita reaccionar cuando el número supera un umbraldeterminado como enviar una alarma, etc.
@Grab('mysql:mysql-connector-java:5.1.6')①
@GrabConfig(systemClassLoader=true)
import groovy.sql.Sql
sql=Sql.newInstance(
"jdbc:mysql://localhost/granja", ②
"user", "password", "com.mysql.jdbc.Driver" ③
)
row = sql.firstRow('SELECT count(*) caidos FROM hosts where lastreport < ?',[new Date()-1]) ④
println row?.caidos ⑤
① Cargamos la dependencia al driver MySQL
② Indicamos el host y la base de datos alojada
③ Indicamos así mismo datos necesarios para la conexion como usuario y password
④ Buscamos el primer registro que cumpla la condicion. Fijate que la query esparametrizable con ?
⑤ Si obtenemos resultados lo imprimimos por pantalla
Este script muestra cómo conectarnos y buscar un registro mediante una queryparametrizable. Fijate que usamos el caracter ? para indicar donde se deben de sustituirlos parámetros. En este ejemplo usamos el día de ayer como parámetro
101 Groovy Script
62 |
Script
@Grab('mysql:mysql-connector-java:5.1.6')①
@GrabConfig(systemClassLoader=true)
import groovy.sql.Sql
sql=Sql.newInstance(
"jdbc:mysql://localhost/granja", ②
"user", "password", "com.mysql.jdbc.Driver" ③
)
row = sql.firstRow('SELECT count(*) caidos FROM hosts where lastreport < ?',[new Date()-1]) ④
println row?.caidos ⑤
101 Groovy Script
| 63
GORM en tus scriptsJorge Aguilera, [email protected] [mailto:jorge.aguilera@puravida-
software.com] 2018-03-30
GORM (Groovy o Grails?) Object Relational Mapping,http://gorm.grails.org/, es la librería pertenciente al proyecto Grails quepermite el acceso y gestión de bases de datos. En un principio sólocontemplaba Hibernate pero con las últimas versiones permite acceder abases de datos de grafos como Neo4J, NoSQL como MongoDB, etcmanteniendo la misma sintáxis y funcionalidades
En este artículo vamos a desarrollar un Groovy Script simple donde veremos quepodemos utilizar todas las funcionalidades de GORM (creación y mantenimiento de lastablas, relaciones entre ellas, persistencia en nuestro modelo de dominio, etc)desarrollando una pequeña biblioteca con libros.
Dependencias
@GrabConfig(systemClassLoader=true)@Grab('org.slf4j:slf4j-api:1.7.10')@Grab('org.xerial:sqlite-jdbc:3.21.0')@Grab('org.grails:grails-datastore-gorm-hibernate5:6.1.6.RELEASE')@Grab('com.enigmabridge:hibernate4-sqlite-dialect:0.1.2')
import groovy.sql.Sqlimport groovy.transform.ToStringimport org.grails.orm.hibernate.HibernateDatastoreimport grails.gorm.annotation.Entity
DomainNuestros objetos de Dominio constan de Biblioteca y Libro donde el primero contiene unarelación de los segundos
En nuestro script declararemos dos clases @Entity donde indicaremos los atributos yrelaciones entre ellas tal como requiere GORM
101 Groovy Script
64 |
@Entity@ToStringclass Libro { String codigo String titulo static constraints = { codigo unique:true }}
@Entity@ToStringclass Biblioteca{ String nombre static hasMany = [ libros : Libro]}
Simplemente por anotar nuestros objetos de dominio con @Entity, GORM los recubrirácon toda una gama de funciones y capacidades que nos permitirán persistir estos objetosasí como recuperarlos. Por simplificar este script no utiliza -validators_ ni otrasfuncionalidades de GORM.
ConfiguraciónPuesto que nuestro script va a utilizar Hibernate y una persistencia SQLite debemosconfigurar dicha librería para ello:
Map getConfiguration(){
[
'dataSource.url' :'jdbc:sqlite:example.db',
'dataSource.drive' :'org.sqlite.JDBC',
'hibernate.hbm2ddl.auto' : 'update',
'hibernate.dialect' :'com.enigmabridge.hibernate.dialect.SQLiteDialect'
]
}
HibernateDatastore initDatastore(){
new HibernateDatastore( configuration, Biblioteca, Libro)
}
Simplemente indicamos la url a utilizar, el dialecto y las clases que queremos queHibernate gestione (las marcadas como @Entity)
Crear una Biblioteca
101 Groovy Script
| 65
void prepareBiblioteca() { Biblioteca.withTransaction {
Biblioteca.list().each{ it.delete() }
new Biblioteca(nombre: 'Biblioteca Nacional').save() }}
Mediante este método accederemos a la base de datos y borraremos todas las bibliotecasque hubiera (probablemente en tu caso no quieras hacer esto. En el nuestro es parademostrar las funcionalidades de GORM) para posteriormente crear una nueva
Como puedes ver usamos un método withTransaction que NO hemos declarado ennuestra @Entity Biblioteca pero que GORM le ha añadido.
Así mismo GORM nos ha añadido métodos como:
• list() para recuperar todos los objetos de ese tipo
• delete() para eliminarlo de la base de datos
• count() para saber cuantos tenemos
• save() para persistir un objeto de dominio
• etc
Añadir Libros
void addLibros(){ Biblioteca.withTransaction{ Biblioteca b = Biblioteca.first() assert b
b.addToLibros(codigo:'abc', titulo: 'libro 1')
b.save() }}
Mediante este método vamos a añadir un libro a la primera biblioteca que exista en labase de datos. Como podemos ver GORM nos ha añadido un método addToLibros a laBiblioteca de tal forma que añadir un Libro y crear la relación de dependencia entre ellosnos es transparente
101 Groovy Script
66 |
Consultar la bibliotecaRealizar consultas a la base de datos es realmente sencillo y sobre todo con GORM nosolvidamos de sentencias SELECT:
void list(){ Biblioteca.withTransaction{ assert Biblioteca.count() ①
def list = Biblioteca.list() ② list.each{ Biblioteca b-> println "Biblioteca $b.nombre ($b.id): " b.libros.each{ Libro l-> ③ println "\t $l.codigo con titulo $l.titulo" }
} }
Biblioteca.withNewSession{ Biblioteca b = Biblioteca.findByNombre('Biblioteca Nacional') ④ assert b println "Biblioteca encontrada $b"
List list = Biblioteca.findAllByNombreLike('Bibli%') ⑤ assert list.size()
List list2 = Biblioteca.withCriteria{ ⑥ eq 'nombre', 'Biblioteca Nacional' } assert list2.size() }}
① similar a select count(*) from biblioteca
② similar a select * from biblioteca . Podemos indicar que en la misma query recupereel detalle de los libros pasando como argumento fetch:[libros:"join"]
③ similar a select * from libros where biblioteca_id = ?
④ findByXXX donde XXX puede ser cualquier atributo del objeto de dominio
⑤ findAll similar a findBy pero recupera una lista en lugar de un sólo registro
⑥ para querys más complejas podemos utilizar una closure withCriteria
Test
101 Groovy Script
| 67
initDatastore()
prepareBiblioteca()
addLibros()
println '-'.multiply(20)
list()println '-'.multiply(20)
101 Groovy Script
68 |
Script
//tag::dependencias[]
@GrabConfig(systemClassLoader=true)
@Grab('org.slf4j:slf4j-api:1.7.10')
@Grab('org.xerial:sqlite-jdbc:3.21.0')
@Grab('org.grails:grails-datastore-gorm-hibernate5:6.1.6.RELEASE')
@Grab('com.enigmabridge:hibernate4-sqlite-dialect:0.1.2')
import groovy.sql.Sql
import groovy.transform.ToString
import org.grails.orm.hibernate.HibernateDatastore
import grails.gorm.annotation.Entity
//end::dependencias[]
//tag::domain[]
@Entity
@ToString
class Libro {
String codigo
String titulo
static constraints = {
codigo unique:true
}
}
@Entity
@ToString
class Biblioteca{
String nombre
static hasMany = [ libros : Libro]
}
//end::domain[]
//tag::config[]
Map getConfiguration(){
[
'dataSource.url' :'jdbc:sqlite:example.db',
'dataSource.drive' :'org.sqlite.JDBC',
'hibernate.hbm2ddl.auto' : 'update',
'hibernate.dialect' :'com.enigmabridge.hibernate.dialect.SQLiteDialect'
]
}
HibernateDatastore initDatastore(){
new HibernateDatastore( configuration, Biblioteca, Libro)
}
//end::config[]
//tag::biblio[]
void prepareBiblioteca() {
Biblioteca.withTransaction {
Biblioteca.list().each{ it.delete() }
new Biblioteca(nombre: 'Biblioteca Nacional').save()
101 Groovy Script
| 69
}
}
//end::biblio[]
//tag::libros[]
void addLibros(){
Biblioteca.withTransaction{
Biblioteca b = Biblioteca.first()
assert b
b.addToLibros(codigo:'abc', titulo: 'libro 1')
b.save()
}
}
//end::libros[]
//tag::list[]
void list(){
Biblioteca.withTransaction{
assert Biblioteca.count() ①
def list = Biblioteca.list() ②
list.each{ Biblioteca b->
println "Biblioteca $b.nombre ($b.id): "
b.libros.each{ Libro l-> ③
println "\t $l.codigo con titulo $l.titulo"
}
}
}
Biblioteca.withNewSession{
Biblioteca b = Biblioteca.findByNombre('Biblioteca Nacional') ④
assert b
println "Biblioteca encontrada $b"
List list = Biblioteca.findAllByNombreLike('Bibli%') ⑤
assert list.size()
List list2 = Biblioteca.withCriteria{ ⑥
eq 'nombre', 'Biblioteca Nacional'
}
assert list2.size()
}
}
//end::list[]
//tag::main[]
initDatastore()
prepareBiblioteca()
addLibros()
println '-'.multiply(20)
101 Groovy Script
70 |
Docker y Groovy (básico)Jorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2017-10-09
Al igual que con Java, Groovy cuenta desde hace un tiempo con una imagen oficial(bueno, en realidad varias, una para cada versión de JDK disponible) lo que hace queposible que se puedan ejecutar proyectos y scripts en este lenguaje dentro de uncontenedor Docker, crear/extender tus imagenes, compartir volúmenes, etc.
El proyecto se encuentra alojado en https://github.com/groovy/docker-groovy y la(s)imagen(es) las puedes encontrar en el repo oficial de docker https://hub.docker.com/_/groovy/
GroovyShellEn su forma más simple el ejecutar un contenedor con cualquiera de estas imágenes nosiniciará una shell de Groovy (lo cual no tiene mucha utilidad en sí mismo):
docker run --rm -it groovy:latest groovy ①
① Obviamente necesitarás tener instalado Docker en tu sistema (ya tardas) para poderejecutar el comando.
Lo interesante es que en ese momento estás ejecutando un contenedor de docker congroovy instalado y con todas las características de cualquier contenedor: network,volúmenes, incluirlo en un docker-compose, etc.
Hello DockerEn el siguiente ejemplo vamos a comprobar que efectivamente se ejecuta en uncontenedor mediante un HolaDocker:
docker run --rm -e hola=caracola -it groovy:latest groovy -e "println System.getenv().each{println it}"
Si el comando se ejecuta correctamente deberías ver en consola las variables de entornopropias del contenedor incluida la que proporcionamos por argumento. Por ejemploHOSTNAME indicará el nombre que le ha asignado docker a tu imagen (y que noencontrarás después porque le hemos indicado con el argumento rm que la elimine alfinalizar)
Montando volumenUna de las características propias de Docker es permitirte montar un directorio del host
101 Groovy Script
72 |
como si fuera propio del contenedor, de tal forma que este pueda leer/escribir en elmismo sin importar que una vez finalizado eliminemos el contenedor.
Si aprovechamos esta característica podemos ubicar nuestros scripts en un directorio delhost e indicar al contenedor que los ejecute en su entorno. Para ello nos descargamos elscript /scripts/docker/DockerBasico.groovy en un directorio de nuestra maquina yejecutamos desde ese directorio:
docker run --rm -v "$PWD":/home/groovy/scripts -w /home/groovy/scripts groovy:latest groovy
DockerBasico.groovy -a ①
<1>Este script ejecuta diferentes acciones según el parámetro que le pasemos. Con -avuelca las variables de sistema
Este script simplemente ejecuta:
if (options.a) { println "------------------------------------------------------------------" println "Hello" System.getenv().each{ println it } println "------------------------------------------------------------------"}
Si todo se ejecuta correctamente obtendrás una salida muy parecida a la del ejemploanterior (casi seguro que HOSTNAME no coinciden y la variable hola no aparece)
Consumir JSONComo un ejemplo más elaborado vamos a realizar una petición a un servicio externo quenos devolverá un JSON el cual indica de forma aleatoria la URL a una imagen de perros(Para más información consultar el API https://dog.ceo/dog-api)
Para ello ejecutaremos:
docker run --rm -v "$PWD":/home/groovy/scripts -w /home/groovy/scripts groovy:latest groovy
DockerBasico.groovy -d ①
<1>El parámetro -d ejecuta la acción de recuperar el JSON y descargar la imgen
Este script simplemente ejecuta:
101 Groovy Script
| 73
if( options.d){
def json = new groovy.json.JsonSlurper().parse(new URL("https://dog.ceo/api/breed/hound/images/random") )
if(json.status=='success'){
new File('perrito.jpg').bytes = new URL(json.message).bytes
}
}
Si el script se ejecuta correctamente deberías tener un fichero nuevo en tu directorioperrito.jpg que se regenera con una imagen nueva cada vez que ejecutes el script.
101 Groovy Script
74 |
Script
def cli = new CliBuilder(usage: 'groovy DockerBasico.groovy]')
cli.with { ①
h(longOpt: 'help', 'Usage Information \n', required: false)
a(longOpt: 'Hello','Al seleccionar "a" te saludara ', required: false)
d(longOpt: 'Dogs', 'Genera imagenes de perros', required:false)
}
def options = cli.parse(args)
if (!options || options.h) {
cli.usage
return
}
//tag::hello[]
if (options.a) {
println "------------------------------------------------------------------"
println "Hello"
System.getenv().each{
println it
}
println "------------------------------------------------------------------"
}
//end::hello[]
//tag::dogs[]
if( options.d){
def json = new groovy.json.JsonSlurper().parse(new URL("https://dog.ceo/api/breed/hound/images/random") )
if(json.status=='success'){
new File('perrito.jpg').bytes = new URL(json.message).bytes
}
}
//end::dogs[]
101 Groovy Script
| 75
Ejecución programadaJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2018-03-19
En el post Docker y Groovy (básico) vimos cómo podemos hacer que la imagen oficial deGroovy para Docker ejecute nuestros scripts en host donde no se encuentre instaladoGroovy. Así mismo esta imagen nos permite aprovechar todas las características deDocker como montar volúmenes, conexión entre contenedores, etc de tal forma quenuestros scripts puedan interactuar con otros contenedores y/o sistema anfitrión.
Si pensamos un poco sobre ello vemos que podemos utilizar estas características paraejecutar de forma programada nuestros scripts incluso desde un servidor remoto, de talforma que no tenemos ni tan siquiera que tener instalado Groovy en nuestro equipo.
Para este ejemplo vamos a utilizar un script que se ejecutará de forma recurrente yque accederá a la información del Ayuntamiento de Madrid [https://datos.madrid.es/portal/site/egob/menuitem.c05c1f754a33a9fbe4b2e4b284f1a5a0/?vgnextoid=255e0ff725b93410VgnVCM1000000b205a0aRCRD&
vgnextchannel=374512b9ace9f310VgnVCM100000171f5a0aRCRD&vgnextfmt=default] sobreincidencias que se están produciendo en ese momento en las vías de esta ciudad.Esta información viene formateada en XML y, de todas las incidencias, el scriptbuscará aquellas que no han sido planificadas y las irá tuiteando una a una.
Para la ejecución recurrente aprovecharemos la posibilidad que ofrece Gitlab deejecutar Pipelines de forma planificada de tal forma que nuestro código seráejecutado por un runner cuando así lo determinemos.
Así pues deberás tener:
• cuenta en Gitlab así como crear un repositorio y añadir el script al mismo.
• cuenta en twitter y crear unas credenciales para que tu script pueda tuitear en tunombre.
101 Groovy Script
76 |
IncidenciasMadrid.groovy
@Grab(group='org.twitter4j', module='twitter4j-core', version='4.0.6')
import twitter4j.TwitterFactory
import twitter4j.StatusUpdate
import twitter4j.conf.ConfigurationBuilder
tf = TwitterFactory.singleton
if( new File('twitter4j.properties').exists() == false ){ ①
def env = System.getenv()
ConfigurationBuilder cb = new ConfigurationBuilder()
cb.setDebugEnabled(true)
.setOAuthConsumerKey(env['CONSUMER_KEY'])
.setOAuthConsumerSecret(env['CONSUMER_SECRET'])
.setOAuthAccessToken(env['ACCESS_TOKEN'])
.setOAuthAccessTokenSecret(env['ACCESS_SECRET']);
tf = new TwitterFactory(cb.build())
}
twitter = tf.instance
body = new URL("http://informo.munimadrid.es/informo/tmadrid/incid_aytomadrid.xml").newReader()
NewDataSet = new XmlSlurper().parse(body) ②
NewDataSet.Incidencias.each{
if( "$it.incid_prevista" == 'N' && "$it.incid_planificada"=='N' ){
String tweet="""
@101GroovyScript te informa
Atención, incidencia no prevista$it.nom_tipo_incidencia:
$it.descripcion
"""
try{
twitter.updateStatus tweet ③
} catch(e){
println "no se ha enviado"
}
}
}
① Si no existe fichero de credenciales de Twitter usamos variables de entorno
② Obtenemos las ultimas incidencias en formato XML filtrando por las no previstas
③ tuiteamos un mensaje con el contenido de cada una.
GitlabPara que Gitlab pueda ejecutar nuestro código tenemos que especificarle cómo debehacerlo y para ello usaremos un fichero .gitlab-ci.yml que deberá estar en el raiz denuestro proyecto. Para más información consulta la documentación oficialhttps://about.gitlab.com/features/gitlab-ci-cd/
101 Groovy Script
| 77
gitlab-ci.yml
execute incidencias: ① image: name: groovy:2.4-jdk8 ② only: - schedules ③ stage: build script: - groovy IncidenciasMadrid.groovy ④
① identificamos nuestro job con un nombre. Podemos tener varios más
② usaremos la imagen oficial de Groovy para ejecutar nuestro script
③ este job sólo se ejecuta de forma planificada por Gitlab
④ comando a ejecutar. Como usamos la imagen Groovy el comando groovy se encuentradisponible
PlanificaciónDesde la consola web de Gitlab podemos configurar cuando queremos que se ejecute elPipeline mediante una expresión chron (minutos horas dia etc) e incluso especificar quérama de nuestro repo queremos utilizar para ello, así como variables de entorno autilizar:
ResultadoComo resultado tendremos que cada cierto tiempo Twitter publicará las incidenciasusando nuestra cuenta como en este ejemplo:
101 Groovy Script
78 |
Como puedes ver gracias a la imagen Docker podemos ejecutar nuestros scripts de formadesatendida y con todas las funcionalidades que ofrece el lenguaje (consumir serviciosREST, SOAP, conexión a servicios externos y/o internos, etc)
101 Groovy Script
| 79
Script
@Grab(group='org.twitter4j', module='twitter4j-core', version='4.0.6')
import twitter4j.TwitterFactory
import twitter4j.StatusUpdate
import twitter4j.conf.ConfigurationBuilder
tf = TwitterFactory.singleton
if( new File('twitter4j.properties').exists() == false ){ ①
def env = System.getenv()
ConfigurationBuilder cb = new ConfigurationBuilder()
cb.setDebugEnabled(true)
.setOAuthConsumerKey(env['CONSUMER_KEY'])
.setOAuthConsumerSecret(env['CONSUMER_SECRET'])
.setOAuthAccessToken(env['ACCESS_TOKEN'])
.setOAuthAccessTokenSecret(env['ACCESS_SECRET']);
tf = new TwitterFactory(cb.build())
}
twitter = tf.instance
body = new URL("http://informo.munimadrid.es/informo/tmadrid/incid_aytomadrid.xml").newReader()
NewDataSet = new XmlSlurper().parse(body) ②
NewDataSet.Incidencias.each{
if( "$it.incid_prevista" == 'N' && "$it.incid_planificada"=='N' ){
String tweet="""
@101GroovyScript te informa
Atención, incidencia no prevista$it.nom_tipo_incidencia:
$it.descripcion
"""
try{
twitter.updateStatus tweet ③
} catch(e){
println "no se ha enviado"
}
}
}
101 Groovy Script
80 |
Mantenimiento de un Registry deDockerJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2018-02-10
Si utilizas una cuenta de Docker Hub gratuita no te habrás
preocupado mucho por el tema de cuantas imágenes has subido a tu
repositorio. De hecho probablemente te guste tener todo el historial de las
mismas por si alguien las sigue usando. Sin embargo cuando utilizas un
repositorio privado el asunto cambia porque estos servicios suelen cobrar
por el número de imágenes que tienes alojadas en ellos. Así que si quieres
tener "controlada" la factura te toca hacer revisiones periódicas y eliminar
aquellas imágenes que consideras obsoletas.
Mediante este script vamos a obtener un informe de la situación de todas tus imágenesalojadas en Docker Hub que podrás automatizar y que te informará de qué imágenespueden ser eliminadas en base a un criterio que definas (en nuestro caso vamos a querermantener sólo las 4 últimas). Así mismo, si lo quieres, el propio script podrá realizar elborrado por tí
DependenciasDocker Hub ofrece un servicio REST que te permite autentificarte y gestionar losrepositorios, imágenes, tags, etc alojados en el espacio del usuario. Para esta gestión RESTvamos a utilizar HttpBuilder-NG (https://http-builder-ng.github.io/http-builder-ng/asciidoc/html5)
Así mismo para la generación del report vamos a utilizar las capacidades de marcadoincluidas en el propio Groovy usando MarkupBuilder (http://docs.groovy-lang.org/docs/groovy-latest/html/api/groovy/xml/MarkupBuilder.html)
101 Groovy Script
| 81
@Grapes([
@Grab(group='org.asciidoctor', module='asciidoctorj', version='1.5.6'),
@Grab(group='io.github.http-builder-ng', module='http-builder-ng-core', version='1.0.3'),
@Grab(group='org.slf4j', module='slf4j-simple', version='1.7.25', scope='test')
])
import static groovyx.net.http.HttpBuilder.configure
import static groovyx.net.http.ContentTypes.JSON
import groovy.json.*
import groovy.xml.MarkupBuilder
import org.asciidoctor.OptionsBuilder
import org.asciidoctor.AttributesBuilder
import org.asciidoctor.SafeMode
import org.asciidoctor.Asciidoctor.Factory
Configuración y argumentos
REPORT=false ①ASCIIDOC=trueDELETE=false ②TRESHOLD=4 ③
user=args[0] ④password=args[1] ⑤namespace=args[2] ⑥
① Si queremos que genere el HTML de report
② Si queremos que borre automaticamente las imagenes obsoletas
③ Número de imágenes a mantener (de más reciente a menos)
④ Primer argumento es el usuario para hacer login
⑤ Segundo argumento es la password para hacer login
⑥ El repositorio que queremos analizar (un usuario puede tener más de uno. Lo usual esque sea el mismo que el usuario)
Autorización
101 Groovy Script
82 |
token = configure { ① request.uri='https://hub.docker.com/' request.contentType=JSON[0] request.accept=['application/json']}.post { ② request.uri.path='/v2/users/login' request.body=[username:user,password:password]}.token ③
dockerHub = configure { ④ request.uri='https://hub.docker.com/' request.contentType=JSON[0] request.accept=['application/json'] request.headers['Authorization']="JWT $token"}
① Configuramos la peticion para enviar un json
② Realizamos un GET con un json en el body
③ Si la petición se realiza correctamente podemos trabajar con la respuesta directamente
④ Creamos un configure para utilizar en sucesivas llamadas con el token obtenido
Iterar repositorios
repos=dockerHub.get { ① request.uri.path="/v2/repositories/$namespace" request.uri.query=[page_size:200]}.results.collect{ repo-> ② [name:repo.name,lastUpdate:Date.parse("yyyy-MM-dd'T'HH:mm:ss",repo.last_updated)]}.sort{ a,b-> ③ a.name <=> b.name}
① Crearemos una lista local de mapas repos con los results obtenidos
② Para cada result extraemos un subconjunto de valores que nos interesan como elnombre y la última actualización
③ Ordenamos la lista alfabéticamente
Obtener Tags
101 Groovy Script
| 83
repos.each{ repo->
repo.tags =dockerHub.get { ①
request.uri.path="/v2/repositories/$namespace/$repo.name/tags"
request.uri.query=[page_size:200]
}.results.collect{ tag-> ②
[id:tag.id,name:tag.name,lastUpdate:Date.parse("yyyy-MM-dd'T'HH:mm:ss",tag.last_updated)]
}.sort{ a,b-> ③
b.lastUpdate<=>a.lastUpdate
}
if(repo.tags.size()>=TRESHOLD){
repo.tags.takeRight(repo.tags.size()-TRESHOLD).each{ it.toRemove=true } ④
}
}
① A cada repo le asignamos una lista de mapas tags con los resultados obtenidos
② Para cada result extraemos un subconjunto de valores que nos interesan como elnombre y la última actualización
③ Ordenamos la lista de más reciente a más antigüo
④ Si la lista sobrepasa el límite marcado marcamos los más antigüos como candidatospara ser eliminados
ReportSi así lo indicamos el script creará un HTML a modo de report ( ver ejemplo en este enlaceDocker Report [./../scripts/docker/docker_report.html] ) que puede ser enviado por correoelectrónico por ejemplo. Para generar el report, simplemente usaremos MarkupBuilderrecorriendo la lista de repos y para cada repo la lista de tags. Así mismo para los tagscandidatos a ser eliminados los crearemos en una sección diferente para ayudar a suidentificación (un h3 en este caso)
101 Groovy Script
84 |
writer=new StringWriter()
html=new MarkupBuilder(writer)
html.html {
head {
title "Docker Hub Images reporting"
}
body(id: "main") {
h1 id: "namespace", "Repository $namespace"
repos.each{ repo->
div {
h2 "$repo.name (Last update:${repo.lastUpdate.format('yyyy-MM-dd HH:mm')})"
repo.tags.findAll{!it.toRemove}.each{tag->
p "$tag.name (Last update:${tag.lastUpdate.format('yyyy-MM-dd HH:mm')})"
}
if(repo.tags.find{it.toRemove} )
h3 "Candidates to remove"
repo.tags.findAll{it.toRemove}.each{tag->
p "$tag.name (Last update:${tag.lastUpdate.format('yyyy-MM-dd HH:mm')})"
}
}
}
}
}
new File('docker_report.html').newWriter().withWriter { w -> w << writer.toString() }
AsciidoctorOtra posibilidad para generar el report es usando Asciidoctor (ver ejemplo en este enlaceDocker Report en Asciidoctor [./../scripts/docker/asciidoc_docker_report.html] ).
Para generar el report, simplemente crearemos un fichero de forma dinámica siguiendola sintaxis adecuada recorriendo la lista de repos y para cada repo la lista de tags. Asímismo para los tags candidatos a ser eliminados los crearemos en una sección diferentepara ayudar a su identificación
101 Groovy Script
| 85
file = new File("/tmp/asciidoc_docker_report.adoc") ①
file.newWriter().withPrintWriter { mainWriter ->
mainWriter.println "= Docker Hub status"
mainWriter.println "Jorge Aguilera <[email protected]>"
mainWriter.println new Date().format('yyyy-MM-dd')
mainWriter.println ":icons: font"
mainWriter.println ":toc: left"
mainWriter.println ""
mainWriter.println """
[abstract]
Report status of repository *${namespace}* at *${new Date().format('yyyy-MM-dd HH:mm')}*
"""
repos.each { repo -> ②
mainWriter.println "\n== ${repo.name}"
mainWriter.println "Last update: ${repo.lastUpdate.format('yyyy-MM-dd HH:mm')}"
mainWriter.println ""
repo.tags.findAll { !it.toRemove }.each { tag ->
mainWriter.println "- ${tag.name} ${tag.lastUpdate.format('yyyy-MM-dd HH:mm')}"
}
if (repo.tags.find { it.toRemove }) {
mainWriter.println "\n=== Candidates to remove"
}
repo.tags.findAll { it.toRemove }.each { tag ->
mainWriter.println "- ${tag.name} ${tag.lastUpdate.format('yyyy-MM-dd HH:mm')}"
}
}
}
asciidoctor = Factory.create();
attributes = AttributesBuilder.attributes().
sectionNumbers(true).
sourceHighlighter("coderay").
get()
options = OptionsBuilder.options().
backend('html').
attributes(attributes).
safe(SafeMode.UNSAFE).
get()
asciidoctor.convertFile(file, options) ③
① Creamos el fichero y especificamos su cabecera
② Creamos secciones para cada repo
③ Invocamos a Asciidoctor para generar el fichero html
LimpiezaSi queremos que el script realize la limpieza de aquellas imágenes que consideramosobsoletas simplemente lo configuraremos para ello de tal forma que el script puedainvocar la(s) llamada(s) REST DELETE oportuna(s).
101 Groovy Script
86 |
repos.each{ repo->
repo.tags.findAll{it.toRemove}.each{ tag->
dockerHub.delete {
request.uri.path="/v2/repositories/$namespace/$repo.name/tags/$tag.name"
}
}
}
101 Groovy Script
| 87
Script
//tag::dependencies[]
@Grapes([
@Grab(group='org.asciidoctor', module='asciidoctorj', version='1.5.6'),
@Grab(group='io.github.http-builder-ng', module='http-builder-ng-core', version='1.0.3'),
@Grab(group='org.slf4j', module='slf4j-simple', version='1.7.25', scope='test')
])
import static groovyx.net.http.HttpBuilder.configure
import static groovyx.net.http.ContentTypes.JSON
import groovy.json.*
import groovy.xml.MarkupBuilder
import org.asciidoctor.OptionsBuilder
import org.asciidoctor.AttributesBuilder
import org.asciidoctor.SafeMode
import org.asciidoctor.Asciidoctor.Factory
//end::dependencies[]
//tag::config[]
REPORT=false ①
ASCIIDOC=true
DELETE=false ②
TRESHOLD=4 ③
user=args[0] ④
password=args[1] ⑤
namespace=args[2] ⑥
//end::config[]
//tag::auth[]
token = configure { ①
request.uri='https://hub.docker.com/'
request.contentType=JSON[0]
request.accept=['application/json']
}.post { ②
request.uri.path='/v2/users/login'
request.body=[username:user,password:password]
}.token ③
dockerHub = configure { ④
request.uri='https://hub.docker.com/'
request.contentType=JSON[0]
request.accept=['application/json']
request.headers['Authorization']="JWT $token"
}
//end::auth[]
//tag::repos[]
repos=dockerHub.get { ①
request.uri.path="/v2/repositories/$namespace"
request.uri.query=[page_size:200]
}.results.collect{ repo-> ②
[name:repo.name,lastUpdate:Date.parse("yyyy-MM-dd'T'HH:mm:ss",repo.last_updated)]
}.sort{ a,b-> ③
a.name <=> b.name
}
101 Groovy Script
88 |
//end::repos[]
//tag::tags[]
repos.each{ repo->
repo.tags =dockerHub.get { ①
request.uri.path="/v2/repositories/$namespace/$repo.name/tags"
request.uri.query=[page_size:200]
}.results.collect{ tag-> ②
[id:tag.id,name:tag.name,lastUpdate:Date.parse("yyyy-MM-dd'T'HH:mm:ss",tag.last_updated)]
}.sort{ a,b-> ③
b.lastUpdate<=>a.lastUpdate
}
if(repo.tags.size()>=TRESHOLD){
repo.tags.takeRight(repo.tags.size()-TRESHOLD).each{ it.toRemove=true } ④
}
}
//end::tags[]
if( REPORT ){
//tag::report[]
writer=new StringWriter()
html=new MarkupBuilder(writer)
html.html {
head {
title "Docker Hub Images reporting"
}
body(id: "main") {
h1 id: "namespace", "Repository $namespace"
repos.each{ repo->
div {
h2 "$repo.name (Last update:${repo.lastUpdate.format('yyyy-MM-dd HH:mm')})"
repo.tags.findAll{!it.toRemove}.each{tag->
p "$tag.name (Last update:${tag.lastUpdate.format('yyyy-MM-dd HH:mm')})"
}
if(repo.tags.find{it.toRemove} )
h3 "Candidates to remove"
repo.tags.findAll{it.toRemove}.each{tag->
p "$tag.name (Last update:${tag.lastUpdate.format('yyyy-MM-dd HH:mm')})"
}
}
}
}
}
new File('docker_report.html').newWriter().withWriter { w -> w << writer.toString() }
//end::report[]
}
if(ASCIIDOC){
//tag::asciidoctor[]
file = new File("/tmp/asciidoc_docker_report.adoc") ①
file.newWriter().withPrintWriter { mainWriter ->
mainWriter.println "= Docker Hub status"
mainWriter.println "Jorge Aguilera <[email protected]>"
mainWriter.println new Date().format('yyyy-MM-dd')
mainWriter.println ":icons: font"
mainWriter.println ":toc: left"
mainWriter.println ""
mainWriter.println """
101 Groovy Script
| 89
[abstract]
Report status of repository *${namespace}* at *${new Date().format('yyyy-MM-dd HH:mm')}*
"""
repos.each { repo -> ②
mainWriter.println "\n== ${repo.name}"
mainWriter.println "Last update: ${repo.lastUpdate.format('yyyy-MM-dd HH:mm')}"
mainWriter.println ""
repo.tags.findAll { !it.toRemove }.each { tag ->
mainWriter.println "- ${tag.name} ${tag.lastUpdate.format('yyyy-MM-dd HH:mm')}"
}
if (repo.tags.find { it.toRemove }) {
mainWriter.println "\n=== Candidates to remove"
}
repo.tags.findAll { it.toRemove }.each { tag ->
mainWriter.println "- ${tag.name} ${tag.lastUpdate.format('yyyy-MM-dd HH:mm')}"
}
}
}
asciidoctor = Factory.create();
attributes = AttributesBuilder.attributes().
sectionNumbers(true).
sourceHighlighter("coderay").
get()
options = OptionsBuilder.options().
backend('html').
attributes(attributes).
safe(SafeMode.UNSAFE).
get()
asciidoctor.convertFile(file, options) ③
//end::asciidoctor[]
}
if( DELETE ){
//tag::delete[]
repos.each{ repo->
repo.tags.findAll{it.toRemove}.each{ tag->
dockerHub.delete {
request.uri.path="/v2/repositories/$namespace/$repo.name/tags/$tag.name"
}
}
}
//end::delete[]
}
101 Groovy Script
90 |
Biblioteca (docudocker)Miguel Rueda <[email protected] [mailto:[email protected]]> 2018-03-05
A continuación se describe el caso de uso que vamos a desarrollar: Un cliente nos trasladala necesidad de crear una biblioteca de documentos, donde colgar manuales o notasinformativas para sus empleados. Esta información cambia de manera mensual, tanto elcontenido como la estructura de directorios. Además debe de correr en cualquier sistemaoperativo ya que al trabajar con varios clientes cada uno cuenta con el suyo propio.
Nuestra solución se basará en una imagen Docker con un servidor web de contenidoestático, donde habremos copiado los documentos proporcionados por el clientegenerando un image versionada con esta información.
Así pues nuestro sistema debe de realizar los siguientes pasos:
Un cliente proporciona la nueva documentación para la biblioteca, esta esactualizada en el servidor preparado para ella (con la función scpDocuments). Con lainformación ya en el servidor lanzamos la tareas de gradle (createImage) y con elloobtenemos la imagen docker con nuestra biblioteca de documentos versionada en elrepositorio en el que estemos trabajando.
Herramientas que vamos a emplear.• DocuDocker [https://gitlab.com/jorge.aguilera/static-documents]:
101 Groovy Script
| 91
En este enlace @jagedn explica paso a paso como crear una biblioteca con docker y jbakeutilizando el proyecto static-documents.
https://www.youtube.com/watch?v=5x8i9Ft_29Y (YouTube video)
Dentro de este proyecto podemos encontrar el fichero Dockerfile que contiene la "receta"para generar nuestra imagen docker de la biblioteca:
FROM httpd:2.4EXPOSE 80COPY . /usr/local/apache2/htdocs/
Este Dockerfile parte de la imagen de docker httpd:2.4 y copia el contenido que tenemosen la ruta actual dentro de su servidor apache. Por lo tanto a la hora de actualizar losdocumentos debemos centrarnos en este paso. En el ejemplo sobre el que vamos atrabajar tiene la estructura: un directorio RRHH (que contiene Convenio_Actual.pdf) yotro Administración (que contiene Cierre_mensual.pdf)
La herramientas que utiliza este proyecto es gradle y para la creación del entorno webestático es JBake.
PreparaciónPara la creación de nuestra biblioteca debemos ejecutar los siguientes pasos:
• Bajarnos en el servidor que deseamos crear la biblioteca el proyecto static-documentspara posteriormente realizar las tareas de gradle:
git clone https://gitlab.com/jorge.aguilera/static-documents.git
• Con el proyecto bajado debemos copiar dentro del proyecto en el directoriosrc/documents/ la estructura de directorios(en nuestro caso RRHH y Administración )que deseamos encontrarnos en nuestra biblioteca , así como la información dentro decada directorio.
• Cuando tengamos recopilada toda la información, ejecutar las tareas de gradle paragenerar la imagen docker. Utilizaremos sshoogr [https://github.com/aestasit/sshoogr] ya queen este caso que vamos a ver la información estará en un servidor remoto.
Descripción del scriptA continuación vamos a pasar a describir el script encargado de realizar cada una de lasfunciones descritas anteriormente:
101 Groovy Script
92 |
Dependencias y configuración necesarias.
Estas son las que vamos a necesitar:
@Grab('com.aestasit.infrastructure.sshoogr:sshoogr:0.9.25')@GrabConfig(systemClassLoader=true)import static com.aestasit.infrastructure.ssh.DefaultSsh.*
options.trustUnknownHosts = true ①
① Desactivación de la comprobación estricta de la clave del host
Actualización de versión.
El primer paso es eliminar del fichero build.gradle del proyecto los campos version ytagVersion para impedir que en la creación de nuestra imagen tomen estas referencias ala hora de crear nuestra biblioteca. Nuestro script creará un fichero gradle.properties conla versión que hemos recibo por parametro y que será el tag de la imagen.
Utilizamos la función updateVersion que recibe por parámetro el servidor donde seencuentra el proyecto y la ruta del fichero gradle.properties que será actualizado.
remoteSession("user:password@$server:22"){ remoteFile("$orig/gradle.properties").text = "version=$version" }
Subida de nuevos documentos
Para este paso utilizaremos la función scpDocuments, a la cual le debemos indicar la rutadonde se encuentra la documentación por parámetro y la ruta en el servidor destino
remoteSession("user:password@$server:22") { scp{ from { localDir orig} into { remoteDir dest}
} }
Creación de la imagen docuDockerYa solo nos queda ejecutar las tareas gradle build y pushDockerRegistry (para subirla anuestro repositorio). Para ello realizaremos:
101 Groovy Script
| 93
remoteSession("user:password@$server:22") { exec "cd $src; ./gradlew build" exec "cd $src; ./gradlew pushDockerRegistry" }
En este punto una nueva imagen etiquetada se encuentra en nuestro repositorio y puedeser descargada por los clientes para ver la última versión de los documentos
101 Groovy Script
94 |
Script
@Grab('com.aestasit.infrastructure.sshoogr:sshoogr:0.9.25')
@GrabConfig(systemClassLoader=true)
import static com.aestasit.infrastructure.ssh.DefaultSsh.*
options.trustUnknownHosts = true
String server = "src_server"
def cli = new CliBuilder(usage: 'groovy CliBuilder.groovy -[husc]')
cli.with { (1)
h(longOpt: 'help ','Usage Information \n', required: false)
u(longOpt: 'Actualización ','-u version servidor ruta -> Ejemplo: -u v1 localhost', required:false)
s(longOpt: 'Copiado de ficheros','-s servidor origen destino -> Ejemplo: -s localhost /tmp /home/docu',
required: false)
c(longOpt: 'Crear Imagen ','-c servidor ruta -> Ejemplo: -c ruta', required: false)
}
def options = cli.parse(args)
if (!options) {
return
}
if (options.h) {
cli.usage()
return
}
if (options.u) {
if (options.arguments().size() < 3) {
cli.usage()
return
}
updateVersion(options.arguments()[0],options.arguments()[1],options.arguments()[2])
}
if (options.s) {
if (options.arguments().size() < 3) {
cli.usage()
return
}
scpDocuments(options.arguments()[0],options.arguments()[1],options.arguments()[2])
}
if (options.c) {
if (options.arguments().size() < 3) {
cli.usage()
return
}
createImage(options.arguments()[0],options.arguments()[1])
}
def updateVersion(version,server,orig){
//tag::version[]
remoteSession("user:password@$server:22"){
remoteFile("$orig/gradle.properties").text = "version=$version"
}
//end::version[]
}
def createImage(server,src){
//tag::create[]
remoteSession("user:password@$server:22") {
exec "cd $src; ./gradlew build"
exec "cd $src; ./gradlew pushDockerRegistry"
}
101 Groovy Script
| 95
//end::create[]
}
def scpDocuments(server,orig,dest){
//tag::put_dir[]
remoteSession("user:password@$server:22") {
scp{
from { localDir orig}
into { remoteDir dest}
}
}
//end::put_dir[]
}
101 Groovy Script
96 |
Tostando imágenesJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2017-10-14
En el post Docker y Groovy (básico) vimos cómo podemos hacer que la imagen oficial deGroovy para Docker ejecute nuestros scripts en host donde no se encuentre instaladoGroovy. Así mismo esta imagen nos permite aprovechar todas las características deDocker como montar volúmenes, conexión entre contenedores, etc de tal forma quenuestros scripts puedan interactuar con otros contenedores y/o sistema anfitrión.
En este post lo que vamos a ver es cómo utilizarla como base para crear nuestras propiasimágenes de tal forma que podamos subirlas a nuestro repositorio ( Hub Docker, nexusprivado, Gitlab, Google o cualquier otro en el que tengas cuenta).
Para este ejemplo vamos a utilizar un script muy sencillo cuya función va a ser mostrarpor pantalla el contenido de un fichero que le pasemos por parámetro y esperar 30segpara volver a repetir la acción (vamos lo que viene siendo a ser un bucle infinito de todala vida).
WatchFile.groovy
if( !args.size() ){ println "necesito un fichero que vigilar" return}
while( true ){ File f = new File(args[0]) f.eachLine{ line -> println line } sleep 30*1000}
INFO
Como puedes ver el script no es lo más importante en este artículo
DockerfileLo primero que vamos a necesitar es un fichero de instrucciones para que Docker puedacrear nuestra imagen. Este fichero suele tener el nombre de Dockerfile y para nuestroejemplo tiene esta pinta:
101 Groovy Script
| 97
FROM groovy:2.4.12-jre8-alpine
COPY WatchFile.groovy /home/groovy/
VOLUME ["/var/watchfile"]
ENTRYPOINT ["groovy", "WatchFile.groovy"]
CMD []
Mediante este fichero simplemente indicamos de qué imagen vamos a extender(groovy:2.4.12-jre8-alpine), copiamos nuestro script en la imagen e indicamos qué sedebe ejecutar al arrancar ( groovy WatchFile.groovy).
También preparamos nuestra imagen para que si no le proporcionamos parámetros deejecución el script lo detecte y nos pueda mostrar alguna ayuda de cómo ejecutarlo, orealizar una acción por defecto, (CMD [])
Así mismo vamos a montar un volumen particular para esta imagen en /var/watchfile, locual nos permitirá, por ejemplo, montar volúmenes de otros contenedores o del hostanfitrión para que nuestro script pueda "vigilarlo"
Generando la imagenDesde el directorio donde tengamos estos dos ficheros ejecutaremos:
docker build --rm -t jagedn/watchfile . ①
① jagedn/watchfile será el nombre de nuestra imagen. Fijate que el comando acaba en unpunto
Si todo va bien, en tu máquina existirá ahora una imagen jagedn/watchfile (o el nombreque le hayas dado)
Para comprobarlo puedes ejecutar
docker images
Ejecutando la imagenPara ejecutar la imagen simplemente haremos:
docker run --rm -it -v /un/path/cualquiera:/var/watchfile jagedn/watchfile /var/watchfile/mifichero.log ①
101 Groovy Script
98 |
Esto debe ejecutar un proceso que cada 30 segundos nos muestre el contenido demifichero.log
Como puedes ver, este ejemplo en sí no realiza nada interesante salvo que muestra lospasos a realizar para hacer que nuestro script se pueda distribuir y ejecutar en unentorno dockerizado por lo que es perfectamente integrable en pipelines de continuousdelivery por ejemplo.
Recuerda que para poder subir tu imagen a un repositorio simplemente deberás hacer unpush, por ejemplo:
docker push jagedn/watchfile
101 Groovy Script
| 99
Script
if( !args.size() ){ println "necesito un fichero que vigilar" return}
while( true ){ File f = new File(args[0]) f.eachLine{ line -> println line } sleep 30*1000}
101 Groovy Script
100 |
De Excel a i18n y viceversaJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2018-03-03
La mayoría de las aplicaciones Java utilizan la funcionalidad que ofrece para el manejo demensajes internacionalizados el cual se basa en una serie de ficheros properties quecomparten un nombre común más un sufijo que indica el idioma al que corresponden lastraducciones incluidas en el mismo.
Por ejemplo, supongamos que nuestra aplicación va a mostrar por defecto los mensajes eninglés pero necesitamos poder mostrarlos también en español y francés. Este caso en Javase corresponde con estos ficheros:
theapp.properties
login=Loginwelcome=Welcome
theapp_es.properties
login=Identificacionwelcome=Bienvenido
theapp_fr.properties
login=Identifierwelcome=Bienvenue
Cuando la aplicación crece, el número de mensajes a mostrar suele hacerlo también y nosvamos centrando en el fichero por defecto hasta tener la mayor cantidad posible deidentificadores. Si en este momento queremos realizar la traducción (o encargarsela aalguien) nos encontramos con una serie de ficheros incompletos e inconexos y aunqueexisten herramientas para facilitar la edición, estas no suelen ser del agrado de quientiene que traducirlos.
Mediante este script vamos a poder realizar dos procesos diferentes aunque relacionados:
• Partiendo de un conjunto de ficheros properties de traducciones incompletascrearemos un fichero Excel donde cada columna corresponderá a un idioma y cadafila a un elemento a traducir en cada idioma
• Partiendo de un excel con las traducciones completas crearemos un conjunto deficheros properties cada uno con las traducciones que le correspondan.
101 Groovy Script
| 101
Vamos a usar Apache POI para ambas situaciones pero para la escrituravamos a usar el DSL Groovy Excel Builder de James Kleeh parademostrar lo fácil que es escribir un excel con Groovy.
DependenciasDe forma general vamos a usar las librerías de Apache POI para leer y escribir el Excel,pero como el DSL que vamos a usar las incluye en sus dependencias podemos indicarsimplemente este en Grappe:
@Grab('com.jameskleeh:excel-builder:0.4.2')
import org.apache.poi.ss.usermodel.*import org.apache.poi.hssf.usermodel.*import org.apache.poi.xssf.usermodel.*import org.apache.poi.ss.util.*import org.apache.poi.ss.usermodel.*import com.jameskleeh.excel.ExcelBuilder
ArgumentosComo hemos dicho el script podrá ejecutar dos acciones diferentes por lo que preparamosun CliBuilder que nos permita interpretar la línea de comandos y los argumentosproporcionados por el usuario:
101 Groovy Script
102 |
def cli = new CliBuilder(usage: 'groovy ExcelI18n.groovy -[h] -action generateProperties/generateExcel
filename.xls ')
cli.with { ①
h(longOpt: 'help', 'Import/Export excel file to i18n properties files.', required: false)
action('generateProperties generateExcel', required: true, args:1, argName:'action')
}
def options = cli.parse(args)
if (!options){
return
}
if(options.h || options.arguments().size()==0) {
cli.usage()
return
}
if( options.action == 'generateProperties'){ ②
return generateProperties(options.arguments()[0])
}
if( options.action == 'generateExcel'){ ③
return generateExcel(options.arguments()[0])
}
cli.usage() ④
① preparamos las opciones disponibles
② de Excel a properties
③ de properties a Excel
④ si no hay acción correcta mostramos el uso
De i18n a ExcelPartiendo de una situación en la que tenemos un conjunto de traducciones incompletas loque pretendemos hacer es cargar todos estos ficheros en un Excel organizando por filas ycolumnas los códigos y los idiomas respectivamente.
Para determinar los idiomas que queremos manejar en nuestra aplicación hay que fijarseen que todos ellos siguen el patrón:
filename (_ i18code)? .properties , es decir un nombre de fichero común, un guión bajo yun código de idioma (por ejemplo es, fr, ca, etc) opcionales y una extension fija .properties
101 Groovy Script
| 103
void generateExcel(filename){
File excelFile = new File(filename)
String dir = excelFile.absolutePath.split(File.separator).dropRight(1).join(File.separator)
String name = excelFile.name.split('\\.').dropRight(1).join('.')
Properties defaultProperties = new Properties()
defaultProperties.load( new File("${name}.properties").newInputStream() ) ①
Map<String,Properties> propertiesMap = [:] ②
new File(dir).eachFileMatch ~"${name}(_[A-Za-z]+)\\.properties", { ③
def matcher = (it.name =~ "${name}(_[A-Za-z]+)\\.properties" )
String lang = matcher[0][1]?.substring(1)
Properties prp = new Properties()
prp.load( it.newInputStream() )
propertiesMap[ lang ] = prp ④
}
ExcelBuilder.output(new FileOutputStream(new File("${filename}"))) {
sheet {
row{
cell("Code")
cell("Default")
propertiesMap.keySet().each { lang ->
cell(lang.toUpperCase())
}
}
defaultProperties.propertyNames().each{ String property-> ⑤
row{
cell(property)
cell(defaultProperties[property])
propertiesMap.keySet().each { lang ->
cell(propertiesMap[lang][property])
}
}
}
}
}
}
① cargar en un Properties el fichero por defecto (el cual contienen todos las keys atraducir)
② preparar un Map<String,Properties>
③ buscar los ficheros de traducciones particulares que cumplen el patrón explicado
④ cargar en el mapa el properties identificado por su código de idioma
⑤ crear un Excel donde cada key será una fila y cada idioma volcará su texto
De Excel a i18nUna vez completado el Excel con las traducciones adecuadas necesitarremos volver areescribir los ficheros properties
101 Groovy Script
104 |
void generateProperties(filename){
File excelFile = new File(filename)
String dir = excelFile.absolutePath.split(File.separator).dropRight(1).join(File.separator)
String name = excelFile.name.split('\\.').dropRight(1).join('.')
InputStream inp = new FileInputStream(excelFile)
①
new File(dir).eachFileMatch ~"${name}(_[A-Za-z]+)?\\.properties", {
it.delete()
}
List<String> languages = []
Workbook wb = WorkbookFactory.create(inp)
Sheet sheet = wb.getSheetAt(0)
sheet.iterator().eachWithIndex{ Row row, int idx-> ②
if( idx == 0){ ③
languages = ['']+row.cellIterator().collect{ '_'+it.stringCellValue }.drop(2)*.toLowerCase()
return
}
String code = row.getCell(0)
languages.eachWithIndex{ String lang, int i -> ④
String txt = row.getCell(i+1)?.stringCellValue
if(txt)
new File("${name}${lang}.properties") << "$code=$txt\n" ⑤
}
}
}
① limpiamos ficheros de traduccion si los hubiera
② iteramos por las filas del Excel
③ la primera fila nos indica los lenguajes que se contemplan además del por defecto
④ para cada lenguaje indicado en la primera fila buscamos si hay traducción en el excel
⑤ si tenemos traducción la concatenamos al fichero correspondiente al idioma
101 Groovy Script
| 105
Script
//tag::dependencies[]
@Grab('com.jameskleeh:excel-builder:0.4.2')
import org.apache.poi.ss.usermodel.*
import org.apache.poi.hssf.usermodel.*
import org.apache.poi.xssf.usermodel.*
import org.apache.poi.ss.util.*
import org.apache.poi.ss.usermodel.*
import com.jameskleeh.excel.ExcelBuilder
//end::dependencies[]
//tag::arguments[]
def cli = new CliBuilder(usage: 'groovy ExcelI18n.groovy -[h] -action generateProperties/generateExcel
filename.xls ')
cli.with { ①
h(longOpt: 'help', 'Import/Export excel file to i18n properties files.', required: false)
action('generateProperties generateExcel', required: true, args:1, argName:'action')
}
def options = cli.parse(args)
if (!options){
return
}
if(options.h || options.arguments().size()==0) {
cli.usage()
return
}
if( options.action == 'generateProperties'){ ②
return generateProperties(options.arguments()[0])
}
if( options.action == 'generateExcel'){ ③
return generateExcel(options.arguments()[0])
}
cli.usage() ④
//end::arguments[]
//tag::generateProperties[]
void generateProperties(filename){
File excelFile = new File(filename)
String dir = excelFile.absolutePath.split(File.separator).dropRight(1).join(File.separator)
String name = excelFile.name.split('\\.').dropRight(1).join('.')
InputStream inp = new FileInputStream(excelFile)
①
new File(dir).eachFileMatch ~"${name}(_[A-Za-z]+)?\\.properties", {
it.delete()
}
List<String> languages = []
Workbook wb = WorkbookFactory.create(inp)
Sheet sheet = wb.getSheetAt(0)
sheet.iterator().eachWithIndex{ Row row, int idx-> ②
if( idx == 0){ ③
languages = ['']+row.cellIterator().collect{ '_'+it.stringCellValue }.drop(2)*.toLowerCase()
101 Groovy Script
106 |
return
}
String code = row.getCell(0)
languages.eachWithIndex{ String lang, int i -> ④
String txt = row.getCell(i+1)?.stringCellValue
if(txt)
new File("${name}${lang}.properties") << "$code=$txt\n" ⑤
}
}
}
//end::generateProperties[]
//tag::generateExcel[]
void generateExcel(filename){
File excelFile = new File(filename)
String dir = excelFile.absolutePath.split(File.separator).dropRight(1).join(File.separator)
String name = excelFile.name.split('\\.').dropRight(1).join('.')
Properties defaultProperties = new Properties()
defaultProperties.load( new File("${name}.properties").newInputStream() ) ①
Map<String,Properties> propertiesMap = [:] ②
new File(dir).eachFileMatch ~"${name}(_[A-Za-z]+)\\.properties", { ③
def matcher = (it.name =~ "${name}(_[A-Za-z]+)\\.properties" )
String lang = matcher[0][1]?.substring(1)
Properties prp = new Properties()
prp.load( it.newInputStream() )
propertiesMap[ lang ] = prp ④
}
ExcelBuilder.output(new FileOutputStream(new File("${filename}"))) {
sheet {
row{
cell("Code")
cell("Default")
propertiesMap.keySet().each { lang ->
cell(lang.toUpperCase())
}
}
defaultProperties.propertyNames().each{ String property-> ⑤
row{
cell(property)
cell(defaultProperties[property])
propertiesMap.keySet().each { lang ->
cell(propertiesMap[lang][property])
}
}
}
}
}
}
//end::generateExcel[]
101 Groovy Script
| 107
Buscar el fichero de mayor tamañorecursivamenteMiguel Rueda <[email protected] [mailto:[email protected]]>2017-08-23
A veces nos surge que necesitamos saber qué fichero está consumiendo más espacio perodebido a que tenemos un número elevado de subdirectorios nos es dificil encontrarlo. Endeterminados sistemas operativos es "fácil" encontrarlo concatenando varios comandoscomo du grep etc.
Mediante este script encontraremos la ruta del fichero con el mayor tamaño sin importarel número de subdirectorios
max=null
void scanDir( File dir ){ dir.eachFile{ f-> max = max && max.size() >= f.size() ? max : f } dir.eachDir{ d -> scanDir(d) }}
scanDir(new File('.'))
println "El fichero mayor es: $max.path y ocupa ${max.size()} bytes"
A continuación pasamos a explicar de forma detallada el contenido de nuestro script degroovy que cumple la función de encontrar el fichero de mayor tamaño:
Definiremos una función llamada scanDir encargada de recorrer de manera recursivatodos los directorios y subdirectorios de la ruta indicada por parámetro. Comparará eltamaño de cada uno de los ficheros que van apareciendo y guadará el mayor.
Para recorrer cada uno de los directorios y subdirectorios de la ruta indicada porparámetro utilizamos:
dir.eachFile{ f->
Para recorrer los ficheros del directorio/subdirectorio encontrado utilizamos:
dir.eachDir{ d ->
101 Groovy Script
108 |
Realizaremos la comparación entre el tamaño de los diferentes ficheros utilizandof.size()(este método nos devuelve la dimensión del fichero):
max = max && max.size() >= f.size() ? max : f
Para ejecutar este proceso simplemente llamaremos a la función scanDir indicando la rutaque queremos revisar (en este caso .):
scanDir(new File('.'))
Al final del proceso obtendremos el fichero del cual podemos obtener la ruta al mismo,tamaño, etc
println "El fichero mayor es: $max.path y ocupa ${max.size()} bytes"
101 Groovy Script
| 109
Script
max=null
void scanDir( File dir ){ dir.eachFile{ f-> max = max && max.size() >= f.size() ? max : f } dir.eachDir{ d -> scanDir(d) }}
scanDir(new File('.'))
println "El fichero mayor es: $max.path y ocupa ${max.size()} bytes"
101 Groovy Script
110 |
Buscar en fichero y volcar coincidentesJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2017-08-23
En algunos sistemas operativos tenemos el comando grep o find que buscan una cadenaen los ficheros de un directorio y la vuelcan por consola. Con este script vamos a realizaralgo parecido para ver cómo podemos leer ficheros de gran tamaño y cómo escribir enotro fichero.
if( args.length != 3){ println "Hey necesito el fichero de entrada, el de salida y la cadena a buscar" return}
inputFile = new File(args[0])outputFile = new File(args[1])search = args[2]
outputFile.newWriter().withWriter { writer-> ① inputFile.eachLine { line, indx -> ② if (line.indexOf(search) != -1) writer << "$indx: $line\n" ③ }}
① Creamos o sobreescribimos un fichero de salida y usamos un writer para escribir en él
② readLine es indicada para leer ficheros de gran tamaño, teniendo otras formas de leerun fichero como .text que leerían todo el fichero en una cadena
③ utilizamos el operator leftshit para ir enviando cadenas al fichero
101 Groovy Script
| 111
Script
if( args.length != 3){ println "Hey necesito el fichero de entrada, el de salida y la cadena a buscar" return}
inputFile = new File(args[0])outputFile = new File(args[1])search = args[2]
outputFile.newWriter().withWriter { writer-> ① inputFile.eachLine { line, indx -> ② if (line.indexOf(search) != -1) writer << "$indx: $line\n" ③ }}
101 Groovy Script
112 |
Maven InventoryJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2019-01-28
Muchas de las librerías que usamos a diario están construidas con Maven y muchas deellas incluyen un fichero pom.properties en el directorio Manifest con los detalles del build(como el grupo, el artefacto y la version)
A veces nos encontramos con un directorio con un buen número de estas librerías ypuede suceder que se encuentren duplicadas en diferentes directorios o lo que es peorque tengamos para el mismo artefacto diferentes versiones y aparezcan problemas con elcargador de clases.
Con este pequeño script podremos hacer un scaneo rápido de estos artefactos y generarun informe con toda esta información que nos permita descubrir estos conflictos.
101 Groovy Script
| 113
import java.util.jar.JarEntry
import java.util.jar.JarFile
import groovy.json.JsonOutput
groups = [:]
path = args.length > 0 ? args[0] : System.properties["user.home"]+"/.m2/repository"; ①
new File(path).traverse(type: groovy.io.FileType.FILES) { it ->
if( it.name.endsWith('.jar')){
inspectJar(it)
}
}
println JsonOutput.prettyPrint(new JsonOutput().toJson(groups))
void inspectJar(File f){
final JarFile jarFile = new JarFile(f);
Enumeration<JarEntry> enumOfJar = jarFile.entries()
JarEntry pom = enumOfJar.find{it.name.endsWith("/pom.properties")}
if( pom )
inspectPom( jarFile, pom )
}
void inspectPom(JarFile jarFile, JarEntry jarEntry){
InputStream input = jarFile.getInputStream(jarEntry);
Properties prp = new Properties();
prp.load(input);
def group = groups[prp['groupId']] ?: [:]
def artifact = group[prp['artifactId']] ?: [:]
def version = artifact[prp['version']] ?: []
version << jarFile.name
artifact[prp['version']]=version
group[prp['artifactId']]=artifact
groups[prp['groupId']] = group
}
① si no se proporciona una ruta se usa la típica de maven
• Iteramos por todos los ficheros que terminan en .jar recursivamente
• Inspecionamos cada fichero buscando si contiene un pom.properties
• Actualizamos un Map (groups) de Map (artifacts) de List (versions) y añadimos elnombre del fichero a la lista de versiones. Si dos ficheros coinciden aparece unaentrada duplicada
AL final de escaneo volcamos en pantalla la información en formato JSON pero puedesusar cualquier otro formato de tu conveniencia.
101 Groovy Script
114 |
ResultadoEste es un ejemplo de lo que se vería por pantalla:
"org.apache.maven.shared": {
"maven-shared-utils": {
"0.1": [
".m2/repository/org/apache/maven/shared/maven-shared-utils/0.1/maven-shared-utils-0.1.jar"
],
"0.3": [
".m2/repository/org/apache/maven/shared/maven-shared-utils/0.3/maven-shared-utils-0.3.jar"
]
},
"maven-shared-incremental": {
"1.1": [
".m2/repository/org/apache/maven/shared/maven-shared-incremental/1.1/maven-shared-
incremental-1.1.jar"
]
},
"maven-repository-builder": {
"1.0-alpha-2": [
".m2/repository/org/apache/maven/shared/maven-repository-builder/1.0-alpha-2/maven-
repository-builder-1.0-alpha-2.jar"
]
},
"maven-invoker": {
"2.1.1": [
".m2/repository/org/apache/maven/shared/maven-invoker/2.1.1/maven-invoker-2.1.1.jar"
],
"2.2": [
".m2/repository/org/apache/maven/shared/maven-invoker/2.2/maven-invoker-2.2.jar"
]
},
"maven-plugin-testing-harness": {
"1.1": [
".m2/repository/org/apache/maven/shared/maven-plugin-testing-harness/1.1/maven-plugin-
testing-harness-1.1.jar"
]
},
"file-management": {
"1.2.1": [
".m2/repository/org/apache/maven/shared/file-management/1.2.1/file-management-1.2.1.jar"
],
"1.1": [
".m2/repository/org/apache/maven/shared/file-management/1.1/file-management-1.1.jar"
]
},
"maven-shared-io": {
"1.1": [
".m2/repository/org/apache/maven/shared/maven-shared-io/1.1/maven-shared-io-1.1.jar"
]
},
101 Groovy Script
| 115
Organizar fotografías por fechaJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2018-08-12
Tengo una cámara nueva que hace unas fotos realmente fantásticas. Incluso puedoconectar mi teléfono y transferirme las fotos a este para compartirlas al momento. Sinembargo lo que no hace (o yo no he encontrado) es organizarme las fotos por carpetassegún la fecha en que la tomé.
Con este pequeño script vamos a poder acceder a la información EXIF que se guardadentro de la imagen para buscar la fecha en que fue tomada y usarla para organizar todaslas fotos de un directorio dado.
101 Groovy Script
116 |
@Grab("org.yaml:snakeyaml:1.16")@Grab("com.drewnoakes:metadata-extractor:2.11.0")
import static groovy.io.FileType.FILES
import com.drew.metadata.*import com.drew.imaging.*import com.drew.imaging.jpeg.*import com.drew.metadata.exif.*
dir = new File(args[0])
outdir = new File(args[1])outdir.mkdirs()
files = []dir.traverse(type: FILES, maxDepth: 0) { files << it }files = files.findAll{ file -> String ext = file.name.toLowerCase().split("\\.").last() ['jpg','png'].contains(ext)}.sort{ it.lastModified() }
if( !files.size() ){ println "No files" return}
files.each{ file -> String gname = new Date(file.lastModified()).format('yyyy-MM-dd')
try { Metadata metadata = ImageMetadataReader.readMetadata(file) metadata.directories.find{ it.name=="Exif IFD0"}.tags.findAll{ it.tagName =="Date/Time"}.each{ tag -> gname = tag.description[0..10].replaceAll(":","-") } }catch( e ){ println "error extracting metadata for $file.name : $e.message" }
File newdir = new File(outdir,gname) newdir.mkdirs()println "Move $file.name to $newdir.name" file.renameTo( new File(newdir, file.name) )}
El script es muy simple:
101 Groovy Script
| 117
Dado un directorio de origen y uno de destino, recorrerá todos los ficheros .jpg y .png delprimero y para cada uno de ellos extraerá la información meta que contiene. Estainformación es muy extensa y cada fabricante añade las suyas propias. De todas ellasbuscaremos el tag EXIF Date/Time el cual es usado para guardar la fecha en que se tomó lafoto.
Simplemente usaremos este tag como nombre del subdirectorio donde mover la foto.
101 Groovy Script
118 |
Script
@Grab("org.yaml:snakeyaml:1.16")@Grab("com.drewnoakes:metadata-extractor:2.11.0")
import static groovy.io.FileType.FILES
import com.drew.metadata.*import com.drew.imaging.*import com.drew.imaging.jpeg.*import com.drew.metadata.exif.*
dir = new File(args[0])
outdir = new File(args[1])outdir.mkdirs()
files = []dir.traverse(type: FILES, maxDepth: 0) { files << it }files = files.findAll{ file -> String ext = file.name.toLowerCase().split("\\.").last() ['jpg','png'].contains(ext)}.sort{ it.lastModified() }
if( !files.size() ){ println "No files" return}
files.each{ file -> String gname = new Date(file.lastModified()).format('yyyy-MM-dd')
try { Metadata metadata = ImageMetadataReader.readMetadata(file) metadata.directories.find{ it.name=="Exif IFD0"}.tags.findAll{ it.tagName =="Date/Time"}.each{ tag -> gname = tag.description[0..10].replaceAll(":","-") } }catch( e ){ println "error extracting metadata for $file.name : $e.message" }
File newdir = new File(outdir,gname) newdir.mkdirs()println "Move $file.name to $newdir.name" file.renameTo( new File(newdir, file.name) )}
101 Groovy Script
| 119
Eliminar lineas con errorJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2018-02-01
A continuación vamos a ver un script realmente sencillo para un caso de uso frecuente:
Tenemos un fichero que contiene un elevado número de líneas y en otro fichero tenemosidentificadas las lineas que queremos suprimir del primero (tal vez sean unos IDs,nombres, etc).
La idea es que tendríamos que recorrer el fichero que contiene los registros a suprimir ybuscar en el fichero de origen aquellos que cumplan la codición reescribiendo una y otravez el fichero hasta haber procesado todos los registros del fichero de error.
Mediante este script lo que hacemos es leer el fichero de origen y convertirlo a una lista(obviamente este script está diseñado para ficheros con varios miles de lineas pero con untamaño que no exceda el de la memoria disponible). Después iremos eliminando de estalista aquellos registros que cumplan la condición específica, para lo que usaremos elmétodo removeIf de Groovy
origen=new File('dump.txt').text.split('\n') as List
new File('errors.txt').withReader{ reader -> line='' while( line=reader.readLine() ){ String id=line.split(' ')[0] ① origen.removeIf{ it.startsWith(id) } ② }}
new File('cleaned.txt').withWriter('ISO-8859-1',{ it.write origen.join('\n') ③})
① Suponemos que el primer campo de cada linea es el Id a buscar
② Eliminamos de la lista todos los registros que cumplan la condicion de comenzar por elID, por ejemplo.
③ Generamos un fichero nuevo uniendo la lista con retornos de carro
101 Groovy Script
120 |
Script
origen=new File('dump.txt').text.split('\n') as List
new File('errors.txt').withReader{ reader -> line='' while( line=reader.readLine() ){ String id=line.split(' ')[0] ① origen.removeIf{ it.startsWith(id) } ② }}
new File('cleaned.txt').withWriter('ISO-8859-1',{ it.write origen.join('\n') ③})
101 Groovy Script
| 121
Scan and ExecuteJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2017-09-16
En algún momento de tu vida tendrás que recorrer un directorio de ficheros buscandoaquellos que cumplan una condición especial y ejecutar algún comando si lo encuentras.A veces la condición es simple, como buscar un texto dentro del fichero, que el nombredel fichero cumpla un patrón, etc, pero otras será algo más complicado.
Recientemente surgió la necesidad de subir todo el contenido de un repositorio maven enlocal (.m2) a un repositorio remoto Nexus3 por lo que un simple copy&paste no erasuficiente. En concreto las condiciones del proceso se resumían en:
• el repositorio Nexus3 está protegido por usuario/password
• buscar recursivamente en un directorio dado
• buscar si existe un fichero POM para el artefacto
• comprobar si existía un jar o un war con el mismo nombre del POM
• si la versión a la que apuntan es un snapshot deberán instalarse en un repo diferenteque si es una release
• ejecutar el comando mvn deploy:deploy-file con los parámetros correctos
Obviamente en tu caso los requisitos serán totalmente distintos yprobablemente podrás resolverlo con un BASH pero tal vez lascondiciones se vayan complicando y será cuando quieras hacerte unscript como este
101 Groovy Script
122 |
repoUrl=""
repoSnapUrl=""
void scanDir( dir ){
dir.eachFile{ f->
if( f.name.endsWith(".pom") ){ ①
def jar = dir.listFiles().find{
(it.name.endsWith(".jar") || it.name.endsWith(".war")) && ②
it.name.split('\\.').dropRight(1).join('.') == f.name.split('\\.').dropRight(1).join
('.')
}
if( jar ){
String url = jar.name.toLowerCase().contains("-snapshot.") ? repoSnapUrl : repoUrl ③
"""mvn deploy:deploy-file
-Dfile=$jar.absolutePath
-DpomFile=$f.absolutePath
-Durl=$url
-DrepositoryId=$repoId""".execute() ④
}
}
}
dir.eachDir{ d-> ⑤
scanDir(d)
}
}
repoId = args[1]
repoUrl = args[2]
repoSnapUrl = args.length > 3 ? args[3] : args[2]
scanDir( new File(args[0]) )
① Comprobamos si existe un fichero POM
② Comprobamos si existe un Jar o War con el mismo nombre (indica que pertenecen a lamisma version)
③ Configuramos repositorio para snapshot o para release
④ Construimos una cadena y la ejecutamos contra el sistema operativo
⑤ Recorremos recursivamente los subdirectorios
101 Groovy Script
| 123
Script
repoUrl=""
repoSnapUrl=""
void scanDir( dir ){
dir.eachFile{ f->
if( f.name.endsWith(".pom") ){ ①
def jar = dir.listFiles().find{
(it.name.endsWith(".jar") || it.name.endsWith(".war")) && ②
it.name.split('\\.').dropRight(1).join('.') == f.name.split('\\.').dropRight(1).join
('.')
}
if( jar ){
String url = jar.name.toLowerCase().contains("-snapshot.") ? repoSnapUrl : repoUrl ③
"""mvn deploy:deploy-file
-Dfile=$jar.absolutePath
-DpomFile=$f.absolutePath
-Durl=$url
-DrepositoryId=$repoId""".execute() ④
}
}
}
dir.eachDir{ d-> ⑤
scanDir(d)
}
}
repoId = args[1]
repoUrl = args[2]
repoSnapUrl = args.length > 3 ? args[3] : args[2]
scanDir( new File(args[0]) )
101 Groovy Script
124 |
A qué dedicas tu tiempo libre (GoogleCalendar)Jorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2017-12-3
Para este post vamos a necesitar los siguientes requisitos: una
cuenta en Google y eventos creados en el calendario. Por defecto usaremos
el calendario principal (pero se podría usar cualquier otro de los que
Google nos permite crear). Así mismo para poder acceder a las APIs de
Google deberemos obtener unas credenciales que autoricen a la aplicación
para acceder a nuestra cuenta.
En este post vamos a utilizar un GroovyScript para conectar el calendario de Googlerevisando todos los eventos que se encuentran creados, agrupándolos en función del díade la semana y el "tema" que le hayamos asignado.
Un evento del calendario de por sí NO incluye ningún campo del tipo"tema" por lo que para poder agruparlos podríamos usar, por ejemplo,algún identificador en el texto del mismo. En este ejemplo lo que vamos ausar es la capacidad de asignar un color a un evento, de tal forma que sien nuestro calendario asignamos el color "rojo" a los eventos de tipo"ocio", el color "azul" a los de "formación", etc. podremos de una formamuy fácil agrupar eventos dispares.
Consola GoogleEn primer lugar deberemos crear una aplicación en la consola de Google enhttps://console.developers.google.com
En esta aplicación habilitaremos (al menos) la API "Google Calendar API"
Así mismo deberemos crear unas credenciales de "Servicio" obteniendo la posibilidad dedescargarlas en un fichero JSON y que deberemos ubicar junto con el script.
GrooglePara facilitar la parte técnica de autentificación y creación de servicios he creado unproyecto de código abierto, Groogle, disponible en https://gitlab.com/puravida-software/groogle el cual publica un artefacto en Bintray y que podremos usar en nuestros scriptssimplemente incluyendo su repositorio.
101 Groovy Script
| 125
@GrabResolver(name='puravida', root="https://dl.bintray.com/puravida-software/repo" )
@Grab(group='org.groovyfx',module='groovyfx',version='8.0.0',transitive=false, noExceptions=true)
@Grab(group = 'com.puravida.groogle', module = 'groogle-core', version = '1.0.0')
import com.puravida.groogle.GroogleScript
import com.google.api.services.calendar.CalendarScopes
Groogle nos permitirá realizar el login de nuestra aplicación y guardar la autorización ennuestro disco de tal forma que en ejecuciones posteriores no se requiera de autorizar denuevo la aplicación (mientras no borremos el fichero con la autorización generada)
InputStream clientSecret = new File('client_secret.json').newInputStream() ①
def groogle = new GroogleScript(applicationName:'101-scripts') ②
groogle.login(clientSecret,[CalendarScopes.CALENDAR_READONLY]) ③
① client-secret.json es el fichero que hemos descargado de la consola de Google y quecontiene las credenciales
② Groogle realiza el login y guarda la autorización en$HOME/.credentials/${applicationName}
③ En este post únicamente requerimos acceso a Calendar para lectura
PersonalizaciónComo los temas son personales cada uno deberá configurar los suyos según suspreferencias. Así mismo creará más o menos temas según el grado de detalle que sequiera obtener. Para este post vamos a utilizar únicamente 3 temas:
def colors = [ '0':'Generico', '10':'Reuniones', ① '11':'Ocio']
① Hasta ahora no he encontrado cómo saber el ID del color a priori por lo que deberásejecutarlo primero y ver cuales te asigna.
BusinessLa lógica de negocio de este script consistirá básicamente en recorrer todos los eventosque nos devuelva Google y para cada uno de ellos buscar en un mapa si existe un sub-mapa "tema" y si para este sub-mapa existe otro sub-map para el día de la semana dondeir acumulando eventos. En caso de que no exista, simplemente lo inicializaremos a cero
101 Groovy Script
126 |
para ir acumulando sobre él.
def week = ['D','L','M','X','J','V','S']
def stadistics = [:]
groogle.calendarService.events().list(calendarId).execute().items.each{ event -> ①
def events = !event.recurrentId ? [event] : ②
groogle.calendarService.events().instances(calendarId, event.id).execute().items
events.each{ item ->
String colorId = item.colorId ?: '0'
if( !stadistics."${colors[colorId]}"){ ③
stadistics."${colors[colorId]}" =[:]
(java.util.Calendar.SUNDAY..java.util.Calendar.SATURDAY).each{ day ->
stadistics."${colors[colorId]}"."${week[day-1]}"= 0
}
}
int day = new Date( (event.start.date ?: event.start.dateTime).value)[java.util.Calendar.DAY_OF_WEEK]
stadistics."${colors[colorId]}"."${week[day-1]}" +=1 ④
}
}
① Recorrer todos los eventos que nos devuelva Google
② Un evento puede ser puntual o recurrente. En este caso deberemos obtener todas lasinstancias del mismo
③ Buscar en el mapa el tema asociado al evento y si no existe inicializarlo
④ Incrementar en uno para el día de la semana del evento
VistaPara la vista usaremos un GroovyFX [http://groovyfx.org/] que nos muestre una gráfica debarras barChart tal que nos permite visualizar agrupados por temas los diferentescontadores de los días de la semana. Sin embargo este componente nos pide que leproporcionemos la serie de datos en una sucesión de 'clave', 'valor', 'clave', 'valor' en lugarde un mapa, por lo que lo primero que haremos será transformar nuestro mapa en unomás adecuado:
def flatten =[:]stadistics.each{ flatten."$it.key" = it.value.collect{ [it.key,it.value]}.flatten() ①}
Y ahora ya podemos construir la vista, generando las series de una forma dinámica.
101 Groovy Script
| 127
start {
stage(visible:true,width:640,height:480) { ②
scene{
tilePane() {
barChart(
yAxis:new NumberAxis(label:'Ocupacion', tickLabelRotation:90),
xAxis:new CategoryAxis(label:'Actividad'),
barGap:3,
categoryGap:20) {
flatten.each{ ①
series(name: it.key, data:it.value)
}
}
}
}
}
}
① Creamos dinámicamente las series en función de los temas que tengamos inicialmente.
Mi calendario quedaría de esta forma:
101 Groovy Script
128 |
Script
//tag::dependencies[]
@GrabResolver(name='puravida', root="https://dl.bintray.com/puravida-software/repo" )
@Grab(group='org.groovyfx',module='groovyfx',version='8.0.0',transitive=false, noExceptions=true)
@Grab(group = 'com.puravida.groogle', module = 'groogle-core', version = '1.0.0')
import com.puravida.groogle.GroogleScript
import com.google.api.services.calendar.CalendarScopes
//end::dependencies[]
import static groovyx.javafx.GroovyFX.start
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.application.Platform
import javafx.scene.SnapshotParameters
import javax.imageio.ImageIO
import java.awt.image.BufferedImage
//tag::login[]
InputStream clientSecret = new File('client_secret.json').newInputStream() ①
def groogle = new GroogleScript(applicationName:'101-scripts') ②
groogle.login(clientSecret,[CalendarScopes.CALENDAR_READONLY]) ③
//end::login[]
String calendarId = 'primary'
/*
Si quieres saber todos tus calendarios simplemente ejecuta esta linea
println groogle.calendarService.calendarList().list().execute().items*.id
*/
//tag::temas[]
def colors = [
'0':'Generico',
'10':'Reuniones', ①
'11':'Ocio'
]
//end::temas[]
//tag::business[]
def week = ['D','L','M','X','J','V','S']
def stadistics = [:]
groogle.calendarService.events().list(calendarId).execute().items.each{ event -> ①
def events = !event.recurrentId ? [event] : ②
groogle.calendarService.events().instances(calendarId, event.id).execute().items
events.each{ item ->
String colorId = item.colorId ?: '0'
if( !stadistics."${colors[colorId]}"){ ③
stadistics."${colors[colorId]}" =[:]
(java.util.Calendar.SUNDAY..java.util.Calendar.SATURDAY).each{ day ->
stadistics."${colors[colorId]}"."${week[day-1]}"= 0
}
}
int day = new Date( (event.start.date ?: event.start.dateTime).value)[java.util.Calendar.DAY_OF_WEEK]
stadistics."${colors[colorId]}"."${week[day-1]}" +=1 ④
}
}
//end::business[]
//tag::flatten[]
101 Groovy Script
| 129
def flatten =[:]
stadistics.each{
flatten."$it.key" = it.value.collect{ [it.key,it.value]}.flatten() ①
}
//end::flatten[]
//tag::vista[]
start {
stage(visible:true,width:640,height:480) { ②
scene{
tilePane() {
barChart(
yAxis:new NumberAxis(label:'Ocupacion', tickLabelRotation:90),
xAxis:new CategoryAxis(label:'Actividad'),
barGap:3,
categoryGap:20) {
flatten.each{ ①
series(name: it.key, data:it.value)
}
}
}
}
}
}
//end::vista[]
101 Groovy Script
130 |
Import/Export de Google Sheet aDatabaseJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2017-12-30
Para este post es necesario tener una cuenta en Google así como
tener al menos una Hoja de Cálculo de Google Drive (SpreadSheet). Así
mismo para poder acceder a las APIs de Google deberemos obtener unas
credenciales que autorizen a la aplicación a acceder a nuestra cuenta
habilitandolo para ello desde la consola developer de Google.
A veces el departamento de informática tiene que dar soporte a otros departamentos enlas tareas de exportar y/o importar datos de la base de daos usando formatos que a otrosdepartamentos les sea cómodo. Sin lugar a dudas, el formato elegido en la mayoría de loscasos es un fichero Excel de Microsoft. (Ver post en categoría bbdd y office para consultarscripts ya documentados)
Sin embargo muchas veces este Excel debe ser revisado por más de una persona antes devolver a ser enviado al departamento de informática para su importación en la base dedatos por lo que se produce un riesgo de disparidad de versiones en el fichero, infinidadde correos adjuntando la última versión, etc.
En un mundo colaborativo como el de hoy en día, sería interesante si todas las partes(incluido el departamento de informática) pudieran utilizar un único fichero dereferencia siendo Google Sheet uno de los productos que lo permiten.
Así pues nuestro script va a consistir en, dado un SpreadSheet de Google y una cuenta deautorización:
• conectarse a la base de datos
• conectarse al SpreadSheet
• si la operación que se le indica es de exportar, recorrer todas las hojas del SpreadSheety volcar sobre cada una de ellas los datos de la tabla cuyo nombre coincida con la hoja
• si la operación es de importar, recorrer todas las hojas del SpreadSheet e importartodos las filas de la hoja en la tabla cuyo nombre coincida con la hoja.
101 Groovy Script
| 131
Id del SpreadSheet
En este post vamos a utilizar un GroovyScript para conectarse a nuestroSpreadSheet de Google para leer y/o escribir en el mismo por lo que necesitaremossaber su id. Este id se puede obtener de la propia URL que utiliza el navegadorcuando lo estamos editando. Por ejemplo, si la URL tiene este formato:
https://docs.google.com/spreadsheets/d/xxxxxxxxxtKhml999999999Icp123/edit#gid=0
Entonces xxxxxxxxxtKhml999999999Icp123 es el id que necesitamosproporcionar como argumento al script.
Consola GoogleEn primer lugar deberemos crear una aplicación en la consola de Google enhttps://console.developers.google.com
En esta aplicación habilitaremos (al menos) la API "Google Sheet API"
Así mismo deberemos crear unas credenciales de "Servicio" tras lo cual dispondremos dela posibilidad de descargar un fichero JSON con las mismas y que deberemos ubicar juntocon el script
GrooglePara facilitar la parte técnica de autentificación y creación de servicios he creado unproyecto de código abierto, Groogle, disponible en https://gitlab.com/puravida-software/groogle el cual publica un artefacto en Bintray y que podremos usar en nuestros scriptssimplemente incluyendo su repositorio. Groogle se compone de varios componentes:
• groogle-core, contiene la parte de autentificación y autorización del usuario así comoproporcionar servicios básicos
• groogle-sheet, facilita la gestión de Sheets mediante un lenguaje específico (DSL)permitiendo la lectura y escritura de una hoja de calculo
• groogle-drive, facilita la gestión de ficheros en Drive (aún está en desarrollo)
• groogle-calendar, facilita la gestión de Eventos de un Calendario mediante un lenguajeespecífico
Dependencias
101 Groovy Script
132 |
@GrabResolver(name='puravida', root="https://dl.bintray.com/puravida-software/repo" )@Grab(group = 'com.puravida.groogle', module = 'groogle-sheet', version = '1.1.1')@Grab('mysql:mysql-connector-java:5.1.23')@GrabConfig(systemClassLoader=true)
import com.google.api.services.sheets.v4.SheetsScopesimport com.puravida.groogle.GroogleScriptimport com.puravida.groogle.SheetScriptimport groovy.sql.Sql
AutorizaciónGroogle nos permitirá realizar el login de nuestra aplicación y guardar la autorización ennuestro disco de tal forma que ejecuciones posteriores no requieran de volver a autorizarla aplicación (mientras no borremos el fichero con la autorización generada)
clientSecret = new File('client_secret.json').newInputStream() ①
groogleScript = new GroogleScript('101-scripts', clientSecret,[SheetsScopes.SPREADSHEETS]) ②
sheetScript = new SheetScript(groogleScript: groogleScript) ③
① client-secret.json es el fichero que hemos descargado de la consola de Google y quecontiene las credenciales
② Groogle realiza el login y guarda la autorización en$HOME/.credentials/${applicationName}
③ SheetScript ofrece un DSL para la gestión de lectura/escritura del SpreadSheet
DatabasePara la gestión de la base de datos hemos creado una serie de métodos simples:
101 Groovy Script
| 133
sqlInstance = Sql.newInstance( "jdbc:mysql://localhost:3306/origen?jdbcCompliantTruncation=false", "user",
"password", "com.mysql.jdbc.Driver")
List metadataTable(String table){ ①
def ret = []
sqlInstance.rows( "select * from $table limit 1".toString(),{ meta->
(1..meta.columnCount).each{
ret.add(meta.getColumnName(it))
}
})
ret
}
void cleanTable(String table){ ②
sqlInstance.execute "truncate table $table".toString()
}
void insertRowsIntoTable(List params, String table, List metadata){ ③
String sql = "insert into $table (${metadata.join(',')}) values (${'?,'.multiply(metadata.size()-1)}?)"
sqlInstance.withBatch(sql){ ps->
params.each{ param ->
ps.addBatch param
}
}
}
List moreRows(String table, int from, int size){ ④
sqlInstance.rows( "select * from $table order by 1".toString(),from, size)*.values()
}
① Dado el nombre de una table, otener sus columnas mediante el metadata
② Borrado de una tabla. En nuestro caso y por ser MySQL hacemos un truncate
③ Insertado optimizado de un List<List<Object>> (lista de lista de valores)
④ Lectura de un lote de valores a partir de un registro dado
ArgumentosPara la ejecución correcta del script necesitamos que nos proporcionen el id de la hoja asícomo la intención de la ejecución, es decir, "De Google a Database" o "De Database aGoogle" lo cual lo representamos mediante los comandos g2d o d2g respectivamente
101 Groovy Script
134 |
cli = new CliBuilder(usage: '-i -s spreadSheetId g2d|d2g')
cli.with {
h(longOpt: 'help', args:0,'Usage Information', required: false)
s(longOpt: 'spreadSheet', args:1, argName:'spreadSheetId', 'El id de la hoja a usar', required: true)
}
options = cli.parse(args)
if (!options) return
if (options.h || options.arguments().size()!=1) {
cli.usage()
return
}
if( ['g2d','d2g'].contains(options.arguments()[0]) == false ){
cli.usage()
return
}
google2Database = options.arguments()[0] == 'g2d'
Así por ejemplo una posible ejecución del scritp sería:
De Google a Database
groovy ImportExportSheet.groovy -s xxxxxxxxxtKhml999999999Icp123 g2d
Comparando MetadatosEn primer lugar, el script validará que los esquemas de la hoja y de la base de datos secorresponden, de tal forma que ambos tienen las mismas columnas (para cada hoja dedatos que contenga el SpreadSheet).
Para ello lee la primera fila de cada hoja y la compara con el metadata de la tabla con elmismo nombre en la base de datos. Si no coinciden (por ejemplo la hoja contiene unacolumna más que la bbdd, o alguien ha cambiado el nombre de una columna) se aborta elscript mediante un assert
101 Groovy Script
| 135
sheetScript.withSpreadSheet options.spreadSheet, { spreadSheet -> ①
sheets.each{ gSheet -> ②
String hojaId = gSheet.properties.title
println "Validando schema de $hojaId"
withSheet hojaId, { sheet -> ③
def sheetColumns = readRows("A1", "AA1").first() ④
def tableColumns = metadataTable(gSheet.properties.title)
assert sheetColumns.intersect(tableColumns).size() == sheetColumns.size(), """
$gSheet.properties.title incompatible schemas:
Sheet: $sheetColumns
MySQL: $tableColumns
"""
}
}
}
① withSpreadSheet(id, closure), ejecuta la closure dentro de un contexto referenciado alSpreadSheet indicado
② el DSL nos permite acceder a todas las hojas mediante sheets
③ withSheet(id, closure) ejecuta la closure dentro de un contexto referenciado a la hojaindicada
④ readRows (rangoA, rangoB) realiza la lectura de un rango de celdas devolviendo unList<List<Object>>
De Google a DatabaseUna vez validados los metadatas de cada hoja el script puede realizar la importancióndesde las hojas a la base de datos (si el argumento de la ejecución es g2d). Para ellorecorrerá las filas de la hoja mediante readRows hasta que no haya datos (cuandoreadRows devuelva null) y para evitar un número excesivo de lecturas lo hará en bloquesde 100
101 Groovy Script
136 |
if( google2Database ) { sheetScript.withSpreadSheet options.spreadSheet, { spreadSheet -> sheets.each { gSheet -> String hojaId = gSheet.properties.title println "Importando hoja $hojaId"
withSheet hojaId, { sheet -> def sheetColumns = readRows("A1", "AA1").first() cleanTable(hojaId)
int idx = 2 def rows = readRows("A$idx", "AA${idx + 100}") while (rows) { insertRowsIntoTable(rows, hojaId, sheetColumns) idx += rows.size() rows = readRows("A$idx", "AA${idx + 100}") }
}
} }}
De Database a GoogleUna vez validados los metadatas de cada hoja el script puede realizar la importancióndesde la base de datos a las hojas (si el argumento de la ejecución es d2g). Para ellorecorrerá los registros de la base de datos en bloques de 100 y los insertará en la hojasimplemente llamando el método appendRows que admite un List<List<Object>>
Previamente cada hoja es inicializada mediante clearRange añadiendo también lostítulos mediante appendRow que requiere un List<Object>
101 Groovy Script
| 137
if( !google2Database ) { sheetScript.withSpreadSheet options.spreadSheet, { spreadSheet -> sheets.each{ gSheet -> String hojaId = gSheet.properties.title println "Exportando a hoja $hojaId"
withSheet hojaId, { sheet -> def sheetColumns = readRows("A1", "AA1").first() clearRange('A','AA')
appendRow(sheetColumns)
int idx=0 def rows = moreRows(hojaId, idx, 100) while( rows.size() ){
appendRows(rows) idx+=rows.size()+1 rows = moreRows(hojaId, idx, 100) } }
} }}
101 Groovy Script
138 |
Script
//tag::dependencies[]
@GrabResolver(name='puravida', root="https://dl.bintray.com/puravida-software/repo" )
@Grab(group = 'com.puravida.groogle', module = 'groogle-sheet', version = '1.1.1')
@Grab('mysql:mysql-connector-java:5.1.23')
@GrabConfig(systemClassLoader=true)
import com.google.api.services.sheets.v4.SheetsScopes
import com.puravida.groogle.GroogleScript
import com.puravida.groogle.SheetScript
import groovy.sql.Sql
//end::dependencies[]
//tag::cli[]
cli = new CliBuilder(usage: '-i -s spreadSheetId g2d|d2g')
cli.with {
h(longOpt: 'help', args:0,'Usage Information', required: false)
s(longOpt: 'spreadSheet', args:1, argName:'spreadSheetId', 'El id de la hoja a usar', required: true)
}
options = cli.parse(args)
if (!options) return
if (options.h || options.arguments().size()!=1) {
cli.usage()
return
}
if( ['g2d','d2g'].contains(options.arguments()[0]) == false ){
cli.usage()
return
}
google2Database = options.arguments()[0] == 'g2d'
//end::cli[]
//tag::database[]
sqlInstance = Sql.newInstance( "jdbc:mysql://localhost:3306/origen?jdbcCompliantTruncation=false", "user",
"password", "com.mysql.jdbc.Driver")
List metadataTable(String table){ ①
def ret = []
sqlInstance.rows( "select * from $table limit 1".toString(),{ meta->
(1..meta.columnCount).each{
ret.add(meta.getColumnName(it))
}
})
ret
}
void cleanTable(String table){ ②
sqlInstance.execute "truncate table $table".toString()
}
void insertRowsIntoTable(List params, String table, List metadata){ ③
String sql = "insert into $table (${metadata.join(',')}) values (${'?,'.multiply(metadata.size()-1)}?)"
sqlInstance.withBatch(sql){ ps->
params.each{ param ->
ps.addBatch param
}
}
}
List moreRows(String table, int from, int size){ ④
sqlInstance.rows( "select * from $table order by 1".toString(),from, size)*.values()
}
101 Groovy Script
| 139
//end::database[]
//tag::login[]
clientSecret = new File('client_secret.json').newInputStream() ①
groogleScript = new GroogleScript('101-scripts', clientSecret,[SheetsScopes.SPREADSHEETS]) ②
sheetScript = new SheetScript(groogleScript: groogleScript) ③
//end::login[]
//tag::schemas[]
sheetScript.withSpreadSheet options.spreadSheet, { spreadSheet -> ①
sheets.each{ gSheet -> ②
String hojaId = gSheet.properties.title
println "Validando schema de $hojaId"
withSheet hojaId, { sheet -> ③
def sheetColumns = readRows("A1", "AA1").first() ④
def tableColumns = metadataTable(gSheet.properties.title)
assert sheetColumns.intersect(tableColumns).size() == sheetColumns.size(), """
$gSheet.properties.title incompatible schemas:
Sheet: $sheetColumns
MySQL: $tableColumns
"""
}
}
}
//end::schemas[]
//tag::g2d[]
if( google2Database ) {
sheetScript.withSpreadSheet options.spreadSheet, { spreadSheet ->
sheets.each { gSheet ->
String hojaId = gSheet.properties.title
println "Importando hoja $hojaId"
withSheet hojaId, { sheet ->
def sheetColumns = readRows("A1", "AA1").first()
cleanTable(hojaId)
int idx = 2
def rows = readRows("A$idx", "AA${idx + 100}")
while (rows) {
insertRowsIntoTable(rows, hojaId, sheetColumns)
idx += rows.size()
rows = readRows("A$idx", "AA${idx + 100}")
}
}
}
}
}
//end::g2d[]
//tag::d2g[]
if( !google2Database ) {
sheetScript.withSpreadSheet options.spreadSheet, { spreadSheet ->
sheets.each{ gSheet ->
String hojaId = gSheet.properties.title
println "Exportando a hoja $hojaId"
withSheet hojaId, { sheet ->
101 Groovy Script
140 |
def sheetColumns = readRows("A1", "AA1").first()
clearRange('A','AA')
appendRow(sheetColumns)
int idx=0
def rows = moreRows(hojaId, idx, 100)
while( rows.size() ){
appendRows(rows)
idx+=rows.size()+1
rows = moreRows(hojaId, idx, 100)
}
}
}
}
}
//end::d2g[]
101 Groovy Script
| 141
Gestionando permisos de Google DriveJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2018-10-12
Para este post es necesario tener una cuenta en Google así como
tener al menos algunos ficheros en Google Drive. Así mismo para poder
acceder a las APIs de Google deberemos obtener unas credenciales que
autorizen a la aplicación a acceder a nuestra cuenta. Para obtener dichas
credenciales debes acceder a https://console.google.developers y crearlas
desde allí. Para este script el tipo de credenciales serán de tipo cuenta (es
decir el script se ejecutará en representación del usuario una vez que lo
autorize)
Hace unos días un usuario/amigo me planteaba una duda en Twitter sobre Groogle:
tengo un problema con mi Drive. Tengo cienes de carpetas y docscompartidos con URL y los quiero "descompartir" todos los que estándentro de una carpeta …
— Michael Gallego, https://twitter.com/micael_gallego/status/1049370562487865347
A partir de la versión 1.5.1 Groogle incluye un DSL específico para la gestión de permisosque analizaremos en este post.
DependenciasLa versión 1.5.1 no ha sido liberada todavía, así que utilizaremos la versión en previewdisponible en Bintray:
@GrabResolver(name='puravida', root="https://dl.bintray.com/puravida-software/repo" )
@Grapes([
@Grab(group = 'com.puravida.groogle', module = 'groogle-drive', version = '1.5.1-dev.32+5d37817')
])
import com.google.api.services.drive.DriveScopes
import com.puravida.groogle.DriveScript
Opciones y loginEl script acepta un parámetro para indicar el comando a ejecutar y si es necesario unsegundo argumento. Al ser un script de ejemplo no es la intención demostrar lasposibilidades de Groovy para trabajar con argumentos proporcionados por línea de
101 Groovy Script
142 |
comando (en la sección Básico puedes encontrar un post donde se explica mejor)
Así mismo como todas las opciones van a necesitar identificar al usuario, usamos unafunción login de utilidad
drive = DriveScript.instance ①
void login() { drive.with { login { applicationName 'test-permissions' withScopes DriveScopes.DRIVE usingCredentials '/client_secret.json' asService false ② } }}
login() ③
switch( args[0] ){ ④ case "dumpFile": return dumpFile(args[1]) case "dumpFolder": return dumpPermissions(args.length>1 ?args[1]:null) case "removeFolder": return removePermissions(args.length>1 ?args[1]:null) case "removeFile": return removeFilePermissions(args[1]) case "shareFile": return shareFilePermissions(args[1],args[2])}
① referencia a DriveScript
② usaremos una cuenta de usuario para acceder a todos sus documentos
③ la primera vez abrirá un navegador para identificarnos. Las siguientes usará la caché
④ opciones que nos va a permitir el script
De forma resumida, tras hacer login, el script nos permitirá indicar para cada ejecución:
• si queremos volcar por pantalla los permisos de un fichero (necesitamos su id)
• si queremos volcar TODOS los ficheros de mi Drive (no pasamos argumento) o de unacarpeta (proporcionamos su id)
• si queremos revocar todos los permisos de un fichero (necesitamos su id)
• si queremos revocar TODOS los permisos excepto owner de TODOS los ficheros de miDrive (no pasamos argumento) o de una carpeta (proporcionamos su id)
101 Groovy Script
| 143
• compartir un fichero con un usuario proporcionado como segundo argumento
Existen más casos de uso, como revocar permisos sólo a ficheros en una carpeta,compartir con role de edición, etc que son fáciles de implementar siguiendo este post y ladocumentación de Groogle
dumpFile / dumpAllEstos métodos se ofrecen a nivel de comprobar los permisos que tiene un fichero/carpetaen un momento dado. Podemos ejecutarlo por ejemplo al principio para comprobar lasituación y al final para ver si se han aplicado los permisos que queríamos.
void dumpFile(fileId){ drive.with { withFile fileId, { println "$file.name ($file.id)" file.permissions.each { permission -> println "\t $permission" } } }}void dumpPermissions(folderId) { def self = this drive.with { withFiles { if(folderId) parentId folderId eachFile { file, idx -> println "$id: $file.name ($file.id)" file.permissions.each { permission -> println "\t $permission" } if( file.mimeType=='application/vnd.google-apps.folder') ① self.dumpPermissions(file.id) } } }}
① workarround para trabajar con las subcarpetas
Ambos comandos son parecidos diferenciandose en que uno actúa sobre un fichero dadoy el otro recorre todos los ficheros de una carpeta.
El script, una vez obtenido el fichero (OJO! no es un java.io.File sino uncom.google.api.services.drive.model.File) itera sobre sus permisos, por ejemplo paravolcarlos por pantalla obteniendo un resultado similar a:
101 Groovy Script
144 |
Documento con 2 permisos (owner y user)
Ejemplo1 (1HRnKA0o_xxxxxxxSSSSSSSSaaaaaaa)
[deleted:false, displayName:Jorge Aguilera Gonzalez, emailAddress:jorge.aguilera@puravida-
software.com, id:12312, kind:drive#permission, photoLink:https://lh3.googleusercontent.com/photo.jpg,
role:reader, type:user]
[deleted:false, displayName:groovy groogle, emailAddress:[email protected], id:111,
kind:drive#permission, photoLink:https://lh6.googleusercontent.com/s64/photo.jpg, role:owner, type:user]
Utilizando las APIs de Google podríamos en este punto eliminar o añadir nuevospermisos, pero Groogle nos permite hacerlo mediante un DSL permissions
removeFile / removeAllAl igual que los métodos dump, ambos comandos son similares salvo que uno actúa sobreun fichero y el otro sobre todos. En ambos casos vamos a eliminar todos los permisos quepudiera tener el fichero excepto el de owner (que por otra parte Google no nos dejaríadando un error)
La misión del DSL es hacer coincidir los permisos existentes con los indicados en elmismo, permitiendo chequear diferentes tipos y roles. Por ejemplo:
• permitir a ciertos usuarios editar el documento
• permitir a ciertos usuarios leer el documento
• permitir a cualquiera con el enlace leer el documento
• permitir a los usuarios de un dominio editar el documento
• etc
Para ello provee diferentes métodos como usersAsReader '[email protected]' para indicarque queremos que un usuario dado pueda leer el documento.
Por defecto el DSL buscará aquellos que hayan sido especificados y NO existen en eldocumento y los creará.
Si además especificamos strick true el DSL se ejecutará en modo estricto y borraráaquellos que estén presentes en el fichero pero no se haya permitido en el DSL
Eliminar todos los permisos
withFile fileId, { file -> permissions { strick true } }
En el ejemplo anterior estamos eliminando TODOS los permisos de acceso (excepto el deowner) que pudiera tener el documento.
101 Groovy Script
| 145
Si una vez ejecutado el comando para eliminar permisos volvieramos a ejecutar el dumpobtendríamos algo parecido a:
Documento con 1 permiso (owner)
Ejemplo1 (1HRnKA0o_xxxxxxxSSSSSSSSaaaaaaa)
[deleted:false, displayName:groovy groogle, emailAddress:[email protected], id:111,
kind:drive#permission, photoLink:https://lh6.googleusercontent.com/s64/photo.jpg, role:owner, type:user]
shareFilePor último el script nos va a permitir compartir un fichero con un usuario,proporcionados ambos por línea de comando para que este pueda acceder en modolectura al documento.
Share File
withFile fileId, { permissions { usersAsReader args
//others: //usersAsWriter args //domainAsReader args //anyoneAsReader //anyoneAsWriter
strick true } }
En un mismo DSL permisssions podemos especificar diferentes casos como por ejemplo:
• usersAsWriter para especificar una lista de usuarios que pueden editar
• domainAsReader para especificar un dominio como lectura
• anyoneAsRead para indicar que cualquiera con el enlace puede leer
• etc
Documentación adicionalPara más info se puede consultar la documentación de Groogle:
https://puravida-software.gitlab.io/groogle/groogle-drive/
101 Groovy Script
146 |
Raffle (Google Sheet + Meetups)Jorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2019-04-9
Para este post vamos a necesitar los siguientes requisitos: una
cuenta en Google y una hoja de cálculo Sheet de Google. La estructura de
la hoja puede ser como quieras pero para este caso vamos a usar las 3
primeras columnas con los valores "nombre", "apellido" y "email"
En este post vamos a utilizar un GroovyScript para leer una hoja Sheet de Google dondelos asistentes a una charla se han registrado (tal vez con Google Form) para realizar unsorteo eligiendo a uno de ellos al azar. Si el elegido no se encuentra (o rechaza el premio)se le elimina de la lista y se vuelve a realizar el sorteo.
Consola GoogleEn primer lugar deberemos crear una aplicación en la consola de Google enhttps://console.developers.google.com
En esta aplicación habilitaremos (al menos) la API "Google Sheet API"
Así mismo deberemos crear unas credenciales de "usuario" obteniendo la posibilidad dedescargarlas en un fichero JSON y que deberemos ubicar junto con el script.
GrooglePara facilitar la parte técnica de autentificación y creación de servicios he creado unproyecto de código abierto, Groogle, disponible en https://groogle.gitlab.com/ el cualpublica un artefacto en Bintray y que podremos usar en nuestros scripts simplementeincluyendo su repositorio.
@Grab(group='com.puravida.groogle', module='groogle-sheet', version='2.0.0')import com.google.api.services.sheets.v4.SheetsScopesimport com.puravida.groogle.*import com.puravida.groogle.sheet.*import groovy.swing.SwingBuilder
import java.awt.BorderLayoutimport java.awt.BorderLayout as BLimport javax.swing.*
Groogle nos permitirá realizar el login de nuestra aplicación y guardar la autorización ennuestro disco de tal forma que en ejecuciones posteriores no se requiera de autorizar de
101 Groovy Script
| 147
nuevo la aplicación (mientras no borremos el fichero con la autorización generada)
credentials { applicationName 'raffle' withScopes SheetsScopes.SPREADSHEETS usingCredentials "client_secret.json" asService false } service(SheetServiceBuilder.build(), SheetService)
① client-secret.json es el fichero que hemos descargado de la consola de Google y quecontiene las credenciales
② Groogle realiza el login y guarda la autorización en$HOME/.credentials/${applicationName}
③ En este post únicamente requerimos acceso a Sheet para lectura
Leyendo la hojaMediante un bucle iremos recuperando filas en bloques de 100 hasta que no haya máselementos y los iremos añadiendo a un List
(La hoja se puede llamar como se desee, por ejemplo una hoja por cada Meetup y seríafácilmente adaptable para ser proporcionada por parámetro. En este ejemplo vamos ausar raffle-example-meetup-1 )
withSheet 'raffle-example-meetup-1', { int i=2 range = writeRange("A$i", "C${i+99}").get() while( range ){ all.addAll range i+=99 range = writeRange("A$i", "C${i+99}").get() } }
PresentaciónPara la interface de usuario vamos a utilizar SwingBuilder , un DSL de groovy, que permitecrear interfaces Swing de forma fácil.
Nuestro interface va a consistir básicamente en un JList con el array de asistentes y unbotón que realizará la acción de elegir uno de ellos aleatoriamente.
Tras mostrar el elegido, si el moderador lo desea puede indicar que el asistente no estápara repetir el sorteo sin él.
101 Groovy Script
148 |
new SwingBuilder().edt {
frame(title: 'GroogleRaffe',
defaultCloseOperation: JFrame.EXIT_ON_CLOSE,
extendedState: JFrame.MAXIMIZED_BOTH,
show: true) {
borderLayout()
panel(constraints: BorderLayout.PAGE_START){
label("Meetup Raffle")
}
panel(constraints: BorderLayout.PAGE_END){
button(text:'Raffle',
actionPerformed: {
Random rnd = new Random()
int selected = rnd.nextInt(all.size())
String msg = "<html><b>Is <font color='red'>${all[selected][0]} ${all[selected][1]}
here ?</font></b></html>"
int yepes = JOptionPane.showConfirmDialog(current,msg)
if( yepes != JOptionPane.YES_OPTION ){
theList.model.remove(selected)
}
}, constraints:BL.SOUTH)
}
scrollPane(constraints: BorderLayout.CENTER){
list(id:'theList', listData: all,
cellRenderer: { list, value, index, isSelected, cellHasFocus->
new JLabel(value[0]+" "+value[1])
}
)
}
}
}
Ejemplo
101 Groovy Script
| 149
Organizando eventosJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2018-02-4
Para este post vamos a necesitar los siguientes requisitos: una
cuenta, una SpreadSheet y un Calendario todo ello de Google . Se
recomienda crear un calendario específico para este ejercicio puesto que
vamos a borrar y crear eventos en el mismo de forma genérica y si lo
hacemos sobre el principal podríamos perder eventos de nuestro interés.
Así mismo para poder acceder a las APIs de Google deberemos obtener
unas credenciales que autoricen a la aplicación para acceder a nuestra
cuenta.
Este post está inspirado en una iniciativa de Alba Roza (@Alba_Roza)https://twitter.com/Alba_Roza/status/957936830657257473
Consola GoogleEn primer lugar deberemos crear una aplicación en la consola de Google enhttps://console.developers.google.com
En esta aplicación habilitaremos las APIs de "Google Calendar API" y "Google Sheet API"
Así mismo deberemos crear unas credenciales de "Servicio" obteniendo la posibilidad dedescargarlas en un fichero JSON y que deberemos ubicar junto con el script.
GrooglePara facilitar la parte técnica de autentificación y creación de servicios he creado unproyecto de código abierto, Groogle, disponible en https://puravida-software.gitlab.io/groogle/ el cual publica un artefacto en Bintray y que podremos usar en nuestros scriptssimplemente incluyendo su repositorio.
Este proyecto se divide a su vez en 4 subprojectos:
• groogle-core, contiene la parte de autentificación principalmente
• groogle-drive, para el manejo de ficheros y carpetas en Drive
• groogle-sheet, para la gestión específica de hojas de cálculo Sheet
• groogle-calendar, para la gestión específica de calendarios
101 Groovy Script
| 151
En nuestro caso vamos a utilizar estos dos últimos (el primero se autoincluye pordependencia transitiva)
@GrabResolver(name='puravida', root="https://dl.bintray.com/puravida-software/repo" )@Grab(group = 'com.puravida.groogle', module = 'groogle-core', version = '1.4.1')@Grab(group = 'com.puravida.groogle', module = 'groogle-sheet', version = '1.4.1')@Grab(group = 'com.puravida.groogle', module = 'groogle-calendar', version = '1.4.1')@GrabConfig(systemClassLoader=true)
import com.google.api.services.calendar.CalendarScopesimport com.google.api.services.sheets.v4.SheetsScopesimport com.puravida.groogle.GroogleScriptimport com.puravida.groogle.CalendarScriptimport com.puravida.groogle.SheetScript
Groogle nos permitirá realizar el login de nuestra aplicación y guardar la autorización ennuestro disco de tal forma que en ejecuciones posteriores no se requiera de autorizar denuevo la aplicación (mientras no borremos el fichero con la autorización generada)
GroogleScript.instance.applicationName='101-groovy'
clientSecret = this.class.getResource('client_secret.json').newInputStream()
GroogleScript.instance.login(clientSecret,[CalendarScopes.CALENDAR, SheetsScopes.SPREADSHEETS])
CalendarScript.instance.groogleScript=GroogleScript.instance
SheetScript.instance.groogleScript=GroogleScript.instance
IdentificadoresInicializamos (por comodidad) el id de la hoja de cálculo así como el del calendario
sheetId='xxxxxxxxxxxxxxxx_yyyyyyyyy-fMLg'calendarId='[email protected]'
SheetPara nuestro ejemplo la hoja Eventos a utilizar será muy sencilla y contendrá la siguienteestructura:
año mes día evento lugar
2018 1 1 Fiesta AñoNuevo
mi casa
2018 2 1 Codificandocomo locos
Mordor
Como los eventos que vamos a gestionar son eventos de todo el día vamos a formatear la
101 Groovy Script
152 |
fecha a YYYY-MM-DD lo cual nos permitirá posteriormente crear eventos en el calendariode forma muy cómoda. Si por ejemplo quisieramos tratar eventos "desde-hasta"simplemente modificaríamos la estructura de la hoja y no realizaríamos este formateo
events=[]
SheetScript.instance.withSpreadSheet sheetId, {
withSheet 'Eventos',{
int rowIdx=2
def row = readRows("A$rowIdx","E$rowIdx")
def calendarScript = CalendarScript.instance
while( row ){
def when =sprintf( '%1$04d-%2$02d-%3$02d',row.first()[0] as int,row.first()[1] as int,row.
first()[2] as int)
def summary ="${row.first()[3]}"
def description="${row.first()[4]}"
println "$when $summary $description"
events.add([when:when,summary:summary,description:description])
rowIdx++
row = readRows("A$rowIdx","E$rowIdx")
}
}
}
Recorremos todas las filas (excepto la primera) y recogemos en un mapa los detalles decada evento. Al finalizar tendremos una lista de eventos que podremos "limpiar", unificar,etc
CalendarUna vez que disponemos de los eventos realizaremos en primer lugar una limpieza delcalendario:
CalendarScript.instance.withCalendar( calendarId, { eachEvent { removeFromCalendar() }})
Por último simplemente tenemos que recorrer la lista de los eventos e ir creandolos ennuestro calendario. Como ya hemos dicho nuestro ejemplo es para eventos todo el día porlo que usaremos el método allDay yyyy-MM-dd Si quisieramos gestionar eventos máscomplejos (varios días, desde una hora hasta otra, etc) utilizaríamos el método betweendateTime1 dateTime2
Así mismo podríamos añadir recordatorios a ciertos eventos, añadir lista de personasinteresadas etc.
101 Groovy Script
| 153
events.each{ evt -> CalendarScript.instance.createEvent( calendarId,{ event.summary = evt.summary event.description = evt.description allDay evt.when })}
101 Groovy Script
154 |
Script
//tag::dependencies[]
@GrabResolver(name='puravida', root="https://dl.bintray.com/puravida-software/repo" )
@Grab(group = 'com.puravida.groogle', module = 'groogle-core', version = '1.4.1')
@Grab(group = 'com.puravida.groogle', module = 'groogle-sheet', version = '1.4.1')
@Grab(group = 'com.puravida.groogle', module = 'groogle-calendar', version = '1.4.1')
@GrabConfig(systemClassLoader=true)
import com.google.api.services.calendar.CalendarScopes
import com.google.api.services.sheets.v4.SheetsScopes
import com.puravida.groogle.GroogleScript
import com.puravida.groogle.CalendarScript
import com.puravida.groogle.SheetScript
//end::dependencies[]
sheetId='1hf6q540q45gvhdHH9BOVv_Ij7Wk0KVPFCs42Xc-fMLg'
calendarId='puravida-software.com_n9tci5mfaqqdegi5askjetpa7s@group.calendar.google.com'
//tag::login[]
GroogleScript.instance.applicationName='101-groovy'
clientSecret = this.class.getResource('client_secret.json').newInputStream()
GroogleScript.instance.login(clientSecret,[CalendarScopes.CALENDAR, SheetsScopes.SPREADSHEETS])
CalendarScript.instance.groogleScript=GroogleScript.instance
SheetScript.instance.groogleScript=GroogleScript.instance
//end::login[]
//tag::readFromSheet[]
events=[]
SheetScript.instance.withSpreadSheet sheetId, {
withSheet 'Eventos',{
int rowIdx=2
def row = readRows("A$rowIdx","E$rowIdx")
def calendarScript = CalendarScript.instance
while( row ){
def when =sprintf( '%1$04d-%2$02d-%3$02d',row.first()[0] as int,row.first()[1] as int,row.
first()[2] as int)
def summary ="${row.first()[3]}"
def description="${row.first()[4]}"
println "$when $summary $description"
events.add([when:when,summary:summary,description:description])
rowIdx++
row = readRows("A$rowIdx","E$rowIdx")
}
}
}
//end::readFromSheet[]
//tag::removeFromCalendar[]
CalendarScript.instance.withCalendar( calendarId, {
eachEvent {
removeFromCalendar()
}
})
//end::removeFromCalendar[]
//tag::writeToCalendar[]
events.each{ evt ->
CalendarScript.instance.createEvent( calendarId,{
event.summary = evt.summary
101 Groovy Script
| 155
event.description = evt.description
allDay evt.when
})
}
//end::writeToCalendar[]
101 Groovy Script
156 |
Crea tu propio DSLJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2018-02-4
En este artículo vamos a ver cómo puedes crear tu propia DSL
(domain specific language) con un ejemplo práctico donde tus usuarios
podrán generar gráficas de funciones usando una sintáxis específica
creada para ello. De esta forma podrás , por ejemplo, transformar las
complejidades técnicas a un lenguaje de negocio en aquellas situaciones
donde un simple paso de argumentos se queda corto.
Para este ejemplo vamos a utilizar la librería Exp4jhttps://www.objecthunter.net/exp4j/ la cual es capaz de parsear unafunción cualquiera y evaluarla cuando le proporcionamos los valores delas incógnitas.
Caso de usoQueremos crear un script capaz de dibujar una gráfica de una función proporcionada porel usuario. Para ello deberá proporcionar el rango de valores que quiere representar asícomo la "granularidad" del dibujo. Así mismo si el usuario lo indica volcaremos a ficherola gráfica genereda con el nombre que nos proporcione.
Como bola extra queremos darle la posibilidad de poder dibujar varias funciones en lamisma gráfica pudiendo indicar para cada una de ellas rangos diferentes.
Como se ve a simple vista los requisitos son demasiados complejos como para poder optarpor una solución basada en el paso de parámetros, por lo que optaremos por crearnosnuestro propio lenguaje.
Exp4jAntes de introducirnos en el diseño de la DSL vamos a ver la parte técnica que usaremospara la generación del diagrama.
exp4j es una librería que permite parsear cualquier función (e incluso añadir las tuyas) yproporcionandole los valores de las incógnitas puede evaluarla y devolvernos el valor.
Este es un ejemplo básico:
101 Groovy Script
| 157
Expression e = new ExpressionBuilder("3 * sin(x) - 2 / (x - 2)") .variables("x") .build() .setVariable("x", 2.3);double result = e.evaluate();
Así pues si somos capaces de proporcionarle la función que nos indique el usuariopodremos realizar un bucle en el rango especificado proporcionando valores a Expressiony obtener así una serie de valores que la representan.
private List calculateFunction(String function, String variable, double ini, double end, double step){
Expression e = new ExpressionBuilder(function).variables(variable).build()
def data=[]
for(double i=ini; i<end; i+=step){
e.setVariable(variable,i)
double v = e.evaluate()
data << [i, Double.isNaN(v) ? 0 : v]
}
data.flatten()
}
También nos será cómodo tener un método que nos pueda agrupar varias series defunciones calculadas en un mapa:
private Map calculateFunctions(List functions, String variable, double ini, double end, double step){
Map ret=[:]
functions.each{func->
ret[func] = calculateFunction(func, variable, ini, end, step)
}
ret
}
Ejemplo DSLTras evaluar con nuestros usuarios las diferentes sintáxis que podemos crear y que leserían legibles en su lenguaje de negocio llegamos a un acuerdo como el siguiente:
101 Groovy Script
158 |
// Podemos comentar el fichero con dos barras
/*
o un bloque con barra + asterisco
*/
// queremos dibujar una funcion usando unos rangos
plot "sin(x*e)", {
// podemos usar from to e incrementing en el orden que le sea más cómodo from (-4) to 6 incrementing 0.01
}
// podemos unir varios plots en el mismo diagrama con diferentes rangos
plot { //si no especificamos funcion lo podemos hacer dentro del DSL
// "function" necesita una cadena con la funcion a dibujar
// y "and" nos permite concatener funciones
function "cos(sin(x))" and "x*cos(e*x)" and "t^4/x"
// podemos especificar los rangos indicando from y to asi como la granularidad con incrementing
from (-3) incrementing 0.01 to 3
// no he sabido cómo usar directamente números negativos así que hay que recubrirlos con (-x)}
// queremos que nos genere la imagen en fichero
saveAs "test3.png"
Como se puede ver en el ejemplo, una vez aprendida la sintáxis, al usuario le es muy fácilcrear nuevos casos de uso.
Como resultado, el usuario obtendrá un fichero "test3.png" con la imagen, parecida a lasiguiente:
101 Groovy Script
| 159
DSLComo podemos ver nuestro DSL cuenta con 2 partes:
1. una lista de plot y el nombre de un fichero a generar
2. cada plot especifica un caso con
a. una o varias function que se pueden indicar de varias formas
b. un rango con from to e incrementing
PlotDSLPlotDSL será una clase embebida en el propio script que nos permitirá modelar la partemás interna de nuestro DSL. Implementará los métodos "from", "to", "and", "function", etcy básicamente irá guardando en properties los valores que se vayan pasando a cada uno.Contiene también, por simplificar el ejemplo, la parte de lógica de negocio en la que conestos valores genera las series de valores vista al principio aunque esto puede realizarseen alguna otra parte del script.
101 Groovy Script
160 |
List<String> _functions; PlotDSL(){ ① _functions=[] } PlotDSL(List<String> functions){① _functions = functions }
PlotDSL function(String f){ ① _functions = [ f ] this }
PlotDSL and(String f){ ② _functions << f this }
String _variable="x" PlotDSL using(String variable){ ③ _variable = variable this }
double _from PlotDSL from(idx){ _from=idx as double this }
double _to PlotDSL to(idx){ _to=idx as double this }
double _steps PlotDSL incrementing(idx){ _steps=idx as double this }
Map series(){ ④ calculateFunctions(_functions, _variable, _from, _to, _steps) }
① 3 maneras de inicializar la función inicial
② cuando escribimos and "x*cos(e*x)" estamos ejecutando el metodo and(String func)
101 Groovy Script
| 161
pasando x*cos(e*x) como parámetro
③ using nos permite indicar a exp4j el nombre de la variable que usamos en la funcioni.e. t*cos(e*t) using "t"
④ método ajeno al DSL que realiza la lógica de negocio de convertir las funciones a seriesde datos
PlotsDSLPlotsDSL (ojo a la s) será la clase embebida encargada de construir los diferentes plot quepueda haber en el DSL.
Un PlotsDSL contiene un array de PlotDSL al que irá añadiendo elementos según seinvoque a su función plot
Esta función plot tiene dos variantes:
• con una closure como argumento
• con un string y una cloruse que corresponde
plot "sin(x*e)", {}
plot {}
Así pues PlotsDSL implementa las dos formas donde una de ellas es simplemente unacomodidad para invocar a la segunda. Lo importante en este punto es que la closure querecibe como argumento será invocada pero usando un PlotDSL como contexto
PlotsDSL plot( Closure closure){ plot(null,closure) }
PlotsDSL plot( String function, Closure closure){ PlotDSL plot = function ? new PlotDSL( [function] ) : new PlotsDSL() Closure cl = closure.clone() cl.resolveStrategy = Closure.DELEGATE_ONLY ① cl.delegate=plot ② cl(plot) ③ plots << plot ④ this }
① Usaremos una estrategia de delegación entre las varias que permite Groovy
② el código de la closure se ejecutará en el contexto del delegate
101 Groovy Script
162 |
③ invocamos a la closure (puede hacerse tambien como cl.call() )
④ una vez ejecutada la closure el objeto plotDSL ha sido inicializado
De texto a ClosureEl primer argumento a nuestro script será un fichero que contiene el DSL que queremosejecutar así que el script lo leerá y convertirá el texto a closure. Como "mejora" si elfichero no existe, el script interpretará que la unión de todos los argumentos como cadenaserá el DSL a ejecutar
Para evaluar un texto y convertirlo a Closure usaremos el método evaluate de GroovyShell.Simplemente tenemos que tener en cuenta que la cadena que espera este métodorepresente una closure por lo que recubrimos el texto del fichero mediante llaves { →$texto }
txt=args.join(' ')if( args.length == 1 && new File(args[0]).exists() ){ txt = new File(args[0]).text}
cl = new GroovyShell().evaluate("{ dsl-> $txt}")plotsDSL = new PlotsDSL()plotsDSL.parse(cl)
JavaFXPor último sólo resta que un PlotsDSL ejecute la closure en su contexto y si no se produceun error generar la vista con la lógica de negocio generada por aquel, para lo queusaremos GroovyFX (Groovy+JavaFX) y su propia DSL
101 Groovy Script
| 163
void saveScreenshot(saveNode, fileName){
def snapshot = saveNode.snapshot(new SnapshotParameters(), null);
BufferedImage bufferedImage = new BufferedImage(saveNode.width as int, saveNode.height as int,
BufferedImage.TYPE_INT_ARGB);
BufferedImage image = javafx.embed.swing.SwingFXUtils.fromFXImage(snapshot, bufferedImage)
File file = new File(fileName)
ImageIO.write(image, "png", file )
}
start {
stage(id:'st',title: "GrExp4j", x: 1024, y: 768, visible: true, style: "decorated", primary: true) {
scene(id: "sc", width: 1024, height: 768) {
saveNode=lineChart(animated:true, createSymbols:false, legendVisible:true, data: plotsDSL.
series())
}
}
if( plotsDSL._screenshot ){
saveScreenshot(saveNode,plotsDSL._screenshot)
}
}
Simplemente crea una aplicación JavaFX y crea un lineChart proporcionandole las seriesde datos que construirá nuestro DSL. Si en el DSL se indica que queremos generar ungráfico usaremos la funcion saveScreenshot para ello.
101 Groovy Script
164 |
Script
@Grapes([
@Grab(group='net.objecthunter', module='exp4j', version='0.4.8'),
@Grab(group='org.groovyfx',module='groovyfx',version='8.0.0',transitive=false, noExceptions=true),
])
import net.objecthunter.exp4j.function.Function
import net.objecthunter.exp4j.operator.Operator
import net.objecthunter.exp4j.*
import static groovyx.javafx.GroovyFX.start
import javafx.application.Platform
import javafx.scene.SnapshotParameters
import javax.imageio.ImageIO
import java.awt.image.BufferedImage
import groovy.transform.ToString
class PlotDSL{
//tag::plotDSL[]
List<String> _functions;
PlotDSL(){ ①
_functions=[]
}
PlotDSL(List<String> functions){①
_functions = functions
}
PlotDSL function(String f){ ①
_functions = [ f ]
this
}
PlotDSL and(String f){ ②
_functions << f
this
}
String _variable="x"
PlotDSL using(String variable){ ③
_variable = variable
this
}
double _from
PlotDSL from(idx){
_from=idx as double
this
}
double _to
PlotDSL to(idx){
_to=idx as double
this
}
double _steps
PlotDSL incrementing(idx){
_steps=idx as double
this
}
101 Groovy Script
| 165
Map series(){ ④
calculateFunctions(_functions, _variable, _from, _to, _steps)
}
//end::plotDSL[]
// tag::calculateFunction[]
private List calculateFunction(String function, String variable, double ini, double end, double step){
Expression e = new ExpressionBuilder(function).variables(variable).build()
def data=[]
for(double i=ini; i<end; i+=step){
e.setVariable(variable,i)
double v = e.evaluate()
data << [i, Double.isNaN(v) ? 0 : v]
}
data.flatten()
}
// end::calculateFunction[]
// tag::calculateFunctions[]
private Map calculateFunctions(List functions, String variable, double ini, double end, double step){
Map ret=[:]
functions.each{func->
ret[func] = calculateFunction(func, variable, ini, end, step)
}
ret
}
// end::calculateFunctions[]
}
class PlotsDSL{
List<PlotDSL> plots = []
PlotsDSL parse( Closure closure ){
Closure cl = closure.clone()
cl.resolveStrategy = Closure.DELEGATE_ONLY
cl.delegate=this
cl(this)
}
//tag::plotsConstructor[]
PlotsDSL plot( Closure closure){
plot(null,closure)
}
PlotsDSL plot( String function, Closure closure){
PlotDSL plot = function ? new PlotDSL( [function] ) : new PlotsDSL()
Closure cl = closure.clone()
cl.resolveStrategy = Closure.DELEGATE_ONLY ①
cl.delegate=plot ②
cl(plot) ③
plots << plot ④
this
}
//end::plotsConstructor[]
String _screenshot
PlotsDSL saveAs(String screenshot){
_screenshot=screenshot
this
}
Map series(){
101 Groovy Script
166 |
def ret=[:]
plots.each{ plot->
plot.series().each{ serie->
ret[serie.key] = serie.value
}
}
ret
}
}
// tag::txtAClosure[]
txt=args.join(' ')
if( args.length == 1 && new File(args[0]).exists() ){
txt = new File(args[0]).text
}
cl = new GroovyShell().evaluate("{ dsl-> $txt}")
plotsDSL = new PlotsDSL()
plotsDSL.parse(cl)
// end::txtAClosure[]
//tag::view[]
void saveScreenshot(saveNode, fileName){
def snapshot = saveNode.snapshot(new SnapshotParameters(), null);
BufferedImage bufferedImage = new BufferedImage(saveNode.width as int, saveNode.height as int,
BufferedImage.TYPE_INT_ARGB);
BufferedImage image = javafx.embed.swing.SwingFXUtils.fromFXImage(snapshot, bufferedImage)
File file = new File(fileName)
ImageIO.write(image, "png", file )
}
start {
stage(id:'st',title: "GrExp4j", x: 1024, y: 768, visible: true, style: "decorated", primary: true) {
scene(id: "sc", width: 1024, height: 768) {
saveNode=lineChart(animated:true, createSymbols:false, legendVisible:true, data: plotsDSL.
series())
}
}
if( plotsDSL._screenshot ){
saveScreenshot(saveNode,plotsDSL._screenshot)
}
}
//end::view[]
101 Groovy Script
| 167
Xml a CalendarJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2018-05-10
Para este post vamos a necesitar los siguientes requisitos: una
cuenta de Google y crear un Calendario de Google. Se recomienda crear un
calendario específico para este ejercicio puesto que vamos a borrar y crear
eventos en el mismo de forma genérica y si lo hacemos sobre el principal
podríamos perder eventos de nuestro interés. Así mismo para poder
acceder a las APIs de Google deberemos obtener unas credenciales que
autoricen a la aplicación para acceder a nuestra cuenta.
En este post vamos ver cómo consumir un XML con los eventos gratuitosque se van a realizar en las bibliotecas municipales de Madrid usando elservicio de Datos Abiertos. Nuestro script se va a ejecutar de formaperiódica, obteniendo del servicio remoto los eventos para los próximos60 días, parseando el Xml y creando los eventos en el calendario con lainformación de interés (Título, descripción, fecha de inicio y fin, etc).Para más información consultar https://datos.madrid.es/portal/site/egob/
XmlEl endpoint que ofrece la web de Datos Abiertos de Madrid nos devuelve un array deelementos contenido tal como se muestra a continuación:
101 Groovy Script
168 |
<contenido>
<tipo>Evento</tipo>
<atributos idioma="es">
<atributo nombre="ID-EVENTO">10701791</atributo>
<atributo nombre="TITULO">A la carta</atributo>
<atributo nombre="GRATUITO">1</atributo>
<atributo nombre="EVENTO-LARGA-DURACION">0</atributo>
<atributo nombre="FECHA-EVENTO">2018-05-19 00:00:00.0</atributo>
<atributo nombre="FECHA-FIN-EVENTO">2018-05-19 23:59:00.0</atributo>
<atributo nombre="HORA-EVENTO">12:00</atributo>
<atributo nombre="DESCRIPCION">
<![CDATA[ A la carta: Fundación Arte que alimenta (clásica). ]]></atributo>
<atributo nombre="CONTENT-URL">
http://www.madrid.es/sites/v/index.jsp?vgnextchannel=1ccd566813946010VgnVCM100000dc0ca8c0RCRD&vgnextoid=60d3e
5e031b23610VgnVCM2000001f4a900aRCRD
</atributo>
<atributo nombre="TITULO-ACTIVIDAD">Conciertos en la Biblioteca Eugenio Trías</atributo><atributo nombre="CONTENT-URL-ACTIVIDAD">
http://www.madrid.es/sites/v/index.jsp?vgnextchannel=1ccd566813946010VgnVCM100000dc0ca8c0RCRD&vgnextoid=8c43e
5e031b23610VgnVCM2000001f4a900aRCRD
</atributo>
<atributo nombre="LOCALIZACION">
<atributo nombre="CONTENT-URL-INSTALACION">
http://www.madrid.es/sites/v/index.jsp?vgnextchannel=9e4c43db40317010VgnVCM100000dc0ca8c0RCRD&vgnextoid=e791b
ed05ceed310VgnVCM1000000b205a0aRCRD
</atributo>
<atributo nombre="NOMBRE-INSTALACION">
Biblioteca Pública Municipal Eugenio Trías. Casa de Fieras de El Retiro</atributo>
<atributo nombre="ID-INSTALACION">6893633</atributo>
<atributo nombre="COORDENADA-X">442345</atributo>
<atributo nombre="COORDENADA-Y">4474221</atributo>
<atributo nombre="LATITUD">40.4166091081099</atributo>
<atributo nombre="LONGITUD">-3.6795930368034804</atributo>
<atributo nombre="LOCALIDAD">MADRID</atributo>
<atributo nombre="PROVINCIA">MADRID</atributo>
<atributo nombre="DISTRITO">RETIRO</atributo>
</atributo>
<atributo nombre="TIPO">/contenido/actividades/Musica</atributo>
</atributos>
</contenido>
De todos estos datos nosotros vamos a usar sólo un pequeño subconjunto de ellos comoson TITULO-ACTIVIDAD, FECHA-EVENTO y LOCALIZACION, teniendo este último laparticularidad de que es a su vez un array de attributo
Para ello tendremos que ser capaces de buscar en los nodos aquellos que tengan elatributo nombre con el valor de interés
Consola GoogleEn primer lugar deberemos crear una aplicación en la consola de Google enhttps://console.developers.google.com
En esta aplicación habilitaremos al menos las APIs de "Google Calendar API"
101 Groovy Script
| 169
Así mismo deberemos crear unas credenciales de "OAuth" obteniendo la posibilidad dedescargarlas en un fichero JSON y que deberemos ubicar junto con el script.
GrooglePara facilitar la parte técnica de autentificación y creación de servicios he creado unproyecto de código abierto, Groogle, disponible en https://puravida-software.gitlab.io/groogle/ el cual publica un artefacto en Bintray y que podremos usar en nuestros scriptssimplemente incluyendo su repositorio.
Este proyecto se divide a su vez en otros subprojectos, como por ejemplo:
• groogle-core, contiene la parte de autentificación principalmente
• groogle-drive, para el manejo de ficheros y carpetas en Drive
• groogle-sheet, para la gestión específica de hojas de cálculo Sheet
• groogle-calendar, para la gestión específica de calendarios
En nuestro caso vamos a utilizar el último (el primero se autoincluye por dependenciatransitiva)
@Grab(group='io.github.http-builder-ng', module='http-builder-ng-apache', version='1.0.3')
@Grab(group='ch.qos.logback', module='logback-classic', version='1.2.3')
@Grab(group = 'com.puravida.groogle', module = 'groogle-core', version = '1.5.0-alpha2')
@Grab(group = 'com.puravida.groogle', module = 'groogle-calendar', version = '1.5.0-alpha2')
@GrabConfig(systemClassLoader=true)
import com.google.api.services.calendar.CalendarScopes
import com.puravida.groogle.GroogleScript
import com.puravida.groogle.CalendarScript
import groovyx.net.http.*
import static groovyx.net.http.HttpBuilder.configure
import static groovyx.net.http.ContentTypes.JSON
import static groovy.json.JsonOutput.prettyPrint
import static groovy.json.JsonOutput.toJson
Groogle nos permitirá realizar el login de nuestra aplicación y guardar la autorización ennuestro disco de tal forma que en ejecuciones posteriores no se requiera de autorizar denuevo la aplicación (mientras no borremos el fichero con la autorización generada)
login { applicationName 'groogle-example' withScopes CalendarScopes.CALENDAR usingCredentials new File('client_secret.json') asService false }
101 Groovy Script
170 |
Eliminando antiguosPara mantener el calendario "limpio" con sólo los eventos de interés vamos a realizar enprimer lugar un borrado de todos los eventos que existen en el mismo
String groogleCalendarId = "[email protected]" withCalendar groogleCalendarId, { eachEvent{ removeFromCalendar() } }
Consumiendo XMLPara realizar la petición al servicio remoto usaremos HttpBuilder-Ng el cual nos permiterecuperar el contenido en formato Xml tal como se muestra a continuación:
XML = ["application/xml", "text/xml", "application/xhtml+xml", "application/atom+xml"]
xmlEncoder = NativeHandlers.Encoders.&xml
http = configure {
request.uri = 'https://datos.madrid.es/egob/catalogo/206717-0-agenda-eventos-bibliotecas.xml'
request.encoder XML, xmlEncoder
request.contentType = 'application/xml'
}.get{
}
Una vez realizado el GET , el objeto http será un groovy.util.Node el cual nos permitiránavegar a través de sus nodos buscando la información de interés.
BusinessPor último sólo resta ir navegando por los nodos recuperados extrayendo la informaciónde interés y creando un evento en el calendario para cada una de ellas
101 Groovy Script
| 171
http.contenido.each{
String titulo = it.atributos.atributo.find{ it.'@nombre'=='TITULO-ACTIVIDAD' }.text()
String descripcion = it.atributos.atributo.find{ it.'@nombre'=='DESCRIPCION' }.text()
String inicio = it.atributos.atributo.find{ it.'@nombre'=='FECHA-EVENTO' }.text()
String fin = it.atributos.atributo.find{ it.'@nombre'=='FECHA-FIN-EVENTO' }.text()
String hora = it.atributos.atributo.find{ it.'@nombre'=='HORA-EVENTO' }.text()
String where = it.atributos.atributo.find{ it.'@nombre'=='LOCALIZACION' }?.atributo.find{it
.'@nombre'=='NOMBRE-INSTALACION'}.text()
Date dini = Date.parse('yyyy-MM-dd HH:mm:ss.S', inicio)
Date dfin = Date.parse('yyyy-MM-dd HH:mm:ss.S', fin)
if( descripcion.trim()=="" )
return
if( dini < fromDate )
return
createEvent groogleCalendarId, {
it.event.summary = titulo
it.event.description = "($hora) $descripcion"
it.event.location=where
from dini
until dfin
}
}
101 Groovy Script
172 |
Script
//tag::dependencies[]
@Grab(group='io.github.http-builder-ng', module='http-builder-ng-apache', version='1.0.3')
@Grab(group='ch.qos.logback', module='logback-classic', version='1.2.3')
@Grab(group = 'com.puravida.groogle', module = 'groogle-core', version = '1.5.0-alpha2')
@Grab(group = 'com.puravida.groogle', module = 'groogle-calendar', version = '1.5.0-alpha2')
@GrabConfig(systemClassLoader=true)
import com.google.api.services.calendar.CalendarScopes
import com.puravida.groogle.GroogleScript
import com.puravida.groogle.CalendarScript
import groovyx.net.http.*
import static groovyx.net.http.HttpBuilder.configure
import static groovyx.net.http.ContentTypes.JSON
import static groovy.json.JsonOutput.prettyPrint
import static groovy.json.JsonOutput.toJson
//end::dependencies[]
//tag::http[]
XML = ["application/xml", "text/xml", "application/xhtml+xml", "application/atom+xml"]
xmlEncoder = NativeHandlers.Encoders.&xml
http = configure {
request.uri = 'https://datos.madrid.es/egob/catalogo/206717-0-agenda-eventos-bibliotecas.xml'
request.encoder XML, xmlEncoder
request.contentType = 'application/xml'
}.get{
}
//end::http[]
Date fromDate = new Date() - 10
CalendarScript.instance.with {
//tag::login[]
login {
applicationName 'groogle-example'
withScopes CalendarScopes.CALENDAR
usingCredentials new File('client_secret.json')
asService false
}
//end::login[]
//tag::prepare[]
String groogleCalendarId = "[email protected]"
withCalendar groogleCalendarId, {
eachEvent{
removeFromCalendar()
}
}
//end::prepare[]
//tag::business[]
http.contenido.each{
String titulo = it.atributos.atributo.find{ it.'@nombre'=='TITULO-ACTIVIDAD' }.text()
String descripcion = it.atributos.atributo.find{ it.'@nombre'=='DESCRIPCION' }.text()
String inicio = it.atributos.atributo.find{ it.'@nombre'=='FECHA-EVENTO' }.text()
String fin = it.atributos.atributo.find{ it.'@nombre'=='FECHA-FIN-EVENTO' }.text()
String hora = it.atributos.atributo.find{ it.'@nombre'=='HORA-EVENTO' }.text()
String where = it.atributos.atributo.find{ it.'@nombre'=='LOCALIZACION' }?.atributo.find{it
101 Groovy Script
| 173
.'@nombre'=='NOMBRE-INSTALACION'}.text()
Date dini = Date.parse('yyyy-MM-dd HH:mm:ss.S', inicio)
Date dfin = Date.parse('yyyy-MM-dd HH:mm:ss.S', fin)
if( descripcion.trim()=="" )
return
if( dini < fromDate )
return
createEvent groogleCalendarId, {
it.event.summary = titulo
it.event.description = "($hora) $descripcion"
it.event.location=where
from dini
until dfin
}
}
//end::business[]
}
101 Groovy Script
174 |
OpenDataMadridJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2018-2-17
En este post vamos a utilizar Gradle como entorno de ejecución denuestro script, en lugar del propio Groovy, y su ecosistema de plugins,para consumir una URL con datos abiertos sobre la ciudad de Madrid yenviar una notificación diaria a un canal de Telegram con un resumen delos próximos eventos que van a ocurrir en la ciudad
Gradle es un sistema de automatización de construcción de códigoabierto que construye sobre los conceptos de Apache Ant y ApacheMaven e introduce un lenguaje especifico del dominio (DSL) basado enGroovy en vez de la forma XML utilizada por Apache Maven paradeclarar la configuración de proyecto.
— Wikipedia, https://es.wikipedia.org/wiki/Gradle
PreparaciónEn primer lugar deberemos preparar nuestro entorno de desarrollo con Gradle. Para ellodisponemos de varios métodos:
• descargarlo, descomprimirlo y ajustar las variables de entorno indicadas
• usar sdkman e instalarlo con sdkman install gradle
• descargar este proyecto semilla https://gitlab.com/groovy-lang/gradle-seed y trabajarsobre él
Con los dos primeros métodos deberemos ejecutar en el directorio donde queramostrabajar el comando gradle init para que nos cree los ficheros necesarios. Si optamos porclonar el repo indicado en el tercer método esto ya se ha hecho y simplemente deberemostrabajar sobre el directorio donde hayamos descargado el repo
Scripts vs TaskGradle es una herramienta para automatizar la construcción de artefactos basada en elconcepto de task, los cuales los podemos ver como pequeños scripts, y que nos permitedefinir dependencias entre ellas. Así mismo proporciona en su DSL la capacidad dedefinir dependencias a los típicos artefactos externos (librerías) para incluir en laejecución del build.
De esta forma podemos usar Gradle como un entorno donde utilizar multitud de librerías
101 Groovy Script
| 175
existentes y mediante scripts interaccionar con el sistema no sólo para construirartefactos sino para ejecutar acciones variopintas como apagar un servidor, enviar uncorreo electrónico con el contenido de un fichero, etc
OpenDataMadridLa ciudad de Madrid, España, cuenta con un amplio catálogo de datos abiertos fácilmenteaccesible vía http en diferentes formatos como pueden ser xml, json, excel e incluso unAPI REST (https://datos.madrid.es/portal/site/egob)
En este post vamos a ver cómo interpretar el catálogo de actividades programadas de lasbibliotecas que se encuentran en la url https://datos.madrid.es/egob/catalogo/206717-0-agenda-eventos-bibliotecas.json
Si inspeccionamos este recurso veremos que las actividades están organizadas en unelemento @graph y cada uno de ellas indica la fecha de inicio y de fín así como el título,lugar, entre otros:
101 Groovy Script
176 |
"@graph": [
{
"@id": "https://datos.madrid.es/egob/catalogo/tipo/evento/10847853-bestia-volvoreta.json",
"@type": "https://datos.madrid.es/egob/kos/actividades/CuentacuentosTiteresMarionetas",
"id": "10847853",
"title": "A lo bestia con Volvoreta",
"description": "El miedo, como el amor, es un sentimiento inherente al ser humano, y los cuentos
consiguen ponerles cara y ojos a todas esas angustias que nos configuran. Ayudándonos a exorcizar todaslas inquietudes que durante una narración en grupo se comparten y en el mejor de los casos, se superan.Por eso, esta sesión es un homenaje a algunos cuentos que ya son clásicos, pero también a aquellashistorias que acaban de nacer porque para no tener pesadillas por la noche, nada como soñar despiertodurante una hora de cuentos. A partir de 4 años.", "price": 1,
"dtstart": "2019-04-11 18:00:00.0",
"dtend": "2019-04-11 23:59:00.0",
"excluded-days": "",
"audience": "Niños,Familias", "uid": "10847853",
"link":
"http://www.madrid.es/sites/v/index.jsp?vgnextchannel=ca9671ee4a9eb410VgnVCM100000171f5a0aRCRD&vgnextoid=3762
de882dba7610VgnVCM2000001f4a900aRCRD",
"event-location": "Biblioteca Pública Municipal Aluche (Latina)", "references": {
"@id":
"http://www.madrid.es/sites/v/index.jsp?vgnextchannel=ca9671ee4a9eb410VgnVCM100000171f5a0aRCRD&vgnextoid=4224
a209a1c71510VgnVCM2000000c205a0aRCRD"
},
"relation": {
"@id": "https://datos.madrid.es/egob/catalogo/tipo/entidadesyorganismos/1757-biblioteca-publica-
municipal-aluche-latina-.json"
},
"address": {
"district": {
"@id":
"https://datos.madrid.es/egob/kos/Provincia/Madrid/Municipio/Madrid/Distrito/Latina"
}
},
"location": {
"latitude": 40.39597072367931,
"longitude": -3.7562716852987847
}
},
La idea de nuestro script es parsear este JSON cada día y buscar qué actividades se van arealizar a dos (2) días vista y enviar un mensaje a un canal de Telegram con un resumende las mismas (en caso de que haya actividades)
Para el envío de Telegram usaremos un plugin de Gradle que nos permiteenviar a diferentes redes sociales como Twitter, Telegram o Slack,(https://puravida-gradle.gitlab.io/social-network/) lo cual nos servirá parademostrar la potencia de Gradle.
101 Groovy Script
| 177
DependenciasMientras que en un típico script usabamos @Grab para resolver las dependencias, conGradle usaremos su DSL buildSrc
Así mismo Gradle nos permite añadir otro tipo de dependencias no existentes con @Grabcomo son los plugins. Básicamente son artefactos orientados a implementar tareas que sepueden integrar en nuestro build tanto para ser ejecutadas directamente como paraextenderlas. En nuestro caso vamos a utilizar el plugin social-network y su tareatelegram
plugins { id 'com.puravida.gradle.socialnetwork' version '0.1.1'}import groovy.json.JsonSlurper
ParseNuestra tarea de parseo va a ser una tarea básica que no extiende de ninguna previa yque va a generar, en caso de que haya eventos, un fichero build/telegram.txt con el textoa enviar en formato Markdown.
Utilizando la sintáxis de Gradle, nuestro script se ejecutará dentro de la closure doLast , enla cual descargamos el JSON y recorremos los elementos comentados anteriormente. Paracada elemento de interés iremos añadiendo a un array las líneas que queremos volcar alfichero (nótese que podíamos ir escribiendo directamente en el fichero, simplementeestamos mostrando otras formas de actualizar un fichero de texto).
101 Groovy Script
178 |
task eventosBibliotecas(){
def msg = file("$buildDir/telegram.txt")
outputs.files msg
doLast{
def arr = []
def today = Calendar.instance.time
arr.add "-".multiply(10)
arr.add "\n"
arr.add "*Hoy es ${today.format( 'dd/MM/yyyy' )}*"
arr.add "\n"
arr.add "\n"
def json = new JsonSlurper().parseText('https://datos.madrid.es/egob/catalogo/206717-0-agenda-
eventos-bibliotecas.json'.toURL().text)
json."@graph".each{ evt->
use(groovy.time.TimeCategory) {
def strstart = Date.parse('yyyy-MM-dd HH:mm:ss',evt.dtstart)
def strend = Date.parse('yyyy-MM-dd HH:mm:ss',evt.dtend)
def duration = strend - today
switch( duration.days ){
case 2:
arr.add "Ἱ�️*${strstart.format( 'dd/MM/yyyy' )} al ${strend.format( 'dd/MM/yyyy'
)}*"
arr.add "Dentro de *${duration.days}* días en ${evt.'event-location'}" arr.add "$evt.title"
arr.add "\n"
}
}
}
if( arr.size() )
msg.text = arr.join('\n')
}
}
TelegramComo hemos comentado usaremos un plugin de Gradle para el envío a un canal deTelegram al cual sólo le tenemos que configurar con las credenciales necesarias así comoel id del canal al que enviar el mensaje, mediante las propiedades telegram_token ytelegram_channel (consultar documentación del plugin)
Como bola extra podemos ver las funcionalidades de Gradle para hacer depender unastareas de otras (telegram de parse en nuestro caso), así como decidir si una tarea debe serejecutada o no en función de las condiciones que necesitemos
101 Groovy Script
| 179
telegram{ credentials { token project.findProperty('telegram_token') channel project.findProperty('telegram_channel') } message file('build/telegram.txt') sendAsMarkdown}
telegram.onlyIf{ file("$buildDir/telegram.txt").exists()}telegram.dependsOn eventosBibliotecas
Ejecución manualPara ejecutar nuestro "script/tarea" simplemente ejecutaremos desde el directorioprincipal del proyecto:
si usamos Linux ejectaremos:
./gradlew -b OpenDataMadrid.gradle build -Ptelegram_token=123123ZZZZ "-Ptelegram_channel=@opendatamadrid" ①
① debido a que @ es un caracter especial en la linea de comandos, lo recubrimos entrecomillas
o si usamos windows
gradlew.bat -b OpenDataMadrid.gradle build -Ptelegram_token=123123ZZZZ -Ptelegram_channel=@opendatamadrid
Si todo va bien, y hay eventos, los usuarios suscritos al canal verían un mensaje parecidoa este:
101 Groovy Script
180 |
Monitorea un servidor (o lo quequieras)Jorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2017-10-23
Qué es JavaFX
JavaFX es una familia de productos y tecnologías de OracleCorporation (inicialmente Sun Microsystems), para la creación deRich Internet Applications (RIAs), esto es, aplicaciones web que tienenlas características y capacidades de aplicaciones de escritorio,incluyendo aplicaciones multimedia interactivas.
Las tecnologías incluidas bajo la denominación JavaFX son JavaFXScript y JavaFX Mobile, aunque hay más productos JavaFX planeados
— Wikipedia, https://es.wikipedia.org/wiki/JavaFX
JavaFX puede verse como el sucesor a Swing en la medida en que nos permite crearaplicaciones gráficas aunque en realidad pretende ocupar espacios donde este no llegó(lectores DVD por ejemplo). Cuenta además con un lenguaje de scripting Java FX Scriptque permite desarrollar aplicaciones con menos código que con el stack tradicionalSwing. (Sí, podríamos decir que es la misma idea que Groovy y Groovy Script).
Y como era de esperar, Groovy cuenta con un proyecto que nos va a permitir hacernos lavida más fácil a la hora de definir nuestros elementos gráficos así como conseguir que loscambios que se producen en el modelo se transmitan a la vista de una forma realmentesencilla, el cual puedes encontrar en http://groovyfx.org/
Caso de UsoPara este ejemplo vamos a desarrollar un pequeño script que se va a conectar a unservidor web (parametrizable) y va a ir monitorizando el tiempo de respuesta, de talforma que si se encuentra caído en algún momento podamos verlo rápidamente. Tanto siestá caído como el tiempo que tarda en conectar lo vamos a mostrar en una ventanagráfica que se irá refrescando automáticamente.
Como el resto de scripts, este pretende ofrecerte las herramientas paraque puedas ajustarlo a tus propias necesidades. En este caso no es tanimportante la funcionalidad sino el cómo.
Más o menos esto es lo que veremos en pantalla:
101 Groovy Script
182 |
Modelo-Vista-ControladorNo. Tranquilo. No vamos a desarrollar ningún sistema MVC complejo (aunque si teinteresa el tema te recomiendo que eches un ojo al proyecto http://griffon-framework.org).Simplemente vamos a separar dentro del mismo script la parte correspondiente alnegocio de la parte correspondiente a la vista para hacer nuestro script más legible.
Al realizar esta separación lo más importante es conseguir que los cambios que seproducen en el negocio sean reflejados en la vista (y viceversa aunque en este ejemplo novamos a tratar esta dirección). Para ello JavaFX proporciona unas clases similares a losBeans tanto en su funcionalidad como en su verbosidad, pero tranquilo que GroovyFxnos permite reducirla.
Como puedes ver a continuación la clase Business simplemente consta de un String paraguardar el server que queremos monitorizar y un FXBindable String el cual guarda eltiempo de respuesta a la última petición. Por lo demás simplemente un par de métodospara calcular cuanto tarda una conexión a un puerto mediante sockets, nada del otromundo (probablemente para tu caso específico será aquí donde el problema no sea tantrivial):
101 Groovy Script
| 183
class Business { String server
@FXBindable String delay = '' ①
void refresh() { Date start = new Date() if (!isReachable(10 * 1000)) { //10seg max delay = "DOWN" return } Date stop = new Date() groovy.time.TimeDuration td = groovy.time.TimeCategory.minus(stop, start) delay = "$td" ② }
boolean isReachable(int timeOutMillis, int port = 80) { try { Socket soc = new Socket() soc.connect(new InetSocketAddress(server, port), timeOutMillis); true } catch (IOException ex) { println ex.toString() false; } }}
① FXBindable es propia del proyecto GroovyFx
② Al actualizar el valor como un String, los cambios se propagan
La segunda parte del script va a hacer uso del DSL que nos ofrece GroovyFX para poderdiseñar los elementos gráficos ( stage, scene, label, inputs, buttons, etc) así comopersonalizar cada uno con los atributos que queramos, como tamaño, posición, colores,texto etc.
Lo más importante (a parte de tener gusto para saber crear la parte gráfica) es unirnuestro modelo con la vista, lo cual conseguimos mediante el método bind. Así en estescript le indicamos a un label que su texto NO es un texto fijo sino que está enlazado a unapropiedad delay del objecto business
101 Groovy Script
184 |
stage(title: 'Latencia', visible: true) { ①
scene(fill: BLACK, width: 800, height: 250) {
hbox(padding: 60) {
text(text: "Ping to $business.server :", font: '20pt sanserif') { ②
fill linearGradient(endX: 0, stops: [PALEGREEN, SEAGREEN])
}
label(text: bind(business, 'delay'), font: '40pt sanserif') { ③
fill linearGradient(endX: 0, stops: [CYAN, DODGERBLUE])
effect dropShadow(color: DODGERBLUE, radius: 25, spread: 0.25)
}
}
}
}
① vamos creando los elementos gráficos
② bind crea un listener a la propiedad de business y actualiza el elemento gráfico enconsecuencia
TransicionesMediante JavaFX/GroovyFX no sólo tenemos aplicaciones estáticas que reaccionan anteeventos del usuario sino que podemos crear nuestras propias transiciones de una formacontínua, de tal forma que nuestra aplicación se puede ir refrescando ella sóla.
sequentialTransition(cycleCount: INDEFINITE) { ① pauseTransition(10.s) { onFinished { business.refresh() ② } } }.playFromStart()
① Creamos un bucle infinito de transiciones
② Al terminar cada transicion refrescamos nuestro negocio
101 Groovy Script
| 185
Script
//tag::dependencies[]
@GrabConfig(systemClassLoader=true)
@Grab(group='org.groovyfx',module='groovyfx',version='8.0.0',transitive=false, noExceptions=true)
//end::dependencies[]
import groovyx.javafx.beans.FXBindable
import static groovyx.javafx.GroovyFX.start
//tag::business[]
class Business {
String server
@FXBindable
String delay = '' ①
void refresh() {
Date start = new Date()
if (!isReachable(10 * 1000)) { //10seg max
delay = "DOWN"
return
}
Date stop = new Date()
groovy.time.TimeDuration td = groovy.time.TimeCategory.minus(stop, start)
delay = "$td" ②
}
boolean isReachable(int timeOutMillis, int port = 80) {
try {
Socket soc = new Socket()
soc.connect(new InetSocketAddress(server, port), timeOutMillis);
true
} catch (IOException ex) {
println ex.toString()
false;
}
}
}
//end::business[]
start {
def business = new Business(server: args[0])
//tag::vista[]
stage(title: 'Latencia', visible: true) { ①
scene(fill: BLACK, width: 800, height: 250) {
hbox(padding: 60) {
text(text: "Ping to $business.server :", font: '20pt sanserif') { ②
fill linearGradient(endX: 0, stops: [PALEGREEN, SEAGREEN])
}
label(text: bind(business, 'delay'), font: '40pt sanserif') { ③
fill linearGradient(endX: 0, stops: [CYAN, DODGERBLUE])
effect dropShadow(color: DODGERBLUE, radius: 25, spread: 0.25)
}
}
}
101 Groovy Script
186 |
}
//end::vista[]
//tag::bucle[]
sequentialTransition(cycleCount: INDEFINITE) { ①
pauseTransition(10.s) {
onFinished {
business.refresh() ②
}
}
}.playFromStart()
//end::bucle[]
business.refresh()
}
101 Groovy Script
| 187
TwitterFXJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2017-11-23
Caso de UsoNuestra empresa ha entrado con ganas en el mundo de las redes sociales y quiere generarcontenido sobre nuestros productos. Hasta hemos contratado a una persona que se va aencargar de generarlos y publicarlos en Twitter.
Esta persona querría poder compartir de una forma sencilla los "Top Ventas" de nuestrosproductos así que para ello todos los días te pide que le busques en las bases de datos qué3 productos son los más vendidos y cuantas unidades hemos vendido de ellos.
Con esta información, y mediante alguna aplicación ofimática tipo Excel, es capaz degenerar un gráfico de tartas, salvar la imagen a su disco, abrir Twitter, escribir unmensaje, adjuntar la imagen y twittearla. Fácil. Pesado. Laborioso.
Twitter AppPara poder publicar tweets de forma automática, el primer paso es crear una app enhttps://apps.twitter.com/ para que Twitter nos genere un conjunto de claves. Si sigues lospasos que ofrece la página al final del proceso obtendras 4 claves que deberás guardar enel fichero twitter4j.properties en el mismo directorio que resida nuestro script.
twitter4j.properties
oauth.consumerKey=XXXXXXXXXXoauth.consumerSecret=YYYYYYYYYYYYYYYYYYYYYYoauth.accessToken=527902906-asdfdsafassssssssssssssssoauth.accessTokenSecret=123mlkjdfd9sfldsjlkj
DependenciasNuestro script va a necesitar las siguientes dependencias agrupadas por funcionalidad:
101 Groovy Script
188 |
@GrabConfig(systemClassLoader=true)
@Grab('mysql:mysql-connector-java:5.1.6')
@Grab(group='org.groovyfx',module='groovyfx',version='8.0.0',transitive=false, noExceptions=true)
@Grab(group='org.twitter4j', module='twitter4j-core', version='4.0.6')
import groovy.sql.Sql
import static groovyx.javafx.GroovyFX.start
import javafx.application.Platform
import javafx.scene.SnapshotParameters
import javax.imageio.ImageIO
import java.awt.image.BufferedImage
import twitter4j.TwitterFactory;
import twitter4j.StatusUpdate
Básicamente:
• conexión a la base de datos
• librerías graficas JavaFX
• librería de twitter
CustomizePara poder ajustar nuestro script de forma cómoda vamos a utilizar las siguientesvariables:
String message ="""Estamos que nos salimos!!!Top Ventas de nuestro catálogo. Gracias a todos por elegirnos#groovy-script"""
String chartTitle="Top Ventas"
int width=height=400
Obtener Top SalesObtener los productos más vendidos puede ser desde una operación trivial hasta unaoperación compleja de agregados. Eso ya dependerá de tu negocio. Para nuestro casovamos a suponer que disponemos de una tabla MySQL donde ya se encuentran agregadoslas ventas por su descripción
101 Groovy Script
| 189
def data=[:] ①
Sql sql = Sql.newInstance( "jdbc:mysql://localhost:3306/scripts?jdbcCompliantTruncation=false",
"root",
"my-secret-pw",
"com.mysql.jdbc.Driver")
sql.eachRow("select product_name, sales from sales order by sales limit 4"){
data[it.product_name] = it.sales as double ②
}
① Usaremos un mapa "producto"=unidades para generar el gráfico de tartas
② Buscamos en la bbdd e insertamos en nuestro mapa
Generamos el gráfico de tartasEn esta parte podremos usar infinidad de técnicas y capacidades que nos ofrece JavaFX,como por ejemplo objetos 3D, rotaciones, hojas de estilo, etc. Por ahora a nosotros nosbastará con un gráfico de tartas donde se reflejen los productos obtenidos anteriormentey un título para el gráfico.
Una vez renderizado el gráfico nos interesará hacer una foto (snapshot) al nodo enconcreto que contiene el gráfico y volcar a fichero la imagen (en formato PNG).
start { def saveNode ① stage(visible: true) { ② scene(fill: BLACK, width: width, height: height) { saveNode = tilePane() { pieChart(data: data, title: chartTitle) ③ } } }
① saveNode será una referencia al nodo que contiene el gráfico
② creamos un gráfico de tartas con los datos obtenidos en la bbdd y almacenados en elmapa
③ asignamos la referencia para poder usarla después
Una vez que la scene JavaFX se encuentra lista realizamos el volcado. Básicamentepediremos a JavaFX que nos vuelque en un objeto Image los pixeles y así podamosconvertirlos a fichero PNG
101 Groovy Script
190 |
def snapshot = saveNode.snapshot(new SnapshotParameters(), null);
BufferedImage bufferedImage = new BufferedImage(saveNode.width as int, saveNode.height as int,
BufferedImage.TYPE_INT_ARGB);
BufferedImage image = javafx.embed.swing.SwingFXUtils.fromFXImage(snapshot, bufferedImage)
File file = new File('screenshot.png')
ImageIO.write(image, "png", file )
A TwitterGenerar un tweet mediante twitter4j es tan sencillo como crear un StatusUpdate,adjuntar un fichero si así lo deseamos y enviarlo:
StatusUpdate status = new StatusUpdate(message) ① status.media(file ) ② TwitterFactory.singleton.updateStatus status
① El mensaje que definimos en la parte de customizacion al principio
② El fichero png screenshot que hemos generado en el apartado anterior
Aquí puedes ver cómo quedaría un tweet:
101 Groovy Script
| 191
Cerrar automáticamentePor último una vez cumplida su misión simplemente indicamos a JavaFX que cierre laventana automáticamente
Platform.exit(); System.exit(0);
101 Groovy Script
192 |
Script
//tag::dependencies[]
@GrabConfig(systemClassLoader=true)
@Grab('mysql:mysql-connector-java:5.1.6')
@Grab(group='org.groovyfx',module='groovyfx',version='8.0.0',transitive=false, noExceptions=true)
@Grab(group='org.twitter4j', module='twitter4j-core', version='4.0.6')
import groovy.sql.Sql
import static groovyx.javafx.GroovyFX.start
import javafx.application.Platform
import javafx.scene.SnapshotParameters
import javax.imageio.ImageIO
import java.awt.image.BufferedImage
import twitter4j.TwitterFactory;
import twitter4j.StatusUpdate
//end::dependencies[]
//tag::customize[]
String message ="""
Estamos que nos salimos!!!
Top Ventas de nuestro catálogo. Gracias a todos por elegirnos#groovy-script
"""
String chartTitle="Top Ventas"
int width=height=400
//end::customize[]
//tag::business[]
def data=[:] ①
Sql sql = Sql.newInstance( "jdbc:mysql://localhost:3306/scripts?jdbcCompliantTruncation=false",
"root",
"my-secret-pw",
"com.mysql.jdbc.Driver")
sql.eachRow("select product_name, sales from sales order by sales limit 4"){
data[it.product_name] = it.sales as double ②
}
//end::business[]
//tag::vista[]
start {
def saveNode ①
stage(visible: true) { ②
scene(fill: BLACK, width: width, height: height) {
saveNode = tilePane() {
pieChart(data: data, title: chartTitle) ③
}
}
}
//end::vista[]
//tag::snapshot[]
def snapshot = saveNode.snapshot(new SnapshotParameters(), null);
BufferedImage bufferedImage = new BufferedImage(saveNode.width as int, saveNode.height as int,
BufferedImage.TYPE_INT_ARGB);
BufferedImage image = javafx.embed.swing.SwingFXUtils.fromFXImage(snapshot, bufferedImage)
101 Groovy Script
| 193
File file = new File('screenshot.png')
ImageIO.write(image, "png", file )
//end::snapshot[]
//tag::twitter[]
StatusUpdate status = new StatusUpdate(message) ①
status.media(file ) ②
TwitterFactory.singleton.updateStatus status
//end::twitter[]
//tag::exit[]
Platform.exit();
System.exit(0);
//end::exit[]
}
101 Groovy Script
194 |
VisualSheet (Mejora la interface de tusscripts)Jorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2018-02-17
Caso de UsoEste script no tiene un caso de uso práctico como tal sino que pretende demostrar laforma de mejorar el interface gráfico de los mismos para conseguir una mejor interaccióncon el usuario.
En este caso vamos a mostrar un diálogo donde cargaremos en una tabla una hoja deGoogle y el usuario podrá interactuar con los valores recuperados.
DependenciasNuestro script va a necesitar las siguientes dependencias agrupadas por funcionalidad:
@GrabConfig(systemClassLoader=true)
@Grab(group='org.groovyfx',module='groovyfx',version='8.0.0',transitive=true, noExceptions=true)
@Grab(group = 'com.puravida.groogle', module = 'groogle-sheet', version = '1.4.1')
import groovy.transform.Canonical
import groovyx.javafx.beans.FXBindable
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import static groovyx.javafx.GroovyFX.start
import com.google.api.services.sheets.v4.SheetsScopes
import com.puravida.groogle.GroogleScript
import com.puravida.groogle.SheetScript
Por un lado vamos a necesitar las dependencias de GroovyFX para la parte visual y porotro vamos a necesitar Groogle para la parte de interactuar con GoogleSheet
BusinessNuestro negocio va a ser realmente muy simple. Vamos a representar cada fila de la hojade cálculo mediante un objeto FxRow el cual contiene dos valores recuperados de esta asícomo un valor calculado por aplicación (coordinate) y otro que se actualiza tras lainteracción del usuario con la fila en cuestión (derivate)
101 Groovy Script
| 195
enum Status { ON, OFF}
@Canonicalclass FxRow { @FXBindable String coordinates @FXBindable String name @FXBindable Status status @FXBindable String derivate}
rows=FXCollections.observableArrayList([])self = this
Para la carga de este modelo vamos a usar una función loadFile la cual leerá la hoja decálculo e inicializará el array de FxRows. Esta función será llamada únicamente cuando elusuario así lo desee pulsando un botón de la vista
def loadFile(){ SheetScript.instance.withSpreadSheet args[0], { spreadSheet -> withSheet 'Hoja 1',{ int idx = 2 def googleRows = readRows("A$idx", "B${idx + 100}") while (googleRows ) { googleRows.eachWithIndex{ gRow, rIdx-> self.rows << new FxRow(coordinates: "A${idx+rIdx}", name:gRow[0], status: gRow[1]=='on'?Status.ON:Status.OFF, derivate: '' ) } idx += googleRows.size() googleRows = readRows("A$idx", "B${idx + 100}") } } }}
VistaLa vista se compone de un botón que activará una action y una tabla que mostrará deforma dinámica el array de FxRows. Así mismo podremos interactuar con los objetos enlas columnas name y status de tal forma que podríamos actualizar la hoja, calcularvalores o en definitiva cualquier acción que requiera el negocio.
101 Groovy Script
196 |
start {
actions {
fxaction(id: 'loadFile',onAction: {loadFile()})
}
stage(title: 'VisualSheet', height: 600, visible: true) {
scene(fill: BLACK, width: 800, height: 250) {
hbox(padding: 60) {
button( loadFile, text:'Load')
}
hbox(padding: 60) {
tableView(selectionMode: "single", cellSelectionEnabled: true, editable: true, items: bind
(self,'rows')) {
tableColumn(editable: false, property: "coordinates", text: "Row", prefWidth: 50)
tableColumn(editable: true, property: "name", text: "Name", prefWidth: 150,
onEditCommit: { event ->
FxRow item = event.tableView.items.get(event.tablePosition.row)
item.name = event.newValue
}
)
tableColumn(editable: true, property: "status", text: "Status", prefWidth: 150, type:
Status,
onEditCommit: { event ->
FxRow item = event.tableView.items.get(event.tablePosition.row)
item.status = event.newValue
item.derivate = event.newValue==Status.ON ? "Yes" : "NO"
}
)
tableColumn(editable: false, property: "derivate", text: "Calculada", prefWidth: 150)
}
}
}
}
}
Esto es una imagen de cómo podría quedar la aplicación:
101 Groovy Script
| 197
Script
//tag::dependencies[]
@GrabConfig(systemClassLoader=true)
@Grab(group='org.groovyfx',module='groovyfx',version='8.0.0',transitive=true, noExceptions=true)
@Grab(group = 'com.puravida.groogle', module = 'groogle-sheet', version = '1.4.1')
import groovy.transform.Canonical
import groovyx.javafx.beans.FXBindable
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import static groovyx.javafx.GroovyFX.start
import com.google.api.services.sheets.v4.SheetsScopes
import com.puravida.groogle.GroogleScript
import com.puravida.groogle.SheetScript
//end::dependencies[]
//tag::business[]
enum Status {
ON, OFF
}
@Canonical
class FxRow {
@FXBindable String coordinates
@FXBindable String name
@FXBindable Status status
@FXBindable String derivate
}
rows=FXCollections.observableArrayList([])
self = this
//end::business[]
//tag::load[]
def loadFile(){
SheetScript.instance.withSpreadSheet args[0], { spreadSheet ->
withSheet 'Hoja 1',{
int idx = 2
def googleRows = readRows("A$idx", "B${idx + 100}")
while (googleRows ) {
googleRows.eachWithIndex{ gRow, rIdx->
self.rows << new FxRow(coordinates: "A${idx+rIdx}",
name:gRow[0],
status: gRow[1]=='on'?Status.ON:Status.OFF,
derivate: ''
)
}
idx += googleRows.size()
googleRows = readRows("A$idx", "B${idx + 100}")
}
}
}
}
//end::load[]
//tag::login[]
GroogleScript.instance.applicationName='101-scripts'
clientSecret = new File('../google/client_secret.json').newInputStream()
SheetScript.instance.groogleScript=GroogleScript.instance.login(clientSecret,[SheetsScopes.SPREADSHEETS])
//end::login[]
101 Groovy Script
| 199
//tag::view[]
start {
actions {
fxaction(id: 'loadFile',onAction: {loadFile()})
}
stage(title: 'VisualSheet', height: 600, visible: true) {
scene(fill: BLACK, width: 800, height: 250) {
hbox(padding: 60) {
button( loadFile, text:'Load')
}
hbox(padding: 60) {
tableView(selectionMode: "single", cellSelectionEnabled: true, editable: true, items: bind
(self,'rows')) {
tableColumn(editable: false, property: "coordinates", text: "Row", prefWidth: 50)
tableColumn(editable: true, property: "name", text: "Name", prefWidth: 150,
onEditCommit: { event ->
FxRow item = event.tableView.items.get(event.tablePosition.row)
item.name = event.newValue
}
)
tableColumn(editable: true, property: "status", text: "Status", prefWidth: 150, type:
Status,
onEditCommit: { event ->
FxRow item = event.tableView.items.get(event.tablePosition.row)
item.status = event.newValue
item.derivate = event.newValue==Status.ON ? "Yes" : "NO"
}
)
tableColumn(editable: false, property: "derivate", text: "Calculada", prefWidth: 150)
}
}
}
}
}
//end::view[]
101 Groovy Script
200 |
Visualizar Extension de FicherosJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2018-02-17
JavaFX (y GroovyFX) nos permiten visualizar datos de forma muy cómoda. En este casovamos a realizar un escaneo de nuestros ficheros y por cada extensión vamos a acumularel tamaño de los ficheros y su número, de tal forma que podamos visualizar ladistribución de los mismos en tres gráficas:
• distribución por tamaños (qué extensión ocupa más disco)
• distribución por número (qué extensión tiene más ficheros)
• relación entre ambos conceptos
CalculoDado un directorio como argumento, el script lo recorrera recursivamente generando 3mapas con la extensión como clave:
• por tamaño, bySize
• por contador, byNumber
• por ambos mediante un submapa con la unión de los 3 conceptos (extension, numeroy tamaño)
101 Groovy Script
| 201
bySize=[:]byNumber=[:]byBoth=[:]
void scanDir( File dir ){ dir.eachFile FileType.FILES,{ f-> split = f.name.split('\\.') ext = split.size() ? split.last() : '.'
bySize."$ext" = (bySize."$ext" ?: 0)+f.size() byNumber."$ext" = (byNumber."$ext" ?: 0)+1
both = byBoth."$ext" ?: [ext,0,0] both[1] +=f.size() both[2] +=1 byBoth."$ext" = both } dir.eachDir{ d -> scanDir(d) }}
scanDir(new File(args[0]))
bySize = bySize.sort{ a,b-> b.value<=>a.value }byNumber = byNumber.sort{ a,b-> b.value<=>a.value }
La gestión de Groovy con mapas es tan potente que nos permite crear elementos en elmapa de una forma realmente simple
VistaLa vista utilizará 3 gráficas, una para cada mapa calculado previamente
101 Groovy Script
202 |
stage title: "My Files", visible: true, { scene { saveNode = stackPane { scrollPane { tilePane(padding: 10, prefTileWidth: 480, prefColumns: 2) {
pieChart(data: bySize, title: "Size")
pieChart(data: byNumber, title: "Number")
scatterChart title:'Size vs Number', { byBoth.values().each { bb-> series(name: bb[0], data: [bb[1],bb[2]]) } } } } } } }
EjemploA continuación se muestra un ejemplo de cómo se representaría un directorio, enconcreto el blog donde vemos que la extensión mayoritaria tanto en tamaño como ennúmero es SVG.
101 Groovy Script
| 203
Script
//tag::dependencies[]
import groovy.io.FileType
@GrabConfig(systemClassLoader=true)
@Grab(group='org.groovyfx',module='groovyfx',version='8.0.0',transitive=false, noExceptions=true)
import static groovyx.javafx.GroovyFX.start
import javafx.application.Platform
import javafx.scene.SnapshotParameters
import javax.imageio.ImageIO
import java.awt.image.BufferedImage
//end::dependencies[]
//tag::calculating[]
bySize=[:]
byNumber=[:]
byBoth=[:]
void scanDir( File dir ){
dir.eachFile FileType.FILES,{ f->
split = f.name.split('\\.')
ext = split.size() ? split.last() : '.'
bySize."$ext" = (bySize."$ext" ?: 0)+f.size()
byNumber."$ext" = (byNumber."$ext" ?: 0)+1
both = byBoth."$ext" ?: [ext,0,0]
both[1] +=f.size()
both[2] +=1
byBoth."$ext" = both
}
dir.eachDir{ d ->
scanDir(d)
}
}
scanDir(new File(args[0]))
bySize = bySize.sort{ a,b-> b.value<=>a.value }
byNumber = byNumber.sort{ a,b-> b.value<=>a.value }
//end::calculating[]
start { app ->
//tag::view[]
stage title: "My Files", visible: true, {
scene {
saveNode = stackPane {
scrollPane {
tilePane(padding: 10, prefTileWidth: 480, prefColumns: 2) {
pieChart(data: bySize, title: "Size")
pieChart(data: byNumber, title: "Number")
scatterChart title:'Size vs Number', {
byBoth.values().each { bb->
series(name: bb[0], data: [bb[1],bb[2]])
}
}
}
}
101 Groovy Script
| 205
}
}
}
//end::view[]
//tag::snapshot[]
def snapshot = saveNode.snapshot(new SnapshotParameters(), null);
BufferedImage bufferedImage = new BufferedImage(saveNode.width as int, saveNode.height as int,
BufferedImage.TYPE_INT_ARGB);
BufferedImage image = javafx.embed.swing.SwingFXUtils.fromFXImage(snapshot, bufferedImage)
File file = new File('myfiles.png')
ImageIO.write(image, "png", file )
//end::snapshot[]
}
101 Groovy Script
206 |
Configuración dinámica de ficheroremotoMiguel Rueda <[email protected] [mailto:[email protected]]>2017-10-13
El caso que vamos a tratar es la modificación del contenido de un fichero con el formatoclave:valor.
Si trabajamos con varias máquinas, seguramente nos hayamos encontrado con que cadauna de ellas tiene ficheros de configuración que indiquen la url de la base de datos a laque debe conectectarse, messages.properties de nuestra aplicación …
El problema de trabajar con estos ficheros es que en la mayoría de los casos, sobre todocuando hablamos de ficheros de configuración, son personalizados y en el caso de tenerque cambiar algún valor debemos de tener mucho cuidado ya que no son ficheros quepodamos copiar a todas nuestras maquinas de manera masiva porque dedemos respetarla configuración que tienen cada uno de ellos.
A continuación vamos a explicar como crear un script que modifique el usuario con elque hacemos la conexión a la base de datos en base a cierta condición propia de lamáquina.
Vamos a usar una herramienta que ya hemos utilizado en alguno denuestros scripts sshoogr [https://github.com/aestasit/sshoogr].
• Añadimos la dependencia de sshoogr
@Grab('com.aestasit.infrastructure.sshoogr:sshoogr:0.9.25')@GrabConfig(systemClassLoader=true)
• Definimos el nombre del servidor y el de nuestro fichero de configuración.
def server_name = "server_1"def file_config = '/tmp/file.properties'
• Creamos el método de lectura de nuestro fichero
101 Groovy Script
| 207
def getFile(file_config,server_name){ remoteSession("user:passwd@$server_name:22") {① result = remoteFile(file_config).text ② } return result.readLines()③}
① Establecemos los parámtros de conexión usuario = user, contraseña = passwd
mientras que el servidor es variable.
② En la función remoteFile le pasamos por parámetro la ruta complete la fichero.
③ Con la función readLines obtenemos una lista con el contenido del fichero.
• Creamos el método para sobreescribir el fichero con los datos actualizados
def putFile(file_name,server_name,new_file){ remoteSession("user:passwd@$server_name:22") {① remoteFile(dir).text = properties ② }}
① Establecemos los parámtros de conexión usuario = user, contraseña = passwd
mientras que el servidor es variable.
② Actualizamos el contenido de nuestro fichero de configuración.
• Aplicamos la lógica de sustitución. En nuestro caso vamos a cambiar la variableuser.sql por el valor de una variable de entorno más una cadena fija _changeme
def read = getFile(file_config,server_name) ①
def properties = read.collect{ if (it.startWith("user.sql")){ ② return "user.sql=${System.getenv('HOSTNAME')}_changeme" } return it}.join('\n')
putFile(file_config,server_name,properties)③
① Lo primero que hacemos es emplear nuestra función getFile para obtener encontenido del fichero.
② Si econtramos la clave en nuestro fichero que queremos actualizar la modificamosy si no saltamos a la siguiente linea.
③ Llamaremos a nuestro método putFile el cúal nos actualizará el contenido denuestro fichero de configuración.
101 Groovy Script
208 |
Si te da error al conectar con el host, prueba a desactivar lacomprobación estricta de la clave del host:
options.trustUnknownHosts = true
101 Groovy Script
| 209
Script
//tag::dependencies[]@Grab('com.aestasit.infrastructure.sshoogr:sshoogr:0.9.25')@GrabConfig(systemClassLoader=true)//end::dependencies[]
import static com.aestasit.infrastructure.ssh.DefaultSsh.*
options.trustUnknownHosts = true
//tag::config[]def server_name = "server_1"def file_config = '/tmp/file.properties'//end::config[]
//tag::main[]def read = getFile(file_config,server_name) ①
def properties = read.collect{ if (it.startWith("user.sql")){ ② return "user.sql=${System.getenv('HOSTNAME')}_changeme" } return it}.join('\n')
putFile(file_config,server_name,properties)③//end::main[]
//tag::write[]def putFile(file_name,server_name,new_file){ remoteSession("user:passwd@$server_name:22") {① remoteFile(dir).text = properties ② }}//end::write[]
//tag::read[]def getFile(file_config,server_name){ remoteSession("user:passwd@$server_name:22") {① result = remoteFile(file_config).text ② } return result.readLines()③}//end::read[]
101 Groovy Script
210 |
Envío de eventos con RabbitMQJorge Aguilera,<[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2018-01-27
En este artículo vamos a desarrollar una solución muy simple para monitorizar ficherosde logs en múltiples máquinas , enviando los cambios que se van produciendo en losmismos a un componente central el cual será el encargado de su tratamiento (persistenciaa base de datos, análisis de las lineas recibidas, alarmas, etc).
Para ello vamos a utilizar el mismo script en dos modos diferentes (proporcionando unargumento en su ejecución):
• producer , monitoriza un fichero y cada vez que se produzca un cambio en el mismolo notificará enviando la última línea que se haya escrito en el fichero. Este modo seráejecutado en varias máquinas de forma simultánea.
• consumer, recibe los eventos de los diferentes producer’S y los va mostrando porpantalla
Para ello usaremos un gestor de mensajería RabbitMQ, el cual será el encargado deproporcionar el canal de envío y recepción así como de la persistencia de los mensajeshasta su consumo.
RabbitMQRabbitMQ es un software destinado a la gestión del intercambio de mensajes, en su másamplia aceptación, entre aplicaciones. Un software cliente se subscribe al mismo pararecibir notificaciones de cuando otro software cliente envía un mensaje siendo el gestor elencargado de gestionar la entrega, persistencia hasta su consumo, reintentos, etc.
Para nuestro scrip vamos a crear una instancia de este broker mediante el uso de laimagen oficial Docker, pero no vamos a entrar en detalles de cómo hacer backups,seguridad etc.
Una vez que tengamos instalado Docker en alguna de nuestras máquinas ejecutaremos:
$docker run -d --hostname my-rabbit \ --name some-rabbit \ -v $(pwd)/rabbit://var/lib/rabbitmq/mnesia/rabbit \ -p 5672:5672 -p 15672:15672 \ rabbitmq:3-management
Básicamente creamos una instancia docker some-rabbit que va a usar un subdirectoriorabbit de la máquina donde se está ejecutando para la persistencia de los mensajes y porúltimo vamos a utlizar un par de puertos para poder acceder a dicha instancia (sólonecesitamos 5672 pero si quieres acceder a la consola de RabbitMQ vía web para poder
101 Groovy Script
| 211
inspeccionarlo usamos 15672)
ConexionLo primero que hará el script es establecer conexión con el gestor (en nuestro caso vamosa usar una conexión en localhost con las credenciales por defecto) y configurar en quéentorno publicará/recibirá los mensajes:
exchangeName="groovy-script"queueName="grabbit"routingKey='#'
factory = new ConnectionFactory() factory.username='guest' factory.password='guest' factory.virtualHost='/' factory.host= args[0] ?: 'localhost' factory.port=5672conn = factory.newConnection()
channel = conn.createChannel()channel.exchangeDeclare(exchangeName, "direct", true)channel.queueDeclare(queueName, true, false, false, null)channel.queueBind(queueName, exchangeName, routingKey)
Por simplificar configuramos tanto el exchange, el routing y la cola en el mismo sitio sinimportar si es consumer o producer
RabbitMQ nos permite crear los canales de múltiples formas (con o sin persistencia,subscripción o directo, etc) Nosotros vamos a usar una configuración típica donde losmensajes se guarden para asegurar su entrega.
Monitorizar fichero (producer)En el modo producer el script utilizará la librería de Apache VFS2 para monitorizar unfichero de tal forma que cada vez que se modifique este fichero recibiremos una llamadaen nuestro código (`void fileChanged(FileChangeEvent evt) throws Exception `)
101 Groovy Script
212 |
monitor = args[2]
if (new File(monitor).exists() == false) new File(monitor).newPrintWriter().println "${new Date()}"
FileSystemManager manager = VFS.getManager(); FileObject fobj = manager.resolveFile(new File(monitor).absolutePath) DefaultFileMonitor fm = new DefaultFileMonitor(this as FileListener) fm.delay = 500 fm.addFile(fobj) fm.start()
Cada vez que recibimos el evento de que el fichero ha sido modificado, lo leeremos desdeel final hacia atrás buscando el último retorno de carro y de esta forma obtener la últimalínea del fichero. Obviamente no es una solución robusta para sistemas donde el cambioen el fichero puedan ser de varias líneas, en nuestro caso es un simple ejemplo sujeto deser modificado a situaciones más robustas
Por otra parte, la librería de RabbitMQ nos permite una gran granularidad en el envío delmensaje. En nuestro caso vamos a usar la forma más simple en la cual simplementeindicamos dónde queremos publicar el mensaje y lo enviamos como una simple cadenade texto (como bytes)
void fileChanged(FileChangeEvent evt) throws Exception {
RandomAccessFile randomAccessFile = new RandomAccessFile(evt.file.localFile, "r"); ①
long fileLength = evt.file.localFile.length() - 1;
randomAccessFile.seek(fileLength);
StringBuilder stringBuilder = new StringBuilder()
for(long pointer = fileLength; pointer >= 0; pointer--){
randomAccessFile.seek(pointer)
char c = (char)randomAccessFile.read()
if(c == '\n' && fileLength == pointer){
continue
}
if(c == '\n' && fileLength != pointer){
break
}
stringBuilder.append(c)
}
println "Soy el producer enviando ${stringBuilder.toString().reverse()}"
channel.basicPublish(exchangeName, routingKey, null, stringBuilder.toString().reverse().bytes) ②
}
① Usamos un RandomAccess que nos permite movernos por el fichero en lugar de leerlosecuencial
② Publicamos en el sistema la linea leída
101 Groovy Script
| 213
Recibir eventos (consumer)La parte "consumer" del script simplemente va a conectarse al gestor de mensajes el cualle avisará cada vez que haya un mensaje que cumpla las condiciones de exchange/routingindicadas. En nuestro caso el script simplemente lo traceará en la consola
boolean autoAck = true; channel.basicConsume(queueName, autoAck, new DefaultConsumer(channel) { public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException{ println "Soy el consumer recibiendo ${new String(body)}" } });
ResultadoEn este screenshot puedes ver un caso de ejecución. En la ventana inferior se encuentracorriendo el consumer a la espera de eventos de RabbitMQ, en la superior se encuentra elproducer esperando eventos de VFS y en la superior derecha se realiza un cambio en elfichero que se está monitorizando (volcando la fecha de ese momento).
El cambio en el fichero es detectado por el producer y propagado hasta el consumer. Eneste ejemplo todo se ejecuta en el mismo equipo pero como hemos dicho su interés radicaen que estos componentes pueden estar ejecutándose en otras máquinas e incluso redes
101 Groovy Script
214 |
DockerPara comprobar que la solución puede ser ejecutada en un entorno distribuido vamos acrear una "mini-red" docker con varios contenedores:
• rabbitmq-container, será el contenedor donde estará corriendo el servidor de colasRabbitMQ
• consumer, será el contenedor donde estará corriendo el script en modo consumer(mostrará por pantalla los eventos que reciba)
• producer1, será un contenedor observando un fichero
• producer2, será un contenedor observando otro fichero diferente
Para ello vamos a crear un fichero docker-compose.yml donde vamos a configurar todosestos containers:
docker-compose.yml
version: '2'
services:
①
rabbitmq-container:
image: rabbitmq:3-management
hostname: my-rabbit
volumes:
- ./rabbit:/var/lib/rabbitmq/mnesia/rabbit
ports:
- 5672:5672
- 15672:15672
②
consumer:
image: groovy:2.4-jdk8
volumes:
- ./:/home/groovy/
command: groovy /home/groovy/grabbit.groovy rabbitmq-container consumer ③
④
producer1:
image: groovy:2.4-jdk8
volumes:
- ./:/home/groovy/
command: groovy /home/groovy/grabbit.groovy rabbitmq-container producer /home/groovy/grabbit.log ⑤
④
producer2:
image: groovy:2.4-jdk8
volumes:
- ./:/home/groovy/
command: groovy /home/groovy/grabbit.groovy rabbitmq-container producer /home/groovy/grabbit2.log ⑥
① rabbitmq-container es el "hostname" por el que el resto de containers lo puedenencontrar en su network
101 Groovy Script
| 215
② consumer es el nombre del container donde corre el script en modo lectura de la cola
③ indicamos el script a ejecutar así como el nombre del host a conectarse y el modo
④ tenemos dos producers (producer1 y producer2)
⑤ producer1 observará un fichero en lo que en su directorio de trabajo
⑥ producer2 observará un fichero diferente en lo que en su directorio de trabajo
Una vez definidos nuestros componentes levantamos el entorno:
$ docker-compose up
y actualizamos cualquiera de los dos ficheros que están siendo observados. En la consolade docker iremos viendo indentificados cada container en las trazas que generan
101 Groovy Script
216 |
Script
@Grapes([
@Grab(group='com.rabbitmq', module='amqp-client', version='3.1.2'),
@Grab(group='org.apache.commons', module='commons-vfs2', version='2.2')
])
import com.rabbitmq.client.*
import groovy.json.*
import org.apache.commons.vfs2.FileChangeEvent
import org.apache.commons.vfs2.FileListener
import org.apache.commons.vfs2.FileObject
import org.apache.commons.vfs2.FileSystemManager
import org.apache.commons.vfs2.VFS
import org.apache.commons.vfs2.impl.DefaultFileMonitor
//tag::conexion[]
exchangeName="groovy-script"
queueName="grabbit"
routingKey='#'
factory = new ConnectionFactory()
factory.username='guest'
factory.password='guest'
factory.virtualHost='/'
factory.host= args[0] ?: 'localhost'
factory.port=5672
conn = factory.newConnection()
channel = conn.createChannel()
channel.exchangeDeclare(exchangeName, "direct", true)
channel.queueDeclare(queueName, true, false, false, null)
channel.queueBind(queueName, exchangeName, routingKey)
//end::conexion[]
if( args[1] == 'producer' ) {
//tag::producer[]
monitor = args[2]
if (new File(monitor).exists() == false)
new File(monitor).newPrintWriter().println "${new Date()}"
FileSystemManager manager = VFS.getManager();
FileObject fobj = manager.resolveFile(new File(monitor).absolutePath)
DefaultFileMonitor fm = new DefaultFileMonitor(this as FileListener)
fm.delay = 500
fm.addFile(fobj)
fm.start()
//end::producer[]
}
if( args[1] == 'consumer' ) {
//tag::consumer[]
boolean autoAck = true;
channel.basicConsume(queueName, autoAck, new DefaultConsumer(channel) {
public void handleDelivery(String consumerTag,
Envelope envelope,
101 Groovy Script
| 217
AMQP.BasicProperties properties,
byte[] body)
throws IOException{
println "Soy el consumer recibiendo ${new String(body)}"
}
});
//end::consumer[]
}
//tag::filechanged[]
void fileChanged(FileChangeEvent evt) throws Exception {
RandomAccessFile randomAccessFile = new RandomAccessFile(evt.file.localFile, "r"); ①
long fileLength = evt.file.localFile.length() - 1;
randomAccessFile.seek(fileLength);
StringBuilder stringBuilder = new StringBuilder()
for(long pointer = fileLength; pointer >= 0; pointer--){
randomAccessFile.seek(pointer)
char c = (char)randomAccessFile.read()
if(c == '\n' && fileLength == pointer){
continue
}
if(c == '\n' && fileLength != pointer){
break
}
stringBuilder.append(c)
}
println "Soy el producer enviando ${stringBuilder.toString().reverse()}"
channel.basicPublish(exchangeName, routingKey, null, stringBuilder.toString().reverse().bytes) ②
}
//end::filechanged[]
void fileCreated(FileChangeEvent arg0) throws Exception {
}
void fileDeleted(FileChangeEvent arg0) throws Exception {
}
101 Groovy Script
218 |
Crear un fichero de texto con todas lasoperaciones disponibles en el API delINEJesús J. Ballano <[email protected] [mailto:[email protected]]> 2018-02-13
En este script vamos a guardar en un fichero de texto todas las operaciones disponiblesque nos devuelve el INE (Instituto Nacional de Estadística de España) desde su endpointhttp://servicios.ine.es//wstempus/js/ES/OPERACIONES_DISPONIBLES.
Crear ficheros de texto en Groovy es fácil y acceder a cualquier API REST conHttpBuilderNG [https://github.com/http-builder-ng/http-builder-ng] también, así que los vamos acombinar.
HttpBuilderNG es una librería que nos facilita mucho el acceso a cualquier API y que nospermite elegir entre 3 posibles implementaciones: core, apache o okhttp. Para este ejemplovamos a usar la implementación core.
@Grab(group='io.github.http-builder-ng', module='http-builder-ng-core', version='1.0.3') ①
import groovyx.net.http.*
String baseUrl = "http://servicios.ine.es"
String path = "/wstempus/js/ES/OPERACIONES_DISPONIBLES"
def httpBin = HttpBuilder.configure { ②
request.uri = baseUrl
}
def operations = httpBin.get { ③
request.uri.path = path
}
File ineOperations = new File('/tmp/ineOperations.txt') ④
operations.each {
ineOperations << "${it}\n" ⑤
}
① Recogemos la librería de HttpBuilderNG.
② Creamos la configuración con la uri base.
③ Ejecutamos la operación get.
④ Accedemos al fichero donde queremos guardar las distintas operaciones.
⑤ Por cada operación que nos devuelve el API, guardamos una línea en el fichero detexto.
101 Groovy Script
| 219
Script
@Grab(group='io.github.http-builder-ng', module='http-builder-ng-core', version='1.0.3') ①
import groovyx.net.http.*
String baseUrl = "http://servicios.ine.es"
String path = "/wstempus/js/ES/OPERACIONES_DISPONIBLES"
def httpBin = HttpBuilder.configure { ②
request.uri = baseUrl
}
def operations = httpBin.get { ③
request.uri.path = path
}
File ineOperations = new File('/tmp/ineOperations.txt') ④
operations.each {
ineOperations << "${it}\n" ⑤
}
101 Groovy Script
220 |
Servidor web en 1 ficheroHolger Garcia <[email protected] [mailto:[email protected]]> 2018-04-07
En este script veremos como usando Groovy y Ratpack podemos crear un servidor webcompleto en un solo fichero.
Para un simple Hello world sería tan sencillo como:
@Grapes([ @Grab('io.ratpack:ratpack-groovy:1.5.2'), @Grab('org.slf4j:slf4j-simple:1.7.25')])import static ratpack.groovy.Groovy.ratpack
ratpack { handlers { get { render "Hello World!" } }}
Si guardamos este fichero con el nombre ratpack.groovy y lo ejecutamos con groovyratpack.groovy obtendremos una traza como la siguiente
[main] INFO ratpack.server.RatpackServer - Starting server...
[main] INFO ratpack.server.RatpackServer - Building registry...
[main] INFO ratpack.server.RatpackServer - Ratpack started (development) for http://localhost:5050
Voila!, hemos creado nuestro primer servidor en unas pocas líneas.
Ahora probemos con algo un poco mas interesante:
Por ejemplo podríamos servir ficheros locales usando
101 Groovy Script
| 221
@Grapes([ @Grab('io.ratpack:ratpack-groovy:1.5.2'), @Grab('org.slf4j:slf4j-simple:1.7.25')])import static ratpack.groovy.Groovy.ratpack
ratpack { handlers { files { dir "." } }}
En este caso estariamos sirviendo todo el directorio en el que se encuentre nuestro script,perfecto si necesitas compartir rapidamente ciertos ficheros con tus compañeros
Tambien podríamos facilmente trabajar con JSON, por ejemplo en caso de que queramospreparar una respuesta sencilla con la que los desarrolladores frontend puedan comenzara trabajar mientras desarrollamos nuestro backend
@Grapes([ @Grab('io.ratpack:ratpack-groovy:1.5.2'), @Grab('org.slf4j:slf4j-simple:1.7.25')])import static ratpack.groovy.Groovy.ratpackimport static ratpack.jackson.Jackson.json;
ratpack { handlers { get { render(json([Ratpack: 'is really cool'])) } }}
Estos pequeños ejemplos solo arañan la superficie de lo que ratpack es capaz de hacer,desde pequeños scripts rapidos como los presentados hasta aplicaciones completas conconexion a base datos, multiples integraciones y toda la logica de negocio que puedasdesear.
Para mas informacion recomiendo visitar la documentacion oficial de Ratpack[https://ratpack.io/manual/current/]
101 Groovy Script
222 |
Script
@Grapes([ @Grab('io.ratpack:ratpack-groovy:1.5.2'), @Grab('org.slf4j:slf4j-simple:1.7.25')])import static ratpack.groovy.Groovy.ratpack
ratpack { handlers { files { dir "." } }}
101 Groovy Script
| 223
Leer un fichero desde un host remotoMiguel Rueda <[email protected] [mailto:[email protected]]>2017-10-12
Seguramente os haya surgido la necesidad de comprobar el contenido de unosdeterminados equipos remotos desde vuestro equipo como por ejemplo ver las trazas deun fichero .log que va dejando la aplicación que habéis instalado, comprobar versiones opropiedades guardadas en un fichero, modificar uno existente y querer comprobar queunos determinados ficheros de configuración apuntan correctamente, etc.
Cuando sólo tienes un equipo que administrar esta tarea no es muy compleja yprobablemente la realizas a mano, pero cuando el número de equipos a comprobar crecela tarea se vuelve muy repetitiva y cansada y es cuando piensas en crearte un script querealice esta tarea en todas las máquinas por tí.
Para crear el script que facilite esta tarea vamos a emplear sshoogr [https://github.com/
aestasit/sshoogr].
Esta herramienta nos permite realizar sobre nuestros equipos muchas más funciones delas que vamos a ver en este post, como copiar archivos y directorios, ejecutar comandosremotos … os ánimo a que reviseis la documentación que seguro que os será de granayuda.
A continuación pasamos a explicar en funcionamiento de nuestro script paso a paso:
• Añadimos la dependencia de sshoogr:
@Grab('com.aestasit.infrastructure.sshoogr:sshoogr:0.9.25')@GrabConfig(systemClassLoader=true)import static com.aestasit.infrastructure.ssh.DefaultSsh.*
• Creamos la función para leer el fichero:
def getFile(dir,host){ remoteSession("user:passwd@$host:22") {① result = remoteFile(dir).text ② } return result.readLines()③
}
① Establecemos los parámtros de conexión usuario = user, contraseña = passwd
mientras que el servidor es variable.
② En la función remoteFile le pasamos por parámetro la ruta complete la fichero.
101 Groovy Script
224 |
③ Con la función readLines obtenemos una lista con el contenido del fichero.
• Creamos la lista de hosts y el nombre del fichero que queremos leer:
def hosts = ['server_1','server_2','server_3','server_4','server_5']def file = '/var/log/www/access_log'
• recorremos los host y leemos el archivo en cuestion:
hosts.each{host-> getFile(file,host).each{line-> println line }}
Si te da error al conectar con el host, prueba a desactivar lacomprobación estricta de la clave del host:
options.trustUnknownHosts = true
101 Groovy Script
| 225
Script
//tag::dependencies[]@Grab('com.aestasit.infrastructure.sshoogr:sshoogr:0.9.25')@GrabConfig(systemClassLoader=true)import static com.aestasit.infrastructure.ssh.DefaultSsh.*//end::dependencies[]
options.trustUnknownHosts = true
//tag::config[]def hosts = ['server_1','server_2','server_3','server_4','server_5']def file = '/var/log/www/access_log'//end::config[]
//tag::main[]hosts.each{host-> getFile(file,host).each{line-> println line }}//end::main[]
//tag::read[]def getFile(dir,host){ remoteSession("user:passwd@$host:22") {① result = remoteFile(dir).text ② } return result.readLines()③
}//end::read[]
101 Groovy Script
226 |
Contacts2QRCodeJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2017-11-03
En ete script vamos a tratar las siguientes capacidades de Groovy:
• Leer todas las Hojas, Filas y Columnas de un Excel
• Escribir una celda en función de los valores leídos y/o lógica de negocio
• Invocar a un servicio remoto y volcar el resultado a un fichero
ObjetivoContacts2QRCode es un script que partiendo de un Excel donde se hayan guardado datosde contactos de una serie de personas, como por ejemplo los empleados de tuorganización, generar para cada uno de ellos un código QRCode para incluir en lastarjetas de visita.
Un código QR (del inglés Quick Response code, "código de respuestarápida") es la evolución del código de barras. Es un módulo paraalmacenar información en una matriz de puntos o en un código debarras bidimensional.
QRCode tiene numerosas utilidades y formatos. En este post trataremoscómo generar un VCard, formato destinado a especificar los datos decontacto de una persona, como nombre, puesto, dirección, teléfono, etc.
Para la generación del código QRCode utilizaremos el servicio gratuitozxing (http://zxing.org/)
ExcelEl excel con los datos de nuestros contactos contendrá la siguiente estructura, siendo laprimera fila destinada a contener la cabecera y por ello ignorada por el script:
Id id del empleado se utiliza como nombre defichero QR
Name nombre del empleado puede contener espacios yser compuesto con apellidos,etc
JobTitle puesto puede ser blanco
101 Groovy Script
| 227
Phone telefóno asegurate que el formato esuna cadena para que no setrate como numérico
eMail puede ser blanco
Address dirección en una sola línea
Organization
URL http://xxxx.yyy/zzzz si disponeis de páginapersonal para cadaempleado o en la empresa
QR esta columan será escritapor el script cada vez que seinvoque
DependenciasPara la lectura del Excel utilizaremos esta vez las librerías de Apache, POI
@Grab('org.apache.poi:poi:3.14')@Grab('org.apache.poi:poi-ooxml:3.14')
Parseo del ExcelDurante la primera pasada, el script leerá todas las hojas de cálculo y sus filas suponiendoque siguen el formato anteriormente explicado, generando para cada fila una URLpersonalizada:
WorkbookFactory.create(f,null,false).withCloseable { workbook -> ① 0.step workbook.getNumberOfSheets(), 1, { sheetNum -> ② println "Working on ${workbook.getSheetName(sheetNum)}" Sheet sheet = workbook.sheets[sheetNum] for( Row row : sheet){ if( row.rowNum ){ ③ row.createCell(8).cellValue = generateQRLink(row) ④ } } } bos = new ByteArrayOutputStream() workbook.write(bos) ⑤}f << bos.toByteArray() ⑥
① Leemos el excel en modo escritura
101 Groovy Script
228 |
② Iteramos en todas las hojas del Excel. Podríamos buscar una en concreto si así loquisieramos
③ Iteramos por todas las filas ignorando la primera
④ Invocamos a la función que genera el link
⑤ Una vez terminado reescribimos el excel en memoria
⑥ Volcamos el buffer de memoria al fichero de origen
Dump de QRCodesSi se especifica un segundo parámetro, este indicará el directorio donde queremos que segeneren los QRCodes de cada empleado. Realizaremos el mismo bucle de lectura que en elapartado anterior pero esta vez buscaremos la columna que se actualizó. Dicha columnaes una URL que nos devuelve un PNG con el código generado, por lo que usaremos lasintaxis de Groovy para descargarlo directamente a un fichero:
if( args.length > 1 ){ File dumpFolder = new File(args[1]) dumpFolder.mkdirs() WorkbookFactory.create(f,null,true).withCloseable { workbook -> ① 0.step workbook.getNumberOfSheets(), 1, { sheetNum -> println "Dumping on ${workbook.getSheetName(sheetNum)}" Sheet sheet = workbook.sheets[sheetNum] for( Row row : sheet){ if( row.rowNum ){ new File(dumpFolder,"${row.getCell(0)}.png").withOutputStream { it.bytes = "${row.getCell(8)}".toURL().bytes ② } } } } }}
① Leemos el excel en modo lectura
② Gracias a Groovy toURL() genera una URL y bytes nos devuelve el contenido de laresponse como un array de bytes
generateQRLinkLa función generateQRLink simplemente lee los campos de la fila Excel y los concatena ala URL de Zxing devolviendo la cadena resultante
101 Groovy Script
| 229
String generateQRLink(Row range){
int col=1
String name = "${range.getCell(col++)}"
String title = "${range.getCell(col++)}"
String tlf= "${range.getCell(col++)}"
String email="${range.getCell(col++)}"
String addr="${range.getCell(col++)}"
String organization="${range.getCell(col++)}"
String url="${range.getCell(col++)}"
String vcard = urlRoot + generateVCard(name, title , tlf , email, addr, organization, url)
vcard
}
GenerateVCardLa función generateVCard a la que se hace referencia en el código anterior es una simpleconcatenación de String en formato VCARD:
String generateVCard( name, title , tlf , email, addr, organization, url){ String getvcard = "BEGIN%3AVCARD%0AVERSION%3A3.0"; if(name) getvcard += "%0AN%3A"+URLEncoder.encode(name,"UTF-8") ① if(organization) getvcard += "%0AORG%3A"+URLEncoder.encode(organization,"UTF-8") if(title) getvcard += "%0ATITLE%3A"+URLEncoder.encode(title,"UTF-8") if(tlf) getvcard += "%0ATEL%3A"+URLEncoder.encode(tlf,"UTF-8"); if(email) getvcard += "%0AEMAIL%3A"+URLEncoder.encode(email,"UTF-8"); if(addr) getvcard += "%0AADR%3A"+URLEncoder.encode(addr,"UTF-8") if(url) getvcard += "%0AURL%3A"+URLEncoder.encode(url,"UTF-8"); getvcard += "%0AEND%3AVCARD"; return getvcard;}
① Utilizaremos URLEncoder para conseguir que los campos con espacio, acentos etc seanconvertidos correctamente
101 Groovy Script
230 |
Script
//tag::dependencies[]
@Grab('org.apache.poi:poi:3.14')
@Grab('org.apache.poi:poi-ooxml:3.14')
//end::dependencies[]
import org.apache.poi.ss.usermodel.*
if (args.length == 0) {
println "Use:"
println " groovy Constact2QRCode.groovy [excel-file]"
return 1
}
urlRoot = "http://zxing.org/w/chart?cht=qr&chs=350x350&chld=H&choe=UTF-8&chl=";
File f = new File(args[0]);
ByteArrayOutputStream bos
//tag::readExcel[]
WorkbookFactory.create(f,null,false).withCloseable { workbook -> ①
0.step workbook.getNumberOfSheets(), 1, { sheetNum -> ②
println "Working on ${workbook.getSheetName(sheetNum)}"
Sheet sheet = workbook.sheets[sheetNum]
for( Row row : sheet){
if( row.rowNum ){ ③
row.createCell(8).cellValue = generateQRLink(row) ④
}
}
}
bos = new ByteArrayOutputStream()
workbook.write(bos) ⑤
}
f << bos.toByteArray() ⑥
//end::readExcel[]
//tag::dumpQr[]
if( args.length > 1 ){
File dumpFolder = new File(args[1])
dumpFolder.mkdirs()
WorkbookFactory.create(f,null,true).withCloseable { workbook -> ①
0.step workbook.getNumberOfSheets(), 1, { sheetNum ->
println "Dumping on ${workbook.getSheetName(sheetNum)}"
Sheet sheet = workbook.sheets[sheetNum]
for( Row row : sheet){
if( row.rowNum ){
new File(dumpFolder,"${row.getCell(0)}.png").withOutputStream {
it.bytes = "${row.getCell(8)}".toURL().bytes ②
}
}
}
}
}
}
//end::dumpQr[]
101 Groovy Script
| 231
//tag::generateVCard[]
String generateVCard( name, title , tlf , email, addr, organization, url){
String getvcard = "BEGIN%3AVCARD%0AVERSION%3A3.0";
if(name) getvcard += "%0AN%3A"+URLEncoder.encode(name,"UTF-8") ①
if(organization) getvcard += "%0AORG%3A"+URLEncoder.encode(organization,"UTF-8")
if(title) getvcard += "%0ATITLE%3A"+URLEncoder.encode(title,"UTF-8")
if(tlf) getvcard += "%0ATEL%3A"+URLEncoder.encode(tlf,"UTF-8");
if(email) getvcard += "%0AEMAIL%3A"+URLEncoder.encode(email,"UTF-8");
if(addr) getvcard += "%0AADR%3A"+URLEncoder.encode(addr,"UTF-8")
if(url) getvcard += "%0AURL%3A"+URLEncoder.encode(url,"UTF-8");
getvcard += "%0AEND%3AVCARD";
return getvcard;
}
//end::generateVCard[]
//tag::generateQRLink[]
String generateQRLink(Row range){
int col=1
String name = "${range.getCell(col++)}"
String title = "${range.getCell(col++)}"
String tlf= "${range.getCell(col++)}"
String email="${range.getCell(col++)}"
String addr="${range.getCell(col++)}"
String organization="${range.getCell(col++)}"
String url="${range.getCell(col++)}"
String vcard = urlRoot + generateVCard(name, title , tlf , email, addr, organization, url)
vcard
}
//end::generateQRLink[]
101 Groovy Script
232 |
Desmenuzando un PdfJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2017-11-03
En ete script vamos a tratar las siguientes capacidades de Groovy:
• Incluir dependencias externas, en concreto PdfBox de Apache
• Realizar bucles anidados de una forma sencilla
• Generar mapas (key/value) al vuelo
ObjetivoMediante este script y usando la librería PdfBox de Apache, dividiremos las páginas de unPdf en áreas de tal forma que hagamos un "barrido" en cada una de ellas y podamosextraer el texto que se encuentre en las regiones. Al ir haciendo el "barrido" podremosidentificar cada línea leída con una posición dentro de la página
DependenciasPara la lectura del Pdf utilizaremos esta vez las librerías de Apache, PdfBox
@Grapes( @Grab(group='org.apache.pdfbox', module='pdfbox', version='2.0.8'))import org.apache.pdfbox.pdmodel.PDDocumentimport org.apache.pdfbox.text.*import java.awt.Rectangle
Script
101 Groovy Script
| 233
margenright = 10 ①
PDDocument document = PDDocument.load(new URL(args[0]).bytes)
def pages = [:]
document.documentCatalog.pages.eachWithIndex { page , pageIndex->
def paragraphs = [:]
PDFTextStripperByArea stripper = new PDFTextStripperByArea(sortByPosition:true)
(0..42).each { ②
stripper.addRegion("$it", new Rectangle( margenright, it * 20, 1500, 20)); ③
}
stripper.extractRegions(page)
stripper.regions.eachWithIndex{ r, index->
def str = stripper.getTextForRegion(r)
if (str.trim().length() > 0)
paragraphs["$index"] = str.trim() ④
}
pages["$pageIndex"] = paragraphs ⑤
}
println pages
① Puedes "jugar" a definir el margen derecho para afinar cuanto texto tomar desde laderecha.
② Definimos 42 regiones de 20 pixeles, suficientes para un A4
③ Definimos un área de 1500x20 suficientes para leer el ancho de un A4
④ Para cada region que encuentra con texto la añadimos dinámicamente a un mapacreado para la página
⑤ Para cada página añadimos las áreas encontradas en un mapa creado para todo eldocumento.
En una linea (más Grappe)@jmiguel en un afán minimalista nos comenta que el código anterior cumple su funciónen una sóla línea:
println new PDFTextStripper().getText(PDDocument.load(new URL(args[0]).bytes))①
<1>Si el primer argumento es una ruta local a un PDF, comienzalo con file://
101 Groovy Script
234 |
Script
//tag::dependencies[]
@Grapes(
@Grab(group='org.apache.pdfbox', module='pdfbox', version='2.0.8')
)
import org.apache.pdfbox.pdmodel.PDDocument
import org.apache.pdfbox.text.*
import java.awt.Rectangle
//end::dependencies[]
//tag::sourceCode[]
margenright = 10 ①
PDDocument document = PDDocument.load(new URL(args[0]).bytes)
def pages = [:]
document.documentCatalog.pages.eachWithIndex { page , pageIndex->
def paragraphs = [:]
PDFTextStripperByArea stripper = new PDFTextStripperByArea(sortByPosition:true)
(0..42).each { ②
stripper.addRegion("$it", new Rectangle( margenright, it * 20, 1500, 20)); ③
}
stripper.extractRegions(page)
stripper.regions.eachWithIndex{ r, index->
def str = stripper.getTextForRegion(r)
if (str.trim().length() > 0)
paragraphs["$index"] = str.trim() ④
}
pages["$pageIndex"] = paragraphs ⑤
}
println pages
//end::sourceCode[]
101 Groovy Script
| 235
Importar datos de un excel a una tablaMiguel Rueda <[email protected] [mailto:[email protected]]>2017-10-08
Seguramente a lo largo de tu vida te habrás encontrado con algúncliente/compañero/amigo/no tan amigo al cual le has preguntado dónde guarda unadeterminada información sobre la que estáis trabajando y te ha contestado que en unExcel en su local. Tras la risa nerviosa habrás pasado por la irá, hasta acabar en la etapadel miedo. Bueno una vez la calma llegue podrás darle la opción de incluirlo en la base dedatos con la que trabajáis. Para ello pasamos a explicar cómo realizarlo.
Para trabajar con este script vamos a necesitar las siguientes librerías:
@Grab('org.apache.poi:poi:3.8')@Grab('org.apache.poi:poi-ooxml:3.8')@Grab('mysql:mysql-connector-java:5.1.6')
El siguiente paso que vamos a realizar será la conexión con la base de datos, en este casocontra nuestra máquina.
def sql = Sql.newInstance( "jdbc:mysql://localhost:3306/origen?jdbcCompliantTruncation=false",
"user",
"password",
"com.mysql.jdbc.Driver")
Las variables necesarias para este script las hemos definido dentro del mismo pero unaforma mejor de hacerlo sería recibir estar información por parámetro y comprobandoque cada uno de los datos son correctos, es decir, que el fichero existe, que la conexióncon la base de datos es correcta y por último que existe la tabla en la que queremos cargarlos datos. En este caso práctico nos vamos a centrar únicamente en la carga de datos porlo tanto vamos a dar valor a cada uno de los valores necesarios:
def table = "myTable"def path_file = "${System.properties['user.home']}/myExcel.xlsx"def list = parse(path_file)
Una vez tenemos los requisitos necesarios vamos a crear el método parse al cual lepasaremos por parámetro la ruta donde se encuentra nuestro Excel y él creará un arraycon los datos de nuestro fichero, en este caso un nombre fijado a myExcel.xlsx
101 Groovy Script
236 |
def parse(path) { InputStream inp = new FileInputStream(path) Workbook wb = WorkbookFactory.create(inp); Sheet sheet = wb.getSheetAt(0);①
Iterator<Row> rowIt = sheet.rowIterator() ② Row row = rowIt.next() def headers = getRowData(row) ③
def rows = [] while(rowIt.hasNext()) { row = rowIt.next() rows << getRowData(row) } [headers, rows]}
① Con el objeto WorkbookFactory, accediendo al método create podemos acceder a nuestrofichero y con el método getSheetAt(0) del objeto Workbook podremos obtener la primerahoja (Sheet) de nuestro Excel.
② Para recorrer nuestra hoja seleccionada utilizaremos la función rowIterator() que nosva permitir acceder a los valores que se encuentran en cada una de las filas.
③ La primera fila asumimos que es la cabecera del Excel y que contiene el nombredescriptivo de cada columna. Para obtener este mapa utilizamos nuestro métodogetRowData:
def getRowData(Row row) { def data = [] for (Cell cell : row) { getValue(row, cell, data) } data}
El cual recorre cada una las filas que tiene nuestro Excel convirtiendo cada valor eninformación con la que podamos trabajar, dependiendo del formato que se encuentre encada celda, es decir, si en una cadena lo formaterá a String, si es un valor entero a int…
Para este trabajo contamos con el método getValue:
101 Groovy Script
| 237
def getValue(Row row, Cell cell, List data) { def rowIndex = row.getRowNum() def colIndex = cell.getColumnIndex() def value = "" switch (cell.getCellType()) { case Cell.CELL_TYPE_STRING: value = cell.getRichStringCellValue().getString(); break; case Cell.CELL_TYPE_NUMERIC: if (DateUtil.isCellDateFormatted(cell)) { value = cell.getDateCellValue(); } else { value = cell.getNumericCellValue(); } break; case Cell.CELL_TYPE_BOOLEAN: value = cell.getBooleanCellValue(); break; case Cell.CELL_TYPE_FORMULA: value = cell.getCellFormula(); break; default: value = "" } data[colIndex] = value data}
En el cual podemos ver claramente como formatea cada unos de los valores. Como lorealizar por ejemplo si el formato de la celda es un String:
case Cell.CELL_TYPE_STRING: value = cell.getRichStringCellValue().getString(); break;
Bien ya hemos conseguido leer el nuestro fichero y convertirlo en una lista con la quepodemos trabajar. Nuestro siguiente paso será abordar nuestra tabla en este caso sobreuna base de datos mysql. Para ello vamos a utilizar:
101 Groovy Script
238 |
def insertValues(table,list,sql){
int index = 0
sql.rows(""" desc $table """.toString()).each{c-> ①
keys = index == 0?(keys + ":$c.Field"):(keys + ",:$c.Field")
index++
}
sql.withBatch(20, """ insert into $table values ( $keys ) """.toString()) { ps -> ②
list.each { t->
ps.addBatch(t)
}
}
}
① Obtenemos el esquema de la tabla recibida.
② A través de la herramienta withBatch vamos a recorrer la lista de valores e insertar de20 en 20 en nuestra base de datos.
101 Groovy Script
| 239
Script
//tag::grab[]
@Grab('org.apache.poi:poi:3.8')
@Grab('org.apache.poi:poi-ooxml:3.8')
@Grab('mysql:mysql-connector-java:5.1.6')
//end::grab[]
import org.apache.poi.ss.usermodel.*
import org.apache.poi.hssf.usermodel.*
import org.apache.poi.xssf.usermodel.*
import org.apache.poi.ss.util.*
import org.apache.poi.ss.usermodel.*
import java.io.*
import groovy.sql.Sql
//tag::mysql[]
def sql = Sql.newInstance( "jdbc:mysql://localhost:3306/origen?jdbcCompliantTruncation=false",
"user",
"password",
"com.mysql.jdbc.Driver")
//end::mysql[]
//tag::variables[]
def table = "myTable"
def path_file = "${System.properties['user.home']}/myExcel.xlsx"
def list = parse(path_file)
//end::variables[]
insertValues(table,list,sql)
//tag::insert[]
def insertValues(table,list,sql){
int index = 0
sql.rows(""" desc $table """.toString()).each{c-> ①
keys = index == 0?(keys + ":$c.Field"):(keys + ",:$c.Field")
index++
}
sql.withBatch(20, """ insert into $table values ( $keys ) """.toString()) { ps -> ②
list.each { t->
ps.addBatch(t)
}
}
}
//end::insert[]
//tag::parse[]
def parse(path) {
InputStream inp = new FileInputStream(path)
Workbook wb = WorkbookFactory.create(inp);
Sheet sheet = wb.getSheetAt(0);①
Iterator<Row> rowIt = sheet.rowIterator() ②
Row row = rowIt.next()
def headers = getRowData(row) ③
def rows = []
while(rowIt.hasNext()) {
row = rowIt.next()
rows << getRowData(row)
}
101 Groovy Script
240 |
[headers, rows]
}
//end::parse[]
//tag::row[]
def getRowData(Row row) {
def data = []
for (Cell cell : row) {
getValue(row, cell, data)
}
data
}
//end::row[]
//tag::value[]
def getValue(Row row, Cell cell, List data) {
def rowIndex = row.getRowNum()
def colIndex = cell.getColumnIndex()
def value = ""
switch (cell.getCellType()) {
//tag::string_f[]
case Cell.CELL_TYPE_STRING:
value = cell.getRichStringCellValue().getString();
break;
//end::string_f[]
case Cell.CELL_TYPE_NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
value = cell.getDateCellValue();
} else {
value = cell.getNumericCellValue();
}
break;
case Cell.CELL_TYPE_BOOLEAN:
value = cell.getBooleanCellValue();
break;
case Cell.CELL_TYPE_FORMULA:
value = cell.getCellFormula();
break;
default:
value = ""
}
data[colIndex] = value
data
}
//end::value[]
101 Groovy Script
| 241
Volcar datos de una tabla a ExcelMiguel Rueda <[email protected] [mailto:[email protected]]>2017-10-08
El caso práctico en el cuál este script nos puede resultar muy útil es cuando por ejemploexiste la necesidad de extraer información de una base de datos para crear una serie deinformes que nos solicita un cliente, como puede ser obtener información de unosdeterminados productos.
Muchos motores de bases de datos y/o herramientas son capaces de exportar estainformación a fichero CSV (fichero delimitado por comas, punto y coma, pipes, etc) yposteriormente importarlo en un Excel.
Supongamos que disponemos de una tabla de productos donde entre otra informaciónguardamos el código de producto y la descripción.
SKU DESCRIPTION
1 LAPICES
2 SACAPUNTAS
XXXXX UN PRODUCTO SUPERCOTIZADO
Extraer esta información es trivial con una simple sentencia SQL:
select concat(sku,'|', description, '|') from products order by sku
y redirigir la salida de esta sentencia a un fichero para ser abierto con Excel.
Pero con este script nos evitamos todo el proceso de conversión, generando un Exceldirectamente:
101 Groovy Script
242 |
@Grab('mysql:mysql-connector-java:5.1.6')①
@Grab('net.sourceforge.jexcelapi:jxl:2.6.12')
@GrabConfig(systemClassLoader=true)
import jxl.*
import jxl.write.*
import groovy.sql.Sql
filename = "informe.xls"
sql = Sql.newInstance( "jdbc:mysql://localhost:3306/origen?jdbcCompliantTruncation=false",
"user",
"password",
"com.mysql.jdbc.Driver")
workbook = Workbook.createWorkbook(new File(filename))
sheet = workbook.createSheet("productos", 0)
first=true
i=0
sql.eachRow("select sku, description from products order by sku") { row -> ②
if( first ){
sheet.addCell (new Label (0,i,"Sku"))
sheet.addCell ( new Label (1,i,"Product") ) ③
first=false
}
sheet.addCell( new Label (0,i+1,"$l.sku") ) ④
sheet.addCell ( new Label (1,i+1,l.description) )
i++
}
workbook.write()
workbook.close()
① Cargamos dependencias (MySQL y JExcel)
② Para cada registro que cumpla la query generamos una fila nueva
③ Podemos generar cabeceras en la primera fila
④ Ejemplo de cómo crear dos celdas de tipo texto, pero existen otros tipos como Date oNumber
101 Groovy Script
| 243
Script
@Grab('mysql:mysql-connector-java:5.1.6')①
@Grab('net.sourceforge.jexcelapi:jxl:2.6.12')
@GrabConfig(systemClassLoader=true)
import jxl.*
import jxl.write.*
import groovy.sql.Sql
filename = "informe.xls"
sql = Sql.newInstance( "jdbc:mysql://localhost:3306/origen?jdbcCompliantTruncation=false",
"user",
"password",
"com.mysql.jdbc.Driver")
workbook = Workbook.createWorkbook(new File(filename))
sheet = workbook.createSheet("productos", 0)
first=true
i=0
sql.eachRow("select sku, description from products order by sku") { row -> ②
if( first ){
sheet.addCell (new Label (0,i,"Sku"))
sheet.addCell ( new Label (1,i,"Product") ) ③
first=false
}
sheet.addCell( new Label (0,i+1,"$l.sku") ) ④
sheet.addCell ( new Label (1,i+1,l.description) )
i++
}
workbook.write()
workbook.close()
101 Groovy Script
244 |
MailMiguel Rueda <[email protected] [mailto:[email protected]]>2017-08-04
Creamos la clase Mail.groovy
import javax.mail.*import javax.mail.internet.*import java.util.Properties;import java.text.DecimalFormat;import javax.activation.DataHandler;import javax.activation.DataSource;import javax.activation.FileDataSource;import javax.mail.BodyPart;import javax.mail.Message;import javax.mail.MessagingException;import javax.mail.Multipart;import javax.mail.PasswordAuthentication;import javax.mail.Session;import javax.mail.Transport;import javax.mail.internet.InternetAddress;import javax.mail.internet.MimeBodyPart;import javax.mail.internet.MimeMessage;import javax.mail.internet.MimeMultipart;
class Mail{ def static sendEmail(subjetc,text,from,to){ ①
def d_email = from, d_uname = "", d_password = "", d_host = "smtp-relay.gmail.com", d_port = "587", //465,587 m_to = to, m_subject = subjetc, m_text = text
def props = new Properties() ② props.put("mail.smtp.user", d_email) props.put("mail.smtp.host", d_host) props.put("mail.smtp.port", d_port) props.put("mail.smtp.starttls.enable","true") props.put("mail.smtp.debug", "false") props.put("mail.smtp.auth", "false") props.put("mail.protocol", "smtp")
101 Groovy Script
| 245
def session = Session.getInstance(props) session.setDebug(true);
def msg = new MimeMessage(session) msg.setText(m_text) msg.setSubject(m_subject) msg.setFrom(new InternetAddress(d_email)) msg.addRecipient(Message.RecipientType.TO, new InternetAddress(m_to))
Transport.send( msg ) ③ }}
① Definimos el método sendEmail(subjetc,text,from,to) al cual mandamos comoargumento el asunto, el texto que que queremos incluir en el email, el emisor y elreceptor.
② Definimos el properties que utilizaremos para enviar el email.
③ Enviamos el email.
Llamada y envío del email
Abrimos una consola de groovy en la misma posición en la que se encuentre nuestoMail.groovy.
Mail.sendEmail("Email de test","Esto es una prueba de lo bien que lo
hacemos",'[email protected]','[email protected])
101 Groovy Script
246 |
Script
@Grab('mysql:mysql-connector-java:5.1.6')①
@Grab('net.sourceforge.jexcelapi:jxl:2.6.12')
@GrabConfig(systemClassLoader=true)
import jxl.*
import jxl.write.*
import groovy.sql.Sql
filename = "informe.xls"
sql = Sql.newInstance( "jdbc:mysql://localhost:3306/origen?jdbcCompliantTruncation=false",
"user",
"password",
"com.mysql.jdbc.Driver")
workbook = Workbook.createWorkbook(new File(filename))
sheet = workbook.createSheet("productos", 0)
first=true
i=0
sql.eachRow("select sku, description from products order by sku") { row -> ②
if( first ){
sheet.addCell (new Label (0,i,"Sku"))
sheet.addCell ( new Label (1,i,"Product") ) ③
first=false
}
sheet.addCell( new Label (0,i+1,"$l.sku") ) ④
sheet.addCell ( new Label (1,i+1,l.description) )
i++
}
workbook.write()
workbook.close()
101 Groovy Script
| 247
Mail con documentos adjuntosMiguel Rueda <[email protected] [mailto:[email protected]]>2017-08-04
Partimos de la clase Mail creada en el apartado anterior:
import javax.mail.*import javax.mail.internet.*import java.util.Properties;import java.text.DecimalFormat;import javax.activation.DataHandler;import javax.activation.DataSource;import javax.activation.FileDataSource;import javax.mail.BodyPart;import javax.mail.Message;import javax.mail.MessagingException;import javax.mail.Multipart;import javax.mail.PasswordAuthentication;import javax.mail.Session;import javax.mail.Transport;import javax.mail.internet.InternetAddress;import javax.mail.internet.MimeBodyPart;import javax.mail.internet.MimeMessage;import javax.mail.internet.MimeMultipart;
class Mail{ def static sendEmail(path,subjetc,text,from,to){ ①
def d_email = from, d_uname = "", d_password = "", d_host = "smtp-relay.gmail.com", d_port = "587", //465,587 m_to = to, m_subject = subjetc, m_text = text
def props = new Properties() props.put("mail.smtp.user", d_email) props.put("mail.smtp.host", d_host) props.put("mail.smtp.port", d_port) props.put("mail.smtp.starttls.enable","true") props.put("mail.smtp.debug", "false") props.put("mail.smtp.auth", "false") props.put("mail.protocol", "smtp")
101 Groovy Script
248 |
def session = Session.getInstance(props) session.setDebug(true);
def msg = new MimeMessage(session) msg.setText(m_text) msg.setSubject(m_subject) msg.setFrom(new InternetAddress(d_email)) msg.addRecipient(Message.RecipientType.TO, new InternetAddress(m_to))
Multipart multipart = new MimeMultipart() def messageBodyPart = new MimeBodyPart() DataSource source = new FileDataSource(path) ② messageBodyPart.setDataHandler(new DataHandler(source)) messageBodyPart.setFileName(filename) multipart.addBodyPart(messageBodyPart) msg.setContent(multipart) Transport.send( msg ) }}
① Incluir la parámetro: path donde se encuentra nuestro fichero
② En esta sea crea el objeto DataSource.
101 Groovy Script
| 249
Script
@Grab('mysql:mysql-connector-java:5.1.6')①
@Grab('net.sourceforge.jexcelapi:jxl:2.6.12')
@GrabConfig(systemClassLoader=true)
import jxl.*
import jxl.write.*
import groovy.sql.Sql
filename = "informe.xls"
sql = Sql.newInstance( "jdbc:mysql://localhost:3306/origen?jdbcCompliantTruncation=false",
"user",
"password",
"com.mysql.jdbc.Driver")
workbook = Workbook.createWorkbook(new File(filename))
sheet = workbook.createSheet("productos", 0)
first=true
i=0
sql.eachRow("select sku, description from products order by sku") { row -> ②
if( first ){
sheet.addCell (new Label (0,i,"Sku"))
sheet.addCell ( new Label (1,i,"Product") ) ③
first=false
}
sheet.addCell( new Label (0,i+1,"$l.sku") ) ④
sheet.addCell ( new Label (1,i+1,l.description) )
i++
}
workbook.write()
workbook.close()
101 Groovy Script
250 |
Memento en el TAEJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2019-09-19
En ete script vamos a hacer uso de la anotación @Memoized de Groovy, útil parasituaciones de cálculo recursivo, mediante un ejemplo práctico para calcular el TAE(Tasa Anual Equivalente) de un préstamo.
No soy financiero por lo que algunas definiciones y/o explicaciones eneste ámbito pueden no ajustarse exactamente a la definición formal.
TAE
La Tasa Anual Equivalente es una referencia orientativa del coste orendimiento efectivo anual de un producto financiero,independientemente de su plazo. Su cáculo incluye la tasa de interésnominal, los gastos, comisiones y permite comparar de una manerahomogénea el rendimiento de productos financieros diferentes
— Wikipedia
Como se puede observar de la definición, el cálculo de la TAE es orientativo. Es decir, noexiste una fórmula matemática exacta que sirva para calcularla. Así mismo tiene encuenta los costes en que se puede incurrir durante la inversión, por ello es un conceptodiferente al interés de una inversión pues este no tiene en cuenta los gastos.
Digamos que queremos pedir un préstamo de 1000 US$ y queremos devolverlo en 10meses. Supongamos que este prestamo nos lo realiza un (buen) amigo y no nos poneningún interés, es decir, cada mes le devolveremos 10 US$ y transcurridos los 10 meseshabremos cancelado nuestra deuda, con un interés 0% y una TAE 0%
Volvemos a pedir este prestamo pero a un conocido que no nos quiere cobrar ningúninterés y que simplemente nos pide que le invitemos a unas cevezas que al final noscuesta 10 US$. El tipo de interés es 0% pero ahora el préstamo no nos sale gratis y vemosque tiene una TAE de cerca de un 2.21%. Si realizas el ejercicio con una calculadora TAEonline verás que puede variar algo dependiendo de la "granularidad" que se aplique.
Básicamente el cálculo es un proceso iterativo que partiendo de un tipo de interésdeterminado trata de buscar por aproximación el valor de la inversión. Si aplicando eseinterés, el valor de la inversión está por debajo del préstamo "real" lo subimos y si por elcontrario está por encima lo bajamos. Este proceso se repite el número de veces que sequiera hasta hallar un interés que aproxime ambos valores.
int closeTo( float a , float b){ ①
101 Groovy Script
| 251
float diff = a-b if( 0 == diff || Math.abs(diff) < 0.01){ return 0 } return a < b ? -1 : 1}float calculaValorInversionPlazo(float cuota, float interes, int plazo){ counter++ ② cuota/ Math.pow( (1+interes), plazo)}float calculaValorInversion(float cuota, float interes, int plazos){ (1..plazos).sum{n-> calculaValorInversionPlazo(cuota, interes, n) }}float calculaTae( float prestamo, float comision, int plazos ){ boolean forward=true float interes = 0.001 float step = 0.001 int deep=1
float cuota = prestamo / plazos float objetivo = prestamo - comision
float lastVac=valorInversion( cuota, interes, plazos) ③
for(int iter=0; iter<ITERACIONES; iter++){
int close = closeTo(lastVac, objetivo)
if( close == 0) { ④ float tae = ( Math.pow(1+interes, 12) -1 )*100 return tae }
// estamos por encima del objetivo, poco interes, if( close > 0 ){ if( !forward ){ deep = deep * 10 forward = true } interes += (step/deep) }
// estamos por debajo del objetivo, mucho interes if( close < 0 ){ // si hay que retrodecer cuando ibamos para adelante hay que profundizar if( forward ){ deep = deep * 10 forward = false } interes -= (step/deep) }
101 Groovy Script
252 |
lastVac=valorInversion( cuota, interes, plazos) ② } return 0}
① Una forma simple de decidir si dos números son aproximados o uno es mayor que elotro
② counter nos servirá para conocer el número de veces que se ejecuta la función. Noforma parte del cálculo de la TAE
③ invocaremos a esta función numerosas veces con parámetros diferentes
④ hemos encontrado un tipo de interés aproximado para la inversión
Como ayuda para testear el código tenemos una función ajena al cálculo del TAE:
void assertTae( float prestamo, float comision, int plazos, float expected){ ②
float tae=calculaTae(prestamo,comision,plazos)
println "tae encontrada $tae%"
if( closeTo(tae, expected) != 0 ){
throw new RuntimeException(
String.format("TAE de %f eur a %d meses y apertura %f debia ser %f pero dice %f",
prestamo, plazos, comision, expected, tae))
}
}
① utilidad para comprobar si una tae cumple con un valor esperado
Así pues nuestro script puede servir para dado un importe, unos gastos de comisión y unnúmero de meses para devolverlo calcular la TAE simplemente ejecutandocalculaTae(prestamo,comision,plazos)
StressComo hemos visto, buscar la TAE es un proceso intenso en cálculo y con la particularidadde que para una búsqueda pocas veces se llama a una misma función con los parámetrosvaliendo lo mismo pues por ejemplo el tipo de interés va cambiando en cada iteración. Sinembargo podemos imaginar situaciones donde este cálculo se tenga que realizar paracientos de clientes donde las condiciones del préstamos sean las mismas (importe, plazo,etc) y en las que nos interese optimizar lo máximo la búsqueda.
Para simular estas situaciones el script cuenta con una función de stress donde se van asimular una serie de préstamos (6 casos diferentes) un número elevado de veces (10.000veces cada una). Para evaluar el rendimiento, esta función devolverá el tiempotranscurrido desde el inicio hasta completar todas las simulaciones.
Así mismo como se explicó al inicio dispondremos de un contador global que iremosincrementando cada vez que se llame a la función calculaValorInversionPlazo paradeterminar el número de veces que se ha ejecutado la misma
101 Groovy Script
| 253
def runExamples() { examples = [ [1000, 10, 0, 0], [1000, 10, 10, 2.21], [500, 10, 10, 4.52], [500, 20, 10, 9.37], [647, 10, 10, 3.47], [10000, 100, 24, 0.97], ]
Date start = new Date() (0..10000).each { examples.each { List item -> assertTae(item[0], item[1], item[2], item[3]) } } Date end = new Date() end-start}
Una vez ejecutadas dichas pruebas en mi máquina (i7, 16Gb memoria) tendremos unresultado similar a:
pasamos por counter 15221522 veces. Tardó 35.964 seconds
MemoizeComo podemos observar en la prueba de stress, algunas funciones son ejecutadasrepetidas veces y en muchos casos el valor de los parámetros han sido los mismos. Sipudieramos disponer de un cache donde asociar los parámetros con el resultadopodríamos optimizar nuestro código evitando ejecutar el mismo caso repetidas veces.
En Groovy esto se consigue simplemente anotando el método con @Memoize. Así puessimplemente tenemos que añadir esta anotación a aquellas funciones que se vayan aejecutar múltiples veces con los mismos parámetros.
Para poder comparar las dos alternativas en un mismo script vamos a"duplicar" los métodos anotando uno de ellos con @Memoize y el otro no.El método anotado como @Memoize simplemente llamará al métodovisto anteriormente.
Así mismo vamos a usar una variable memoize que indique qué versióndel método queremos ejecutar, de tal forma que cuando esta variablevalga false el algoritmo invocará a la función no anotada con @Memoizey a la inversa
101 Groovy Script
254 |
@Memoizedint closeTo( float a , float b){ ① float diff = a-b if( 0 == diff || Math.abs(diff) < 0.01){ return 0 } return a < b ? -1 : 1}@Memoizedfloat calculaValorInversionPlazoMemoized(float cuota, float interes, int plazo){ calculaValorInversionPlazo(cuota, interes, plazo)}@Memoizedfloat calculaValorInversionMemoized(float cuota, float interes, int plazos){ calculaValorInversion(cuota, interes, plazos)}
① Una forma simple de decidir si dos números son aproximados o uno es mayor que elotro
② counter nos servirá para conocer el número de veces que se ejecuta la función. Noforma parte del cálculo de la TAE
En las mismas condiciones repetimos los test de stress y obtenemos:
pasamos por counter 1482 veces. Tardó 10.044 seconds
Comparativa
Versión Llamadas a función Tiempo ejecución
No memoize 15221522 36seg
Memoize 1482 10seg
101 Groovy Script
| 255
TweetReport (Persistencia con SQLite)Jorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2017-12-09
En este post vamos a analizar un caso de negocio que seguramente no ocurre muy amenudo pero que espero nos ayude a resolver situaciones parecidas. En este caso vamos adesarrollar un script que se podrá ejecutar repetidas veces a lo largo del tiempo (cadahora, varias veces al día, etc) cuya lógica de negocio queremos que se mantenga durantela ejecución de las mismas.
Nuestro Community Manager siente curiosidad por ciertos usuarios de Twitter que nossiguen en nuestra cuenta oficial y quiere hacer un pequeño estudio sobre los mismos queva a durar un cierto tiempo (dias/semanas/meses). Durante este tiempo nos iráproporcionando usuarios de Twitter de los que tendremos que obtener ciertos datospúblicos e ir guardándolos. Así mismo durante este período nos irá pidiendo que lehagamos un pequeño informe con los datos guardados.
En un escenario convencional, podríamos optar por escribir en un fichero plano lainformación de cada ejecución o si queremos algo más robusto instalaríamos un motor debase de datos tipo MySQL, Postgre, SQLServer, etc. Mientras que lo primero es muy simplede mantener resulta muy complejo de gestionar/programar. Por el contrario la segundaopción es mucho más robusta pero más compleja.
A medio de camino de ambas soluciones contamos con proyectos como SQLite que nospermitirán crear y acceder a los datos mediante un acceso JDBC (base de datos) pero sinlas complejidades de tener que instalar y mantener un motor de datos más completo.
DependenciasPara este scritp necesitaremos acceder a Twitter y a una base de datos SQLite por lo quedefinimos las dependencias:
@GrabConfig(systemClassLoader=true)@Grab(group='org.twitter4j', module='twitter4j-core', version='4.0.6')@Grab(group='org.xerial', module='sqlite-jdbc', version='3.21.0')import groovy.sql.Sqlimport twitter4j.Twitter;import twitter4j.TwitterFactory;import twitter4j.StatusUpdate
ArgumentosEl script admitirá la siguiente lista de argumentos:
101 Groovy Script
256 |
• --initialize, recrea la base de datos
• --username, busca un usuario proporcionado por parámetro y lo incluye en la base dedatos
• --all, recorre todos los usuarios existentes en la base de datos y los actualiza
• --report, genera un informe con los datos guardados hasta la fecha
def cli = new CliBuilder(usage: '-i -u username -r')
cli.with {
h(longOpt: 'help', args:0,'Usage Information', required: false)
i(longOpt: 'initialize',args:0,'Borrar todos los datos y crear la base de datos en limpio', required:
false)
u(longOpt: 'username', args:1, argName:'username', 'El usuario a investigar', required: false)
a(longOpt: 'all',args:0,'Actualiza la info de todos los usuarios registrados', required: false)
r(longOpt: 'report',args:0,'Genera un report con los usuarios existentes', required: false)
}
def options = cli.parse(args)
if (options.h ) {
cli.usage()
return
}
ModeloPara ayudar en la encapsulación de los datos definimos una clase TwitterUser la cual seauto-actualiza si usamos el constructor que proporciona un id de Twitter:
class TwitterUser{ String id String name int tweets int followers int friends String timeZone
TwitterUser(){ }
TwitterUser( String id){ def tuser = TwitterFactory.singleton.users().showUser(id) this.id=id this.name = tuser.name this.tweets = tuser.statusesCount this.followers = tuser.followersCount this.friends = tuser.friendsCount this.timeZone = tuser.timeZone }}
101 Groovy Script
| 257
PrepareAntes de realizar ninguna acción contra la base de datos el propio script realiza unapreparación de la misma. Si se indicó el parámetro --initialize realizará un borrado de labase de datos. En cualquier caso creará la tabla si no existe
void prepareDatabase(boolean drop) {
Sql sql = Sql.newInstance("jdbc:sqlite:tweetreport.db")
if( drop )
sql.execute "drop table if exists tweetreport"
String sentence = """CREATE table if not EXISTS tweetreport
(id varchar(20), name VARCHAR(40), tweets NUMBER(5), followers NUMBER(5), friends number(5), timezone
varchar(10))
"""
sql.execute sentence
}
Update UserSi se indica un usuario, el script creará un objeto de la clase TwitterUser y mediante unasimple select por su id determinará si existe ya. Si existe se procederá a actualizar elregistro mientras que si no existe se insertará un registro nuevo
void updateUser(TwitterUser user){
Sql sql = Sql.newInstance("jdbc:sqlite:tweetreport.db")
def row = sql.firstRow("select * from tweetreport where id=?",[user.id])
String sentence = row ?
"update tweetreport set name=?.name, tweets=?.tweets, followers=?.followers, friends=?.friends,
timezone=?.timeZone where id=?.id"
:
"insert into tweetreport (id,name,tweets,followers,friends,timezone) values
(?.id,?.name,?.tweets,?.followers,?.friends,?.timeZone)"
sql.execute sentence, user
}
Mediante la opción --all hacemos que el script recorra todos los usuarios guardados hastala fecha e invoque el método upate para cada uno de ellos
Report UserEn cualquier momento podemos solicitar que el script genere un informe con el estado dela base de datos, lo cual en nuestro ejemplo consistirá en recorrer los registros ymostrarlos por consola:
101 Groovy Script
258 |
void report(){ Sql sql = Sql.newInstance("jdbc:sqlite:tweetreport.db") sql.eachRow"select * from tweetreport order by id",{ row-> println row }}
PersistenciaAl ejecutar el script se creará un fichero tweetreport.db (indicado en la cadena deconexión Sql) que SQLite utilizará para ofrecer el servicio de persistencia.
De esta forma simple podremos dotar a nuestros scritps de la capacidad de tener una basede datos sencilla y sin complejidades de instalación ni administración
101 Groovy Script
| 259
Script
//tag::dependencies[]
@GrabConfig(systemClassLoader=true)
@Grab(group='org.twitter4j', module='twitter4j-core', version='4.0.6')
@Grab(group='org.xerial', module='sqlite-jdbc', version='3.21.0')
import groovy.sql.Sql
import twitter4j.Twitter;
import twitter4j.TwitterFactory;
import twitter4j.StatusUpdate
//end::dependencies[]
//tag::model[]
class TwitterUser{
String id
String name
int tweets
int followers
int friends
String timeZone
TwitterUser(){
}
TwitterUser( String id){
def tuser = TwitterFactory.singleton.users().showUser(id)
this.id=id
this.name = tuser.name
this.tweets = tuser.statusesCount
this.followers = tuser.followersCount
this.friends = tuser.friendsCount
this.timeZone = tuser.timeZone
}
}
//end::model[]
//tag::prepare[]
void prepareDatabase(boolean drop) {
Sql sql = Sql.newInstance("jdbc:sqlite:tweetreport.db")
if( drop )
sql.execute "drop table if exists tweetreport"
String sentence = """CREATE table if not EXISTS tweetreport
(id varchar(20), name VARCHAR(40), tweets NUMBER(5), followers NUMBER(5), friends number(5), timezone
varchar(10))
"""
sql.execute sentence
}
//end::prepare[]
//tag::update[]
void updateUser(TwitterUser user){
Sql sql = Sql.newInstance("jdbc:sqlite:tweetreport.db")
def row = sql.firstRow("select * from tweetreport where id=?",[user.id])
String sentence = row ?
"update tweetreport set name=?.name, tweets=?.tweets, followers=?.followers, friends=?.friends,
timezone=?.timeZone where id=?.id"
:
"insert into tweetreport (id,name,tweets,followers,friends,timezone) values
(?.id,?.name,?.tweets,?.followers,?.friends,?.timeZone)"
sql.execute sentence, user
}
//end::update[]
List listUsers(){
101 Groovy Script
260 |
Sql sql = Sql.newInstance("jdbc:sqlite:tweetreport.db")
sql.rows("select id from tweetreport")*.id
}
//tag::report[]
void report(){
Sql sql = Sql.newInstance("jdbc:sqlite:tweetreport.db")
sql.eachRow"select * from tweetreport order by id",{ row->
println row
}
}
//end::report[]
//tag::cli[]
def cli = new CliBuilder(usage: '-i -u username -r')
cli.with {
h(longOpt: 'help', args:0,'Usage Information', required: false)
i(longOpt: 'initialize',args:0,'Borrar todos los datos y crear la base de datos en limpio', required:
false)
u(longOpt: 'username', args:1, argName:'username', 'El usuario a investigar', required: false)
a(longOpt: 'all',args:0,'Actualiza la info de todos los usuarios registrados', required: false)
r(longOpt: 'report',args:0,'Genera un report con los usuarios existentes', required: false)
}
def options = cli.parse(args)
if (options.h ) {
cli.usage()
return
}
//end::cli[]
prepareDatabase(options.i )
if( options.username ) {
TwitterUser user = new TwitterUser(options.username)
updateUser(user)
}
if( options.all ){
listUsers().each{ id->
println "update $id"
TwitterUser user = new TwitterUser(id)
updateUser(user)
}
}
if( options.report ){
report()
}
101 Groovy Script
| 261
Buscando claves ssh en BitBucketJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2018-03-09
Seguramente este caso de uso no será muy común pero espero que te aporte ideas sitrabajas con BitBucket
Hace unos días en B2Boost [http://www.b2boost.eu] estabamos intentando executar unpipeline de integración contínua para un proyecto que dependía de otros y necesitabaclonarlos primeramente para construir el producto.
Sin embargo surgió un problema cuando nos dimos cuenta que necesitabamos configurartodos los proyectos involucrados y añadirle una clave ssh común para poder clonarlos. Enun escenario ideal esto hubiera sido tan simple como ubicar esta clave a nivel deOrganización y todos los proyectos que dependen de ella quedarían configurados a su vez.Pero BitBucket no permite usar una misma clave ssh en dos proyectos y/o organizacionesa la vez y por razones históricas nosotros la estabamos usando en algunos repositorios. Elproblema era que no sabíamos en cuales
Así pues, teníamos dos opciones:
• generar una nueva clave ssh desde nuestro sistema de CI y añadirla a nivel deorganización
• buscar qué proyectos usaban la clave y eliminarla
La primera opción probablemente era la más rápida pero se nos quedarían repositorioscon claves sin usar y además corríamos el riesgo de que otros proyectos se vieranafectados.
La segunda opción era fácil y limpia pero tediosa porque conllevaba navegar entre losrepos buscando la clave en los settings de cada uno … y eran más de 45
Rest al rescateBitBucket tiene un interface REST (https://developer.atlassian.com/server/bitbucket/how-tos/command-line-rest/) con dos versiones (/1.0 and /2.0) con las que puedes manejar tusrepositorios tras haber obtenido un token de autentificación.
Si consultas el manual verás que puedes usar herramientas de línea como curl peroimplementar toda la lógica que necesitamos en un bash puede convertirlo en dificil deentender, depurar y reutilizar. Pero con Groovy y HttpBuilder-ng lo puedes hacer en unscript de menos de 50 lineas
101 Groovy Script
262 |
OAuth ConsumerEn primer lugar necesitas crear un Oauth Consumer en tu cuenta de BitBucket (Profile,Settings, Oauth)
Después de crearlo obtendrás un Key y un Secret que se lo proporcionaremos al script víaargumentos de línea junto con el nombre de nuestra organización
DependenciasSólo vamos a necesitar http-builder y slf4j:
@Grab(group='io.github.http-builder-ng', module='http-builder-ng-apache', version='1.0.3')
@Grab(group='ch.qos.logback', module='logback-classic', version='1.2.3')
import groovyx.net.http.*
import static groovyx.net.http.HttpBuilder.configure
import static groovyx.net.http.ContentTypes.JSON
import static groovy.json.JsonOutput.prettyPrint
import static groovy.json.JsonOutput.toJson
AutorizaciónEsta parte fue la más difícul, no por sus detalles técnicos sino porque el mecanismo deautentificación usado por HttpBuilder-NG no es compatible 100% con BitBucket.
HttpBulder-Ng tiene el método basic in the auth para enviar la cabecera Authorization:Basic xxxx pero sólo lo envía si el servidor lo requiere y BitBucket lo requiere en laprimera petición.
La solución pasa por construir nosotros la cabecera de forma manual codificando elusuario y la password en base64 (donde usuario y password son el Key`y el `Secret). Asímismo tenemos que enviar la petición en formato form porque no hemos conseguido queBitBucket reconozca esta petición como JSON
101 Groovy Script
| 263
organization = args[0] // organization
username = args[1] //secretId
password = args[2] //token
creds = "$username:$password".bytes.encodeBase64()
def http = configure {
request.uri = 'https://bitbucket.org/'
request.headers['Authorization'] = "Basic $creds"
request.contentType = JSON[0]
}
def token = http.post{
request.uri.path='/site/oauth2/access_token'
request.body=[grant_type:'client_credentials'] ①
request.contentType = 'application/x-www-form-urlencoded'
request.encoder 'application/x-www-form-urlencoded', NativeHandlers.Encoders.&form
}.access_token
① send the grant_type as form because BitBucket doesn’t reconigze JSON in this request
BitBucketUna vez obtenido un token podemos configurar un objecto bitbucket que usaremos a lolargo del script con los parámetros comunes a todas las peticiones:
def bitbucket = configure { request.uri = 'https://api.bitbucket.org/' request.headers['Authorization'] = "Bearer $token" request.accept=['application/json'] request.contentType = JSON[0]}
Listar repositoriosPodemos obtener los detalles de todos nuestros repos usando una petición paginada. Cadarepo obtenido contiene gran cantidad de información de la que nosotros vamos a usarúnicamente 'slug' como identificador del repo
101 Groovy Script
264 |
def page=1def repos=[]while( true ) { def list = bitbucket.get{ request.uri.path="/2.0/repositories/$organization" request.uri.query=[page:page] } repos.addAll list.values if( repos.size() >= list.size ) break page++}
Inspeccionar un repo
def keys=[:] ①repos.findAll{it.slug}.each{ repo-> def repokeys = bitbucket.get{ request.uri.path="/1.0/repositories/$organization/${repo.slug}/deploy-keys" } keys[repo.slug]=repokeys}
① construimos un mapa con valores tipo repo-name:json
ReportUna vez obtenidos todos los repos iteraremos por todos ellos haciendo una petición enbusca de las claves ssh que tiene y si alguna es el ID que buscamos
println "Total repos founded : ${repos.size()}"
println "Total keys founded : ${keys.size()}"
println prettyPrint(toJson(keys))
def remove = keys.findAll{ it.value.find{ it.key.indexOf('XXXXXXXXXXXXXXXXXXX')!=-1 } } ①
println "Remove: ${remove*.key}"
① filtrar claves que contienen nuestro ID
ConclusionTras ejecutar el script encontramos que sólo unos pocos repos estaban configurados conesta clave ssh pero sin el script probablemente hubieramos perdido una cantidad detiempo importante, navegando por todos ellos corriendo el riesgo de olvidar alguno lo quenos obligaría a volver a comenzar
101 Groovy Script
| 265
Script
//tag::dependencies[]
@Grab(group='io.github.http-builder-ng', module='http-builder-ng-apache', version='1.0.3')
@Grab(group='ch.qos.logback', module='logback-classic', version='1.2.3')
import groovyx.net.http.*
import static groovyx.net.http.HttpBuilder.configure
import static groovyx.net.http.ContentTypes.JSON
import static groovy.json.JsonOutput.prettyPrint
import static groovy.json.JsonOutput.toJson
//end::dependencies[]
//tag::login[]
organization = args[0] // organization
username = args[1] //secretId
password = args[2] //token
creds = "$username:$password".bytes.encodeBase64()
def http = configure {
request.uri = 'https://bitbucket.org/'
request.headers['Authorization'] = "Basic $creds"
request.contentType = JSON[0]
}
def token = http.post{
request.uri.path='/site/oauth2/access_token'
request.body=[grant_type:'client_credentials'] ①
request.contentType = 'application/x-www-form-urlencoded'
request.encoder 'application/x-www-form-urlencoded', NativeHandlers.Encoders.&form
}.access_token
//end::login[]
//tag::bitbucket[]
def bitbucket = configure {
request.uri = 'https://api.bitbucket.org/'
request.headers['Authorization'] = "Bearer $token"
request.accept=['application/json']
request.contentType = JSON[0]
}
//end::bitbucket[]
//tag::repos[]
def page=1
def repos=[]
while( true ) {
def list = bitbucket.get{
request.uri.path="/2.0/repositories/$organization"
request.uri.query=[page:page]
}
repos.addAll list.values
if( repos.size() >= list.size )
break
page++
}
101 Groovy Script
266 |
//end::repos[]
//tag::v10[]
def keys=[:] ①
repos.findAll{it.slug}.each{ repo->
def repokeys = bitbucket.get{
request.uri.path="/1.0/repositories/$organization/${repo.slug}/deploy-keys"
}
keys[repo.slug]=repokeys
}
//end::v10[]
//tag::report[]
println "Total repos founded : ${repos.size()}"
println "Total keys founded : ${keys.size()}"
println prettyPrint(toJson(keys))
def remove = keys.findAll{ it.value.find{ it.key.indexOf('XXXXXXXXXXXXXXXXXXX')!=-1 } } ①
println "Remove: ${remove*.key}"
//end::report[]
101 Groovy Script
| 267
Valida tu NIF o SOAP hecho fácilJorge Aguilera <[email protected] [mailto:jorge.aguilera@puravida-
software.com]> 2017-10-26
Qué es SOAP
SOAP (originalmente las siglas de Simple Object Access Protocol) esun protocolo estándar que define cómo dos objetos en diferentesprocesos pueden comunicarse por medio de intercambio de datosXML. Este protocolo deriva de un protocolo creado por Dave Winer en1998, llamado XML-RPC. SOAP fue creado por Microsoft, IBM y otros.Está actualmente bajo el auspicio de la W3C. Es uno de los protocolosutilizados en los servicios Web.
— Wikipedia, https://es.wikipedia.org/wiki/Simple_Object_Access_Protocol
Si como yo, siempre has creido que la S no era de Simple sino de Sufre, yla P de Pánico, este es tu post.
En este script vamos a explicar como consumir un servicio SOAP ofrecido por la AgenciaTributaria Española para consultas de calidad de datos identificativos, es decir, validar siun NIF corresponde a la persona que dice ser. Mediante este WebServices podemos enviaruna lista de NIFs junto con el nombre del titular y la respuesta nos indicará para cada unode ellos si según sus registros existe una correspondencia total, parcial o no existe. De estaforma podemos mejorar la calidad de los datos de nuestros clientes por ejemplo.
Para completar el script haremos que la lista de NIFs/Nombres a consultar se tome de unabase de datos que será actualizada con la información de respuesta. Así pues deberemoscontar con una tabla nifes con la siguiente estructura:
Field Type Description
nif varchar(10) NIF proporcionado por elusuario
nombre varchar(200) Apellido y nombreproporcionado por elusuario
estado varchar(10) INCORRECTO /IDENTIFICADO /PARCIALMENTE-IDENTIFICADO
nombre_aeat varchar(200) Apellidos y Nombreproporcionado por AEAT
101 Groovy Script
268 |
Así pues el script primeramente creará una petición con los NIF/Nombres en estadoIncorrecto (o nulo) y parseará la respuesta actualizándolos con los datos obtenidos de laAEAT
SOAP y JavaSi nunca has tenido que consumir un servicio SOAP puedes echarle un ojo a este artículohttps://docs.oracle.com/javaee/5/tutorial/doc/bnayn.html de Oracle donde en apariencia noes tan difícil de hacer, sobre todo gracias a las anotaciones que existen hoy en día.
Básicamente SOAP es una forma de consumir un servicio remoto vía HTTP a través deintercambios de mensajes XML (y qué mensajes!!) Al ser HTTP no hace falta exponerpuertos especiales en nuestros sistemas (o si los abrimos podremos tratarlos comocualquier puerto que atienda este protocolo), así como aprovechar todo el stack deseguridad como puede ser el uso de SSL.
Por una parte diseñas el interface a exponer junto con sus parámetros tanto de entradacomo de salida y con la ayuda del veneno que prefieras (Axis, Spring, CXF, …) generas unpunto de entrada al mismo.
Con ayuda de tu veneno expones también el WSDL que es lo que necesitará cualquierprograma cliente que quiera consumirlo (como puedes imaginar, el XML no está pensadopara los ojos humanos, al menos no para los mios) usando a su vez su propio veneno
wsimport y wsdl2java son sólo algunos de los venenos que puedes elegir para ayudarte agenerar la parte cliente y que de forma genérica te solicitarán una URL donde resida elWSDL (suele ser un servidor web o también una ruta a un fichero si te lo has descargado).Con esta definición tu veneno será capaz de crearte una clases "esqueleto" para que lasincluyas en tu proyecto y puedas así invocar al servicio de forma transparente.
SOAP y GroovyGroovy cuenta con el proyecto groovy-wslite [https://github.com/jwagenleitner/groovy-wslite] elcual hace realmente simple el consumir un servicio SOAP.
Como avisa el README del proyecto, esta librería asume que conoces elservicio que vas a consumir. Es decir, necesitas saber el "nombre" delmétodo que quieres ejecutar así como sus parámetros. En mi caso esto haocurrido en el 99.9999% de las veces, independiente de que un venenome generara el stub
Mediante esta librería tendremos control absoluto tanto de la Request como de laResponse, así como de todos los atributos que se necesiten enviar. Incluso en el peor delos casos, puedes construirte el XML mediante un String y enviarlo directamente.
La idea principal es que modelaremos nuestro intercambio mediante closures y lalibrería generará los mensajes al vuelo, sin necesidad de una fase inicial de
101 Groovy Script
| 269
convertir el WSDL a código:
def client = new SOAPClient('http://www.holidaywebservice.com/Holidays/US/Dates/USHolidayDates.asmx') ①
def response = client.send() {
body {
GetMothersDay('xmlns':'http://www.27seconds.com/Holidays/US/Dates/') { ②
year(2011) ③
}
}
}
① Construimos un cliente SOAP indicando la ruta al servicio
② Invocamos la acción GetMothersDay pudiendo incluso cualificarla con su namespace
③ Pasamos un parámetro que nos pide el método llamado year
Así mismo la respuesta la podemos analizar sin necesidad de ningún stub:
println "${response.GetMothersDayResponse?.GetMothersDayResult}" ①
① Vamos "navegando" por la estructura retornada
Consulta de NIF válido según AEATSaber cuándo es el día de la madre de un año cualquiera está muy bien pero con pocautilidad, así que vamos a desarrollar un pequeño script que nos valide si un NIF es de lapersona que dice ser. Para ello la Agencia Tributaria ofrece un servicio SOAP de consulta(hasta 10K NIF en una sóla petición!!) donde nos confirma si un NIF corresponde con unnombre en base a sus registros.
Puedes encontrar la definición de este servicio en Información sobre Web Services deCalidad de Datos Identificativos[http://www.agenciatributaria.es/AEAT.internet/Inicio/Ayuda/Manuales__Folletos_y_Videos/Manuales_tecnicos/Web_service/Modelos_030__036__037/Informacion_sobre_Web_Services_de_Calidad_de_Datos_Identi
ficativos/Informacion_sobre_Web_Services_de_Calidad_de_Datos_Identificativos.shtml]
Para poder usar este servicio necesitarás un certificado digital que teidentifique. Puede ser de persona física, empleado público, FNMT oempresas. Según entiendo se requiere simplemente para evitar el abusodel servicio
A continuación un ejemplo de consulta y su respuesta extraidas del documento
101 Groovy Script
270 |
Ejemplo de consulta
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:vnif="http://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/burt/j
dit
/ws/VNifV2Ent.xsd"> ①
<soapenv:Header/>
<soapenv:Body>
<vnif:VNifV2Ent> ②
<vnif:Contribuyente> ③
<vnif:Nif>99999999R</vnif:Nif> ④
<vnif:Nombre>ESPAÑOL ESPAÑOL JUAN</vnif:Nombre></vnif:Contribuyente>
</vnif:VNifV2Ent>
</soapenv:Body>
</soapenv:Envelope>
① Schema "vnif" que hay que utilizar.
② Usando el schema "vnif" identificamos la funcion a ejecutar
③ Usando el schema "vnif" describimos una estructura de entrada
④ Usando el schema "vnif" indicamos un parametro
Ejemplo de respuesta
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<env:Body>
<VNifV2Sal:VNifV2Sal
xmlns:VNifV2Sal="http://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/b
urt/jdit/ws/VNifV2Sal.xsd"> ①
<VNifV2Sal:Contribuyente> ②
<VNifV2Sal:Nif>99999999R</VNifV2Sal:Nif>
<VNifV2Sal:Nombre>ESPAÑOL ESPAÑOL JUAN</VNifV2Sal:Nombre> <VNifV2Sal:Resultado>Identificado</VNifV2Sal:Resultado>
</VNifV2Sal:Contribuyente>
</VNifV2Sal:VNifV2Sal>
</env:Body>
</env:Envelope>
① Schema "VNIFV2Sal" a utilizar
② Estructura de respuesta
Consultar un NIFPara invocar ese servicio un script simple sería:
101 Groovy Script
| 271
@Grab('com.github.groovy-wslite:groovy-wslite:1.1.2')
def client = new SOAPClient('https://www1.agenciatributaria.gob.es/wlpl/BURT-JDIT/ws/VNifV2SOAP')
def response = client.send() {
envelopeAttributes([
"xmlns:vnif":
"http://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/burt/jdit/ws/VNif
V2Ent.xsd"
])
body {
'vnif:VNifV2Ent' {
'vnif:Contribuyente'{
'vnif:Nif'(0123456789X')
'vnif:Nombre'('APELLIDO APELLIDO NOMBRE')
}
}
}
}
println "${response.VNifV2Sal.Contribuyente.Resultado}"
Consultar hasta 10K NIFsPartiendo del ejemplo anterior donde consultamos un NIF vamos a desarrollar un scriptmás complejo donde la petición se construye dinámicamente en función de los valores dela base de datos. Así mismo trataremos una respuesta iterando por cada elemento deinterés.
dependencias
@Grab('com.github.groovy-wslite:groovy-wslite:1.1.2')@Grab('mysql:mysql-connector-java:5.1.6')@GrabConfig(systemClassLoader=true)
crear SOAP Client
def client = new SOAPClient('https://www1.agenciatributaria.gob.es/wlpl/BURT-JDIT/ws/VNifV2SOAP') ①
① Proporcionamos directamente la URL al servicio
crear cabecera
def response = client.send() {
envelopeAttributes([
"xmlns:vnif":
"http://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/burt/jdit/ws/VNif
V2Ent.xsd" ①
])
① Podemos ajustar la petición, incluyendo namespaces por ejemplo
101 Groovy Script
272 |
body dinámico y con un bucle
body {
sql.eachRow("select * from nifes where estado is null or estado <> 'IDENTIFICADO' "){ row-> ①
'vnif:VNifV2Ent' { ②
'vnif:Contribuyente'{ ③
'vnif:Nif'(row.nif)
'vnif:Nombre'(row.nombre.toUpperCase())
}
}
}
① Recorremos la tabla y construimos elementos dinámicamente
② Cualificamos la función según el schema que corresponda si es necesario
③ Cualificamos los parametros según el schema que corresponda si es necesario
tratamiento respuesta dinámico
sql.withBatch( batchSize, "update people set nombre_aeat=?,estado=? where nif=?"){ ps->
response.VNifV2Sal.Contribuyente.each{ result -> ①
ps.addBatch([
"$result.Nombre".toString(), ②
"$result.Resultado".toString(),
"$result.Nif".toString()
])
erroneos+= "$result.Resultado".equals('IDENTIFICADO') ? 0 : 1
}
}
① response.VNifV2Sal.Contribuyente nos devuelve un array de elementos Contribuyente
② Obtenemos el valor de un elemento mediante .text() o con .toString()
excepciones
} catch (SOAPFaultException sfe) { ① println sfe.request?.contentAsString println sfe.request.contentAsString} catch (SOAPClientException sce) { ② // This indicates an error with underlying HTTP Client (i.e., 404 Not Found) println sce.request?.contentAsString println sce.response?.contentAsString}
① Excepción de "negocio"
② Excepción de "transporte"
101 Groovy Script
| 273
Ejecución y certificadoComo ya se ha comentado el servicio de la AEAT requiere que la conexión sea realizadacon un certificado por parte del cliente que sirva para identificarle. La forma más comúnes tener este certificado en un fichero .p12 protegido con contraseña de tal forma que alejecutar el script podamos indicarle a Groovy (en realidad a Java) donde encontrarlo.
Así pues la ejecución del script sería algo parecida a:
groovy -Djavax.net.ssl.keyStore=./test.p12 -Djavax.net.ssl.keyStorePassword=LAPWD
-Djavax.net.ssl.keyStoreTYpe=PKCS12 ConsultaNif.groovy USER PWD ①
① LAPWD correspondería a la password para abrir el keystore mientras que USER YPWD corresponderían al usuario de la base de datos
101 Groovy Script
274 |
Script
//tag::dependencies[]
@Grab('com.github.groovy-wslite:groovy-wslite:1.1.2')
@Grab('mysql:mysql-connector-java:5.1.6')
@GrabConfig(systemClassLoader=true)
//end::dependencies[]
import groovy.sql.Sql
import wslite.soap.*
import groovy.xml.*
try{
sql=Sql.newInstance("jdbc:mysql://localhost/mydatabase",args[0], args[1], "com.mysql.jdbc.Driver")
//tag::cliente[]
def client = new SOAPClient('https://www1.agenciatributaria.gob.es/wlpl/BURT-JDIT/ws/VNifV2SOAP') ①
//end::cliente[]
//tag::cabecera[]
def response = client.send() {
envelopeAttributes([
"xmlns:vnif":
"http://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/burt/jdit/ws/VNif
V2Ent.xsd" ①
])
//end::cabecera[]
//tag::body[]
body {
sql.eachRow("select * from nifes where estado is null or estado <> 'IDENTIFICADO' "){ row-> ①
'vnif:VNifV2Ent' { ②
'vnif:Contribuyente'{ ③
'vnif:Nif'(row.nif)
'vnif:Nombre'(row.nombre.toUpperCase())
}
}
}
//end::body[]
}
}
batchSize=20
erroneos=0
//tag::respuesta[]
sql.withBatch( batchSize, "update people set nombre_aeat=?,estado=? where nif=?"){ ps->
response.VNifV2Sal.Contribuyente.each{ result -> ①
ps.addBatch([
"$result.Nombre".toString(), ②
"$result.Resultado".toString(),
"$result.Nif".toString()
])
erroneos+= "$result.Resultado".equals('IDENTIFICADO') ? 0 : 1
}
}
//end::respuesta[]
println "Hay $erroneos no identificados o identificados parcialmente"
//tag::exceptions[]
} catch (SOAPFaultException sfe) { ①
println sfe.request?.contentAsString
println sfe.request.contentAsString
101 Groovy Script
| 275