Add openrewrite module: Boot 2.7 to 3.5 to 4.0 upgrade with OpenRewrite recipes, leftovers and CI gate
Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Gth99spB1CzQmBFofXmU4Z
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
# openrewrite
|
||||
|
||||
Companion module for the ankurm.com post *Automating javax → jakarta and Boot 3 → 4 Upgrades with OpenRewrite*.
|
||||
|
||||
`legacy-app/` is a small Spring Boot 2.7.18 application (JPA, validation, security, a servlet filter, `RestTemplate`, a JUnit 4 test, a `spring.factories` auto-configuration). `scripts/run-all.sh` copies it to a scratch directory outside this checkout, runs two OpenRewrite recipes in sequence, builds and tests after each, and records everything under `output/`.
|
||||
|
||||
| Tool | Version |
|
||||
|---|---|
|
||||
| rewrite-maven-plugin | 6.46.1 |
|
||||
| rewrite-spring | 6.37.1 |
|
||||
| rewrite-migrate-java | 3.42.1 |
|
||||
| JDK / Maven | 17 / 3.9 |
|
||||
|
||||
```bash
|
||||
export JAVA_HOME=/path/to/jdk17
|
||||
./scripts/run-all.sh
|
||||
```
|
||||
|
||||
| Output | Shows |
|
||||
|---|---|
|
||||
| `00-gotcha-gitignore.txt` | OpenRewrite silently skips sources that git ignores |
|
||||
| `01-baseline.txt` | Legacy app builds and passes on Boot 2.7.18 |
|
||||
| `02-step1-diff.txt` / `03-step1-build.txt` | `UpgradeSpringBoot_3_5`: diff and build |
|
||||
| `04-step2-diff.txt` / `05-step2-build.txt` | `UpgradeSpringBoot_4_0`: diff and build |
|
||||
| `06-what-it-left-behind.txt` | What the recipes did not change |
|
||||
| `07-step3-boot-4.1.1.txt` | Hand bump from 4.0.x to 4.1.1, build and test |
|
||||
| `08-ci-gate.txt` | `rewrite:dryRun` exit codes, including a property-name trap |
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.7.18</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
<groupId>com.ankurm</groupId>
|
||||
<artifactId>legacy-app</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency>
|
||||
<dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope></dependency>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
|
||||
<dependency><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId><scope>test</scope></dependency>
|
||||
<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-test</artifactId><scope>test</scope></dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.ankurm.legacy;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class AuditAutoConfiguration {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.ankurm.legacy;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.validation.constraints.Email;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
@Entity
|
||||
public class Customer {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
@NotBlank
|
||||
private String name;
|
||||
@Email
|
||||
private String email;
|
||||
|
||||
protected Customer() {}
|
||||
public Customer(String name, String email) { this.name = name; this.email = email; }
|
||||
public Long getId() { return id; }
|
||||
public String getName() { return name; }
|
||||
public String getEmail() { return email; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public void setEmail(String email) { this.email = email; }
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.ankurm.legacy;
|
||||
|
||||
import java.util.List;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/customers")
|
||||
public class CustomerController {
|
||||
|
||||
private final CustomerRepository repository;
|
||||
|
||||
public CustomerController(CustomerRepository repository) { this.repository = repository; }
|
||||
|
||||
@PostConstruct
|
||||
void seed() {
|
||||
repository.save(new Customer("Ada", "[email protected]"));
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
List<Customer> all() { return repository.findAll(); }
|
||||
|
||||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
Customer create(@Valid @RequestBody Customer c) { return repository.save(c); }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.ankurm.legacy;
|
||||
|
||||
import javax.persistence.LockModeType;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Lock;
|
||||
import org.springframework.data.jpa.repository.QueryHints;
|
||||
import javax.persistence.QueryHint;
|
||||
|
||||
public interface CustomerRepository extends JpaRepository<Customer, Long> {
|
||||
|
||||
@Lock(LockModeType.PESSIMISTIC_WRITE)
|
||||
@QueryHints(@QueryHint(name = "javax.persistence.lock.timeout", value = "3000"))
|
||||
Customer findFirstByName(String name);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.ankurm.legacy;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class LegacyApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(LegacyApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.ankurm.legacy;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@Component
|
||||
public class RemoteClient {
|
||||
private final RestTemplate rest = new RestTemplate();
|
||||
|
||||
public String fetch(String url) {
|
||||
return rest.getForObject(url, String.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ankurm.legacy;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
@Component
|
||||
public class RequestLogFilter extends OncePerRequestFilter {
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||
throws ServletException, IOException {
|
||||
response.setHeader("X-Seen-By", "RequestLogFilter");
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.ankurm.legacy;
|
||||
|
||||
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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.csrf().disable()
|
||||
.authorizeRequests().antMatchers("/actuator/health").permitAll()
|
||||
.anyRequest().permitAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ankurm.legacy.AuditAutoConfiguration
|
||||
@@ -0,0 +1,6 @@
|
||||
spring.redis.host=localhost
|
||||
spring.redis.port=6379
|
||||
management.metrics.export.prometheus.enabled=true
|
||||
spring.jpa.hibernate.ddl-auto=create-drop
|
||||
server.max-http-header-size=16KB
|
||||
spring.jpa.properties.javax.persistence.validation.mode=none
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.ankurm.legacy;
|
||||
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
public class CustomerControllerTest {
|
||||
|
||||
@Autowired MockMvc mvc;
|
||||
|
||||
@Test
|
||||
public void listsSeededCustomer() throws Exception {
|
||||
mvc.perform(get("/customers")).andExpect(status().isOk()).andExpect(jsonPath("$[0].name").value("Ada"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# 00-gotcha-gitignore: same recipe, same project, sources under a .gitignore'd directory
|
||||
[INFO] Project [legacy-app] Parsing source files
|
||||
[INFO] Applying recipes would make no changes. No patch file generated.
|
||||
@@ -0,0 +1,3 @@
|
||||
# 01-baseline: legacy app, Spring Boot 2.7.18, JDK 17
|
||||
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
|
||||
[INFO] BUILD SUCCESS
|
||||
@@ -0,0 +1,173 @@
|
||||
# 02-step1-diff: recipe org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_5 on the legacy app (52 s)
|
||||
diff -ru -x target legacy-app/pom.xml step1/pom.xml
|
||||
--- legacy-app/pom.xml
|
||||
+++ step1/pom.xml
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
- <version>2.7.18</version>
|
||||
+ <version>3.5.16</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
<groupId>com.ankurm</groupId>
|
||||
@@ -14,6 +14,10 @@
|
||||
<java.version>17</java.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
+ <dependency>
|
||||
+ <groupId>jakarta.servlet</groupId>
|
||||
+ <artifactId>jakarta.servlet-api</artifactId>
|
||||
+ </dependency>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
|
||||
@@ -21,7 +25,6 @@
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency>
|
||||
<dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope></dependency>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
|
||||
- <dependency><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId><scope>test</scope></dependency>
|
||||
<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-test</artifactId><scope>test</scope></dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
diff -ru -x target legacy-app/src/main/java/com/ankurm/legacy/AuditAutoConfiguration.java step1/src/main/java/com/ankurm/legacy/AuditAutoConfiguration.java
|
||||
--- legacy-app/src/main/java/com/ankurm/legacy/AuditAutoConfiguration.java
|
||||
+++ step1/src/main/java/com/ankurm/legacy/AuditAutoConfiguration.java
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.ankurm.legacy;
|
||||
|
||||
-import org.springframework.context.annotation.Configuration;
|
||||
+import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
|
||||
-@Configuration
|
||||
+@AutoConfiguration
|
||||
public class AuditAutoConfiguration {
|
||||
}
|
||||
diff -ru -x target legacy-app/src/main/java/com/ankurm/legacy/Customer.java step1/src/main/java/com/ankurm/legacy/Customer.java
|
||||
--- legacy-app/src/main/java/com/ankurm/legacy/Customer.java
|
||||
+++ step1/src/main/java/com/ankurm/legacy/Customer.java
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.ankurm.legacy;
|
||||
|
||||
-import javax.persistence.Entity;
|
||||
-import javax.persistence.GeneratedValue;
|
||||
-import javax.persistence.Id;
|
||||
-import javax.validation.constraints.Email;
|
||||
-import javax.validation.constraints.NotBlank;
|
||||
+import jakarta.persistence.Entity;
|
||||
+import jakarta.persistence.GeneratedValue;
|
||||
+import jakarta.persistence.Id;
|
||||
+import jakarta.validation.constraints.Email;
|
||||
+import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
@Entity
|
||||
public class Customer {
|
||||
diff -ru -x target legacy-app/src/main/java/com/ankurm/legacy/CustomerController.java step1/src/main/java/com/ankurm/legacy/CustomerController.java
|
||||
--- legacy-app/src/main/java/com/ankurm/legacy/CustomerController.java
|
||||
+++ step1/src/main/java/com/ankurm/legacy/CustomerController.java
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.ankurm.legacy;
|
||||
|
||||
import java.util.List;
|
||||
-import javax.annotation.PostConstruct;
|
||||
-import javax.validation.Valid;
|
||||
+import jakarta.annotation.PostConstruct;
|
||||
+import jakarta.validation.Valid;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
diff -ru -x target legacy-app/src/main/java/com/ankurm/legacy/CustomerRepository.java step1/src/main/java/com/ankurm/legacy/CustomerRepository.java
|
||||
--- legacy-app/src/main/java/com/ankurm/legacy/CustomerRepository.java
|
||||
+++ step1/src/main/java/com/ankurm/legacy/CustomerRepository.java
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.ankurm.legacy;
|
||||
|
||||
-import javax.persistence.LockModeType;
|
||||
+import jakarta.persistence.LockModeType;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Lock;
|
||||
import org.springframework.data.jpa.repository.QueryHints;
|
||||
-import javax.persistence.QueryHint;
|
||||
+import jakarta.persistence.QueryHint;
|
||||
|
||||
public interface CustomerRepository extends JpaRepository<Customer, Long> {
|
||||
|
||||
diff -ru -x target legacy-app/src/main/java/com/ankurm/legacy/RequestLogFilter.java step1/src/main/java/com/ankurm/legacy/RequestLogFilter.java
|
||||
--- legacy-app/src/main/java/com/ankurm/legacy/RequestLogFilter.java
|
||||
+++ step1/src/main/java/com/ankurm/legacy/RequestLogFilter.java
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.ankurm.legacy;
|
||||
|
||||
import java.io.IOException;
|
||||
-import javax.servlet.FilterChain;
|
||||
-import javax.servlet.ServletException;
|
||||
-import javax.servlet.http.HttpServletRequest;
|
||||
-import javax.servlet.http.HttpServletResponse;
|
||||
+import jakarta.servlet.FilterChain;
|
||||
+import jakarta.servlet.ServletException;
|
||||
+import jakarta.servlet.http.HttpServletRequest;
|
||||
+import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
diff -ru -x target legacy-app/src/main/java/com/ankurm/legacy/SecurityConfig.java step1/src/main/java/com/ankurm/legacy/SecurityConfig.java
|
||||
--- legacy-app/src/main/java/com/ankurm/legacy/SecurityConfig.java
|
||||
+++ step1/src/main/java/com/ankurm/legacy/SecurityConfig.java
|
||||
@@ -1,17 +1,19 @@
|
||||
package com.ankurm.legacy;
|
||||
|
||||
+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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
+import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
-public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
- @Override
|
||||
- protected void configure(HttpSecurity http) throws Exception {
|
||||
- http.csrf().disable()
|
||||
- .authorizeRequests().antMatchers("/actuator/health").permitAll()
|
||||
- .anyRequest().permitAll();
|
||||
+public class SecurityConfig {
|
||||
+ @Bean
|
||||
+ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
+ http.csrf(csrf -> csrf.disable())
|
||||
+ .authorizeHttpRequests(requests -> requests.requestMatchers("/actuator/health").permitAll()
|
||||
+ .anyRequest().permitAll());
|
||||
+ return http.build();
|
||||
}
|
||||
}
|
||||
Only in step1/src/main/resources/META-INF: spring
|
||||
diff -ru -x target legacy-app/src/main/resources/application.properties step1/src/main/resources/application.properties
|
||||
--- legacy-app/src/main/resources/application.properties
|
||||
+++ step1/src/main/resources/application.properties
|
||||
@@ -3,4 +3,4 @@
|
||||
management.metrics.export.prometheus.enabled=true
|
||||
spring.jpa.hibernate.ddl-auto=create-drop
|
||||
server.max-http-header-size=16KB
|
||||
-spring.jpa.properties.javax.persistence.validation.mode=none
|
||||
+spring.jpa.properties.jakarta.persistence.validation.mode=none
|
||||
diff -ru -x target legacy-app/src/test/java/com/ankurm/legacy/CustomerControllerTest.java step1/src/test/java/com/ankurm/legacy/CustomerControllerTest.java
|
||||
--- legacy-app/src/test/java/com/ankurm/legacy/CustomerControllerTest.java
|
||||
+++ step1/src/test/java/com/ankurm/legacy/CustomerControllerTest.java
|
||||
@@ -4,15 +4,12 @@
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
-import org.junit.Test;
|
||||
-import org.junit.runner.RunWith;
|
||||
+import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
-import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
-@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
public class CustomerControllerTest {
|
||||
Only in step1/src/main/resources/META-INF: spring
|
||||
@@ -0,0 +1,3 @@
|
||||
# 03-step1-build: after step 1, JDK 17
|
||||
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
|
||||
[INFO] BUILD SUCCESS
|
||||
@@ -0,0 +1,65 @@
|
||||
# 04-step2-diff: recipe org.openrewrite.java.spring.boot4.UpgradeSpringBoot_4_0 applied on top of step 1 (32 s)
|
||||
diff -ru -x target step1/pom.xml step2/pom.xml
|
||||
--- step1/pom.xml
|
||||
+++ step2/pom.xml
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
- <version>3.5.16</version>
|
||||
+ <version>4.0.8</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
<groupId>com.ankurm</groupId>
|
||||
@@ -18,14 +18,23 @@
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
</dependency>
|
||||
- <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
|
||||
+ <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webmvc</artifactId></dependency>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency>
|
||||
+ <dependency>
|
||||
+ <groupId>org.springframework.boot</groupId>
|
||||
+ <artifactId>spring-boot-starter-restclient</artifactId>
|
||||
+ </dependency>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency>
|
||||
<dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope></dependency>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
|
||||
- <dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-test</artifactId><scope>test</scope></dependency>
|
||||
+ <dependency>
|
||||
+ <groupId>org.springframework.boot</groupId>
|
||||
+ <artifactId>spring-boot-starter-webmvc-test</artifactId>
|
||||
+ <scope>test</scope>
|
||||
+ </dependency>
|
||||
+ <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security-test</artifactId><scope>test</scope></dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
diff -ru -x target step1/src/main/resources/application.properties step2/src/main/resources/application.properties
|
||||
--- step1/src/main/resources/application.properties
|
||||
+++ step2/src/main/resources/application.properties
|
||||
@@ -1,6 +1,6 @@
|
||||
-spring.redis.host=localhost
|
||||
-spring.redis.port=6379
|
||||
-management.metrics.export.prometheus.enabled=true
|
||||
+spring.data.redis.host=localhost
|
||||
+spring.data.redis.port=6379
|
||||
+management.prometheus.metrics.export.enabled=true
|
||||
spring.jpa.hibernate.ddl-auto=create-drop
|
||||
-server.max-http-header-size=16KB
|
||||
+server.max-http-request-header-size=16KB
|
||||
spring.jpa.properties.jakarta.persistence.validation.mode=none
|
||||
diff -ru -x target step1/src/test/java/com/ankurm/legacy/CustomerControllerTest.java step2/src/test/java/com/ankurm/legacy/CustomerControllerTest.java
|
||||
--- step1/src/test/java/com/ankurm/legacy/CustomerControllerTest.java
|
||||
+++ step2/src/test/java/com/ankurm/legacy/CustomerControllerTest.java
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
-import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
+import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# 05-step2-build: after step 2, JDK 17
|
||||
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
|
||||
[INFO] BUILD SUCCESS
|
||||
@@ -0,0 +1,13 @@
|
||||
# 06-what-it-left-behind (in /tmp/or-work/step2, after both recipes)
|
||||
--- leftover 'javax.' string literals or keys in source and resources:
|
||||
src/main/java/com/ankurm/legacy/CustomerRepository.java:12: @QueryHints(@QueryHint(name = "javax.persistence.lock.timeout", value = "3000"))
|
||||
--- META-INF files:
|
||||
./spring.factories
|
||||
./spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
|
||||
--- explicit jakarta.servlet-api dependency added to the pom:
|
||||
1
|
||||
--- RestTemplate usages (recipe does not touch them):
|
||||
src/main/java/com/ankurm/legacy/RemoteClient.java:4:import org.springframework.web.client.RestTemplate;
|
||||
src/main/java/com/ankurm/legacy/RemoteClient.java:8: private final RestTemplate rest = new RestTemplate();
|
||||
--- Spring Boot version the recipe stopped at:
|
||||
<version>4.0.8</version>
|
||||
@@ -0,0 +1,4 @@
|
||||
# 07-step3-boot-4.1.1: parent bumped by hand from the recipe's 4.0.x to 4.1.1, JDK 17
|
||||
<version>4.1.1</version>
|
||||
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
|
||||
[INFO] BUILD SUCCESS
|
||||
@@ -0,0 +1,4 @@
|
||||
# 08-ci-gate (mvn rewrite:dryRun, exit code decides the CI job)
|
||||
legacy app, -Drewrite.failOnDryRunResults=true (the name you would guess): exit code 0
|
||||
legacy app, -DfailOnDryRunResults=true (the real user property): exit code 1
|
||||
migrated app, -DfailOnDryRunResults=true: exit code 0
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# usage: rewrite.sh <from-dir> <to-dir> <recipe-fqn> (copies from-dir to to-dir and runs the recipe there)
|
||||
set -uo pipefail
|
||||
RW=6.46.1; SPRING=6.37.1; MIGRATE=3.42.1
|
||||
rm -rf "$2"; mkdir -p "$(dirname "$2")"; cp -r "$1" "$2"; rm -rf "$2/target"
|
||||
cd "$2"
|
||||
mvn -B -q org.openrewrite.maven:rewrite-maven-plugin:$RW:run \
|
||||
-Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-spring:$SPRING,org.openrewrite.recipe:rewrite-migrate-java:$MIGRATE \
|
||||
-Drewrite.activeRecipes="$3" -Drewrite.exportDatatables=false 2>&1
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
# Regenerates output/. Needs JDK 17 on JAVA_HOME (the legacy app targets 17), Maven 3.9, network access to Maven Central.
|
||||
# Work happens in $WORK (default /tmp/or-work) OUTSIDE this git checkout, because OpenRewrite honours .gitignore
|
||||
# and ignores files git ignores (see output/00-gotcha-gitignore.txt).
|
||||
set -uo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
WORK=${WORK:-/tmp/or-work}; OUT=$PWD/output; mkdir -p "$OUT"
|
||||
R1=org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_5
|
||||
R2=org.openrewrite.java.spring.boot4.UpgradeSpringBoot_4_0
|
||||
tests() { (cd "$1" && mvn -B test 2>&1 | grep -E "Tests run:.*Fail|BUILD|ERROR" | grep -v "Time elapsed" | head -${2:-8}); }
|
||||
difff() { diff -ru -x target "$1" "$2" | sed -E 's/\t20[0-9-]+ [0-9:.]+ \+0000$//; s#'"$WORK"'/##g'; }
|
||||
|
||||
{ echo "# 01-baseline: legacy app, Spring Boot 2.7.18, JDK 17"; tests legacy-app; } > "$OUT/01-baseline.txt"
|
||||
|
||||
s=$(date +%s); ./scripts/rewrite.sh legacy-app "$WORK/step1" $R1 >/dev/null 2>&1; e=$(date +%s)
|
||||
{ echo "# 02-step1-diff: recipe $R1 on the legacy app ($((e-s)) s)"; difff legacy-app "$WORK/step1"; diff -rq legacy-app "$WORK/step1" -x target | grep '^Only in' | sed "s#$WORK/##"; } > "$OUT/02-step1-diff.txt"
|
||||
{ echo "# 03-step1-build: after step 1, JDK 17"; tests "$WORK/step1"; } > "$OUT/03-step1-build.txt"
|
||||
|
||||
s=$(date +%s); ./scripts/rewrite.sh "$WORK/step1" "$WORK/step2" $R2 >/dev/null 2>&1; e=$(date +%s)
|
||||
{ echo "# 04-step2-diff: recipe $R2 applied on top of step 1 ($((e-s)) s)"; difff "$WORK/step1" "$WORK/step2"; } > "$OUT/04-step2-diff.txt"
|
||||
{ echo "# 05-step2-build: after step 2, JDK 17"; tests "$WORK/step2"; } > "$OUT/05-step2-build.txt"
|
||||
|
||||
{
|
||||
echo "# 06-what-it-left-behind (in $WORK/step2, after both recipes)"
|
||||
echo "--- leftover 'javax.' string literals or keys in source and resources:"
|
||||
grep -rn 'javax\.' "$WORK/step2/src" | sed "s#$WORK/step2/##" || true
|
||||
echo "--- META-INF files:"; (cd "$WORK/step2/src/main/resources/META-INF" && find . -type f | sort)
|
||||
echo "--- explicit jakarta.servlet-api dependency added to the pom:"; grep -c '<artifactId>jakarta.servlet-api</artifactId>' "$WORK/step2/pom.xml"
|
||||
echo "--- RestTemplate usages (recipe does not touch them):"; grep -rn 'RestTemplate' "$WORK/step2/src" | sed "s#$WORK/step2/##"
|
||||
echo "--- Spring Boot version the recipe stopped at:"; grep -A1 '<artifactId>spring-boot-starter-parent' "$WORK/step2/pom.xml" | grep version
|
||||
} > "$OUT/06-what-it-left-behind.txt"
|
||||
|
||||
rm -rf "$WORK/step3"; cp -r "$WORK/step2" "$WORK/step3"; rm -rf "$WORK/step3/target"
|
||||
sed -i 's#<version>4.0.[0-9]*</version>#<version>4.1.1</version>#' "$WORK/step3/pom.xml"
|
||||
{ echo "# 07-step3-boot-4.1.1: parent bumped by hand from the recipe's 4.0.x to 4.1.1, JDK 17"; grep -A1 '<artifactId>spring-boot-starter-parent' "$WORK/step3/pom.xml" | grep version; tests "$WORK/step3"; } > "$OUT/07-step3-boot-4.1.1.txt"
|
||||
|
||||
# CI gate: dryRun with failOnDryRunResults. Fails on the legacy app, passes once migrated.
|
||||
gate() { (cd "$1" && rm -rf target && mvn -B -q org.openrewrite.maven:rewrite-maven-plugin:6.46.1:dryRun \
|
||||
-Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-spring:6.37.1,org.openrewrite.recipe:rewrite-migrate-java:3.42.1 \
|
||||
-Drewrite.activeRecipes="$2" -D"$3"=true >/dev/null 2>&1; echo "exit code $?"); }
|
||||
|
||||
rm -rf "$WORK/g1" "$WORK/g2"; cp -r legacy-app "$WORK/g1"; cp -r "$WORK/step2" "$WORK/g2"
|
||||
{ echo "# 08-ci-gate (mvn rewrite:dryRun, exit code decides the CI job)"
|
||||
echo "legacy app, -Drewrite.failOnDryRunResults=true (the name you would guess): $(gate "$WORK/g1" $R1 rewrite.failOnDryRunResults)"
|
||||
echo "legacy app, -DfailOnDryRunResults=true (the real user property): $(gate "$WORK/g1" $R1 failOnDryRunResults)"
|
||||
echo "migrated app, -DfailOnDryRunResults=true: $(gate "$WORK/g2" $R1 failOnDryRunResults)"; } > "$OUT/08-ci-gate.txt"
|
||||
|
||||
{ echo "# 00-gotcha-gitignore: same recipe, same project, sources under a .gitignore'd directory"
|
||||
rm -rf "$WORK/ign"; mkdir -p "$WORK/ign"; (cd "$WORK/ign" && git init -q && echo 'legacy/' > .gitignore && cp -r "$OLDPWD/legacy-app" legacy && cd legacy \
|
||||
&& mvn -B org.openrewrite.maven:rewrite-maven-plugin:6.46.1:dryRun -Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-spring:6.37.1,org.openrewrite.recipe:rewrite-migrate-java:3.42.1 -Drewrite.activeRecipes=$R1 2>&1 | grep -E "Applying recipes|Parsing|patch file" )
|
||||
} > "$OUT/00-gotcha-gitignore.txt"
|
||||
ls -1 "$OUT"
|
||||
Reference in New Issue
Block a user