OpenRewrite, ¿Que es, para que sirve?
1.¿Que cubre?
Es un software que plantea un enfoque para el mantenimiento y “modernizacion”(actualización) de proyectos.
Es decir:
- Si a dia de hoy hay mejoras en el framework que usa mi codigo, quiero poder actualizarme
- Si hay mejoras de seguridad en mis piezas, quiero poder actualziarme
- Si hay bugs que se hayan detectado en las piezas que uso quiero poder actualizarme
- Etc..
2. ¿Como lo hace?
Se basa en el concepto Receta (Recipe). Al igual que las de tu abuela, una receta que contiene pasos.
Asi por ejemplo si trabajamos con Spring security 5.4 y queremos pasar a la version 5.5 habitualmente iríamos a la pagina oficial donde encontraríamos un changelog y unas guias de migracion.
En este caso pongamos
- Actualizar la dependencia
- Actualizar los objetos java que estemos usando: Pueden ser objetos que han sido sustituidos, invocaciones que ahora tienen mas parametros, etc… lo que sea.
- Actualizar propiedades en nuestros ficheros de configuracion (properties/yaml).
El concepto Receta, integraria toda la informacion necesaria para realizar esos pasos, de manera que nosotros tenemos que preocuparnos de pasar esa receta a nuestro proyecto y la receta se encargaría de hacer todos esos pasos por nosotros.
3. ¿Quien lo hace?:
Se hace llamar OpenRewrite porque es un proyecto openSource, donde los desarrolladores de frameworks/libs/etc se dedican a escribir las recetas necesarias que necesitarian las migraciones de las herramientas que han desarrolado.
Por ejemplo, la gente de SpringBoot cada vez que hace una actualizacion en su framework, escribe la receta asociada para realizar esa migracion.
-
Spring Framework: Aqui podemos ver su repo y explorar sus Recetas https://github.com/openrewrite/rewrite-spring
-
Quarkus: La gente de quarkus tambien https://github.com/openrewrite/rewrite-quarkus
-
GithubActions: ¿Tu proyecto usa githubActions? y han cambiado el api, no pasa nada, hay recetas para ello https://docs.openrewrite.org/recipes/github
-
Kubernetes: No pasa nada, tenemos recetas para migrar kubernetes o corregir/modificar cosas que no tengamos muy finas https://docs.openrewrite.org/recipes/kubernetes
-
Y muchismas mas …
El propio core de OpenRewrite escribe recetas para migraciones comunes que no pertenecen a ningun framework https://github.com/openrewrite/rewrite.
Las podemos encontrar aqui :
-
Migraciones y manipulaciones Maven https://github.com/openrewrite/rewrite/tree/main/rewrite-maven/src/main/java/org/openrewrite/maven
-
Migraciones a J2EE 9 https://github.com/openrewrite/rewrite/tree/main/rewrite-java
-
Modificaciones yaml https://github.com/openrewrite/rewrite/tree/main/rewrite-yaml
-
Y muchas mas
Recetas populares:
En la propia pagina de OpenRewrite hay una seccion que nos ofrece las recetas mas comunes que suelen ser las mas buscadas:
https://docs.openrewrite.org/running-recipes/popular-recipe-guides
.
Recetas propias:
No solo podemos nutrirnos de las recetas que escriben otros, si no que tambien podemos escribir nuestras propias recetas.
Si necesitamos unos pasos especificos para actualizar nuestros proyectos (por ejemplo cambiamos objetos en libs, invocaciones, etc..) Podemos escribir nuestras recetas y ejecutarlas.
¿Como se ejecuta?
El core de openRewrite es la ejecucion como un Maven-plugin, asi que podemos hacer uso de él del modo habitual en el que ejecutamos un maven-plugin.
La gente de Moderne.io ofrece tambien un SaaS https://www.moderne.io/ que ofrece todo lo que ofrece el core pero de una manera masiva, que es justo lo que nos interesa, pasar recetas a tantos repositorios tengamos para tenerlos siempre actualizados
EL producto SaaS es de pago, y su configuracion es basica, le damos credenciales para acceder a nuestros repositorios GIT y él se encargara de tener actualizado y pasar las recetas que indiquemos de manera autonoma. Ofrece, estadisticas, y un sin fin de cosas como te puedes imaginar.
¿Como funciona?
Tradicionalmente se utilizada Java AST (Abstract Syntax Tree) para la manipulacion y creacion de nodos que conformaran un archivo java.
Con él eramos capaces de hacer transformaciones aisladas, es decir no teniamos contexto de si nuestro archivo java tiene sentido o relacion con otros elementos dentro de nuestro proyecto.
OpenRewrite se basa en LostLees Semantic Trees. Este enfoque no solo es capaz de transformar nuestro codigo java, si no que es capaz de interpretar el contexto y las relaciones entre los distintos elementos de nuestro proyecto, es decir entiende la semantica del codigo que analiza.
LST monta el arbol de relaciones de bloques de codigo montando una estructura de relaciones.
https://docs.openrewrite.org/concepts-explanations/lst-examples
Asi vemos no solo que lee el codigo y sabe que es una variable y donde esta escrita, si no que tambien sabe donde se esta usando y referenciando. Asi lo mismo con los metodos, constructores y demas elementos del codigo:
2 Ejemplos sencillos
A traves de varios ejemplos sencillos que puedes ir siguiendo en tu ordenador vamos a ir probando algunas recetas publicas y vamos a escribir algunas propias.
2.1.2 Preparando el conejillo de indias
Todo el codigo de esta demo lo puedes encontrar en este repositorio https://github.com/deadveloper666/openrewrite-tutorial
En la branch demo1 encontraremos 2 carpetas pricipalesLa carpeta recipes contendra nuestras recetas La carpeta singleprojectstarter es un proyecto hecho son SpringInitalizer con la version Spring boot 2.7.9 y java 11
REWRITE-MAVEN
Como he comentado antes, hay muchos “proveedores” de recetas, en los siguientes ejemplos vamos a usar recetas del rewrite-maven. Estas recetas estan enfocadas unicamente en modificaciones de ficheros pom.
- Migraciones y manipulaciones Maven https://github.com/openrewrite/rewrite/tree/main/rewrite-maven/src/main/java/org/openrewrite/maven
2.2 Usando la primera receta
Si nos fijamos el parent que trae es el de Spring Boot (2.7.9 y Java 11)
Ahora vamos a buscar una receta que sea capaz de cambiarnos el parent en las recetas disponibles en el repo oficial de openrewrite:
Entre las recetas precocinadas de maven https://docs.openrewrite.org/recipes/maven/ veo que hay una para cambiar el parent https://docs.openrewrite.org/recipes/maven/changeparentpom
Vemos que la receta es parametrizada asi que editamos el archivo recipes.yml con este contenido en la carpeta recipes del repo:
Asi que escribimos la primera receta:
---
type: specs.openrewrite.org/v1beta/recipe
name: com.openrewrite.demo.Recipe1
displayName: Some recipe examples
recipeList:
- org.openrewrite.maven.ChangeParentPom:
oldGroupId: org.springframework.boot
newGroupId: org.springframework.boot
oldArtifactId: spring-boot-starter-parent
newArtifactId: spring-boot-starter-parent
newVersion: 2.7.13
Hay parametros generales que debemos especificar para el recetario que estamos creando:
---
type: specs.openrewrite.org/v1beta/recipe
name: com.openrewrite.demo.Recipe1
displayName: Some recipe examples
recipeList:
RecipeList
En este caso nuestro recetario tiene una unica receta de tipo org.openrewrite.maven.ChangeParentPom que es una receta parametrizada:
---
type: specs.openrewrite.org/v1beta/recipe
name: com.openrewrite.demo.Recipe1
displayName: Some recipe examples
recipeList:
- org.openrewrite.maven.ChangeParentPom:
oldGroupId: org.springframework.boot
newGroupId: org.springframework.boot
oldArtifactId: spring-boot-starter-parent
newArtifactId: spring-boot-starter-parent
newVersion: 2.7.13
- oldGroupId: Un rollo porque no es opcional (tienes que conocerlo, no esta mal porque asi tampoco metes la pata…en fin…)
- oldArtifactId: Coordenadas Maven
- newGroupId: Coordenadas Maven objetivo
- newArtifactId: Coordenadas Maven Objetivo
- newVersion: Version del artefacto
Ejecutando la receta sobre el proyecto**
Como indicamos al principio, vamos a usar el rewrite-maven-plugin y por lo tanto necesitamos estar en el directorio del proyecto donde vamos a ejecutar el plugin.
cd singleprojectstarter
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=org.openrewrite:rewrite-maven:8.1.2 \
-Drewrite.activeRecipes=com.openrewrite.demo.Recipe1 \
-Drewrite.configLocation=../recipes/recipes.yaml
-
recipeArtifactCoordinates: hace referencia a las dependencias maven que necesitamos para ejecutar nuestra receta. En este caso como en el RecipeList estamos usando org.openrewrite.maven.ChangeParentPom necesitamos traer el artefacto que contiene esa receta. Esa receta esta empaquetada en org.openrewrite:rewrite-maven:8.1.2 . Si hubieramos creado una receta nosotros mismos, necesitariamos incluir en recipeArtifactCoordinates las coordenadas maven de nuestro artefacto
-
activeRecipes: Las recetas (separadas por coma) que vamos a ejecutar. Hace referencia al campo name de nuestro recetario
-
configLocation: es opcional, pero en nuestro caso como estamos escribiendo la receta (activeRecipes) nosotros, debemos indicar donde puede encontrar la definicion de la receta, por eso ponemos la ruta al fichero.
Proyectos multimodulo
Cuando nuestro proyecto es multimodulo, es necesario que la ruta del configLocation sea absoluta, ya que se ejecutara la receta para el modulo maven padre y todos sus modulos hijos. Si la ruta fuera relativa, cuando entrara la ejecucion del plugin en el modulo hijo no cuadraría, asi que poniendo la ruta absoluta nos quitamos de problemas.
El final de la ejecucion nos muestra un resumen de las recetas ejecutadas y los cambios:
...
[INFO] Validating active recipes...
[INFO] Project [singleprojectstarter] Resolving Poms...
[INFO] Project [singleprojectstarter] Parsing source files
[INFO] Running recipe(s)...
[WARNING] Changes have been made to singleprojectstarter/pom.xml by:
[WARNING] com.openrewrite.demo.Recipe1
[WARNING] org.openrewrite.maven.ChangeParentPom: {oldGroupId=org.springframework.boot, newGroupId=org.springframework.boot, oldArtifactId=spring-boot-starter-parent, newArtifactId=spring-boot-starter-parent, newVersion=2.7.13}
[WARNING] Please review and commit the results.
[WARNING] Estimate time saved: 5m
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.911 s
[INFO] Finished at: 2024-05-30T12:39:43+02:00
...
Podemos ver el diff y vemos los cambios que ha realizado:
--- a/singleprojectstarter/pom.xml
+++ b/singleprojectstarter/pom.xml
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
- <version>2.7.9</version>
+ <version>2.7.13</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
Es poco, pero ya hemos ejecutado una receta parametrizada
2.3 Recapitulando
Hemos visto que: - Hay recetas que hacen cosas - Muchas son publicas - Las recetas pueden ser parametrizadas - Las ejecutamos como maven plugin indicando el parametro de la configuracion de la receta.
3. Actualizar Parent de un proyecto
Ahora le voy a decir que me suba a la ultima version del Spring boot, por ejemplo a la version 3.
En el ejemplo anterior tambien hemos subido de version, pero la receta era ChangeParentPom, al no cambiar el groupId ni el artifactId pues no hemos cambiando el parent, si no que lo hemos actualizado.
Para ello tenemos recetas precompiladas del rewrite-maven-plugin, aqui la tenemos https://docs.openrewrite.org/recipes/maven/upgradeparentversion
Esta receta es mas correcta para este proposito y si nos fijamos en su parametrizacion no deja cambiar el groupId ni el artifactId. Solo tiene 3 parametros que indican que si el parent del proyecto tiene ese groupId y ese artifactId debe migrarlo a la nueva version 3.1.0.
Asi que añadimos otro paso a nuestra receta com.openrewrite.demo.Recipe1, para ejecutar org.openrewrite.maven.UpgradeParentVersion.
Indicamos la version que queremos subir aceptar semantic version, asi que para indicamos la ultima version.
Nos quedara asi :
---
type: specs.openrewrite.org/v1beta/recipe
name: com.openrewrite.demo.Recipe1
displayName: Change Maven Parent Pom
recipeList:
- org.openrewrite.maven.ChangeParentPom:
oldGroupId: org.springframework.boot
newGroupId: org.springframework.boot
oldArtifactId: spring-boot-starter-parent
newArtifactId: spring-boot-starter-parent
newVersion: 2.7.13
- org.openrewrite.maven.UpgradeParentVersion:
groupId: org.springframework.boot
artifactId: spring-boot-starter-parent
newVersion: 3.1.0
Reiniciamos los cambios (git stash) y vamos a ver que se ejecutan las recetas en orden:
y como no ha cambiado el nombre de la receta, volvemos a ejecutar lo mismo:
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=org.openrewrite:rewrite-maven:8.1.2 \
-Drewrite.activeRecipes=com.openrewrite.demo.Recipe1 \
-Drewrite.configLocation=../recipes/recipes.yaml
...
[INFO] Validating active recipes...
[INFO] Project [singleprojectstarter] Resolving Poms...
[INFO] Project [singleprojectstarter] Parsing source files
[INFO] Running recipe(s)...
[WARNING] Changes have been made to singleprojectstarter/pom.xml by:
[WARNING] com.openrewrite.demo.Recipe1
[WARNING] org.openrewrite.maven.ChangeParentPom: {oldGroupId=org.springframework.boot, newGroupId=org.springframework.boot, oldArtifactId=spring-boot-starter-parent, newArtifactId=spring-boot-starter-parent, newVersion=2.7.13}
[WARNING] org.openrewrite.maven.UpgradeParentVersion: {groupId=org.springframework.boot, artifactId=spring-boot-starter-parent, newVersion=3.1.0}
[WARNING] Please review and commit the results.
[WARNING] Estimate time saved: 5m
Vemos que ha ejecutado en orden la recipeList, primero subio a la 2.7.9 y en el siguiente paso a la 3.1.0
Si vemos el diff vemos que ha subido la version (ahora tenemos la 3.1.0), que era la foto final que queriamos:
--- a/singleprojectstarter/pom.xml
+++ b/singleprojectstarter/pom.xml
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
- <version>2.7.9</version>
+ <version>3.1.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.dppware</groupId>
Hacemos git stash para volver a la foto inicial
4. Actualizando properties
Encontramos otra receta de actuazalicion o añadido de property https://docs.openrewrite.org/recipes/maven/addproperty. Vamos a aprovecharla para subir a java 17.
En este caso no queremos que este asociada a la receta del com.openrewrite.demo.ChangeParentPom, queremos tenerla en el recetario, pero separada para poder ejecutarla aisladamente.
Asi que en el mismo fichero añadimos “—” para indicar que son parametros de otra receta y añadimos nuestros datos.
Quedara asi el fichero final:
---
type: specs.openrewrite.org/v1beta/recipe
name: com.openrewrite.demo.Recipe1
displayName: Change Maven Parent Pom
recipeList:
- org.openrewrite.maven.ChangeParentPom:
oldGroupId: org.springframework.boot
newGroupId: org.springframework.boot
oldArtifactId: spring-boot-starter-parent
newArtifactId: spring-boot-starter-parent
newVersion: 2.7.13
- org.openrewrite.maven.UpgradeParentVersion:
groupId: org.springframework.boot
artifactId: spring-boot-starter-parent
newVersion: 3.1.0
---
type: specs.openrewrite.org/v1beta/recipe
name: com.openrewrite.demo.Recipe2
displayName: Update java property to 17
recipeList:
- org.openrewrite.maven.AddProperty:
key: java.version
value: 17
preserveExistingValue: false
trustParent: false
Ahora vamos a ejecutar la receta com.openrewrite.demo.UpdateJava17 que vemos que hace uso de la receta interna org.openrewrite.maven.AddProperty:
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=org.openrewrite:rewrite-maven:8.1.2 \
-Drewrite.activeRecipes=com.openrewrite.demo.Recipe2 \
-Drewrite.configLocation=../recipes/recipes.yaml
[INFO] Validating active recipes...
[INFO] Project [singleprojectstarter] Resolving Poms...
[INFO] Project [singleprojectstarter] Parsing source files
[INFO] Running recipe(s)...
[WARNING] Changes have been made to singleprojectstarter/pom.xml by:
[WARNING] com.openrewrite.demo.Recipe2
[WARNING] org.openrewrite.maven.AddProperty: {key=java.version, value=17, preserveExistingValue=false, trustParent=false}
[WARNING] Please review and commit the results.
[WARNING] Estimate time saved: 5m
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
@@ -14,7 +14,7 @@
<name>singleprojectstarter</name>
<description>Demo project for Spring Boot</description>
<properties>
- <java.version>11</java.version>
+ <java.version>17</java.version>
</properties>
<dependencies>
5. Añadiendo dependencias
Vamos a darle una naturaleza web añadiendo el fwkcna-starter-web.
Encontramos la receta https://docs.openrewrite.org/recipes/maven/adddependency.
Y como esta receta tiene entidad propia, pues creamos una definicion con el nombre com.openrewrite.demo.AddWebNature en nuestro fichero de configuracion Añadimos:
---
type: specs.openrewrite.org/v1beta/recipe
name: com.openrewrite.demo.AddWebNature
displayName: Add Web nature
recipeList:
- org.openrewrite.maven.AddDependency:
groupId: org.springframework.boot
artifactId: spring-boot-starter-web
version: 3.1.0
onlyIfUsing: org.springframework.boot.*
scope: compile
acceptTransitive: true
version: Podiamos haber especificado la version poniendola a mano (semver) o acceptTransitive si la trae el parent, que es lo que ocurre en nuestro caso. Si hubieramos querido usar una especifica porque tuviera un fix podiamos haberlo puesto especificamente.
onlyIfUsing: Este plugin es tan listo que lleva a confusion, el parametro onlyIfUsing, indica que esa dependencia solo se incluira en el caso de que nuestro codigo este usando la paqueteria indicada. De forma que no tengamos imports que no se usen.
El arbol LST es muy listo
Quizas a veces queremos tener la dependencia pero no la usamos en codigo. Asi que bueno, en este caso le estoy engañando indicando que si que tengo en la clase Application.java un import de org.springframework.boot. De esta manera openrewrite al montar el arbol vera que si se esta usando ese import y entonces si que ejecutara la receta añadiendo la dependencia.
Y ejecutamos la receta com.openrewrite.demo.AddWebNature:
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=org.openrewrite:rewrite-maven:8.1.2 \
-Drewrite.activeRecipes=com.openrewrite.demo.AddWebNature \
-Drewrite.configLocation=../recipes/recipes.yaml
[INFO] Running recipe(s)...
[WARNING] Changes have been made to singleprojectstarter/pom.xml by:
[WARNING] com.openrewrite.demo.AddWebNature
[WARNING] org.openrewrite.maven.AddDependency: {groupId=org.springframework.boot, artifactId=spring-boot-starter-web, version=3.1.0, scope=compile, onlyIfUsing=org.springframework.boot.*, acceptTransitive=true}
[WARNING] Please review and commit the results.
[WARNING] Estimate time saved: 5m
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ </dependency>
<dependency>
6. Quitando dependencias
Imaginemos que tenemos que retroceder una version de una lib determinada porque tiene un bug.
Vamos a llamarla WebErrorWorkAround (com.openrewrite.demo.WebErrorWorkAround)
En nuestra intervencion tenemos que hacer estos pasos: 1. Tenemos que borrar version 2. Añadir otra version de la lib 3. Añadir un comentario en el pom.xml indicando el porque de ese cambio
Usamos el step de borrar y el de añadir en ese orden
La receta nos quedara asi (la añadimos al recetario):
---
type: specs.openrewrite.org/v1beta/recipe
name: com.openrewrite.demo.WebErrorWorkAround
displayName: Workaround for Web error on latest version
recipeList:
- org.openrewrite.maven.RemoveDependency:
groupId: org.springframework.boot
artifactId: spring-boot-starter-web
scope: compile
- org.openrewrite.maven.AddDependency:
groupId: org.springframework.boot
artifactId: spring-boot-starter-web
version: 3.1.1
scope: runtime
onlyIfUsing: org.springframework.boot.test.context.*
type: jar
classifier: ''
optional: null
acceptTransitive: false
- org.openrewrite.maven.AddCommentToMavenDependency:
xPath: /project/dependencies/dependency
groupId: org.springframework.boot
artifactId: spring-boot-starter-web
commentText: This is excluded due to CVE <X> and will be removed when we upgrade the next version is available.
Como podemos ver, quitamos el starter web, añadimos el starter web en otra version , y añadimos un comentario al pom.xml indicando porque se ha realizado este cambio usando la receta publica org.openrewrite.maven.AddCommentToMavenDependency.
Ejecututamos:
Y ejecutamos la receta com.openrewrite.demo.WebErrorWorkAround:
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=org.openrewrite:rewrite-maven:8.1.2 \
-Drewrite.activeRecipes=com.openrewrite.demo.WebErrorWorkAround \
-Drewrite.configLocation=../recipes/recipes.yaml
[INFO] Running recipe(s)...
[WARNING] Changes have been made to singleprojectstarter/pom.xml by:
[WARNING] com.openrewrite.demo.WebErrorWorkAround
[WARNING] org.openrewrite.maven.RemoveDependency: {groupId=org.springframework.boot, artifactId=spring-boot-starter-web, scope=compile}
[WARNING] org.openrewrite.maven.AddDependency: {groupId=org.springframework.boot, artifactId=spring-boot-starter-web, version=3.1.1, scope=runtime, onlyIfUsing=org.springframework.boot.test.context.*, type=jar, classifier=, acceptTransitive=false}
[WARNING] org.openrewrite.maven.AddCommentToMavenDependency: {xPath=/project/dependencies/dependency, groupId=org.springframework.boot, artifactId=spring-boot-starter-web, commentText=This is excluded due to CVE <X> and will be removed when we upgrade the next version is available.}
[WARNING] Please review and commit the results.
[WARNING] Estimate time saved: 5m
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
+ <dependency>
+ <!--This is excluded due to CVE <X> and will be removed when we upgrade the next version is available.-->
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ <version>3.1.1</version>
+ <classifier></classifier>
+ <scope>runtime</scope>
+ </dependency>
Recapitulando
Si te has perdido, el contenido total de nuestro recipes.yaml es este:
docker-compose.yaml
Hasta aqui hemos visto como en un proyecto Maven podemos ir ejecutando recetas y hemos visto algunas de las Recetas Comunes , como configurarlas y como ejecutarlas. En el siguiente paso vamos a ver Recetas especificas de Spring Framework
7. REWRITE-SPRING
Spring es un framework altamente conocido y usado, por lo tanto tendremos mucho interés en sus recetas, ya que los desarrolladores de Spring nos van facilitar las migraciones y el mantenimiento.
Todas las recetas oficiales de Spring las documentadas en https://docs.openrewrite.org/recipes/java/spring
Entre las recetas mas populares https://docs.openrewrite.org/running-recipes/popular-recipe-guides encontramos una que migra de spring boot 2 a spring boot 3, en los tiempos que corren mas de uno tendra pendiente esta migracion, asi que vamos a ver como se comporta esta receta en un proyecto SpringBoot 2 y pasarlo a la version 3.
7.1 Preparacion de la prueba (Ni trampa ni carton)
Vamos a coger un proyecto en spring boot antiguo 2 y vamos a pasarlo a la 3 y para que veamos como funciona sin “trampas” (ya lo entenderás) vamos a coger un proyecto al azar de los muchos que hay de tutoriales en internet y vamos a ejectar varias recetas en él para ver si esto de OpenRewrite es tan magico o no es tan magico y si lo hace bien o si no es oro todo lo que reluce..
Ponemos cualquier tutorial en google “spring boot simple crud application”
La primera que sale es esta: https://www.javatpoint.com/spring-boot-crud-operations Buscamos la palabra “download” y descargamos un zip con el proyecto:
El enlace del zip es este : https://static.javatpoint.com/springboot/download/spring-boot-crud-operation.zip
cd project
wget https://static.javatpoint.com/springboot/download/spring-boot-crud-operation.zip
unzip spring-boot-crud-operation.zip
rm spring-boot-crud-operation.zip
cd spring-boot-crud-operation
Vemos su pom y vemos que esta en version 2.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.M1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
- Insertamos
```sh curl -X POST http://localhost:8080/books \ -H ‘Content-Type: application/json’ \ -d ‘{ “bookid”: “5433”,”bookname”: “Core and Advance Java”, “author”: “R. Nageswara Rao”, “price”: “800” } ‘
El punto de partida esta bien aparentemente, fenomenal!!
7.2 Estrategia
La migracion consistira en estos pasos:
- Java 17 a tope (usaremos esta receta https://docs.openrewrite.org/recipes/java/migrate/javaversion17)
- Cambio a J2EE 9 (jakarta) javax to jakarta (https://docs.openrewrite.org/recipes/java/migrate/jakarta/javaxmigrationtojakarta)
- Parent de spring boot 2 a spring boot 3 (https://docs.openrewrite.org/recipes/java/spring/boot3/upgradespringboot_3_0)
- Cambio en controladores
- Cambio en entidades JPA (hibernate 5 a Hibernate 6)
Es muy ambicioso, puesto que es una actualizacion muy grande que no podemos dar por pasos, porque una vez que cambiemos el parent se va a escojonar todo. Por ejemplo si cambiamos java a java 17 y realizamos clean install, nos va a dar error de class version, asi que necesitamos hacerlo todo de un paso, partiendo del proyecto que compila actualmente
OPEN REWRITE trabaja solo a partir de proyectos que compilan, si no, no podria analizar la semantica de invocaciones.
Escribimos una nueva receta recipes/recipe_spring_migration.yaml para hacerlo todo en una transaccion:
type: specs.openrewrite.org/v1beta/recipe
name: com.openrewrite.demo.SpringMigration
displayName: Migrate Spring boot project
recipeList:
- org.openrewrite.java.migrate.UpgradeToJava17
- org.openrewrite.java.migrate.jakarta.JavaxMigrationToJakarta
- org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0
Como vemos las recetas pertenecen a distintos artefactos, - java/migrate/javaversion17: pertenece a org.openrewrite.recipe:rewrite-migrate-java - java/migrate/jakarta/javaxmigrationtojakarta tambien pertenece a org.openrewrite.recipe:rewrite-migrate-java - spring/boot3/upgradespringboot_3_0 pertenece al artefacto org.openrewrite.recipe:rewrite-spring
Asi que la ejecucion del plugin nos queda indicando todas las coordenadas de los artefactos que contienen las recetas que vamos a usar:
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-migrate-java:LATEST,org.openrewrite.recipe:rewrite-spring:LATEST \
-Drewrite.activeRecipes=com.openrewrite.demo.SpringMigration \
-Drewrite.configLocation=../recipes/recipe_spring_migration.yaml
Ejecutamos y vemos que se han ejecutado muchas tareas. Podemos apreciar que hay tareas que son dependientes de otras:
[INFO] Validating active recipes...
[INFO] Project [spring-boot-crud-operation] Resolving Poms...
[INFO] Project [spring-boot-crud-operation] Parsing source files
[INFO] Running recipe(s)...
[WARNING] Changes have been made to project/spring-boot-crud-operation/pom.xml by:
[WARNING] com.openrewrite.demo.SpringMigration
[WARNING] org.openrewrite.java.migrate.UpgradeToJava17
[WARNING] org.openrewrite.java.migrate.Java8toJava11
[WARNING] org.openrewrite.java.migrate.JavaVersion11
[WARNING] org.openrewrite.java.migrate.UpgradeJavaVersion: {version=11}
[WARNING] org.openrewrite.java.migrate.javax.AddJaxbDependencies
[WARNING] org.openrewrite.java.migrate.javax.AddJaxbRuntime: {runtime=glassfish}
[WARNING] org.openrewrite.java.migrate.javax.AddJaxbRuntime$AddJaxbRuntimeMaven
[WARNING] org.openrewrite.java.migrate.JavaVersion17
[WARNING] org.openrewrite.java.migrate.UpgradeJavaVersion: {version=17}
[WARNING] org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0
[WARNING] org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_7
[WARNING] org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_6
[WARNING] org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_5
[WARNING] org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_4
[WARNING] org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_3
[WARNING] org.openrewrite.maven.UpgradeParentVersion: {groupId=org.springframework.boot, artifactId=spring-boot-starter-parent, newVersion=2.3.x}
[WARNING] org.openrewrite.maven.UpgradeParentVersion: {groupId=org.springframework.boot, artifactId=spring-boot-starter-parent, newVersion=2.4.x}
[WARNING] org.openrewrite.maven.RemoveExclusion: {groupId=org.springframework.boot, artifactId=spring-boot-starter-test, exclusionGroupId=org.junit.vintage, exclusionArtifactId=junit-vintage-engine}
[WARNING] org.openrewrite.maven.UpgradeParentVersion: {groupId=org.springframework.boot, artifactId=spring-boot-starter-parent, newVersion=2.5.x}
[WARNING] org.openrewrite.maven.UpgradeParentVersion: {groupId=org.springframework.boot, artifactId=spring-boot-starter-parent, newVersion=2.6.x}
[WARNING] org.openrewrite.maven.UpgradeParentVersion: {groupId=org.springframework.boot, artifactId=spring-boot-starter-parent, newVersion=2.7.x, retainVersions=[mysql:mysql-connector-java]}
[WARNING] org.openrewrite.maven.UpgradeParentVersion: {groupId=org.springframework.boot, artifactId=spring-boot-starter-parent, newVersion=3.0.x, retainVersions=[org.thymeleaf:thymeleaf-spring5, org.thymeleaf.extras:thymeleaf-extras-springsecurity5]}
[WARNING] Changes have been made to project/spring-boot-crud-operation/src/main/java/com/javatpoint/model/Books.java by:
[WARNING] com.openrewrite.demo.SpringMigration
[WARNING] org.openrewrite.java.migrate.UpgradeToJava17
[WARNING] org.openrewrite.java.migrate.Java8toJava11
[WARNING] org.openrewrite.java.migrate.JavaVersion11
[WARNING] org.openrewrite.java.migrate.UpgradeJavaVersion: {version=11}
[WARNING] org.openrewrite.java.migrate.JavaVersion17
[WARNING] org.openrewrite.java.migrate.UpgradeJavaVersion: {version=17}
[WARNING] org.openrewrite.java.migrate.jakarta.JavaxMigrationToJakarta
[WARNING] org.openrewrite.java.migrate.jakarta.JavaxPersistenceToJakartaPersistence
[WARNING] org.openrewrite.java.ChangePackage: {oldPackageName=javax.persistence, newPackageName=jakarta.persistence, recursive=true}
[WARNING] Changes have been made to project/spring-boot-crud-operation/src/main/java/com/javatpoint/controller/BooksController.java by:
[WARNING] com.openrewrite.demo.SpringMigration
[WARNING] org.openrewrite.java.migrate.UpgradeToJava17
[WARNING] org.openrewrite.java.migrate.Java8toJava11
[WARNING] org.openrewrite.java.migrate.JavaVersion11
[WARNING] org.openrewrite.java.migrate.UpgradeJavaVersion: {version=11}
[WARNING] org.openrewrite.java.migrate.JavaVersion17
[WARNING] org.openrewrite.java.migrate.UpgradeJavaVersion: {version=17}
[WARNING] org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0
[WARNING] org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_7
[WARNING] org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_6
[WARNING] org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_5
[WARNING] org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_4
[WARNING] org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_3
[WARNING] org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_2
[WARNING] org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_1
[WARNING] org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_0
[WARNING] org.openrewrite.java.spring.boot2.SpringBoot2BestPractices
[WARNING] org.openrewrite.java.spring.ImplicitWebAnnotationNames
[WARNING] Please review and commit the results.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
Vamos ver los cambios con git diff - Vemos la nueva version de java - La ultima version de spring boot parent - La modificacion de la paqueteria - etc…
Probamos que compila y que funciona: - Vemos que compila “mvn clean install” - Arrancamos: mvn clean install spring-boot:run -Dspring-boot.run.profiles=local - Lanzamos un curl para ver que todo esta ok
- Insertamos ```sh curl -X POST http://localhost:8080/books \ -H ‘Content-Type: application/json’ \ -d ‘{ “bookid”: “5433”,”bookname”: “Core and Advance Java”, “author”: “R. Nageswara Rao”, “price”: “800” } ‘
Ya tenemos nuestro proyecto en Spring boot 3 java 17
7.3 Renombrando packages
Vamos a ver como hacer este proyecto nuestro de manera sencilla, utilizaremos el refactor de package para cambiar la paqueteria completa
Usaremos https://docs.openrewrite.org/recipes/java/changepackage
Añadimos la definicion de la receta al archivo de configuracion que ya teniamos
Como es parametrizada preparamos nuestra receta AdaptToDemoPackaging:
---
type: specs.openrewrite.org/v1beta/recipe
name: com.openrewrite.demo.AdaptToDemoPackaging
displayName: Un dia facil en la oficina
recipeList:
- org.openrewrite.java.ChangePackage:
oldPackageName: com.javatpoint
newPackageName: com.openrewrite.demo
recursive: true
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=org.openrewrite:rewrite-maven:8.1.2 \
-Drewrite.activeRecipes=com.openrewrite.demo.AdaptToDemoPackaging \
-Drewrite.configLocation=../recipes/recipe_spring_migration.yaml
....
[INFO] Project [spring-boot-crud-operation] Resolving Poms...
[INFO] Project [spring-boot-crud-operation] Parsing source files
[INFO] Running recipe(s)...
[WARNING] File has been moved from project/spring-boot-crud-operation/src/main/java/com/javatpoint/SpringBootCrudOperationApplication.java to project/spring-boot-crud-operation/src/main/java/com/dppware/SpringBootCrudOperationApplication.java by:
[WARNING] com.openrewrite.demo.AdaptToDemoPackaging
[WARNING] org.openrewrite.java.ChangePackage: {oldPackageName=com.javatpoint, newPackageName=com.openrewrite.demo, recursive=true}
[WARNING] File has been moved from project/spring-boot-crud-operation/src/main/java/com/javatpoint/model/Books.java to project/spring-boot-crud-operation/src/main/java/com/dppware/model/Books.java by:
[WARNING] com.openrewrite.demo.AdaptToDemoPackaging
[WARNING] org.openrewrite.java.ChangePackage: {oldPackageName=com.javatpoint, newPackageName=com.openrewrite.demo, recursive=true}
[WARNING] File has been moved from project/spring-boot-crud-operation/src/main/java/com/javatpoint/controller/BooksController.java to project/spring-boot-crud-operation/src/main/java/com/dppware/controller/BooksController.java by:
[WARNING] com.openrewrite.demo.AdaptToDemoPackaging
[WARNING] org.openrewrite.java.ChangePackage: {oldPackageName=com.javatpoint, newPackageName=com.openrewrite.demo, recursive=true}
[WARNING] File has been moved from project/spring-boot-crud-operation/src/main/java/com/javatpoint/service/BooksService.java to project/spring-boot-crud-operation/src/main/java/com/dppware/service/BooksService.java by:
[WARNING] com.openrewrite.demo.AdaptToDemoPackaging
[WARNING] org.openrewrite.java.ChangePackage: {oldPackageName=com.javatpoint, newPackageName=com.openrewrite.demo, recursive=true}
[WARNING] File has been moved from project/spring-boot-crud-operation/src/main/java/com/javatpoint/repository/BooksRepository.java to project/spring-boot-crud-operation/src/main/java/com/dppware/repository/BooksRepository.java by:
[WARNING] com.openrewrite.demo.AdaptToDemoPackaging
[WARNING] org.openrewrite.java.ChangePackage: {oldPackageName=com.javatpoint, newPackageName=com.openrewrite.demo, recursive=true}
[WARNING] File has been moved from project/spring-boot-crud-operation/src/test/java/com/javatpoint/SpringBootCrudOperationApplicationTests.java to project/spring-boot-crud-operation/src/test/java/com/dppware/SpringBootCrudOperationApplicationTests.java by:
[WARNING] com.openrewrite.demo.AdaptToDemoPackaging
[WARNING] org.openrewrite.java.ChangePackage: {oldPackageName=com.javatpoint, newPackageName=com.openrewrite.demo, recursive=true}
[WARNING] Please review and commit the results.
7.4 Vamos a complicar las cosas
De momento han sido todo casos “sencillos” y se ha portado bien, pero vamos a ponerle un caso mas complicado.
Volvemos al punto de partida:
7.5 Añadiendo seguridad
Vamos a añadir la seguridad. El proyecto esta en la 2.3.0, voy a subirlo a la 2.7.9 y añadir la configuracion de seguridad de esa version. Nos vale tambien para la prueba porque vamos a subir de version 2 a version 3.
Pasos que realizamos para prepararlo : Subir el spring-boot-parent
Añadir el starter de seguridad al pom.xml <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
y la clase de configuracion, con usuarios en memoria por simplificar el ejemplo
package com.javatpoint.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authz) -> authz
.antMatchers("/books").hasRole("ADMIN")
.antMatchers("/book").hasRole("USER")
).csrf().disable()
.httpBasic();
}
// In-memory authentication to authenticate the user i.e. the user credentials are stored in the memory.
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("guest").password("{noop}guest1234").roles("USER");
auth.inMemoryAuthentication().withUser("admin").password("{noop}admin1234").roles("ADMIN");
}
}
Arrancamos y comprobamos que el punto de partida compila y funciona la seguridad ```sh mvn clean install spring-boot:run -Dspring-boot.run.profiles=local
Comprobamos que la seguridad funciona porque nos devuelve un 401 si no indicamos credenciales de seguridad:
```sh
curl -X GET http://localhost:8080/book --> 401
```
Comprobamos que funciona indicando la cabecera Authorization: Basic Z3Vlc3Q6Z3Vlc3QxMjM0 (autogenerada por curl con las basicAuth en la url):
```sh
curl -X GET http://guest:guest1234@localhost:8080/book
```
```sh
curl -X POST http://admin:admin1234@localhost:8080/books \
-H 'Content-Type: application/json' \
-d '{ "bookid": "5433","bookname": "Core and Advance Java", "author": "R. Nageswara Rao", "price": "800" } '
Pasamos el recetario a ver como se comporta
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-migrate-java:LATEST,org.openrewrite.recipe:rewrite-spring:LATEST \
-Drewrite.activeRecipes=com.openrewrite.demo.SpringMigration \
-Drewrite.configLocation=../recipes/recipe_spring_migration.yaml
WARNING] Changes have been made to project/spring-boot-crud-operation/src/main/java/com/dppware/config/SecurityConfiguration.java by:
NO ES ORO TODO LO QUE RELUCE
Analizamos la clase y vemos que algunas cosas las ha hecho bien y otras mal Ha quitado que extendamos el WebConfigurerAdapter, pero luego en codigo no le ha ido tambien , deja cosas que no compila y la migracion de los usuarios en memoria no la ha hecho bien.
Esta receta la escribio alguien y le funcionó porque la llego a publicar. Asi que como podemos observar, vale para unos casos, pero no todas las recetas valen para todos los casos y como en Spring se pueden hacer las cosas de 5 formas distintas pues habra cosas que nos valdran y cosas que no
Antes de seguir lo dejamos todo ok
No podemos irnos sin arreglarlo, asi que vamos a aplicar los cambios necesarios que debemos realizar sobre lo que ha dejado mal openrewrite, para que nos funcione la seguridad en spring boot 3: Basta con cambiar la clase SecurityConfiguration.java con este contenido
package com.javatpoint.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.User.UserBuilder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((authz) -> authz
.requestMatchers("/books").hasRole("ADMIN")
.requestMatchers("/book").hasRole("USER")
)
.httpBasic()
.and()
.csrf().disable()
;
return http.build();
}
// In-memory authentication to authenticate the user i.e. the user credentials are stored in the memory.
@Bean
InMemoryUserDetailsManager inMemoryAuthManager() throws Exception {
return new InMemoryUserDetailsManager(User.builder().username("admin").password("{noop}admin1234").roles("ADMIN").build(),
User.builder().username("guest").password("{noop}guest1234").roles("USER").build());
}
}
Se puede arrancar y probar los curl y vemos que tenemos el proyecto en Spring Boot 3 con seguridad en memoria.