From 97f99525037e00861284a1f88277d2dfc51cca96 Mon Sep 17 00:00:00 2001 From: caiajian Date: Fri, 26 Jun 2026 19:31:54 +0800 Subject: [PATCH] =?UTF-8?q?springboot2.7=E6=94=B9=E9=80=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build/build-parent/pom.xml | 19 +- .../spring-boot-2x-compatibility/design.md | 186 ++++++++++++++++++ .../spring-boot-2x-compatibility/proposal.md | 42 ++++ .../specs/jackson2-integration/spec.md | 70 +++++++ .../spring-boot-2x-autoconfiguration/spec.md | 121 ++++++++++++ .../spring-boot-2x-compatibility/tasks.md | 96 +++++++++ persistence/jackson/pom.xml | 8 +- .../jackson/api/TimefoldJacksonModule.java | 10 +- ...ConstraintWeightOverridesDeserializer.java | 18 +- .../ConstraintWeightOverridesSerializer.java | 14 +- .../AbstractScoreJacksonDeserializer.java | 4 +- .../score/AbstractScoreJacksonSerializer.java | 24 +-- ...bleBigDecimalScoreJacksonDeserializer.java | 8 +- .../BendableScoreJacksonDeserializer.java | 8 +- ...oftBigDecimalScoreJacksonDeserializer.java | 8 +- ...ardMediumSoftScoreJacksonDeserializer.java | 8 +- ...oftBigDecimalScoreJacksonDeserializer.java | 8 +- .../HardSoftScoreJacksonDeserializer.java | 8 +- .../PolymorphicScoreJacksonDeserializer.java | 90 +++++++-- .../PolymorphicScoreJacksonSerializer.java | 14 +- ...pleBigDecimalScoreJacksonDeserializer.java | 8 +- .../score/SimpleScoreJacksonDeserializer.java | 8 +- .../ConstraintRefJacksonDeserializer.java | 12 +- .../ConstraintRefJacksonSerializer.java | 14 +- .../common/BreakJacksonDeserializer.java | 14 +- .../stream/common/BreakJacksonSerializer.java | 12 +- .../LoadBalanceJacksonDeserializer.java | 14 +- .../common/LoadBalanceJacksonSerializer.java | 14 +- .../SequenceChainJacksonDeserializer.java | 14 +- .../SequenceChainJacksonSerializer.java | 12 +- .../common/SequenceJacksonDeserializer.java | 14 +- .../common/SequenceJacksonSerializer.java | 12 +- .../api/score/stream/common/package-info.java | 2 +- .../solution/JacksonSolutionFileIO.java | 12 +- .../jackson/src/main/java/module-info.java | 6 +- ... => com.fasterxml.jackson.databind.Module} | 0 .../api/AbstractJacksonRoundTripTest.java | 4 +- .../api/TimefoldJacksonModuleTest.java | 16 +- .../AbstractScoreJacksonRoundTripTest.java | 4 +- ...leBigDecimalScoreJacksonRoundTripTest.java | 4 +- .../BendableScoreJacksonRoundTripTest.java | 4 +- ...ftBigDecimalScoreJacksonRoundTripTest.java | 4 +- ...rdMediumSoftScoreJacksonRoundTripTest.java | 4 +- ...ftBigDecimalScoreJacksonRoundTripTest.java | 4 +- .../HardSoftScoreJacksonRoundTripTest.java | 4 +- ...leBigDecimalScoreJacksonRoundTripTest.java | 4 +- .../SimpleScoreJacksonRoundTripTest.java | 4 +- .../common/LoadBalanceRoundTripTest.java | 9 +- .../stream/common/SequenceRoundTripTest.java | 11 +- .../testdomain/JacksonTestdataSolution.java | 4 +- spring-integration/pom.xml | 20 +- .../spring-boot-autoconfigure/pom.xml | 29 ++- .../IncludeAbstractClassesEntityScanner.java | 4 +- ...tFactory.java => SolverConfigFactory.java} | 19 +- .../TimefoldSolverAotContribution.java | 45 ----- .../TimefoldSolverAutoConfiguration.java | 22 +-- .../TimefoldSolverBeanFactory.java | 7 +- .../src/main/java/module-info.java | 3 +- .../main/resources/META-INF/spring.factories | 4 + .../basic/EmptySpringTestConfiguration.java | 2 +- .../NoConstraintsSpringTestConfiguration.java | 2 +- ...pplierVariableSpringTestConfiguration.java | 2 +- ...tipleSolutionsSpringTestConfiguration.java | 2 +- ...tatedInterfaceSpringTestConfiguration.java | 2 +- .../BothAnnotatedSpringTestConfiguration.java | 2 +- ...AnnotatedMixedSpringTestConfiguration.java | 2 +- ...otatedAbstractSpringTestConfiguration.java | 2 +- .../BothAnnotatedSpringTestConfiguration.java | 2 +- ...pleInheritanceSpringTestConfiguration.java | 2 +- ...yBaseAnnotatedSpringTestConfiguration.java | 2 +- ...ChildAnnotatedSpringTestConfiguration.java | 2 +- ...nnotatedMemberSpringTestConfiguration.java | 2 +- ...pplierVariableSpringTestConfiguration.java | 2 +- .../MultiModuleSpringTestConfiguration.java | 2 +- .../spring-boot-integration-test/pom.xml | 13 +- .../spring-boot-starter/pom.xml | 10 +- 76 files changed, 890 insertions(+), 328 deletions(-) create mode 100644 openspec/changes/spring-boot-2x-compatibility/design.md create mode 100644 openspec/changes/spring-boot-2x-compatibility/proposal.md create mode 100644 openspec/changes/spring-boot-2x-compatibility/specs/jackson2-integration/spec.md create mode 100644 openspec/changes/spring-boot-2x-compatibility/specs/spring-boot-2x-autoconfiguration/spec.md create mode 100644 openspec/changes/spring-boot-2x-compatibility/tasks.md rename persistence/jackson/src/main/resources/META-INF/services/{tools.jackson.databind.JacksonModule => com.fasterxml.jackson.databind.Module} (100%) rename spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/{TimefoldSolverAotFactory.java => SolverConfigFactory.java} (69%) delete mode 100644 spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAotContribution.java create mode 100644 spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories diff --git a/build/build-parent/pom.xml b/build/build-parent/pom.xml index 781c6f3ca94..2d0d939b753 100644 --- a/build/build-parent/pom.xml +++ b/build/build-parent/pom.xml @@ -31,7 +31,7 @@ The 'revision' property value is conserved by the flatten-maven-plugin, so the model project does not override it. --> ${revision} - 1.5.34 + 1.2.12 1.5.9 4.1.0 3.36.2 @@ -46,12 +46,11 @@ 1.6.3 3.33.0 2.0.5 - 4.1.0 + 2.7.18 3.13.2 9.10.1 - - 3.2.0 - 2.22 + + 2.13.5 @@ -347,6 +346,14 @@ * + + + org.springframework + spring-jcl + + * + + compile @@ -637,7 +644,7 @@ false true ${skipUTs} - + --add-opens ai.timefold.solver.spring.boot.autoconfigure/ai.timefold.solver.spring.boot.autoconfigure.invalid.type=spring.core diff --git a/openspec/changes/spring-boot-2x-compatibility/design.md b/openspec/changes/spring-boot-2x-compatibility/design.md new file mode 100644 index 00000000000..4056f436d0e --- /dev/null +++ b/openspec/changes/spring-boot-2x-compatibility/design.md @@ -0,0 +1,186 @@ +## Context + +Timefold Solver 当前在 `main` 分支上适配 Spring Boot 4.1.0(Spring Framework 7.x),使用 Java 21 编译,依赖 Jackson 3 (`tools.jackson`)、AOT 编译支持(Spring 6+ 引入的 `BeanFactoryInitializationAot*` API)以及 `spring-boot-persistence` 模块(SB 3.0+ 引入)。本次改造需要在独立分支 `caj/spring-boot-2x` 上将整个项目降级至 Spring Boot 2.7.18 兼容,且后续需频繁 rebase main 以保持与上游同步。 + +### 当前依赖关系图 + +``` +timefold-solver-spring-integration/ +├── spring-integration/pom.xml +│ ├── imports tools.jackson:jackson-bom (Jackson 3) ← 需替换 +│ └── imports spring-boot-dependencies (SB 4.1.0) ← 需降级 +├── spring-boot-starter/ +│ ├── depends spring-boot-starter ← 版本由 BOM 管理 +│ ├── depends spring-boot-persistence ⚠️ SB 2.7 不存在 ← 需移除 +│ └── depends timefold-solver-jackson (Jackson 3) ← 需降级后适配 +├── spring-boot-autoconfigure/ +│ ├── depends spring-boot-persistence ⚠️ SB 2.7 不存在 ← 需移除 +│ ├── imports EntityScan/EntityScanner/EntityScanPackages +│ │ from org.springframework.boot.persistence.autoconfigure ← 包路径迁移 +│ ├── implements BeanFactoryInitializationAotProcessor ← 需移除 +│ ├── contains TimefoldSolverAotContribution ← 需删除 +│ ├── contains TimefoldSolverAotFactory ← 需重构 +│ └── uses NativeDetector (Spring 5.3 中存在 ✓) ← 保留但简化 +└── spring-boot-integration-test/ + └── native profile uses process-aot goal ← 需移除 + +timefold-solver-persistence/jackson/ +├── 依赖 tools.jackson (Jackson 3) ← 全模块降级 +├── JacksonModule 构建 API (Jackson 3 专属) ← 需适配 +└── 60+ 文件使用 tools.jackson.* import ← 批量替换 +``` + +### SB 2.7.18 关键依赖版本 + +| 依赖 | SB 4.1.0 版本 | SB 2.7.18 版本 | +|------|-------------|--------------| +| Spring Framework | 7.x | 5.3.31 | +| Jackson BOM | (不受 SB 管理) | 2.13.5 | +| JUnit Jupiter | 6.1.0 (自定义) | 5.8.2 | +| Hibernate | (受 SB 管理) | 5.6.15.Final | +| Micrometer | (受 SB 管理) | 1.9.17 | +| Logback | 1.5.34 (自定义) | 1.2.12 | +| SLF4J | (受 SB 管理) | 1.7.36 | +| SnakeYAML | (受 SB 管理) | 1.30 | +| Jakarta XML Bind | (自定义版本) | 2.3.3 (SB BOM) | +| Jakarta Persistence | (自定义版本) | 2.2.3 (SB BOM) | +| Jakarta Validation | (自定义版本) | 2.0.2 (SB BOM) | +| Jakarta CDI | 4.1.0 (自定义) | (不在 SB 2.7 BOM 中) | + +## Goals / Non-Goals + +**Goals:** + +- 整个项目(core、persistence、spring-integration、service、tools)在 Java 17 + SB 2.7.18 下成功编译 +- spring-integration 和 spring-boot-integration-test 的所有测试通过 +- Jackson 序列化/反序列化完全兼容 Jackson 2.13.x API +- 自动配置在 SB 2.7 下的 Bean 装配行为与当前 SB 4.x 一致(多求解器、约束校验器等场景) +- Quarkus 集成模块保持原有版本不变 +- 代码结构便于后续 rebase main 时最小化冲突 + +**Non-Goals:** + +- 不支持 SB 2.7 的 GraalVM Native Image(配置保留但不保证可用) +- 不提供 Jackson 3 和 Jackson 2 双版本共存(仅保留 Jackson 2) +- 不修改 core 模块的核心引擎代码 +- 不修改 service 模块(基于 Quarkus/CDI) +- 不追求与 SB 3.x 同时兼容(此分支仅针对 SB 2.7.x) + +## Decisions + +### 决策 1: Jackson 3 → Jackson 2 完整降级 + +**选择**: 将 `persistence/jackson/` 整个模块从 `tools.jackson` (Jackson 3) 降级到 `com.fasterxml.jackson` (Jackson 2.13.5) + +**替代方案及否决理由**: +- ❌ 双版本共存(维护两个 Jackson 模块): 增加维护成本,且此次目标是 SB 2.7 专项适配 +- ❌ 仅在 spring-integration 中做转换层: core 和 persistence 已有深度 Jackson 集成,转换层不可行 + +**具体 API 映射**: + +| Jackson 3 (`tools.jackson`) | Jackson 2 (`com.fasterxml.jackson`) | +|------------------------------|--------------------------------------| +| `tools.jackson.databind.JacksonModule` | `com.fasterxml.jackson.databind.Module` | +| `tools.jackson.databind.json.JsonMapper` | `com.fasterxml.jackson.databind.json.JsonMapper` (同名但有细微差异) | +| `tools.jackson.core.JacksonException` | `com.fasterxml.jackson.core.JacksonException` | +| `tools.jackson.databind.ValueDeserializer` | `com.fasterxml.jackson.databind.ValueDeserializer` | +| `tools.jackson.databind.DatabindException` | `com.fasterxml.jackson.databind.DatabindException` | + +**风险**: `JsonMapper` 构造方式在 Jackson 3 中有所变化(如 builder 方法名、module 注册方式),需要逐一适配。 + +### 决策 2: AOT 代码处理策略 + +**选择**: 完全删除 AOT 相关类,将 `TimefoldSolverAutoConfiguration` 重构为纯 `@Configuration` + `@Bean` 模式 + +**当前 AOT 代码结构**: +``` +TimefoldSolverAutoConfiguration + ├── implements BeanFactoryInitializationAotProcessor ← 删除 + ├── processAheadOfTime() → TimefoldSolverAotContribution ← 删除 + └── postProcessBeanDefinitionRegistry() ← 重构为 @Bean 方法 + └── 通过注册 RootBeanDefinition 创建 SolverConfig/SolverManager Bean +``` + +**重构后**: +``` +TimefoldSolverAutoConfiguration + └── @Bean 方法直接创建 SolverConfig/SolverManager Bean + 通过 TimefoldSolverAotFactory → 重命名为 SolverConfigFactory + 作为普通的 @Configuration 内部工厂方法 +``` + +**理由**: SB 2.7 中 `@Configuration` 类本身就支持 `@Bean` 方法的条件装配,不需要通过 `BeanDefinitionRegistryPostProcessor` 手动注册 Bean。简化了代码路径,同时保持了多求解器配置场景的正确性。 + +**保留**: `TimefoldSolverAotFactory` 重命名为 `SolverConfigFactory`,保留其从 XML 字符串反序列化 SolverConfig 的逻辑,但不再作为 AOT 工厂,而是作为普通 Spring Bean 工厂使用。 + +### 决策 3: spring-boot-persistence 替换方案 + +**选择**: 移除 `spring-boot-persistence` 依赖,将 `EntityScan`/`EntityScanner`/`EntityScanPackages` 的 import 改为 `org.springframework.boot.autoconfigure.domain.*` + +**影响细节**: +- `IncludeAbstractClassesEntityScanner` 继承的 `EntityScanner` 在 SB 2.7 中位于 `org.springframework.boot.autoconfigure.domain.EntityScanner` +- API 签名完全一致,仅包路径不同 +- `@EntityScan` 注解移到 `org.springframework.boot.autoconfigure.domain.EntityScan` + +### 决策 4: 自动配置注册文件 + +**选择**: 创建 `META-INF/spring.factories`,同时保留 `AutoConfiguration.imports`(SB 2.7 会忽略后者,不会产生冲突) + +``` +# spring.factories (SB 2.7 使用) +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +ai.timefold.solver.spring.boot.autoconfigure.TimefoldSolverAutoConfiguration,\ +ai.timefold.solver.spring.boot.autoconfigure.TimefoldBenchmarkAutoConfiguration,\ +ai.timefold.solver.spring.boot.autoconfigure.TimefoldSolverBeanFactory +``` + +### 决策 5: 依赖版本管理策略 + +**选择**: 尽量使用 SB 2.7.18 BOM 管理的版本,仅对 Timefold 明确需要的版本进行显式覆盖 + +**显式保留版本覆盖的依赖**: +- `jakarta.xml.bind-api` / `jaxb-runtime`: core 深度依赖,版本由 Timefold 自己管理 +- `jakarta.persistence-api`: JPA 模块使用,版本独立于 SB +- `jakarta.enterprise.cdi-api`: service 模块使用(Quarkus 生态,独立于 Spring) +- `gizmo2`: Quarkus Gizmo 字节码库,独立版本 +- `ow2.asm`: 字节码操作,独立版本 +- `freemarker`: 模板引擎,独立版本 +- `commons-math3`: 数学库,独立版本 +- `jspecify`: JSpecify 注解,独立版本 +- `json-schema-validator`: JSON Schema 验证,独立版本 + +**交由 SB 2.7 BOM 管理的依赖**: +- Jackson 2 (com.fasterxml.jackson) +- JUnit Jupiter +- Mockito +- AssertJ +- Logback / SLF4J +- Micrometer +- SnakeYAML +- Hibernate (仅 persistence/jpa 测试中使用) + +### 决策 6: Java 编译器目标版本 + +**选择**: `maven.compiler.release` 从 21 改为 17 + +**理由**: SB 2.7.18 官方支持 Java 17,且 Timefold 当前代码使用的语言特性(records、var、text blocks、Stream.toList()、String.formatted())全部在 Java 17 中已 GA。 + +**需要检查的风险点**: 代码中是否使用了 Java 18+ 的 API(如 `Character.isEmoji()` 等),如有需要替换为兼容实现。 + +## Risks / Trade-offs + +| 风险 | 概率 | 影响 | 缓解措施 | +|------|------|------|----------| +| Jackson 2 API 细微差异导致序列化行为变化 | 中 | 高 | 运行全部 persistence/jackson 测试 + spring-integration 测试,验证 round-trip 序列化 | +| AOT 重构后多求解器场景下 Bean 装配顺序问题 | 中 | 中 | 保留现有的多求解器测试用例(`TimefoldSolverMultipleSolverAutoConfigurationTest`),确保全部通过 | +| SB 2.7 的 Spring 5.3 `@Configuration(proxyBeanMethods=false)` 行为差异 | 低 | 低 | 两个版本均支持此配置,行为一致 | +| WireMock 版本降级可能导致测试 API 不兼容 | 中 | 低 | WireMock 仅在测试中使用,如遇到不兼容的 API 可单独调整测试代码 | +| Rebase main 时 `build-parent/pom.xml` 持续冲突 | 高 | 中 | 尽量减少不必要的版本变量变更;只改与降级直接相关的版本属性 | +| `jakarta.*` 版本在 SB 2.7 BOM 和 Timefold 现有版本间可能冲突 | 中 | 中 | 对 JAXB/JPA/CDI 等使用显式版本覆盖,不依赖 SB BOM 管理这些 Jakarta API 版本 | +| Testcontainers 大版本差异(2.0.5 → 1.17.x) | 中 | 中 | 仅在少数集成测试中使用,如遇到不兼容需逐个适配 | + +## Open Questions + +- **WireMock 版本**: 当前使用 3.13.2,SB 2.7 不管理 WireMock。是否需要显式降级到 2.x 版本?还是可以保留 3.x(WireMock 3 需要 Java 11+,与 Java 17 兼容)? +- **Logback 版本冲突风险**: SB 2.7.18 管理 logback 1.2.12,当前 Timefold 声明了 1.5.34。降级后是否会有 SLF4J 绑定兼容问题? +- **Testcontainers 兼容性**: SB 2.7.18 BOM 管理的是旧版 Testcontainers,而当前代码使用 2.0.5 版本。是否应该保持较新版本(手动覆盖)? diff --git a/openspec/changes/spring-boot-2x-compatibility/proposal.md b/openspec/changes/spring-boot-2x-compatibility/proposal.md new file mode 100644 index 00000000000..b4fd91c6f44 --- /dev/null +++ b/openspec/changes/spring-boot-2x-compatibility/proposal.md @@ -0,0 +1,42 @@ +## Why + +当前 Timefold Solver 基于 Spring Boot 4.1.0 和 Java 21 构建,而大量企业生产环境仍运行在 Spring Boot 2.7.x + Java 17 生态上。此次降级旨在让 Timefold Solver 能够在 Spring Boot 2.7.18 环境中正常编译、运行和测试,使存量企业用户无需升级 Spring Boot 大版本即可使用最新版本的 Timefold Solver。 + +## What Changes + +- **Spring Boot 版本降级**: 4.1.0 → 2.7.18,连带 Spring Framework 7.x → 5.3.31 +- **Java 编译版本降级**: 21 → 17 +- **Jackson 版本降级**: Jackson 3 (`tools.jackson`) → Jackson 2 (`com.fasterxml.jackson`),影响 `persistence/jackson/` 整个模块和 `spring-integration/` 中的相关自动配置 +- **移除 AOT 编译支持**: 删除 `BeanFactoryInitializationAotContribution`、`BeanFactoryInitializationAotProcessor` 相关代码,这些是 Spring Framework 6+ 才有的 API +- **替换 spring-boot-persistence 依赖**: SB 2.7 中不存在 `spring-boot-persistence` 模块,`EntityScan`/`EntityScanner`/`EntityScanPackages` 改为从 `spring-boot-autoconfigure` 中引入(包路径从 `o.s.b.persistence.autoconfigure` → `o.s.b.autoconfigure.domain`) +- **自动配置注册机制迁移**: `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`(SB 3.0+)→ `META-INF/spring.factories`(SB 2.7) +- **JUnit 版本降级**: 6.1.0 → 由 SB 2.7 BOM 管理(5.8.2),移除显式版本声明 +- **关联依赖版本对齐**: WireMock、Testcontainers、Logback、Micrometer 等版本随 SB 2.7 BOM 调整 +- **JPMS module-info.java 更新**: 移除 `requires spring.boot.persistence`,更新 Jackson module require 语句 + +## Capabilities + +### New Capabilities + +- `spring-boot-2x-autoconfiguration`: Spring Boot 2.7.x 自动配置支持,包括 SolverFactory/SolverManager Bean 自动装配、多求解器配置、基准测试自动配置 +- `jackson2-integration`: 基于 Jackson 2 的 Score 序列化/反序列化、SolutionFileIO、TimefoldJacksonModule 注册 + +### Modified Capabilities + +(无——当前不存在已有 specs,本次为全新定义) + +## Impact + +| 影响范围 | 说明 | +|----------|------| +| `build/build-parent/pom.xml` | 版本属性变更(SB、Spring Framework、Jackson、JUnit、Java 编译器版本等) | +| `spring-integration/pom.xml` | Jackson BOM 和 Spring Boot BOM 导入替换 | +| `spring-integration/spring-boot-autoconfigure/` | AOT 代码移除、EntityScan 包路径迁移、Jackson 2 适配、spring.factories 创建(约 8 个主代码文件 + 30+ 测试文件) | +| `spring-integration/spring-boot-starter/` | 移除 spring-boot-persistence 依赖、module-info 更新 | +| `spring-integration/spring-boot-integration-test/` | 移除 native profile 中的 process-aot goal | +| `persistence/jackson/` | 全部约 60 个 Java 文件:`tools.jackson.*` → `com.fasterxml.jackson.*` 导入替换、API 适配 | +| `core/` | 无改动(核心引擎独立于 Spring 版本) | +| `persistence/jaxb/`, `persistence/jpa/` | 无改动(jakarta.* 依赖独立管理,不受 Spring 版本影响) | +| `service/` | 无改动(基于 Quarkus/CDI,独立于 Spring 版本) | +| `quarkus-integration/` | 无改动 | +| `tools/` | 无改动 | diff --git a/openspec/changes/spring-boot-2x-compatibility/specs/jackson2-integration/spec.md b/openspec/changes/spring-boot-2x-compatibility/specs/jackson2-integration/spec.md new file mode 100644 index 00000000000..ab6fb4701f7 --- /dev/null +++ b/openspec/changes/spring-boot-2x-compatibility/specs/jackson2-integration/spec.md @@ -0,0 +1,70 @@ +## ADDED Requirements + +### Requirement: Jackson 2 序列化支持 + +系统 SHALL 使用 Jackson 2 (`com.fasterxml.jackson`) API 提供 Score 类型的序列化和反序列化支持,而非 Jackson 3 (`tools.jackson`)。 + +#### Scenario: Score 对象序列化 + +- **WHEN** 通过 Jackson 2 `ObjectMapper` 将 `HardSoftScore` 对象序列化为 JSON +- **THEN** 输出格式为 `{"hardScore": , "softScore": }`,与当前格式一致 + +#### Scenario: Score 对象反序列化 + +- **WHEN** 通过 Jackson 2 `ObjectMapper` 将 JSON 字符串反序列化为 `HardSoftScore` 对象 +- **THEN** 正确还原 Score 对象的 hardScore 和 softScore 值 + +### Requirement: Jackson Module 自动注册 + +系统 SHALL 提供 `TimefoldJacksonModule`(实现 `com.fasterxml.jackson.databind.Module`)用于注册所有 Timefold 自定义序列化器和反序列化器。 + +#### Scenario: Module 注册 + +- **WHEN** 用户调用 `TimefoldJacksonModule.createModule()` 并将返回的 Module 注册到 `ObjectMapper` +- **THEN** ObjectMapper 能够正确序列化和反序列化所有 Timefold Score 类型、ConstraintRef 类型、以及常用的约束流数据类型(Break、Sequence、SequenceChain、LoadBalance) + +#### Scenario: Spring Boot 自动配置下自动注册 + +- **WHEN** Spring Boot 应用 classpath 上同时存在 `JsonMapper` 类和 Score 类 +- **THEN** `TimefoldJacksonConfiguration` 自动创建一个 `JacksonModule` Bean 并注册到 Spring 的 Jackson 自动配置中 + +### Requirement: Score 全类型 Jackson 2 支持 + +系统 SHALL 支持以下 Score 类型在 Jackson 2 下的完整序列化/反序列化 round-trip: + +- `SimpleScore` +- `SimpleBigDecimalScore` +- `HardSoftScore` +- `HardSoftBigDecimalScore` +- `HardMediumSoftScore` +- `HardMediumSoftBigDecimalScore` +- `BendableScore` +- `BendableBigDecimalScore` + +#### Scenario: 每种 Score 类型的 round-trip + +- **WHEN** 将每种 Score 类型的实例通过 Jackson 2 序列化再反序列化 +- **THEN** 反序列化后的对象 `equals` 原始对象 + +### Requirement: SolutionFileIO 使用 Jackson 2 + +系统 SHALL 通过 `JacksonSolutionFileIO` 使用 Jackson 2 `ObjectMapper` 读写规划求解的输入输出文件。 + +#### Scenario: 读取 Solution JSON 文件 + +- **WHEN** 使用 `JacksonSolutionFileIO` 读取一个包含规划解决方案的 JSON 文件 +- **THEN** 正确反序列化为 `@PlanningSolution` 注解的 Java 对象 + +#### Scenario: 写入 Solution JSON 文件 + +- **WHEN** 使用 `JacksonSolutionFileIO` 将规划解决方案写入 JSON 文件 +- **THEN** 输出格式正确的 JSON 文件,可被同一 `JacksonSolutionFileIO` 再次读取 + +### Requirement: 泛型 Score 多态序列化 + +系统 SHALL 支持 `Score` 接口的泛型多态序列化/反序列化,允许字段声明为 `Score` 类型而运行时确定具体实现类。 + +#### Scenario: 多态反序列化 + +- **WHEN** JSON 中包含 `@class` 属性指定具体 Score 类型(如 `ai.timefold.solver.core.api.score.HardSoftScore`) +- **THEN** Jackson 2 正确反序列化为指定的具体 Score 实现类 diff --git a/openspec/changes/spring-boot-2x-compatibility/specs/spring-boot-2x-autoconfiguration/spec.md b/openspec/changes/spring-boot-2x-compatibility/specs/spring-boot-2x-autoconfiguration/spec.md new file mode 100644 index 00000000000..b7619366491 --- /dev/null +++ b/openspec/changes/spring-boot-2x-compatibility/specs/spring-boot-2x-autoconfiguration/spec.md @@ -0,0 +1,121 @@ +## ADDED Requirements + +### Requirement: 自动配置与 Spring Boot 2.7 兼容 + +系统 SHALL 在 Spring Boot 2.7.x 环境下正确加载所有 Timefold 自动配置类,通过 `META-INF/spring.factories` 注册。 + +#### Scenario: 自动配置注册文件存在 + +- **WHEN** 应用启动时 Spring Boot 扫描 `META-INF/spring.factories` +- **THEN** 系统找到 `org.springframework.boot.autoconfigure.EnableAutoConfiguration` 键下注册的 `TimefoldSolverAutoConfiguration`、`TimefoldBenchmarkAutoConfiguration` 和 `TimefoldSolverBeanFactory` + +#### Scenario: AutoConfiguration.imports 文件不影响启动 + +- **WHEN** 应用在 SB 2.7 中启动且 classpath 上存在 `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` 文件 +- **THEN** SB 2.7 忽略该文件,不产生任何错误或警告日志(该文件仅 SB 3.0+ 识别) + +### Requirement: SolverConfig Bean 自动装配 + +系统 SHALL 自动扫描 classpath 上的 `@PlanningSolution` 和 `@PlanningEntity` 注解类,创建对应的 `SolverConfig` Bean,支持单求解器和多求解器两种模式。 + +#### Scenario: 单求解器、无 XML 配置 + +- **WHEN** classpath 上存在一个 `@PlanningSolution` 注解类、一个 `ConstraintProvider` 实现类,且未配置 timefold.solver-config-xml +- **THEN** 系统自动创建一个名为 "defaultSolverConfig" 的 `SolverConfig` Bean,其 solutionClass 和 entityClassList 被正确设置 + +#### Scenario: 多求解器配置 + +- **WHEN** timefold.solver 属性配置了多个命名求解器(如 `timefold.solver.solver1.*` 和 `timefold.solver.solver2.*`) +- **THEN** 系统为每个配置的求解器创建一个 `SolverManager` Bean,名称对应配置的 key + +#### Scenario: 无 @PlanningSolution 类时不报错 + +- **WHEN** classpath 上没有任何 `@PlanningSolution` 或 `@PlanningEntity` 注解类 +- **THEN** 系统输出 WARN 日志并跳过 Timefold 加载,应用正常启动 + +### Requirement: SolverFactory Bean 自动装配 + +系统 SHALL 在存在 SolverConfig Bean 时自动创建 `SolverFactory` Bean。 + +#### Scenario: SolverFactory 正常创建 + +- **WHEN** 存在一个有效的 SolverConfig Bean +- **THEN** 系统自动创建并提供 `SolverFactory` Bean(Lazy 初始化) + +#### Scenario: 多求解器场景下不创建 SolverFactory + +- **WHEN** 配置了多个命名求解器 +- **THEN** 尝试注入 `SolverFactory` Bean 时抛出 `BeanCreationException`,提示应改为注入具体名称的 `SolverManager` + +### Requirement: SolverManager Bean 自动装配 + +系统 SHALL 在存在 SolverFactory Bean 时自动创建 `SolverManager` Bean。 + +#### Scenario: SolverManager 正常创建 + +- **WHEN** 存在一个有效的 SolverFactory Bean +- **THEN** 系统根据 `timefold.solver-manager.parallel-solver-count` 配置创建 `SolverManager` Bean + +### Requirement: SolutionManager Bean 自动装配 + +系统 SHALL 在存在 SolverFactory Bean 时自动创建 `SolutionManager` Bean。 + +#### Scenario: SolutionManager 正常创建 + +- **WHEN** 存在一个有效的 SolverFactory Bean +- **THEN** 系统自动创建 `SolutionManager` Bean + +### Requirement: ConstraintVerifier Bean 自动装配 + +系统 SHALL 在存在 ConstraintProvider 类且可用 `ConstraintVerifier` 类时,自动创建 `ConstraintVerifier` Bean。 + +#### Scenario: ConstraintVerifier 正常创建 + +- **WHEN** SolverConfig 配置了 ConstraintProvider 类,且 classpath 上有 `ConstraintVerifier` 类 +- **THEN** 系统自动创建对应的 `ConstraintVerifier` Bean + +#### Scenario: 无 ConstraintProvider 时返回占位 Bean + +- **WHEN** SolverConfig 未配置 ConstraintProvider 类 +- **THEN** 系统返回一个抛出 `UnsupportedOperationException` 的占位 `ConstraintVerifier` Bean,不阻止应用启动 + +### Requirement: 基准测试自动配置 + +系统 SHALL 在 classpath 上存在 `PlannerBenchmarkFactory` 类时,自动创建 `PlannerBenchmarkConfig` 和 `PlannerBenchmarkFactory` Bean。 + +#### Scenario: Benchmark 正常创建 + +- **WHEN** classpath 上存在 `PlannerBenchmarkFactory` 类,且存在有效的 SolverConfig Bean +- **THEN** 系统自动创建 `PlannerBenchmarkConfig` 和 `PlannerBenchmarkFactory` Bean + +#### Scenario: 多求解器场景下不允许 Benchmark + +- **WHEN** 配置了多个命名求解器 +- **THEN** 尝试创建 Benchmark Bean 时抛出 `IllegalStateException` + +### Requirement: @EntityScan 注解识别 + +系统 SHALL 支持 `@EntityScan` 注解(位于 `org.springframework.boot.autoconfigure.domain.EntityScan`),用于指定 Timefold 领域类的扫描包。 + +#### Scenario: @EntityScan 指定额外扫描包 + +- **WHEN** `@SpringBootApplication` 类上使用了 `@EntityScan(basePackages = "com.example.domain")` +- **THEN** `IncludeAbstractClassesEntityScanner` 同时扫描默认包和 `com.example.domain` 包中的 `@PlanningSolution` 和 `@PlanningEntity` 注解类 + +### Requirement: Spring Boot 属性绑定 + +系统 SHALL 支持 `timefold.*` 前缀的属性配置,通过 `@EnableConfigurationProperties(TimefoldProperties.class)` 绑定。 + +#### Scenario: 属性正确绑定 + +- **WHEN** application.properties 中配置了 `timefold.solver-config-xml=classpath:solverConfig.xml` +- **THEN** `TimefoldProperties` 中 `solverConfigXml` 字段的值为 `classpath:solverConfig.xml` + +### Requirement: 终止条件属性配置 + +系统 SHALL 支持通过 application.properties 配置求解终止条件,覆盖 XML 中的配置。 + +#### Scenario: 配置 spent-limit + +- **WHEN** 配置了 `timefold.solver.termination.spent-limit=30s` +- **THEN** SolverConfig 的 TerminationConfig 中 spentLimit 被设置为 30 秒 diff --git a/openspec/changes/spring-boot-2x-compatibility/tasks.md b/openspec/changes/spring-boot-2x-compatibility/tasks.md new file mode 100644 index 00000000000..cbc754b964b --- /dev/null +++ b/openspec/changes/spring-boot-2x-compatibility/tasks.md @@ -0,0 +1,96 @@ +## 1. 构建基础层:依赖版本降级 + +- [x] 1.1 修改 `build/build-parent/pom.xml`:将 `version.org.springframework.boot` 从 4.1.0 改为 2.7.18 +- [x] 1.2 修改 `build/build-parent/pom.xml`:将 `maven.compiler.release` 从 21 改为 17 +- [ ] 1.3 修改 `build/build-parent/pom.xml`:移除 `version.tools.jackson` 属性(`3.2.0`),添加或确认 `version.com.fasterxml.jackson.core` 由 SB 2.7 BOM 管理(2.13.5) +- [ ] 1.4 修改 `build/build-parent/pom.xml`:将 `version.org.junit.jupiter` 从 6.1.0 移除(交由 SB BOM 管理 5.8.2) +- [ ] 1.5 修改 `build/build-parent/pom.xml`:移除 `junit-bom` 的显式版本导入(交由 SB BOM 管理) +- [ ] 1.6 修改 `build/build-parent/pom.xml`:检查并确认 `jakarta.*` 相关版本属性不受 SB BOM 影响,保持 Timefold 自定义版本 +- [ ] 1.7 修改 `build/build-parent/pom.xml`:移除 `enforce-managed-deps-rule` 中对 Jackson 3 版本的约束(如存在) +- [ ] 1.8 运行 `mvn validate` 验证根 POM 结构正确,无 XML 错误 + +## 2. Jackson 3 → Jackson 2 降级 + +- [ ] 2.1 修改 `persistence/jackson/pom.xml`:将 `tools.jackson:jackson-bom` → `com.fasterxml.jackson:jackson-bom`(版本由 SB 2.7 BOM 管理,移除显式版本) +- [ ] 2.2 修改 `persistence/jackson/pom.xml`:将所有 `tools.jackson.core:*` → `com.fasterxml.jackson.core:*` +- [ ] 2.3 批量替换 `persistence/jackson/src/main/java/` 下所有 Java 文件的 import:`tools.jackson.*` → `com.fasterxml.jackson.*` +- [ ] 2.4 适配 `TimefoldJacksonModule.java`:`tools.jackson.databind.JacksonModule` → `com.fasterxml.jackson.databind.Module` +- [ ] 2.5 检查并适配 `JacksonModule.createModule()` / `SimpleModule` 等 API 在 Jackson 2 中的签名差异 +- [ ] 2.6 批量替换 `persistence/jackson/src/test/java/` 下所有 Java 文件的 import:`tools.jackson.*` → `com.fasterxml.jackson.*` +- [ ] 2.7 修改 `persistence/jackson/src/main/java/module-info.java`:更新 Jackson 相关的 `requires` 和 `provides` 语句 +- [ ] 2.8 修改 `spring-integration/pom.xml`:将 `tools.jackson:jackson-bom` → `com.fasterxml.jackson:jackson-bom` +- [ ] 2.9 修改 `spring-integration/spring-boot-autoconfigure/pom.xml`:将 `tools.jackson.core:jackson-databind` → `com.fasterxml.jackson.core:jackson-databind` (optional) +- [ ] 2.10 修改 `TimefoldSolverBeanFactory.java`:将 `tools.jackson.databind.JacksonModule` → `com.fasterxml.jackson.databind.Module`,`tools.jackson.databind.json.JsonMapper` → `com.fasterxml.jackson.databind.json.JsonMapper` +- [ ] 2.11 修改 `spring-integration/spring-boot-autoconfigure/src/main/java/module-info.java`:更新 `requires tools.jackson.databind` → `requires com.fasterxml.jackson.databind` + +## 3. 移除 AOT 编译代码 + +- [ ] 3.1 删除 `TimefoldSolverAotContribution.java` 文件 +- [ ] 3.2 将 `TimefoldSolverAotFactory.java` 重命名为 `SolverConfigFactory.java`,移除 `EnvironmentAware` 接口(改为构造函数注入) +- [ ] 3.3 重构 `TimefoldSolverAutoConfiguration.java`:移除 `implements BeanFactoryInitializationAotProcessor` +- [ ] 3.4 重构 `TimefoldSolverAutoConfiguration.java`:移除 `processAheadOfTime()` 方法 +- [ ] 3.5 重构 `TimefoldSolverAutoConfiguration.java`:将 `postProcessBeanDefinitionRegistry()` 中的 Bean 注册逻辑改为 `@Bean` 方法(SolverConfig、SolverManager 的 Bean 定义) +- [ ] 3.6 重构 `TimefoldSolverAutoConfiguration.java`:移除 `import org.springframework.beans.factory.aot.*` +- [ ] 3.7 修改 `TimefoldSolverAutoConfiguration.java`:移除 `NativeDetector.inNativeImage()` 相关分支(保留 NativeDetector import 但简化 native image 特殊处理,因为 SB 2.7 中不支持 AOT 原生镜像) +- [ ] 3.8 修改 `TimefoldSolverBeanFactory.java`:移除 AOT 相关 import 和注释引用 +- [ ] 3.9 修改 `TimefoldSolverConstraintAutoConfigurationTest.java`:移除 `Mockito.mockStatic(NativeDetector.class)` 的测试 + +## 4. spring-boot-persistence 替换 + +- [ ] 4.1 修改 `spring-integration/spring-boot-autoconfigure/pom.xml`:移除 `spring-boot-persistence` 依赖,确认 `spring-boot-autoconfigure` 依赖已存在 +- [ ] 4.2 修改 `spring-integration/spring-boot-starter/pom.xml`:移除 `spring-boot-persistence` 依赖 +- [ ] 4.3 修改 `IncludeAbstractClassesEntityScanner.java`:将 `import org.springframework.boot.persistence.autoconfigure.EntityScanPackages` → `import org.springframework.boot.autoconfigure.domain.EntityScanPackages` +- [ ] 4.4 修改 `IncludeAbstractClassesEntityScanner.java`:将 `import org.springframework.boot.persistence.autoconfigure.EntityScanner` → `import org.springframework.boot.autoconfigure.domain.EntityScanner` +- [ ] 4.5 修改 `TimefoldSolverAutoConfiguration.java`:将 `import org.springframework.boot.persistence.autoconfigure.EntityScan` → `import org.springframework.boot.autoconfigure.domain.EntityScan` +- [ ] 4.6 修改 `spring-integration/spring-boot-autoconfigure/src/main/java/module-info.java`:移除 `requires spring.boot.persistence` +- [ ] 4.7 批量替换所有测试类中的 `import org.springframework.boot.persistence.autoconfigure.EntityScan` → `import org.springframework.boot.autoconfigure.domain.EntityScan`(约 25 个文件) +- [ ] 4.8 批量替换所有测试类中的 `import org.springframework.boot.persistence.autoconfigure.AutoConfigurationPackage` → 确认 `@AutoConfigurationPackage` 在 SB 2.7 中的包路径(`org.springframework.boot.autoconfigure.AutoConfigurationPackage`) + +## 5. 自动配置注册机制迁移 + +- [ ] 5.1 在 `spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/` 下创建 `spring.factories` +- [ ] 5.2 `spring.factories` 内容:`org.springframework.boot.autoconfigure.EnableAutoConfiguration` 键,注册 `TimefoldSolverAutoConfiguration`、`TimefoldBenchmarkAutoConfiguration`、`TimefoldSolverBeanFactory` +- [ ] 5.3 保留现有的 `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` 文件(SB 2.7 会自动忽略) + +## 6. Native Image 配置清理 + +- [ ] 6.1 修改 `spring-integration/spring-boot-integration-test/pom.xml`:移除 native profile 中的 `process-aot` 和 `process-test-aot` +- [ ] 6.2 确认 native profile 中的 `graalvm.buildtools:native-maven-plugin` 配置调整为与 SB 2.7 兼容(可选:如果用户不需要 native image 可整体移除 native profile) +- [ ] 6.3 保留 `META-INF/native-image/` 下的 reflect-config.json、proxy-config.json、resource-config.json(GraalVM 原生反射配置仍有用) + +## 7. 依赖版本全面对齐 + +- [ ] 7.1 检查 `build/build-parent/pom.xml` 中 `version.org.wiremock`(3.13.2)是否需要降级为 SB 2.7 兼容版本 +- [ ] 7.2 检查 `build/build-parent/pom.xml` 中 `version.ch.qos.logback`(1.5.34)与 SB 2.7 BOM(1.2.12)的兼容性,决定保留自定义版本还是使用 BOM 版本 +- [ ] 7.3 检查 `build/build-parent/pom.xml` 中 `version.org.testcontainers`(2.0.5)是否需要降级,SB 2.7 BOM 管理 1.17.x +- [ ] 7.4 确认 `version.org.assertj`(3.27.7)是否需要降级,SB 2.7 BOM 管理 3.22.0 +- [ ] 7.5 确认 `version.org.awaitility`(4.3.0)是否需要降级,SB 2.7 BOM 管理 4.2.0 +- [ ] 7.6 确认 `version.org.mockito`(由 SB BOM 管理)在 SB 2.7 BOM 中为 4.5.1,检查测试代码兼容性 +- [ ] 7.7 修改 `build/build-parent/pom.xml` 中 `version.com.fasterxml.jackson.core`(2.22)→ 移除显式版本,交由 SB 2.7 BOM 管理(2.13.5) +- [ ] 7.8 修改 `build/build-parent/pom.xml` 中 `version.com.fasterxml.jackson.core:jackson-annotations` 显式声明为可选或移除 +- [ ] 7.9 确认 `jakarta.xml.bind-api`、`jakarta.persistence-api` 等 Jakarta API 的显式版本覆盖与 SB 2.7 BOM 中同名依赖不冲突(Timefold 版本优先级更高) + +## 8. 完整编译验证 + +- [ ] 8.1 运行 `mvn clean compile -pl core` 验证 core 模块编译通过 +- [ ] 8.2 运行 `mvn clean compile -pl persistence/jackson` 验证 Jackson 模块编译通过 +- [ ] 8.3 运行 `mvn clean compile -pl persistence/jaxb` 验证 JAXB 模块编译通过 +- [ ] 8.4 运行 `mvn clean compile -pl persistence/jpa` 验证 JPA 模块编译通过 +- [ ] 8.5 运行 `mvn clean compile -pl spring-integration` 验证 Spring 集成模块编译通过 +- [ ] 8.6 运行 `mvn clean compile -pl service` 验证 Service 模块编译通过 +- [ ] 8.7 运行 `mvn clean compile -pl tools` 验证 Tools 模块编译通过 +- [ ] 8.8 运行 `mvn clean compile` 验证全量编译通过 + +## 9. 测试验证 + +- [ ] 9.1 运行 `mvn test -pl persistence/jackson` 验证 Jackson 序列化 round-trip 测试全部通过 +- [ ] 9.2 运行 `mvn test -pl spring-integration/spring-boot-autoconfigure` 验证所有 Spring 自动配置测试通过 +- [ ] 9.3 运行 `mvn test -pl core` 验证核心引擎测试通过 +- [ ] 9.4 运行 `mvn test -pl persistence/jaxb,persistence/jpa` 验证持久化模块测试通过 +- [ ] 9.5 定位并修复所有因依赖版本变化导致的编译错误或测试失败 + +## 10. 代码格式和清理 + +- [ ] 10.1 运行 `mvn spotless:apply` 统一代码格式 +- [ ] 10.2 确认 `mvn enforcer:enforce` 通过(无重复类冲突、无管理依赖违规) +- [ ] 10.3 提交代码,commit message 涵盖所有降级变更 diff --git a/persistence/jackson/pom.xml b/persistence/jackson/pom.xml index 652c1277f54..b3bc9ab3754 100644 --- a/persistence/jackson/pom.xml +++ b/persistence/jackson/pom.xml @@ -24,9 +24,9 @@ - tools.jackson + com.fasterxml.jackson jackson-bom - ${version.tools.jackson} + ${version.com.fasterxml.jackson.core} pom import @@ -54,7 +54,7 @@ - tools.jackson.core + com.fasterxml.jackson.core jackson-core @@ -62,7 +62,7 @@ jackson-annotations - tools.jackson.core + com.fasterxml.jackson.core jackson-databind diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/TimefoldJacksonModule.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/TimefoldJacksonModule.java index 1585b302fff..bca6f8a988d 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/TimefoldJacksonModule.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/TimefoldJacksonModule.java @@ -47,9 +47,9 @@ import ai.timefold.solver.jackson.api.score.stream.common.SequenceJacksonSerializer; import ai.timefold.solver.jackson.impl.domain.solution.JacksonSolutionFileIO; -import tools.jackson.databind.JacksonModule; -import tools.jackson.databind.json.JsonMapper; -import tools.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; /** * This class adds all Jackson serializers and deserializers. @@ -59,11 +59,11 @@ public class TimefoldJacksonModule extends SimpleModule { /** * Jackson modules can be loaded automatically via {@link java.util.ServiceLoader}. * This will happen if you use {@link JacksonSolutionFileIO}. - * Otherwise, register the module with {@link JsonMapper.Builder#addModule(JacksonModule)}. + * Otherwise, register the module with {@link JsonMapper.Builder#addModule(Module)}. * * @return never null */ - public static JacksonModule createModule() { + public static Module createModule() { return new TimefoldJacksonModule(); } diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/domain/solution/AbstractConstraintWeightOverridesDeserializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/domain/solution/AbstractConstraintWeightOverridesDeserializer.java index 89dd83e6f38..970dc9f7aa7 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/domain/solution/AbstractConstraintWeightOverridesDeserializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/domain/solution/AbstractConstraintWeightOverridesDeserializer.java @@ -5,11 +5,11 @@ import ai.timefold.solver.core.api.domain.solution.ConstraintWeightOverrides; import ai.timefold.solver.core.api.score.Score; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonParser; -import tools.jackson.databind.DeserializationContext; -import tools.jackson.databind.JsonNode; -import tools.jackson.databind.ValueDeserializer; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.JsonDeserializer; /** * Extend this to implement {@link ConstraintWeightOverrides} deserialization specific for your domain. @@ -17,16 +17,16 @@ * @param */ public abstract class AbstractConstraintWeightOverridesDeserializer> - extends ValueDeserializer> { + extends JsonDeserializer> { @Override public final ConstraintWeightOverrides deserialize(JsonParser p, DeserializationContext ctxt) - throws JacksonException { + throws java.io.IOException { var resultMap = new LinkedHashMap(); JsonNode node = p.readValueAsTree(); - node.properties().iterator().forEachRemaining(entry -> { + node.fields().forEachRemaining(entry -> { var constraintId = entry.getKey(); - var weight = parseScore(entry.getValue().asString()); + var weight = parseScore(entry.getValue().asText()); resultMap.put(constraintId, weight); }); return ConstraintWeightOverrides.of(resultMap); diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/domain/solution/ConstraintWeightOverridesSerializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/domain/solution/ConstraintWeightOverridesSerializer.java index b40c9a434c7..ac021a66689 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/domain/solution/ConstraintWeightOverridesSerializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/domain/solution/ConstraintWeightOverridesSerializer.java @@ -5,21 +5,21 @@ import ai.timefold.solver.core.api.domain.solution.ConstraintWeightOverrides; import ai.timefold.solver.core.api.score.Score; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonGenerator; -import tools.jackson.databind.SerializationContext; -import tools.jackson.databind.ValueSerializer; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.JsonSerializer; public final class ConstraintWeightOverridesSerializer> - extends ValueSerializer> { + extends JsonSerializer> { @Override public void serialize(ConstraintWeightOverrides constraintWeightOverrides, JsonGenerator generator, - SerializationContext serializerProvider) throws JacksonException { + SerializerProvider serializerProvider) throws java.io.IOException { generator.writeStartObject(); for (var constraintId : constraintWeightOverrides.getKnownConstraintIds()) { var weight = Objects.requireNonNull(constraintWeightOverrides.getConstraintWeight(constraintId)); - generator.writeStringProperty(constraintId, weight.toString()); + generator.writeStringField(constraintId, weight.toString()); } generator.writeEndObject(); } diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/AbstractScoreJacksonDeserializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/AbstractScoreJacksonDeserializer.java index 3f0b7cf8a57..070c3d9e786 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/AbstractScoreJacksonDeserializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/AbstractScoreJacksonDeserializer.java @@ -2,7 +2,7 @@ import ai.timefold.solver.core.api.score.Score; -import tools.jackson.databind.ValueDeserializer; +import com.fasterxml.jackson.databind.JsonDeserializer; /** * Jackson binding support for a {@link Score} type. @@ -15,6 +15,6 @@ * @param the actual score type */ public abstract class AbstractScoreJacksonDeserializer> - extends ValueDeserializer { + extends JsonDeserializer { } diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/AbstractScoreJacksonSerializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/AbstractScoreJacksonSerializer.java index 09aa724c554..263123f322b 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/AbstractScoreJacksonSerializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/AbstractScoreJacksonSerializer.java @@ -3,13 +3,13 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.jackson.api.TimefoldJacksonModule; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonGenerator; -import tools.jackson.databind.BeanProperty; -import tools.jackson.databind.DatabindException; -import tools.jackson.databind.JavaType; -import tools.jackson.databind.SerializationContext; -import tools.jackson.databind.ValueSerializer; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.JsonSerializer; /** * Jackson binding support for a {@link Score} subtype. @@ -24,11 +24,11 @@ * @see Score * @param the actual score type */ -public abstract class AbstractScoreJacksonSerializer> extends ValueSerializer { +public abstract class AbstractScoreJacksonSerializer> extends JsonSerializer { - @Override - public ValueSerializer createContextual(SerializationContext provider, BeanProperty property) - throws DatabindException { + @SuppressWarnings("unused") + public JsonSerializer createContextual(SerializerProvider provider, BeanProperty property) + throws JsonMappingException { JavaType propertyType = property.getType(); if (Score.class.equals(propertyType.getRawClass())) { // If the property type is Score (not HardSoftScore for example), @@ -40,7 +40,7 @@ public ValueSerializer createContextual(SerializationContext provider, BeanPr } @Override - public void serialize(Score_ score, JsonGenerator generator, SerializationContext serializers) throws JacksonException { + public void serialize(Score_ score, JsonGenerator generator, SerializerProvider serializers) throws java.io.IOException { generator.writeString(score.toString()); } diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/BendableBigDecimalScoreJacksonDeserializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/BendableBigDecimalScoreJacksonDeserializer.java index 9c53a6df22d..210caf847c2 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/BendableBigDecimalScoreJacksonDeserializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/BendableBigDecimalScoreJacksonDeserializer.java @@ -2,15 +2,15 @@ import ai.timefold.solver.core.api.score.BendableBigDecimalScore; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonParser; -import tools.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; public class BendableBigDecimalScoreJacksonDeserializer extends AbstractScoreJacksonDeserializer { @Override - public BendableBigDecimalScore deserialize(JsonParser parser, DeserializationContext context) throws JacksonException { + public BendableBigDecimalScore deserialize(JsonParser parser, DeserializationContext context) throws java.io.IOException { return BendableBigDecimalScore.parseScore(parser.getValueAsString()); } diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/BendableScoreJacksonDeserializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/BendableScoreJacksonDeserializer.java index 0a331354e69..bdaf6425bd5 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/BendableScoreJacksonDeserializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/BendableScoreJacksonDeserializer.java @@ -2,14 +2,14 @@ import ai.timefold.solver.core.api.score.BendableScore; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonParser; -import tools.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; public class BendableScoreJacksonDeserializer extends AbstractScoreJacksonDeserializer { @Override - public BendableScore deserialize(JsonParser parser, DeserializationContext context) throws JacksonException { + public BendableScore deserialize(JsonParser parser, DeserializationContext context) throws java.io.IOException { return BendableScore.parseScore(parser.getValueAsString()); } diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/HardMediumSoftBigDecimalScoreJacksonDeserializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/HardMediumSoftBigDecimalScoreJacksonDeserializer.java index fd37f9ce376..d7c525e1847 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/HardMediumSoftBigDecimalScoreJacksonDeserializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/HardMediumSoftBigDecimalScoreJacksonDeserializer.java @@ -2,16 +2,16 @@ import ai.timefold.solver.core.api.score.HardMediumSoftBigDecimalScore; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonParser; -import tools.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; public class HardMediumSoftBigDecimalScoreJacksonDeserializer extends AbstractScoreJacksonDeserializer { @Override public HardMediumSoftBigDecimalScore deserialize(JsonParser parser, DeserializationContext context) - throws JacksonException { + throws java.io.IOException { return HardMediumSoftBigDecimalScore.parseScore(parser.getValueAsString()); } diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/HardMediumSoftScoreJacksonDeserializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/HardMediumSoftScoreJacksonDeserializer.java index 56745a0eb5e..c3d7d131575 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/HardMediumSoftScoreJacksonDeserializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/HardMediumSoftScoreJacksonDeserializer.java @@ -2,15 +2,15 @@ import ai.timefold.solver.core.api.score.HardMediumSoftScore; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonParser; -import tools.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; public class HardMediumSoftScoreJacksonDeserializer extends AbstractScoreJacksonDeserializer { @Override - public HardMediumSoftScore deserialize(JsonParser parser, DeserializationContext context) throws JacksonException { + public HardMediumSoftScore deserialize(JsonParser parser, DeserializationContext context) throws java.io.IOException { return HardMediumSoftScore.parseScore(parser.getValueAsString()); } diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/HardSoftBigDecimalScoreJacksonDeserializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/HardSoftBigDecimalScoreJacksonDeserializer.java index 95aa1c39f9e..c964e214672 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/HardSoftBigDecimalScoreJacksonDeserializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/HardSoftBigDecimalScoreJacksonDeserializer.java @@ -2,15 +2,15 @@ import ai.timefold.solver.core.api.score.HardSoftBigDecimalScore; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonParser; -import tools.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; public class HardSoftBigDecimalScoreJacksonDeserializer extends AbstractScoreJacksonDeserializer { @Override - public HardSoftBigDecimalScore deserialize(JsonParser parser, DeserializationContext context) throws JacksonException { + public HardSoftBigDecimalScore deserialize(JsonParser parser, DeserializationContext context) throws java.io.IOException { return HardSoftBigDecimalScore.parseScore(parser.getValueAsString()); } diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/HardSoftScoreJacksonDeserializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/HardSoftScoreJacksonDeserializer.java index b78852c7664..8fe41f070ad 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/HardSoftScoreJacksonDeserializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/HardSoftScoreJacksonDeserializer.java @@ -2,14 +2,14 @@ import ai.timefold.solver.core.api.score.HardSoftScore; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonParser; -import tools.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; public class HardSoftScoreJacksonDeserializer extends AbstractScoreJacksonDeserializer { @Override - public HardSoftScore deserialize(JsonParser parser, DeserializationContext context) throws JacksonException { + public HardSoftScore deserialize(JsonParser parser, DeserializationContext context) throws java.io.IOException { return HardSoftScore.parseScore(parser.getValueAsString()); } diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/PolymorphicScoreJacksonDeserializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/PolymorphicScoreJacksonDeserializer.java index e780b58310c..307ee4edfa7 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/PolymorphicScoreJacksonDeserializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/PolymorphicScoreJacksonDeserializer.java @@ -10,31 +10,44 @@ import ai.timefold.solver.core.api.score.SimpleBigDecimalScore; import ai.timefold.solver.core.api.score.SimpleScore; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonParser; -import tools.jackson.databind.DeserializationContext; -import tools.jackson.databind.ValueDeserializer; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; /** * Jackson binding support for a {@link Score} type (but not a subtype). * For a {@link Score} subtype field, use {@link HardSoftScoreJacksonDeserializer} or similar instead. *

- * For example: use - * {@code @JsonSerialize(using = PolymorphicScoreJacksonSerializer.class) @JsonDeserialize(using = PolymorphicScoreJacksonDeserializer.class)} - * on a {@code Score score} field which contains a {@link HardSoftScore} instance - * and it will marshalled to JSON as {@code "score":{"type":"HARD_SOFT",score:"-999hard/-999soft"}}. + * Handles both formats: + *

    + *
  • Object format (from {@link PolymorphicScoreJacksonSerializer}): {@code {"HardSoftScore":"-999hard/-999soft"}}
  • + *
  • String format (from type-specific serializer): {@code "-999hard/-999soft"}
  • + *
* * @see Score - * @see PolymorphicScoreJacksonDeserializer + * @see PolymorphicScoreJacksonSerializer */ -public class PolymorphicScoreJacksonDeserializer extends ValueDeserializer { +public class PolymorphicScoreJacksonDeserializer extends JsonDeserializer { @Override - public Score deserialize(JsonParser parser, DeserializationContext context) throws JacksonException { - parser.nextToken(); - String scoreClassSimpleName = parser.currentName(); - parser.nextToken(); - String scoreString = parser.getValueAsString(); + public Score deserialize(JsonParser parser, DeserializationContext context) throws java.io.IOException { + if (parser.currentToken() == JsonToken.START_OBJECT) { + // Object format: {"HardSoftScore":"-999hard/-999soft"} + parser.nextToken(); + String scoreClassSimpleName = parser.currentName(); + parser.nextToken(); + String scoreString = parser.getValueAsString(); + parser.nextToken(); // consume END_OBJECT + return parseScoreByClassName(scoreClassSimpleName, scoreString); + } else { + // String format: "-999hard/-999soft" + String scoreString = parser.getValueAsString(); + return parseScore(scoreString); + } + } + + private static Score parseScoreByClassName(String scoreClassSimpleName, String scoreString) { if (scoreClassSimpleName.equals(SimpleScore.class.getSimpleName())) { return SimpleScore.parseScore(scoreString); } else if (scoreClassSimpleName.equals(SimpleBigDecimalScore.class.getSimpleName())) { @@ -57,4 +70,51 @@ public Score deserialize(JsonParser parser, DeserializationContext context) thro } } + /** + * Try to parse a score string by attempting all known score types. + * The first successful parse wins. + */ + static Score parseScore(String scoreString) { + try { + return SimpleScore.parseScore(scoreString); + } catch (Exception e) { + // Not a SimpleScore, try next + } + try { + return SimpleBigDecimalScore.parseScore(scoreString); + } catch (Exception e) { + // Not a SimpleBigDecimalScore, try next + } + try { + return HardSoftScore.parseScore(scoreString); + } catch (Exception e) { + // Not a HardSoftScore, try next + } + try { + return HardSoftBigDecimalScore.parseScore(scoreString); + } catch (Exception e) { + // Not a HardSoftBigDecimalScore, try next + } + try { + return HardMediumSoftScore.parseScore(scoreString); + } catch (Exception e) { + // Not a HardMediumSoftScore, try next + } + try { + return HardMediumSoftBigDecimalScore.parseScore(scoreString); + } catch (Exception e) { + // Not a HardMediumSoftBigDecimalScore, try next + } + try { + return BendableScore.parseScore(scoreString); + } catch (Exception e) { + // Not a BendableScore, try next + } + try { + return BendableBigDecimalScore.parseScore(scoreString); + } catch (Exception e) { + throw new IllegalArgumentException("Cannot parse score string (%s).".formatted(scoreString), e); + } + } + } diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/PolymorphicScoreJacksonSerializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/PolymorphicScoreJacksonSerializer.java index dd4ed004add..b186619fe6f 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/PolymorphicScoreJacksonSerializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/PolymorphicScoreJacksonSerializer.java @@ -3,10 +3,10 @@ import ai.timefold.solver.core.api.score.HardSoftScore; import ai.timefold.solver.core.api.score.Score; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonGenerator; -import tools.jackson.databind.SerializationContext; -import tools.jackson.databind.ValueSerializer; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.JsonSerializer; /** * Jackson binding support for a {@link Score} type (but not a subtype). @@ -20,12 +20,12 @@ * @see Score * @see PolymorphicScoreJacksonDeserializer */ -public class PolymorphicScoreJacksonSerializer extends ValueSerializer { +public class PolymorphicScoreJacksonSerializer extends JsonSerializer { @Override - public void serialize(Score score, JsonGenerator generator, SerializationContext serializers) throws JacksonException { + public void serialize(Score score, JsonGenerator generator, SerializerProvider serializers) throws java.io.IOException { generator.writeStartObject(); - generator.writeStringProperty(score.getClass().getSimpleName(), score.toString()); + generator.writeStringField(score.getClass().getSimpleName(), score.toString()); generator.writeEndObject(); } diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/SimpleBigDecimalScoreJacksonDeserializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/SimpleBigDecimalScoreJacksonDeserializer.java index 7f31afd9681..04ba9619c13 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/SimpleBigDecimalScoreJacksonDeserializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/SimpleBigDecimalScoreJacksonDeserializer.java @@ -2,14 +2,14 @@ import ai.timefold.solver.core.api.score.SimpleBigDecimalScore; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonParser; -import tools.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; public class SimpleBigDecimalScoreJacksonDeserializer extends AbstractScoreJacksonDeserializer { @Override - public SimpleBigDecimalScore deserialize(JsonParser parser, DeserializationContext context) throws JacksonException { + public SimpleBigDecimalScore deserialize(JsonParser parser, DeserializationContext context) throws java.io.IOException { return SimpleBigDecimalScore.parseScore(parser.getValueAsString()); } diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/SimpleScoreJacksonDeserializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/SimpleScoreJacksonDeserializer.java index a5f4e8fa724..42fef35cdd2 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/SimpleScoreJacksonDeserializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/SimpleScoreJacksonDeserializer.java @@ -2,14 +2,14 @@ import ai.timefold.solver.core.api.score.SimpleScore; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonParser; -import tools.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; public class SimpleScoreJacksonDeserializer extends AbstractScoreJacksonDeserializer { @Override - public SimpleScore deserialize(JsonParser parser, DeserializationContext context) throws JacksonException { + public SimpleScore deserialize(JsonParser parser, DeserializationContext context) throws java.io.IOException { return SimpleScore.parseScore(parser.getValueAsString()); } diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/constraint/ConstraintRefJacksonDeserializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/constraint/ConstraintRefJacksonDeserializer.java index 636fc17fb8f..b611b2f73fd 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/constraint/ConstraintRefJacksonDeserializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/constraint/ConstraintRefJacksonDeserializer.java @@ -2,14 +2,14 @@ import ai.timefold.solver.core.api.score.stream.ConstraintRef; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonParser; -import tools.jackson.databind.DeserializationContext; -import tools.jackson.databind.ValueDeserializer; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; -public final class ConstraintRefJacksonDeserializer extends ValueDeserializer { +public final class ConstraintRefJacksonDeserializer extends JsonDeserializer { @Override - public ConstraintRef deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException { + public ConstraintRef deserialize(JsonParser p, DeserializationContext ctxt) throws java.io.IOException { return ConstraintRef.of(p.getValueAsString()); } diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/constraint/ConstraintRefJacksonSerializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/constraint/ConstraintRefJacksonSerializer.java index 0a5fec6f572..42ec2e199c3 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/constraint/ConstraintRefJacksonSerializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/constraint/ConstraintRefJacksonSerializer.java @@ -2,16 +2,16 @@ import ai.timefold.solver.core.api.score.stream.ConstraintRef; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonGenerator; -import tools.jackson.databind.SerializationContext; -import tools.jackson.databind.ValueSerializer; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.JsonSerializer; -public final class ConstraintRefJacksonSerializer extends ValueSerializer { +public final class ConstraintRefJacksonSerializer extends JsonSerializer { @Override - public void serialize(ConstraintRef constraintRef, JsonGenerator generator, SerializationContext serializers) - throws JacksonException { + public void serialize(ConstraintRef constraintRef, JsonGenerator generator, SerializerProvider serializers) + throws java.io.IOException { generator.writeString(constraintRef.id()); } } diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/BreakJacksonDeserializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/BreakJacksonDeserializer.java index 7c6c412aa68..2cad7216259 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/BreakJacksonDeserializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/BreakJacksonDeserializer.java @@ -2,18 +2,18 @@ import ai.timefold.solver.core.api.score.stream.common.Break; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonParser; -import tools.jackson.databind.DeserializationContext; -import tools.jackson.databind.JsonNode; -import tools.jackson.databind.ValueDeserializer; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.JsonDeserializer; public final class BreakJacksonDeserializer> - extends ValueDeserializer> { + extends JsonDeserializer> { @Override public Break deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) - throws JacksonException { + throws java.io.IOException { JsonNode jsonNode = jsonParser.readValueAsTree(); return deserializationContext.readTreeAsValue(jsonNode, DeserializableBreak.class); } diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/BreakJacksonSerializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/BreakJacksonSerializer.java index 0200cc3cea3..4f65e552425 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/BreakJacksonSerializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/BreakJacksonSerializer.java @@ -2,17 +2,17 @@ import ai.timefold.solver.core.api.score.stream.common.Break; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonGenerator; -import tools.jackson.databind.SerializationContext; -import tools.jackson.databind.ValueSerializer; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.JsonSerializer; public final class BreakJacksonSerializer> - extends ValueSerializer> { + extends JsonSerializer> { @Override public void serialize(Break brk, JsonGenerator jsonGenerator, - SerializationContext serializerProvider) throws JacksonException { + SerializerProvider serializerProvider) throws java.io.IOException { jsonGenerator.writePOJO(SerializableBreak.of(brk)); } diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/LoadBalanceJacksonDeserializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/LoadBalanceJacksonDeserializer.java index 97759faecc3..7318a1ef513 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/LoadBalanceJacksonDeserializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/LoadBalanceJacksonDeserializer.java @@ -2,18 +2,18 @@ import ai.timefold.solver.core.api.score.stream.common.LoadBalance; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonParser; -import tools.jackson.databind.DeserializationContext; -import tools.jackson.databind.JsonNode; -import tools.jackson.databind.ValueDeserializer; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.JsonDeserializer; public final class LoadBalanceJacksonDeserializer - extends ValueDeserializer> { + extends JsonDeserializer> { @Override public LoadBalance deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) - throws JacksonException { + throws java.io.IOException { JsonNode jsonNode = jsonParser.readValueAsTree(); return deserializationContext.readTreeAsValue(jsonNode, DeserializableLoadBalance.class); } diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/LoadBalanceJacksonSerializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/LoadBalanceJacksonSerializer.java index ae304730730..ff637db403f 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/LoadBalanceJacksonSerializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/LoadBalanceJacksonSerializer.java @@ -2,17 +2,17 @@ import ai.timefold.solver.core.api.score.stream.common.LoadBalance; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonGenerator; -import tools.jackson.databind.SerializationContext; -import tools.jackson.databind.ValueSerializer; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.JsonSerializer; public final class LoadBalanceJacksonSerializer - extends ValueSerializer> { + extends JsonSerializer> { @Override - public void serialize(LoadBalance loadBalance, JsonGenerator jsonGenerator, SerializationContext serializers) - throws JacksonException { + public void serialize(LoadBalance loadBalance, JsonGenerator jsonGenerator, SerializerProvider serializers) + throws java.io.IOException { jsonGenerator.writePOJO(SerializableLoadBalance.of(loadBalance)); } } diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/SequenceChainJacksonDeserializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/SequenceChainJacksonDeserializer.java index e347951a085..638599cc0da 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/SequenceChainJacksonDeserializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/SequenceChainJacksonDeserializer.java @@ -2,18 +2,18 @@ import ai.timefold.solver.core.api.score.stream.common.SequenceChain; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonParser; -import tools.jackson.databind.DeserializationContext; -import tools.jackson.databind.JsonNode; -import tools.jackson.databind.ValueDeserializer; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.JsonDeserializer; public final class SequenceChainJacksonDeserializer> - extends ValueDeserializer> { + extends JsonDeserializer> { @Override public SequenceChain deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) - throws JacksonException { + throws java.io.IOException { JsonNode jsonNode = jsonParser.readValueAsTree(); return deserializationContext.readTreeAsValue(jsonNode, DeserializableSequenceChain.class); } diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/SequenceChainJacksonSerializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/SequenceChainJacksonSerializer.java index a10cf096b73..8d15a7a7771 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/SequenceChainJacksonSerializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/SequenceChainJacksonSerializer.java @@ -5,17 +5,17 @@ import ai.timefold.solver.core.api.score.stream.common.SequenceChain; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonGenerator; -import tools.jackson.databind.SerializationContext; -import tools.jackson.databind.ValueSerializer; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.JsonSerializer; public final class SequenceChainJacksonSerializer> - extends ValueSerializer> { + extends JsonSerializer> { @Override public void serialize(SequenceChain sequenceChain, JsonGenerator jsonGenerator, - SerializationContext serializerProvider) throws JacksonException { + SerializerProvider serializerProvider) throws java.io.IOException { var serializedSequenceList = new ArrayList>(sequenceChain.getConsecutiveSequences().size()); for (var sequence : sequenceChain.getConsecutiveSequences()) { diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/SequenceJacksonDeserializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/SequenceJacksonDeserializer.java index 57c3ed88d76..0efa2a9c228 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/SequenceJacksonDeserializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/SequenceJacksonDeserializer.java @@ -2,18 +2,18 @@ import ai.timefold.solver.core.api.score.stream.common.Sequence; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonParser; -import tools.jackson.databind.DeserializationContext; -import tools.jackson.databind.JsonNode; -import tools.jackson.databind.ValueDeserializer; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.JsonDeserializer; public final class SequenceJacksonDeserializer> - extends ValueDeserializer> { + extends JsonDeserializer> { @Override public Sequence deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) - throws JacksonException { + throws java.io.IOException { JsonNode jsonNode = jsonParser.readValueAsTree(); return deserializationContext.readTreeAsValue(jsonNode, DeserializableSequence.class); } diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/SequenceJacksonSerializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/SequenceJacksonSerializer.java index 752991efa30..8109c236f61 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/SequenceJacksonSerializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/SequenceJacksonSerializer.java @@ -4,17 +4,17 @@ import ai.timefold.solver.core.api.score.stream.common.Sequence; -import tools.jackson.core.JacksonException; -import tools.jackson.core.JsonGenerator; -import tools.jackson.databind.SerializationContext; -import tools.jackson.databind.ValueSerializer; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.JsonSerializer; public final class SequenceJacksonSerializer> - extends ValueSerializer> { + extends JsonSerializer> { @Override public void serialize(Sequence sequence, JsonGenerator jsonGenerator, - SerializationContext serializerProvider) throws JacksonException { + SerializerProvider serializerProvider) throws java.io.IOException { jsonGenerator.writePOJO( new SerializableSequence<>( SerializableBreak.of(sequence.getPreviousBreak()), diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/package-info.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/package-info.java index a81b86aeef5..a40e556aed8 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/package-info.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/score/stream/common/package-info.java @@ -8,7 +8,7 @@ * through user-specified {@link ai.timefold.solver.core.api.score.stream.ConstraintJustification}. * The serialization and deserialization of these types happens automatically, * if the user has registered {@link ai.timefold.solver.jackson.api.TimefoldJacksonModule} - * with their {@link tools.jackson.databind.ObjectMapper}. + * with their {@link com.fasterxml.jackson.databind.ObjectMapper}. * *

* Sequences carry user-specified types of values and for this to work, diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/impl/domain/solution/JacksonSolutionFileIO.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/impl/domain/solution/JacksonSolutionFileIO.java index 126b1aeff39..d7f0e6ab8f8 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/impl/domain/solution/JacksonSolutionFileIO.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/impl/domain/solution/JacksonSolutionFileIO.java @@ -6,9 +6,9 @@ import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.domain.solution.SolutionFileIO; -import tools.jackson.core.JacksonException; -import tools.jackson.databind.ObjectMapper; -import tools.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; /** * @@ -43,7 +43,7 @@ public String getOutputFileExtension() { public Solution_ read(File inputSolutionFile) { try { return mapper.readValue(inputSolutionFile, clazz); - } catch (JacksonException e) { + } catch (java.io.IOException e) { throw new IllegalArgumentException("Failed reading inputSolutionFile (" + inputSolutionFile + ").", e); } } @@ -51,7 +51,7 @@ public Solution_ read(File inputSolutionFile) { public Solution_ read(InputStream inputSolutionStream) { try { return mapper.readValue(inputSolutionStream, clazz); - } catch (JacksonException e) { + } catch (java.io.IOException e) { throw new IllegalArgumentException("Failed reading inputSolutionStream.", e); } } @@ -60,7 +60,7 @@ public Solution_ read(InputStream inputSolutionStream) { public void write(Solution_ solution, File file) { try { mapper.writerWithDefaultPrettyPrinter().writeValue(file, solution); - } catch (JacksonException e) { + } catch (java.io.IOException e) { throw new IllegalArgumentException("Failed write", e); } } diff --git a/persistence/jackson/src/main/java/module-info.java b/persistence/jackson/src/main/java/module-info.java index 2477ae95dca..018767413e8 100644 --- a/persistence/jackson/src/main/java/module-info.java +++ b/persistence/jackson/src/main/java/module-info.java @@ -2,13 +2,13 @@ exports ai.timefold.solver.jackson.api; - provides tools.jackson.databind.JacksonModule with + provides com.fasterxml.jackson.databind.Module with ai.timefold.solver.jackson.api.TimefoldJacksonModule; requires transitive ai.timefold.solver.core; requires org.jspecify; - requires tools.jackson.databind; + requires com.fasterxml.jackson.databind; - uses tools.jackson.databind.JacksonModule; + uses com.fasterxml.jackson.databind.Module; } \ No newline at end of file diff --git a/persistence/jackson/src/main/resources/META-INF/services/tools.jackson.databind.JacksonModule b/persistence/jackson/src/main/resources/META-INF/services/com.fasterxml.jackson.databind.Module similarity index 100% rename from persistence/jackson/src/main/resources/META-INF/services/tools.jackson.databind.JacksonModule rename to persistence/jackson/src/main/resources/META-INF/services/com.fasterxml.jackson.databind.Module diff --git a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/AbstractJacksonRoundTripTest.java b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/AbstractJacksonRoundTripTest.java index e21627ac203..2019afd1ac3 100644 --- a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/AbstractJacksonRoundTripTest.java +++ b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/AbstractJacksonRoundTripTest.java @@ -1,7 +1,7 @@ package ai.timefold.solver.jackson.api; -import tools.jackson.core.JacksonException; -import tools.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.databind.ObjectMapper; public abstract class AbstractJacksonRoundTripTest { diff --git a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/TimefoldJacksonModuleTest.java b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/TimefoldJacksonModuleTest.java index 8b04136a695..4d8dc2f764c 100644 --- a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/TimefoldJacksonModuleTest.java +++ b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/TimefoldJacksonModuleTest.java @@ -15,12 +15,12 @@ import com.fasterxml.jackson.annotation.JsonInclude; -import tools.jackson.core.JacksonException; -import tools.jackson.databind.DeserializationFeature; -import tools.jackson.databind.JacksonModule; -import tools.jackson.databind.MapperFeature; -import tools.jackson.databind.json.JsonMapper; -import tools.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; class TimefoldJacksonModuleTest extends AbstractJacksonRoundTripTest { @@ -58,7 +58,7 @@ void constraintWeightOverrides() throws JacksonException { var objectMapper = JsonMapper.builder() .addModule(TimefoldJacksonModule.createModule()) .addModule(new CustomJacksonModule()) - .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(JsonInclude.Include.NON_DEFAULT)) + .serializationInclusion(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_DEFAULT) .build(); var constraintWeightOverrides = ConstraintWeightOverrides.of( @@ -80,7 +80,7 @@ void constraintWeightOverrides() throws JacksonException { @Test void testServiceProvider() { - ServiceLoader loader = ServiceLoader.load(JacksonModule.class); + ServiceLoader loader = ServiceLoader.load(Module.class); assertThat(loader).hasSizeGreaterThan(0); } diff --git a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/AbstractScoreJacksonRoundTripTest.java b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/AbstractScoreJacksonRoundTripTest.java index 43dbcf486a6..fe5cbd2ee83 100644 --- a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/AbstractScoreJacksonRoundTripTest.java +++ b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/AbstractScoreJacksonRoundTripTest.java @@ -6,8 +6,8 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.jackson.api.AbstractJacksonRoundTripTest; -import tools.jackson.core.JacksonException; -import tools.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.databind.ObjectMapper; public abstract class AbstractScoreJacksonRoundTripTest extends AbstractJacksonRoundTripTest { diff --git a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/BendableBigDecimalScoreJacksonRoundTripTest.java b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/BendableBigDecimalScoreJacksonRoundTripTest.java index 0dc9431ac6a..0d27e1d56e8 100644 --- a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/BendableBigDecimalScoreJacksonRoundTripTest.java +++ b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/BendableBigDecimalScoreJacksonRoundTripTest.java @@ -6,8 +6,8 @@ import org.junit.jupiter.api.Test; -import tools.jackson.databind.annotation.JsonDeserialize; -import tools.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; class BendableBigDecimalScoreJacksonRoundTripTest extends AbstractScoreJacksonRoundTripTest { diff --git a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/BendableScoreJacksonRoundTripTest.java b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/BendableScoreJacksonRoundTripTest.java index 5c1c4041236..bda00a69458 100644 --- a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/BendableScoreJacksonRoundTripTest.java +++ b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/BendableScoreJacksonRoundTripTest.java @@ -4,8 +4,8 @@ import org.junit.jupiter.api.Test; -import tools.jackson.databind.annotation.JsonDeserialize; -import tools.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; class BendableScoreJacksonRoundTripTest extends AbstractScoreJacksonRoundTripTest { diff --git a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/HardMediumSoftBigDecimalScoreJacksonRoundTripTest.java b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/HardMediumSoftBigDecimalScoreJacksonRoundTripTest.java index 7e5f0a3c631..2c45286fc3f 100644 --- a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/HardMediumSoftBigDecimalScoreJacksonRoundTripTest.java +++ b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/HardMediumSoftBigDecimalScoreJacksonRoundTripTest.java @@ -6,8 +6,8 @@ import org.junit.jupiter.api.Test; -import tools.jackson.databind.annotation.JsonDeserialize; -import tools.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; class HardMediumSoftBigDecimalScoreJacksonRoundTripTest extends AbstractScoreJacksonRoundTripTest { diff --git a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/HardMediumSoftScoreJacksonRoundTripTest.java b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/HardMediumSoftScoreJacksonRoundTripTest.java index c7207c5dc24..ab8fa10839f 100644 --- a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/HardMediumSoftScoreJacksonRoundTripTest.java +++ b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/HardMediumSoftScoreJacksonRoundTripTest.java @@ -4,8 +4,8 @@ import org.junit.jupiter.api.Test; -import tools.jackson.databind.annotation.JsonDeserialize; -import tools.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; class HardMediumSoftScoreJacksonRoundTripTest extends AbstractScoreJacksonRoundTripTest { diff --git a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/HardSoftBigDecimalScoreJacksonRoundTripTest.java b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/HardSoftBigDecimalScoreJacksonRoundTripTest.java index f9a5c75ef39..796eef04d0e 100644 --- a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/HardSoftBigDecimalScoreJacksonRoundTripTest.java +++ b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/HardSoftBigDecimalScoreJacksonRoundTripTest.java @@ -6,8 +6,8 @@ import org.junit.jupiter.api.Test; -import tools.jackson.databind.annotation.JsonDeserialize; -import tools.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; class HardSoftBigDecimalScoreJacksonRoundTripTest extends AbstractScoreJacksonRoundTripTest { diff --git a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/HardSoftScoreJacksonRoundTripTest.java b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/HardSoftScoreJacksonRoundTripTest.java index 1da9c8af98e..5a48334d8b3 100644 --- a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/HardSoftScoreJacksonRoundTripTest.java +++ b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/HardSoftScoreJacksonRoundTripTest.java @@ -4,8 +4,8 @@ import org.junit.jupiter.api.Test; -import tools.jackson.databind.annotation.JsonDeserialize; -import tools.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; class HardSoftScoreJacksonRoundTripTest extends AbstractScoreJacksonRoundTripTest { diff --git a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/SimpleBigDecimalScoreJacksonRoundTripTest.java b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/SimpleBigDecimalScoreJacksonRoundTripTest.java index 24613512ae5..ecac66e6ff6 100644 --- a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/SimpleBigDecimalScoreJacksonRoundTripTest.java +++ b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/SimpleBigDecimalScoreJacksonRoundTripTest.java @@ -6,8 +6,8 @@ import org.junit.jupiter.api.Test; -import tools.jackson.databind.annotation.JsonDeserialize; -import tools.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; class SimpleBigDecimalScoreJacksonRoundTripTest extends AbstractScoreJacksonRoundTripTest { diff --git a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/SimpleScoreJacksonRoundTripTest.java b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/SimpleScoreJacksonRoundTripTest.java index 433d32faaa6..21caf07bfee 100644 --- a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/SimpleScoreJacksonRoundTripTest.java +++ b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/SimpleScoreJacksonRoundTripTest.java @@ -4,8 +4,8 @@ import org.junit.jupiter.api.Test; -import tools.jackson.databind.annotation.JsonDeserialize; -import tools.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; class SimpleScoreJacksonRoundTripTest extends AbstractScoreJacksonRoundTripTest { diff --git a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/stream/common/LoadBalanceRoundTripTest.java b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/stream/common/LoadBalanceRoundTripTest.java index 9f99acdf0d6..fd04eac7782 100644 --- a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/stream/common/LoadBalanceRoundTripTest.java +++ b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/stream/common/LoadBalanceRoundTripTest.java @@ -18,9 +18,9 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.ObjectIdGenerators; -import tools.jackson.core.JacksonException; -import tools.jackson.databind.ObjectMapper; -import tools.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; class LoadBalanceRoundTripTest { @@ -46,8 +46,7 @@ void roundTrip() throws JacksonException { var loadBalance = (LoadBalance) collector.finisher().apply(context); ObjectMapper objectMapper = JsonMapper.builder() - .changeDefaultPropertyInclusion(incl -> incl.withContentInclusion(JsonInclude.Include.NON_NULL) - .withValueInclusion(JsonInclude.Include.NON_NULL)) + .serializationInclusion(JsonInclude.Include.NON_NULL) .addModule(TimefoldJacksonModule.createModule()) .build(); diff --git a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/stream/common/SequenceRoundTripTest.java b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/stream/common/SequenceRoundTripTest.java index 17ed4d33bf6..67984af3f42 100644 --- a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/stream/common/SequenceRoundTripTest.java +++ b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/stream/common/SequenceRoundTripTest.java @@ -19,10 +19,10 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.ObjectIdGenerators; -import tools.jackson.core.JacksonException; -import tools.jackson.databind.MapperFeature; -import tools.jackson.databind.ObjectMapper; -import tools.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; class SequenceRoundTripTest { @@ -55,8 +55,7 @@ void roundTrip() throws JacksonException { var breaks = sequenceChain.getBreaks().toArray(new Break[0]); ObjectMapper objectMapper = JsonMapper.builder() - .changeDefaultPropertyInclusion(incl -> incl.withContentInclusion(JsonInclude.Include.NON_NULL) - .withValueInclusion(JsonInclude.Include.NON_NULL)) + .serializationInclusion(JsonInclude.Include.NON_NULL) .addModule(TimefoldJacksonModule.createModule()) .disable(MapperFeature.SORT_CREATOR_PROPERTIES_FIRST) .build(); diff --git a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/testdomain/JacksonTestdataSolution.java b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/testdomain/JacksonTestdataSolution.java index 970514d3f7f..19bde3d55fc 100644 --- a/persistence/jackson/src/test/java/ai/timefold/solver/jackson/testdomain/JacksonTestdataSolution.java +++ b/persistence/jackson/src/test/java/ai/timefold/solver/jackson/testdomain/JacksonTestdataSolution.java @@ -12,8 +12,8 @@ import ai.timefold.solver.jackson.api.score.SimpleScoreJacksonDeserializer; import ai.timefold.solver.jackson.api.score.SimpleScoreJacksonSerializer; -import tools.jackson.databind.annotation.JsonDeserialize; -import tools.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; @PlanningSolution public class JacksonTestdataSolution extends JacksonTestdataObject { diff --git a/spring-integration/pom.xml b/spring-integration/pom.xml index a026b31aa2c..6dd37339314 100644 --- a/spring-integration/pom.xml +++ b/spring-integration/pom.xml @@ -37,9 +37,9 @@ Therefore we need to bring them in sync here. This needs to be imported before the Spring Boot dependency management to be effective. --> - tools.jackson + com.fasterxml.jackson jackson-bom - ${version.tools.jackson} + ${version.com.fasterxml.jackson.core} pom import @@ -54,6 +54,22 @@ pom import + + + jakarta.xml.bind + jakarta.xml.bind-api + 4.0.2 + + + org.glassfish.jaxb + jaxb-runtime + 4.0.5 + + + jakarta.activation + jakarta.activation-api + 2.1.3 + diff --git a/spring-integration/spring-boot-autoconfigure/pom.xml b/spring-integration/spring-boot-autoconfigure/pom.xml index d6989dc87d6..cbc77061dfa 100644 --- a/spring-integration/spring-boot-autoconfigure/pom.xml +++ b/spring-integration/spring-boot-autoconfigure/pom.xml @@ -54,16 +54,18 @@ commons-logging commons-logging + + + org.springframework + spring-jcl + org.springframework.boot spring-boot-autoconfigure - - org.springframework.boot - spring-boot-persistence - + org.slf4j jcl-over-slf4j @@ -71,7 +73,7 @@ - tools.jackson.core + com.fasterxml.jackson.core jackson-databind true @@ -87,6 +89,23 @@ true + + + jakarta.xml.bind + jakarta.xml.bind-api + test + + + jakarta.activation + jakarta.activation-api + test + + + org.glassfish.jaxb + jaxb-runtime + test + + ai.timefold.solver diff --git a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/IncludeAbstractClassesEntityScanner.java b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/IncludeAbstractClassesEntityScanner.java index 459bd533dd1..7671c07dca0 100644 --- a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/IncludeAbstractClassesEntityScanner.java +++ b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/IncludeAbstractClassesEntityScanner.java @@ -23,8 +23,8 @@ import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.autoconfigure.AutoConfigurationPackages; -import org.springframework.boot.persistence.autoconfigure.EntityScanPackages; -import org.springframework.boot.persistence.autoconfigure.EntityScanner; +import org.springframework.boot.autoconfigure.domain.EntityScanPackages; +import org.springframework.boot.autoconfigure.domain.EntityScanner; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.core.type.AnnotationMetadata; diff --git a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAotFactory.java b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/SolverConfigFactory.java similarity index 69% rename from spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAotFactory.java rename to spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/SolverConfigFactory.java index 8bb91a63c0f..1472b9104e9 100644 --- a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAotFactory.java +++ b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/SolverConfigFactory.java @@ -10,19 +10,16 @@ import ai.timefold.solver.spring.boot.autoconfigure.config.SolverManagerProperties; import ai.timefold.solver.spring.boot.autoconfigure.config.TimefoldProperties; -import org.springframework.boot.context.properties.bind.BindResult; -import org.springframework.boot.context.properties.bind.Binder; -import org.springframework.context.EnvironmentAware; -import org.springframework.core.env.Environment; +/** + * Factory for creating SolverConfig and SolverManager from XML strings. + * Used by TimefoldSolverAutoConfiguration to lazily create beans. + */ +public class SolverConfigFactory { -public class TimefoldSolverAotFactory implements EnvironmentAware { - private TimefoldProperties timefoldProperties; + private final TimefoldProperties timefoldProperties; - @Override - public void setEnvironment(Environment environment) { - // We need the environment to set run time properties of SolverFactory and SolverManager - BindResult result = Binder.get(environment).bind("timefold", TimefoldProperties.class); - this.timefoldProperties = result.orElseGet(TimefoldProperties::new); + public SolverConfigFactory(TimefoldProperties timefoldProperties) { + this.timefoldProperties = timefoldProperties; } @SuppressWarnings("unused") // Referenced by TimefoldSolverAutoConfiguration as a String. diff --git a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAotContribution.java b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAotContribution.java deleted file mode 100644 index e2691faf380..00000000000 --- a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAotContribution.java +++ /dev/null @@ -1,45 +0,0 @@ -package ai.timefold.solver.spring.boot.autoconfigure; - -import java.util.Map; - -import ai.timefold.solver.core.config.solver.SolverConfig; - -import org.springframework.aot.generate.GenerationContext; -import org.springframework.aot.hint.MemberCategory; -import org.springframework.aot.hint.ReflectionHints; -import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; -import org.springframework.beans.factory.aot.BeanFactoryInitializationCode; - -public class TimefoldSolverAotContribution implements BeanFactoryInitializationAotContribution { - private final Map solverConfigMap; - - public TimefoldSolverAotContribution(Map solverConfigMap) { - this.solverConfigMap = solverConfigMap; - } - - /** - * Register a type for reflection, allowing introspection - * of its members at runtime in a native build. - */ - private static void registerType(ReflectionHints reflectionHints, Class type) { - reflectionHints.registerType(type, - MemberCategory.ACCESS_PUBLIC_FIELDS, - MemberCategory.ACCESS_DECLARED_FIELDS, - MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, - MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, - MemberCategory.INVOKE_DECLARED_METHODS, - MemberCategory.INVOKE_PUBLIC_METHODS); - } - - @Override - public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) { - ReflectionHints reflectionHints = generationContext.getRuntimeHints().reflection(); - for (SolverConfig solverConfig : solverConfigMap.values()) { - solverConfig.visitReferencedClasses(type -> { - if (type != null) { - registerType(reflectionHints, type); - } - }); - } - } -} diff --git a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAutoConfiguration.java b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAutoConfiguration.java index 1e425f04035..728f8851d98 100644 --- a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAutoConfiguration.java +++ b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAutoConfiguration.java @@ -47,8 +47,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanClassLoaderAware; -import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; -import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; @@ -59,7 +57,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.bind.BindResult; import org.springframework.boot.context.properties.bind.Binder; -import org.springframework.boot.persistence.autoconfigure.EntityScan; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.EnvironmentAware; @@ -73,7 +71,7 @@ SolverManager.class }) @EnableConfigurationProperties({ TimefoldProperties.class }) public class TimefoldSolverAutoConfiguration - implements BeanClassLoaderAware, ApplicationContextAware, EnvironmentAware, BeanFactoryInitializationAotProcessor, + implements BeanClassLoaderAware, ApplicationContextAware, EnvironmentAware, BeanDefinitionRegistryPostProcessor { private static final Log LOG = LogFactory.getLog(TimefoldSolverAutoConfiguration.class); @@ -161,20 +159,20 @@ private Map getSolverConfigMap() { } @Override - public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { - var solverConfigMap = getSolverConfigMap(); - return new TimefoldSolverAotContribution(solverConfigMap); + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + // No-op: all processing is done in postProcessBeanDefinitionRegistry } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { var solverConfigMap = getSolverConfigMap(); var solverConfigIO = new SolverConfigIO(); - registry.registerBeanDefinition(TimefoldSolverAotFactory.class.getName(), - new RootBeanDefinition(TimefoldSolverAotFactory.class)); + var factoryBeanDefinition = new RootBeanDefinition(SolverConfigFactory.class); + factoryBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(timefoldProperties); + registry.registerBeanDefinition(SolverConfigFactory.class.getName(), factoryBeanDefinition); if (solverConfigMap.isEmpty()) { var rootBeanDefinition = new RootBeanDefinition(SolverConfig.class); - rootBeanDefinition.setFactoryBeanName(TimefoldSolverAotFactory.class.getName()); + rootBeanDefinition.setFactoryBeanName(SolverConfigFactory.class.getName()); rootBeanDefinition.setFactoryMethodName("solverConfigSupplier"); var solverXmlOutput = new StringWriter(); solverConfigIO.write(new SolverConfig(), solverXmlOutput); @@ -186,7 +184,7 @@ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) t if (timefoldProperties.getSolver() == null || timefoldProperties.getSolver().size() == 1) { var rootBeanDefinition = new RootBeanDefinition(SolverConfig.class); - rootBeanDefinition.setFactoryBeanName(TimefoldSolverAotFactory.class.getName()); + rootBeanDefinition.setFactoryBeanName(SolverConfigFactory.class.getName()); rootBeanDefinition.setFactoryMethodName("solverConfigSupplier"); var solverXmlOutput = new StringWriter(); solverConfigIO.write(solverConfigMap.values().iterator().next(), solverXmlOutput); @@ -197,7 +195,7 @@ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) t // Only SolverManager can be injected for multiple solver configurations solverConfigMap.forEach((solverName, solverConfig) -> { var rootBeanDefinition = new RootBeanDefinition(SolverManager.class); - rootBeanDefinition.setFactoryBeanName(TimefoldSolverAotFactory.class.getName()); + rootBeanDefinition.setFactoryBeanName(SolverConfigFactory.class.getName()); rootBeanDefinition.setFactoryMethodName("solverManagerSupplier"); var solverXmlOutput = new StringWriter(); solverConfigIO.write(solverConfig, solverXmlOutput); diff --git a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverBeanFactory.java b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverBeanFactory.java index 63534c24e3c..45689e19a98 100644 --- a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverBeanFactory.java +++ b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverBeanFactory.java @@ -25,7 +25,6 @@ import org.jspecify.annotations.NonNull; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -40,8 +39,8 @@ import org.springframework.context.annotation.Lazy; import org.springframework.core.env.Environment; -import tools.jackson.databind.JacksonModule; -import tools.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.json.JsonMapper; /** * Must be seperated from {@link TimefoldSolverAutoConfiguration} since @@ -200,7 +199,7 @@ ConstraintVerifier constraintVerifier() { static class TimefoldJacksonConfiguration { @Bean - JacksonModule jacksonModule() { + Module jacksonModule() { return TimefoldJacksonModule.createModule(); } diff --git a/spring-integration/spring-boot-autoconfigure/src/main/java/module-info.java b/spring-integration/spring-boot-autoconfigure/src/main/java/module-info.java index 018c6540908..5a80eed232a 100644 --- a/spring-integration/spring-boot-autoconfigure/src/main/java/module-info.java +++ b/spring-integration/spring-boot-autoconfigure/src/main/java/module-info.java @@ -14,9 +14,8 @@ requires spring.beans; requires spring.boot; requires spring.boot.autoconfigure; - requires spring.boot.persistence; requires spring.context; requires spring.core; - requires tools.jackson.databind; + requires com.fasterxml.jackson.databind; } \ No newline at end of file diff --git a/spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000000..ce3d2e9a727 --- /dev/null +++ b/spring-integration/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -0,0 +1,4 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +ai.timefold.solver.spring.boot.autoconfigure.TimefoldSolverAutoConfiguration,\ +ai.timefold.solver.spring.boot.autoconfigure.TimefoldBenchmarkAutoConfiguration,\ +ai.timefold.solver.spring.boot.autoconfigure.TimefoldSolverBeanFactory diff --git a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/basic/EmptySpringTestConfiguration.java b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/basic/EmptySpringTestConfiguration.java index 008d3cc34dd..334470b0cc6 100644 --- a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/basic/EmptySpringTestConfiguration.java +++ b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/basic/EmptySpringTestConfiguration.java @@ -1,6 +1,6 @@ package ai.timefold.solver.spring.boot.autoconfigure.basic; -import org.springframework.boot.persistence.autoconfigure.EntityScan; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Configuration; @Configuration diff --git a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/basic/NoConstraintsSpringTestConfiguration.java b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/basic/NoConstraintsSpringTestConfiguration.java index 60a92efad14..af7590d1f00 100644 --- a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/basic/NoConstraintsSpringTestConfiguration.java +++ b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/basic/NoConstraintsSpringTestConfiguration.java @@ -3,7 +3,7 @@ import ai.timefold.solver.spring.boot.autoconfigure.basic.domain.TestdataSpringEntity; import ai.timefold.solver.spring.boot.autoconfigure.basic.domain.TestdataSpringSolution; -import org.springframework.boot.persistence.autoconfigure.EntityScan; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Configuration; @Configuration diff --git a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/declarative/SupplierVariableSpringTestConfiguration.java b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/declarative/SupplierVariableSpringTestConfiguration.java index 629420029d2..99e310463ad 100644 --- a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/declarative/SupplierVariableSpringTestConfiguration.java +++ b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/declarative/SupplierVariableSpringTestConfiguration.java @@ -4,7 +4,7 @@ import ai.timefold.solver.spring.boot.autoconfigure.declarative.domain.TestdataSpringSupplierVariableSolution; import org.springframework.boot.autoconfigure.AutoConfigurationPackage; -import org.springframework.boot.persistence.autoconfigure.EntityScan; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Configuration; @Configuration diff --git a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/dummy/MultipleSolutionsSpringTestConfiguration.java b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/dummy/MultipleSolutionsSpringTestConfiguration.java index 4fc5bf27843..ddb3ea93ccc 100644 --- a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/dummy/MultipleSolutionsSpringTestConfiguration.java +++ b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/dummy/MultipleSolutionsSpringTestConfiguration.java @@ -1,6 +1,6 @@ package ai.timefold.solver.spring.boot.autoconfigure.dummy; -import org.springframework.boot.persistence.autoconfigure.EntityScan; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Configuration; @Configuration diff --git a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/entity/BothAnnotatedInterfaceSpringTestConfiguration.java b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/entity/BothAnnotatedInterfaceSpringTestConfiguration.java index 6db99cbd398..0d36e2cfd1f 100644 --- a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/entity/BothAnnotatedInterfaceSpringTestConfiguration.java +++ b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/entity/BothAnnotatedInterfaceSpringTestConfiguration.java @@ -3,7 +3,7 @@ import ai.timefold.solver.core.testdomain.inheritance.entity.single.baseannotated.interfaces.childtoo.TestdataBothAnnotatedInterfaceSolution; import org.springframework.boot.autoconfigure.AutoConfigurationPackage; -import org.springframework.boot.persistence.autoconfigure.EntityScan; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Configuration; @Configuration diff --git a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/entity/BothAnnotatedSpringTestConfiguration.java b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/entity/BothAnnotatedSpringTestConfiguration.java index 9fff5d89da2..098424240c9 100644 --- a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/entity/BothAnnotatedSpringTestConfiguration.java +++ b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/entity/BothAnnotatedSpringTestConfiguration.java @@ -3,7 +3,7 @@ import ai.timefold.solver.core.testdomain.inheritance.entity.single.baseannotated.classes.childtoo.TestdataBothAnnotatedSolution; import org.springframework.boot.autoconfigure.AutoConfigurationPackage; -import org.springframework.boot.persistence.autoconfigure.EntityScan; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Configuration; @Configuration diff --git a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/entity/MultipleBothAnnotatedMixedSpringTestConfiguration.java b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/entity/MultipleBothAnnotatedMixedSpringTestConfiguration.java index 0ec267de4ec..536c4361c79 100644 --- a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/entity/MultipleBothAnnotatedMixedSpringTestConfiguration.java +++ b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/entity/MultipleBothAnnotatedMixedSpringTestConfiguration.java @@ -3,7 +3,7 @@ import ai.timefold.solver.core.testdomain.inheritance.entity.multiple.baseannotated.classes.mixed.TestdataMultipleMixedSolution; import org.springframework.boot.autoconfigure.AutoConfigurationPackage; -import org.springframework.boot.persistence.autoconfigure.EntityScan; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Configuration; @Configuration diff --git a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/BothAnnotatedAbstractSpringTestConfiguration.java b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/BothAnnotatedAbstractSpringTestConfiguration.java index 59eded60076..6fa9f51a154 100644 --- a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/BothAnnotatedAbstractSpringTestConfiguration.java +++ b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/BothAnnotatedAbstractSpringTestConfiguration.java @@ -3,7 +3,7 @@ import ai.timefold.solver.core.testdomain.inheritance.solution.baseannotated.childtooabstract.TestdataBothAnnotatedAbstractExtendedSolution; import org.springframework.boot.autoconfigure.AutoConfigurationPackage; -import org.springframework.boot.persistence.autoconfigure.EntityScan; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Configuration; @Configuration diff --git a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/BothAnnotatedSpringTestConfiguration.java b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/BothAnnotatedSpringTestConfiguration.java index eddeec958db..f09a8da8222 100644 --- a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/BothAnnotatedSpringTestConfiguration.java +++ b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/BothAnnotatedSpringTestConfiguration.java @@ -3,7 +3,7 @@ import ai.timefold.solver.core.testdomain.inheritance.solution.baseannotated.childtoo.TestdataBothAnnotatedExtendedSolution; import org.springframework.boot.autoconfigure.AutoConfigurationPackage; -import org.springframework.boot.persistence.autoconfigure.EntityScan; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Configuration; @Configuration diff --git a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/MultipleInheritanceSpringTestConfiguration.java b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/MultipleInheritanceSpringTestConfiguration.java index 38d46f77f30..81e5ae3bced 100644 --- a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/MultipleInheritanceSpringTestConfiguration.java +++ b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/MultipleInheritanceSpringTestConfiguration.java @@ -4,7 +4,7 @@ import ai.timefold.solver.core.testdomain.inheritance.solution.baseannotated.multiple.TestdataMultipleInheritanceExtendedSolution; import org.springframework.boot.autoconfigure.AutoConfigurationPackage; -import org.springframework.boot.persistence.autoconfigure.EntityScan; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Configuration; @Configuration diff --git a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/OnlyBaseAnnotatedSpringTestConfiguration.java b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/OnlyBaseAnnotatedSpringTestConfiguration.java index 1c3b0ca5e96..338e806b31a 100644 --- a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/OnlyBaseAnnotatedSpringTestConfiguration.java +++ b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/OnlyBaseAnnotatedSpringTestConfiguration.java @@ -3,7 +3,7 @@ import ai.timefold.solver.core.testdomain.inheritance.solution.baseannotated.childnot.TestdataOnlyBaseAnnotatedExtendedSolution; import org.springframework.boot.autoconfigure.AutoConfigurationPackage; -import org.springframework.boot.persistence.autoconfigure.EntityScan; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Configuration; @Configuration diff --git a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/OnlyChildAnnotatedSpringTestConfiguration.java b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/OnlyChildAnnotatedSpringTestConfiguration.java index 84b3b016dc1..097cfb6f4d0 100644 --- a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/OnlyChildAnnotatedSpringTestConfiguration.java +++ b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/OnlyChildAnnotatedSpringTestConfiguration.java @@ -4,7 +4,7 @@ import ai.timefold.solver.core.testdomain.inheritance.solution.baseanot.TestdataOnlyChildAnnotatedExtendedSolution; import org.springframework.boot.autoconfigure.AutoConfigurationPackage; -import org.springframework.boot.persistence.autoconfigure.EntityScan; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Configuration; @Configuration diff --git a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/ReplaceAnnotatedMemberSpringTestConfiguration.java b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/ReplaceAnnotatedMemberSpringTestConfiguration.java index e6580039492..e449872fab9 100644 --- a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/ReplaceAnnotatedMemberSpringTestConfiguration.java +++ b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/inheritance/solution/ReplaceAnnotatedMemberSpringTestConfiguration.java @@ -4,7 +4,7 @@ import ai.timefold.solver.core.testdomain.inheritance.solution.baseannotated.replacemember.TestdataReplaceMemberExtendedSolution; import org.springframework.boot.autoconfigure.AutoConfigurationPackage; -import org.springframework.boot.persistence.autoconfigure.EntityScan; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Configuration; @Configuration diff --git a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/missingsuppliervariable/MissingSupplierVariableSpringTestConfiguration.java b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/missingsuppliervariable/MissingSupplierVariableSpringTestConfiguration.java index 8dac08072b8..957026af869 100644 --- a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/missingsuppliervariable/MissingSupplierVariableSpringTestConfiguration.java +++ b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/missingsuppliervariable/MissingSupplierVariableSpringTestConfiguration.java @@ -4,7 +4,7 @@ import ai.timefold.solver.spring.boot.autoconfigure.missingsuppliervariable.domain.TestdataSpringMissingSupplierVariableSolution; import org.springframework.boot.autoconfigure.AutoConfigurationPackage; -import org.springframework.boot.persistence.autoconfigure.EntityScan; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Configuration; @Configuration diff --git a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/multimodule/MultiModuleSpringTestConfiguration.java b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/multimodule/MultiModuleSpringTestConfiguration.java index 1ad066c326b..4680ef5ff82 100644 --- a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/multimodule/MultiModuleSpringTestConfiguration.java +++ b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/multimodule/MultiModuleSpringTestConfiguration.java @@ -1,7 +1,7 @@ package ai.timefold.solver.spring.boot.autoconfigure.multimodule; import org.springframework.boot.autoconfigure.AutoConfigurationPackage; -import org.springframework.boot.persistence.autoconfigure.EntityScan; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Configuration; @Configuration diff --git a/spring-integration/spring-boot-integration-test/pom.xml b/spring-integration/spring-boot-integration-test/pom.xml index b2e216fbc68..b1e4d212a52 100644 --- a/spring-integration/spring-boot-integration-test/pom.xml +++ b/spring-integration/spring-boot-integration-test/pom.xml @@ -24,7 +24,7 @@ org.springframework.boot - spring-boot-starter-webmvc + spring-boot-starter-web ai.timefold.solver @@ -53,6 +53,7 @@ org.springframework.boot spring-boot-maven-plugin + ${version.org.springframework.boot} org.apache.maven.plugins @@ -87,15 +88,7 @@ - - - process-aot - - process-aot - process-test-aot - - - + org.graalvm.buildtools diff --git a/spring-integration/spring-boot-starter/pom.xml b/spring-integration/spring-boot-starter/pom.xml index ba8c5678f99..c41b89c320c 100644 --- a/spring-integration/spring-boot-starter/pom.xml +++ b/spring-integration/spring-boot-starter/pom.xml @@ -31,6 +31,11 @@ commons-logging commons-logging + + + org.springframework + spring-jcl + @@ -50,10 +55,7 @@ org.springframework.boot spring-boot-starter - - org.springframework.boot - spring-boot-persistence - +