Saltar a contenido

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

  1. Actualizar la dependencia
  2. Actualizar los objetos java que estemos usando: Pueden ser objetos que han sido sustituidos, invocaciones que ahora tienen mas parametros, etc… lo que sea.
  3. 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.

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 :

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 Receta Candentes.

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: Vertical AST Example

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

git clone https://github.com/deadveloper666/openrewrite-tutorial
git checkout demo1
En la branch demo1 encontraremos 2 carpetas pricipales

images/demo_folders.png

La 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.

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:
- type: specificacion para poder parsear (dice que es de la v1beta) - name: es el nombre de la receta - displayName: Cuando se ejecuta la tarea, se printea esto y se usa para ver el reporte de los cambios

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
Argumentos:

  • 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.

      groupId: org.springframework.boot
      artifactId: spring-boot-starter-parent
      newVersion: 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:

git stash

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
Vemos en el diff que se ha actualizado.

@@ -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
Vemos el diff y vemos que ha metido la dependencia

        </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
Vemos el diff y encontramos la nueva version del fwkcna-starter-web y el comentario añadido

        <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
---
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 
---
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     
---            
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.        

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>
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” } ‘

Pregutnamos:

```sh
curl -X GET http://localhost:8080/book

El punto de partida esta bien aparentemente, fenomenal!!

7.2 Estrategia

La migracion consistira en estos pasos:

  1. Java 17 a tope (usaremos esta receta https://docs.openrewrite.org/recipes/java/migrate/javaversion17)
  2. Cambio a J2EE 9 (jakarta) javax to jakarta (https://docs.openrewrite.org/recipes/java/migrate/jakarta/javaxmigrationtojakarta)
  3. Parent de spring boot 2 a spring boot 3 (https://docs.openrewrite.org/recipes/java/spring/boot3/upgradespringboot_3_0)
  4. Cambio en controladores
  5. 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” } ‘
    Pregutnamos:
    
    ```sh
    curl -X GET http://localhost:8080/book
    

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  
Y ejecutamos
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.
Vemos los cambios y vemos que ha cambiado la paqueteria y ya tenemos un proyecto “pirateado”. Arrancamos y vemos que funciona bien.

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:

rm -R spring-boot-crud-operation
unzip spring-boot-crud-operation.zip
cd spring-boot-crud-operation

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

<version>2.7.9</version>
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:
Vemos que nos indica que ha detectado algo de seguridad y que le ha hecho cambios, vamos a verlo:

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.