Spring es un framework altamente conocido y usado, sus recetas generan mucho interés y los equipos de desarrollo de Spring nos van facilitar las migraciones y el mantenimiento.

En el post anterior hablamos sobre rewrite-maven y cómo modificar ficheros paso a paso. Hoy veremos en detalle rewrite-spring y todas las recetas oficiales documentadas.

Entre las recetas más populares encontramos una que migra de Spring Boot 2 a Spring Boot 3. Si estás leyendo este post, seguro que tienes esta migración pendiente, así que vamos a ver cómo se comporta esta receta en un proyecto Spring Boot 2 y pasarlo a la versión 3.

Preparación de la prueba (sin trampa ni cartón)

Vamos a coger un proyecto en Spring Boot 2 y vamos a pasarlo a la versión 3. Para que veamos cómo funciona sin “trampas”, vamos a elegir un proyecto al azar de los muchos tutoriales que encontramos en internet y vamos a ejecutar varias recetas en él. Así, veremos si OpenRewrite es tan mágico o no es tan mágico y si lo hace bien o no es oro todo lo que reluce.

Buscamos cualquier tutorial en Google. Por ejemplo, podemos buscar “spring boot simple crud application”. Este es el primer resultado que aparece. A continuación, buscamos la palabra “download” y descargamos este enlace zip con el proyecto.

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

Echamos un vistazo a su pom y vemos que está en versión 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>

Hacemos una prueba para asegurarnos que compila y que funciona:

A continuación, insertamos:

curl -X POST https://www.paradigmadigital.com/books \
  -H 'Content-Type: application/json' \
  -d '{ "bookid": "5433","bookname": "Core and Advance Java", "author": "R. Nageswara Rao", "price": "800" } '

Y preguntamos:

curl -X GET https://www.paradigmadigital.com/book

De esta forma confirmamos que el punto de partida está bien.

Estrategia

La migración consistirá en estos pasos:

  1. Java 17. Usaremos esta receta.
  2. Cambio a J2EE 9 (Jakarta) Javax to Jakarta.
  3. Parent de Spring Boot 2 a Spring Boot 3.
  4. Cambio en controladores.
  5. Cambio en entidades JPA (Hibernate 5 a Hibernate 6).

Esta actualización es muy grande y no podemos hacerla por pasos ya que, una vez que cambiemos el parent, se puede romper todo. Por ejemplo, si cambiamos Java a Java 17 y realizamos clean install, nos va a dar error de class version, así que necesitamos hacerlo todo de un paso, partiendo del proyecto que compila actualmente.

OpenRewrite trabaja solo a partir de proyectos que compilan, sino no podría analizar la semántica de invocaciones. Escribimos una nueva receta recipes/recipe_spring_migration.yaml para hacerlo todo en una transacción:

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:

Así que nos queda indicar todas las coordenadas de los artefactos que contienen las recetas que vamos a usar para ejecutar el plugin:

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

A continuación, vemos que se han ejecutado muchas tareas y 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 a ver los cambios con git diff:

Probamos que compila y que funciona:

Insertamos:

curl -X POST https://www.paradigmadigital.com/books \
  -H 'Content-Type: application/json' \
  -d '{ "bookid": "5433","bookname": "Core and Advance Java", "author": "R. Nageswara Rao", "price": "800" } '

Preguntamos:

curl -X GET https://www.paradigmadigital.com/book

Ya tenemos nuestro proyecto en Spring Boot 3 Java 17.

Renombrando packages

Vamos a ver cómo hacer nuestro proyecto de manera sencilla y utilizaremos el refactor de package para cambiar la paquetería completa. Para ello, usaremos esta receta.

Añadimos la definición de la receta al archivo de configuración que ya teníamos. 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/openrewrite/demo/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/openrewrite/demo/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/openrewrite/demo/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/openrewrite/demo/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/openrewirte/demo/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/openrewrite/demo/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.
En los cambios vemos que ha cambiado la paquetería y ya tenemos un proyecto “pirateado”. Arrancamos y confirmamos que funciona bien.

Añadiendo seguridad

De momento hemos visto casos “sencillos” que se han portado bien, pero vamos a ponerle un caso más complicado.

Volvemos al punto de partida:

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

Ahora, vamos a añadir la seguridad. El proyecto está en la versión 2.3.0, lo subimos a la 2.7.9 y añadimos la configuración de seguridad de esa versión. Nos vale también para la prueba porque vamos a subir de la versión 2 a la versión 3. Seguiremos los siguientes pasos para prepararlo:

  1. Subir el spring-boot-parent.
<version>2.7.9</version><br>
  1. Añadir el starter de seguridad al pom.xml.
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
</dependency>
  1. Y también la clase de configuración, con usuarios en memoria para 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");
    }
}
  1. Arrancamos y comprobamos si compila y funciona.
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 las credenciales de seguridad:

curl -X GET https://www.paradigmadigital.com/book  --> 401
  1. Comprobamos que funciona indicando la cabecera Authorization: Basic Z3Vlc3Q6Z3Vlc3QxMjM0 (autogenerada por cURL con las basicAuth en la url).
curl -X GET http://guest:guest1234@localhost:8080/book 
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" } '
  1. Pasamos el recetario para ver cómo 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/openrewrite/demo/config/SecurityConfiguration.java by:

Vemos que nos indica que ha detectado algo de seguridad y que le ha hecho cambios. Los analizamos y vemos que no es oro todo lo que reluce. Hay cosas que ha hecho bien, como quitar que extendamos el WebConfigurerAdapter, pero en el código ha dejado partes sin compilar y no ha completado bien la migración de los usuarios en la memoria.

En Spring, las cosas pueden hacerse de varias formas distintas y llegar al mismo resultado. Es por ello que habrá recetas que nos servirán y recetas que no. El ejemplo que acabamos de ver nos confirma que a la persona que la escribió le funcionó pero que, en nuestro caso, no ha funcionado bien.

Arreglamos los errores

No podemos irnos sin arreglarlo, así que aplicamos 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());
    }
}
Arrancamos, probamos los cURL y vemos que tenemos el proyecto en Spring Boot 3 con seguridad en memoria.

Conclusiones

Como hemos visto, OpenRewrite es una herramienta muy poderosa que nos permite de una manera muy ágil refactorizar proyectos Java.

Los equipos de seguridad de las empresas podrían actualizar el código de aplicaciones legacy si se detecta alguna vulnerabilidad en alguna de las piezas con las que se construyó de una manera masiva sin necesitar un equipo de desarrollo dedicado. Cada vez más frameworks se suben al carro de OpenRewrite para que las migraciones de versión sean más sencillas para los equipos de desarrollo.

Pero también hemos visto que, a día de hoy, no es oro todo lo que reluce ya que las recetas esperan que nuestro código esté de cierta manera, y no contempla todas las posibilidades que existen para hacer una misma programación.

Y, como no todas las recetas oficiales se adaptan a nuestro código, vamos a aprender a escribir nuestras propias recetas en el siguiente post de OpenRewrite 😉.

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete