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