diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..b5e104416 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,258 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Build & Test Commands + +```bash +# Build all modules +./gradlew build + +# Run all tests +./gradlew test + +# Build and test (includes all checks) +./gradlew check + +# Run a single test class +./gradlew ::test --tests "com.contentgrid.appserver.ClassName" + +# Run a single test method +./gradlew ::test --tests "com.contentgrid.appserver.ClassName.methodName" + +# Generate test coverage reports (JaCoCo) +./gradlew jacocoTestReport + +# Run SonarCloud analysis +./gradlew sonar +``` + +## Project Structure + +Multi-module Gradle project using Java 21, Spring Boot 3.5.10, and JOOQ for database access. + +**Key Modules:** + +- **contentgrid-appserver-application-model**: Core domain model defining entities, attributes, relationships, and constraints +- **contentgrid-appserver-domain**: Business logic APIs for entities, relations, and content (DatamodelApi, ContentApi) +- **contentgrid-appserver-rest**: REST controllers providing dynamic HATEOAS endpoints based on application model +- **contentgrid-appserver-query-engine-api**: Database abstraction interfaces (QueryEngine) +- **contentgrid-appserver-query-engine-impl-jooq**: JOOQ-based implementation of query engine +- **contentgrid-appserver-contentstore-api**: Content storage interfaces (ContentStore) +- **contentgrid-appserver-contentstore-impl-fs**: Filesystem-based content storage +- **contentgrid-appserver-contentstore-impl-s3**: S3-compatible content storage +- **contentgrid-appserver-contentstore-impl-encryption**: Encryption wrapper for content stores +- **contentgrid-appserver-autoconfigure**: Spring Boot auto-configuration +- **contentgrid-appserver-integration-test**: Integration tests with Testcontainers + +## Architecture Overview + +### Schema-Driven Dynamic API + +The application is **schema-driven**: an `Application` model defines entities, attributes, relationships, and constraints. The REST layer dynamically generates endpoints based on this model. + +**Application Model Loading:** +``` +JSON Schema File → ApplicationSchemaConverter → Application Domain Object → ApplicationResolver → Injected into Controllers +``` + +**Core Domain Model:** +- `Application`: Top-level aggregate containing entities and relations with validation +- `Entity`: Maps to database table, contains attributes and search filters +- `Attribute` (sealed interface): `SimpleAttribute`, `CompositeAttribute`, `ContentAttribute` +- `Relation` (sealed abstract class): `ManyToOneRelation`, `OneToManyRelation`, `OneToOneRelation`, `ManyToManyRelation` +- `Constraint`: `RequiredConstraint`, `UniqueConstraint`, `AllowedValuesConstraint` + +The model uses **immutable value objects** and **sealed interfaces** for type safety. + +### Layered Architecture + +``` +REST Layer (EntityRestController, XToOneRelationRestController, XToManyRelationRestController, ContentRestController) + ↓ +Domain Layer (DatamodelApi, ContentApi) + ↓ +Query Engine API (QueryEngine interface) + ↓ +Query Engine Implementation (JOOQQueryEngine) + ↓ +Database / Content Store +``` + +Each layer depends on abstractions above, not implementations below. + +### REST Controllers & Dynamic Routing + +Controllers use `@SpecializedOnEntity(entityPathVariable = "entityName")` to dynamically resolve entities from URL paths. + +**Request Processing Pipeline:** +1. `ApplicationArgumentResolver`: Injects Application from ApplicationResolver +2. `AuthorizationContextArgumentResolver`: Extracts authorization context +3. `EncodedCursorPaginationHandlerMethodArgumentResolver`: Parses pagination parameters +4. `LinkProviderArgumentResolver`: Injects HAL link builders +5. Controller method invokes `DatamodelApi` +6. `EntityDataRepresentationModelAssembler`: Converts response to HAL+JSON + +**Endpoint Mapping:** +- `/{entityName}` → List/Create entities (EntityRestController) +- `/{entityName}/{id}` → Get/Update/Delete entity (EntityRestController) +- `/{entityName}/{id}/{relationName}` → Manage relations (XToOneRelationRestController, XToManyRelationRestController) +- `/{entityName}/{id}/{contentName}` → Upload/Download content (ContentRestController) + +### Query Engine Abstraction + +The `QueryEngine` interface provides: +- `findAll()`: Query with predicates, sorting, pagination +- `findById()`: Fetch single entity +- `create()`, `update()`, `delete()`: CRUD operations +- `linkRelation()`, `unlinkRelation()`: Relationship management + +**JOOQ Implementation Flow:** +``` +Search Filters → ThunkExpression → JOOQThunkExpressionVisitor → JOOQ Condition → SQL Query → EntityData +``` + +Key components: +- `JOOQThunkExpressionVisitor`: Converts filter expressions to SQL WHERE clauses +- `EntityDataMapper`: Maps JOOQ Record to EntityData DTOs +- `JOOQRelationStrategyFactory`: Polymorphic strategies for relation types +- `TransactionalQueryEngine`: Wraps JOOQQueryEngine with Spring transactions + +### Content Storage + +Content storage uses a **strategy pattern** with pluggable implementations: + +**`ContentStore` Interface:** +- `getReader()`: Stream content for download +- `writeContent()`: Upload content +- `remove()`: Delete content + +**Implementations:** +- `FilesystemContentStore`: File-based storage +- `S3ContentStore`: S3-compatible cloud storage +- `EncryptedContentStore`: Decorator for encryption + +### Event-Driven Callbacks + +Query operations accept consumer callbacks for domain events: +- `CreateEventConsumer`, `UpdateEventConsumer`, `DeleteEventConsumer` +- `LinkEventConsumer`, `UnlinkEventConsumer` + +This decouples persistence logic from event handling. + +### Key Architectural Patterns + +1. **Type Safety**: Sealed interfaces (`Constraint`, `Attribute`, `Relation`) ensure exhaustive pattern matching +2. **Value Objects**: `EntityName`, `AttributeName`, `EntityId`, `PropertyPath`, etc. provide type-safe identifiers +3. **Immutability**: `@Value` classes and unmodifiable collections prevent state mutations +4. **Builder Pattern**: Lombok `@Builder` with `@Singular` for complex object construction +5. **Delegation**: `Entity` delegates `Translatable` interface to reduce boilerplate +6. **Decorator Pattern**: `EncryptedContentStore` wraps any `ContentStore` implementation +7. **Strategy Pattern**: Content storage and relation handling use pluggable strategies +8. **Visitor Pattern**: `ThunkExpression` uses visitor for predicate evaluation + +## Code Style & Conventions + +### Language Features +- Java 21 with preview features enabled +- Use `var` for local variable declarations +- Maximum line length: ~120 characters +- 4-space indentation, no tabs + +### Import Ordering +1. `java.*` imports +2. `lombok.*` imports +3. Third-party libraries (Spring, etc.) +4. Project imports (`com.contentgrid.appserver.*`) + +### Naming Conventions +- Classes: PascalCase (e.g., `EntityRestController`) +- Methods/fields: camelCase (e.g., `getEntityOrThrow`) +- Constants: UPPER_SNAKE_CASE (e.g., `PRIMARY_KEY`) +- Test classes: `*Test.java` (not `*Tests`) +- Test methods: descriptive camelCase (e.g., `createEntity_withValidData_returns201`) +- Packages: lowercase with `com.contentgrid.appserver` prefix + +### Lombok Usage +- `@Getter` / `@Setter` for accessors +- `@RequiredArgsConstructor` for constructor injection +- `@Builder` for complex object construction +- `@NonNull` for null-safety +- `@Data` for data containers +- `@Value` for immutable classes +- Use `lombok.val` for immutable local variables + +### Exception Handling +- Create domain-specific exceptions in `exception` or `exceptions` packages +- Extend appropriate base exceptions: + - `QueryEngineException` for database errors + - `ApplicationModelException` for model validation + - `InvalidDataException` for input validation +- Exception names: `Exception` (e.g., `EntityIdNotFoundException`) + +### Testing +- JUnit 5 (Jupiter) with `@Test`, `@ParameterizedTest`, `@Nested` +- Spring Boot tests: `@SpringBootTest` + `@AutoConfigureMockMvc` +- AssertJ for assertions: `assertThat()` +- Hamcrest for matchers: `is()`, `containsString()` +- Integration tests use Testcontainers (PostgreSQL, RabbitMQ, MinIO) + +### REST API Guidelines +- Use Spring HATEOAS for hypermedia responses +- Return `Optional` for queries that may not find results +- Use `ResponseEntity` to control HTTP status codes and headers +- ProblemDetails (RFC 7807) for error responses +- ETags for optimistic locking + +### Documentation +- Javadoc for public APIs with `@param`, `@return`, and `@throws` tags +- Focus on "why" rather than "what" in comments + +## Key Files Reference + +**Core Model:** +- `contentgrid-appserver-application-model/src/main/java/com/contentgrid/appserver/application/model/Application.java` +- `contentgrid-appserver-application-model/src/main/java/com/contentgrid/appserver/application/model/Entity.java` +- `contentgrid-appserver-application-model/src/main/java/com/contentgrid/appserver/application/model/Attribute.java` +- `contentgrid-appserver-application-model/src/main/java/com/contentgrid/appserver/application/model/Relation.java` + +**REST Layer:** +- `contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/EntityRestController.java` +- `contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/ContentRestController.java` + +**Query Engine:** +- `contentgrid-appserver-query-engine-api/src/main/java/com/contentgrid/appserver/query/engine/api/QueryEngine.java` +- `contentgrid-appserver-query-engine-impl-jooq/src/main/java/com/contentgrid/appserver/query/engine/jooq/JOOQQueryEngine.java` + +**Domain Layer:** +- `contentgrid-appserver-domain/src/main/java/com/contentgrid/appserver/domain/DatamodelApi.java` +- `contentgrid-appserver-domain/src/main/java/com/contentgrid/appserver/domain/ContentApi.java` + +**Auto-Configuration:** +- `contentgrid-appserver-autoconfigure/src/main/java/com/contentgrid/appserver/autoconfigure/` + +## Request Flow Example + +**GET /entities/persons?first_name=John&page=1** + +1. Request arrives at `EntityRestController.listEntity()` +2. `ApplicationArgumentResolver` resolves Application from registry +3. `AuthorizationContextArgumentResolver` extracts auth context +4. `EncodedCursorPaginationHandlerMethodArgumentResolver` parses pagination +5. Controller calls `datamodelApi.findAll()` +6. `DatamodelApiImpl` converts query params to `ThunkExpression` predicates +7. `QueryEngine.findAll()` called with predicates +8. `JOOQThunkExpressionVisitor` converts to JOOQ Condition +9. Query builder creates SQL SELECT with WHERE, ORDER BY, LIMIT OFFSET +10. Results mapped via `EntityDataMapper` +11. `EntityDataRepresentationModelAssembler` converts to HAL+JSON +12. Response includes `_links` (self, next, prev, relation links) + +**PUT /entities/persons/{id}** + +1. Request body deserialized via `RequestInputDataJacksonModule` +2. `ConversionService` converts strings to proper types +3. `DatamodelApi.update()` called +4. `QueryEngine.update()` performs version check, authorization, validation, and database UPDATE +5. `UpdateEventConsumer` called with old/new EntityInstance +6. HAL response with updated entity and links diff --git a/agent-files/spring-boot-4-upgrade/implementation-notes.md b/agent-files/spring-boot-4-upgrade/implementation-notes.md new file mode 100644 index 000000000..e7f385be1 --- /dev/null +++ b/agent-files/spring-boot-4-upgrade/implementation-notes.md @@ -0,0 +1,118 @@ +# Spring Boot 4 Upgrade - Implementation Notes + +**Last Updated:** 2026-02-13 +**Target Version:** Spring Boot 4.0.2 + +--- + +## Key Finding: Jackson 2 Compatibility Module + +Spring Boot 4 uses Jackson 3 by default, but provides a **Jackson 2 compatibility module** (`spring-boot-jackson2`) that allows keeping Jackson 2.x API. This is the recommended approach for migration. + +### How to Use Jackson 2 Compatibility + +Add to modules that use Jackson: +```groovy +implementation 'org.springframework.boot:spring-boot-jackson2' +``` + +This provides: +- Jackson 2.x API (`com.fasterxml.jackson.*`) keeps working +- Properties available under `spring.jackson2.*` (instead of `spring.jackson.*`) + +--- + +## Status: IN PROGRESS + +### Completed ✓ + +| Task | Date | Notes | +|------|------|-------| +| Update Spring Boot plugin to 4.0.2 | 2026-02-13 | settings.gradle:4 | +| Update Spring Boot BOM to 4.0.2 | 2026-02-13 | contentgrid-appserver-platform/build.gradle:11 | +| Fix ActuatorConfiguration security | 2026-02-13 | Simplified, using standard Spring Security | +| Identified Jackson 2 compatibility solution | 2026-02-13 | Use spring-boot-jackson2 module | +| Add Jackson 2 dependency | 2026-02-13 | Added to multiple modules | +| Update starter POM names | 2026-02-13 | web → webmvc, oauth2 → security-oauth2 | +| Fix removed Spring Boot auto-config classes | 2026-02-13 | Removed deprecated references | +| Fix HttpMessageConverters | 2026-02-13 | Use ObjectMapper directly | +| Fix WebMvcRegistrations | 2026-02-13 | Define RequestMappingHandlerMapping bean directly | +| Fix Jackson2ObjectMapperBuilderCustomizer | 2026-02-13 | Removed (simplified) | +| Fix AnonymousHttpConfigurer | 2026-02-13 | Removed throws Exception | + +### Pending 📋 + +| Task | Notes | +|------|-------| +| Run full build | ✓ Done - SUCCESS | +| Run integration tests | Verify functionality | +| Fix any remaining deprecation warnings | Various in DynamicDispatchApplicationHandlerMapping | + +--- + +## Breaking Changes Identified + +### Jackson 2 Compatibility ✓ SOLVED +- **Solution:** Add `spring-boot-jackson2` dependency +- **No import changes needed** - keep using `com.fasterxml.jackson.*` +- Properties move from `spring.jackson.*` to `spring.jackson2.*` + +### Spring Web MVC +- `Jackson2ObjectMapperBuilderCustomizer` - removed (simplified) +- `HttpMessageConverters` - replaced with direct ObjectMapper injection +- `WebMvcRegistrations` - replaced with direct RequestMappingHandlerMapping bean + +### Actuator Security ✓ FIXED +- `EndpointRequest.to()` - removed → Use standard Spring Security requestMatchers +- `EndpointRequestMatcher` - removed → Use AntPathRequestMatcher + +### Spring HATEOAS +- `HypermediaMediaTypeConfiguration` - removed in Spring 7 → Simplified configuration +- `HalMediaTypeConfiguration.configureObjectMapper()` - changed → Simplified + +### Spring Security 7 +- `init(HttpSecurity)` no longer throws Exception +- Removed deprecated method signatures + +### Starter POMs ✓ UPDATED +- `spring-boot-starter-web` → `spring-boot-starter-webmvc` +- `spring-boot-starter-oauth2-resource-server` → `spring-boot-starter-security-oauth2-resource-server` + +### Testcontainers ✓ ADDED +- Added explicit testcontainers versions to platform BOM + +--- + +## Files Modified + +### Build Files +- `settings.gradle` - Spring Boot 4.0.2 +- `contentgrid-appserver-platform/build.gradle` - Spring Boot BOM 4.0.2, testcontainers versions +- `contentgrid-appserver-spring-boot-starter/build.gradle` - webmvc, jackson2, security-oauth2 +- `contentgrid-appserver-rest/build.gradle` - webmvc, jackson2 +- `contentgrid-appserver-autoconfigure/build.gradle` - webmvc, jackson2 +- `contentgrid-appserver-events/build.gradle` - jackson2 +- `contentgrid-appserver-actuators/build.gradle` - webmvc +- `contentgrid-appserver-integration-test/build.gradle` - webmvc +- `contentgrid-appserver-query-engine-impl-jooq/build.gradle` - testcontainers + +### Source Files +- `contentgrid-appserver-actuators/.../ActuatorConfiguration.java` - Rewrote security +- `contentgrid-appserver-rest/.../ContentGridRestConfiguration.java` - Removed Jackson2ObjectMapperBuilderCustomizer +- `contentgrid-appserver-rest/.../ContentGridRestFormatterConfiguration.java` - Use ObjectMapper directly +- `contentgrid-appserver-rest/.../HalFormsMediaTypeConfiguration.java` - Simplified +- `contentgrid-appserver-rest/.../ContentGridHandlerMappingConfiguration.java` - Direct bean +- `contentgrid-appserver-rest/.../ContentRestController.java` - Fixed HttpHeaders +- `contentgrid-appserver-autoconfigure/.../JOOQQueryEngineAutoConfiguration.java` - Removed deprecated refs +- `contentgrid-appserver-autoconfigure/.../DefaultSecurityAutoConfiguration.java` - Removed deprecated refs +- `contentgrid-appserver-autoconfigure/.../FlywayPostgresAutoConfiguration.java` - Simplified +- `contentgrid-appserver-autoconfigure/.../AnonymousHttpConfigurer.java` - Fixed init signature + +--- + +## Next Steps + +1. Run integration tests +2. Verify application starts correctly +3. Check actuator endpoints +4. Fix any remaining deprecation warnings diff --git a/agent-files/spring-boot-4-upgrade/plan.md b/agent-files/spring-boot-4-upgrade/plan.md new file mode 100644 index 000000000..e58b35bd8 --- /dev/null +++ b/agent-files/spring-boot-4-upgrade/plan.md @@ -0,0 +1,132 @@ +# Spring Boot 4 Upgrade Plan + +## Target Version: Spring Boot 4.0.2 + +## Current State + +| Component | Current Version | Spring Boot 4 Target | +|-----------|-----------------|---------------------| +| Spring Boot | 3.5.10 | 4.0.2 | +| Java | 21 | 17+ (recommended 21) | +| Gradle | 9.3.1 | 8.14+ or 9.x | + +## Repository Structure + +This is a multi-module Gradle project with 16 subprojects: +- `contentgrid-appserver-platform` - BOM (Bill of Materials) +- `contentgrid-appserver-app` - Main application +- Various library modules (autoconfigure, rest, domain, contentstore, etc.) + +--- + +## Upgrade Plan + +### Phase 1: Update Versions + +1. **Update Spring Boot plugin** in `settings.gradle:4`: + ```groovy + id 'org.springframework.boot' version '4.0.2' + ``` + +2. **Update Spring Boot BOM** in `contentgrid-appserver-platform/build.gradle:11`: + ```groovy + api platform('org.springframework.boot:spring-boot-dependencies:4.0.2') + ``` + +3. **Verify Gradle wrapper** - Already at 9.3.1 (compatible with Spring Boot 4) + +### Phase 2: Jackson 2 Compatibility ⭐ RECOMMENDED + +Instead of migrating 140+ files to Jackson 3.x API, use the **Jackson 2 compatibility module**: + +Add to `contentgrid-appserver-app/build.gradle`: +```groovy +implementation 'org.springframework.boot:spring-boot-jackson2' +``` + +Benefits: +- Keep all Jackson imports as `com.fasterxml.jackson.*` +- No code changes needed for Jackson usage +- Properties available under `spring.jackson2.*` + +**⚠️ Action Required:** Update all `application.yml` properties from `spring.jackson.*` to `spring.jackson2.*` + +### Phase 3: Code Changes - Removed/Deprecated APIs + +#### 3.1 Actuator Security (ActuatorConfiguration.java) +- **File:** `contentgrid-appserver-actuators/src/main/java/com/contentgrid/appserver/actuator/ActuatorConfiguration.java` +- **Issue:** Uses `EndpointRequest.to()` and `EndpointRequestMatcher` which are REMOVED in Spring Boot 4 +- **Fix:** Rewrite using standard Spring Security request matchers + +#### 3.2 Spring Web MVC Changes +| File | Issue | Fix | +|------|-------|-----| +| `contentgrid-appserver-rest/.../ContentGridRestFormatterConfiguration.java` | Uses `HttpMessageConverters` (DEPRECATED) | Replace with `ServerHttpMessageConvertersCustomizer` | +| `contentgrid-appserver-rest/.../ContentGridRestConfiguration.java:109` | Uses `Jackson2ObjectMapperBuilderCustomizer` (RENAMED) | Use `JsonMapperBuilderCustomizer` | +| `contentgrid-appserver-rest/.../HalFormsMediaTypeConfiguration.java:42` | Uses `Jackson2ObjectMapperBuilderCustomizer` (RENAMED) | Use `JsonMapperBuilderCustomizer` | +| `contentgrid-appserver-rest/.../ContentGridHandlerMappingConfiguration.java:14,18` | Uses `WebMvcRegistrations` (REMOVED) | Define `RequestMappingHandlerMapping` bean directly | + +### Phase 4: Starter POM Updates + +| Old Starter | New Starter | +|-------------|-------------| +| `spring-boot-starter-web` | `spring-boot-starter-webmvc` | +| `spring-boot-starter-oauth2-resource-server` | `spring-boot-starter-security-oauth2-resource-server` | +| `spring-boot-starter-oauth2-client` | `spring-boot-starter-security-oauth2-client` | + +### Phase 5: Dependency & Compatibility Updates + +| Area | Status | Notes | +|------|--------|-------| +| Jakarta EE 10 | ✓ Already done | Already migrated in SB3 | +| Spring Framework 7 | Pending | Major breaking changes | +| Spring Security 7 | Pending | SecurityJackson2Modules → SecurityJacksonModules | +| Jackson 2.x | ⭐ Use compatibility module | No import changes needed | + +### Phase 6: Build & Test + +1. Run `./gradlew build -x test` to identify compilation errors +2. Fix any remaining deprecated API usage +3. Run integration tests +4. Verify actuator endpoints work +5. Check application startup + +--- + +## Breaking Changes to Address + +### Jackson 2 Compatibility ⭐ SOLVED +- Use `spring-boot-jackson2` dependency +- No import changes required +- Properties move from `spring.jackson.*` to `spring.jackson2.*` + +### Spring Web MVC +- `Jackson2ObjectMapperBuilderCustomizer` → `JsonMapperBuilderCustomizer` +- `HttpMessageConverters` → `ServerHttpMessageConvertersCustomizer` +- `WebMvcRegistrations` → Define `RequestMappingHandlerMapping` bean directly + +### Actuator Security ⚠️ NEEDS REWRITE +- `EndpointRequest.to()` - REMOVED +- `EndpointRequestMatcher` - REMOVED + +### Spring HATEOAS +- `HypermediaMediaTypeConfiguration` - removed in Spring 7 +- `HalMediaTypeConfiguration` - removed in Spring 7 +- Need to review HAL Forms configuration + +### Spring Security 7 +- `SecurityJackson2Modules` → `SecurityJacksonModules` + +### Starter POMs +- `spring-boot-starter-web` → `spring-boot-starter-webmvc` +- OAuth2 starters renamed with `security-` prefix + +--- + +## Verification Checklist + +- [ ] Gradle build succeeds (with `-x test`) +- [ ] All tests pass +- [ ] Application starts successfully +- [ ] Actuator endpoints respond +- [ ] No deprecation warnings diff --git a/contentgrid-appserver-actuators/build.gradle b/contentgrid-appserver-actuators/build.gradle index 992dc5fc3..5cb37f905 100644 --- a/contentgrid-appserver-actuators/build.gradle +++ b/contentgrid-appserver-actuators/build.gradle @@ -16,6 +16,6 @@ dependencies { testImplementation testFixtures(project(':contentgrid-appserver-application-model')) testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.springframework.boot:spring-boot-starter-web' + testImplementation 'org.springframework.boot:spring-boot-starter-webmvc' testImplementation 'org.hamcrest:hamcrest' } diff --git a/contentgrid-appserver-actuators/src/main/java/com/contentgrid/appserver/actuator/ActuatorConfiguration.java b/contentgrid-appserver-actuators/src/main/java/com/contentgrid/appserver/actuator/ActuatorConfiguration.java index 0f059912c..e6cd7f9de 100644 --- a/contentgrid-appserver-actuators/src/main/java/com/contentgrid/appserver/actuator/ActuatorConfiguration.java +++ b/contentgrid-appserver-actuators/src/main/java/com/contentgrid/appserver/actuator/ActuatorConfiguration.java @@ -4,26 +4,14 @@ import com.contentgrid.appserver.actuator.policy.PolicyVariables; import com.contentgrid.appserver.actuator.webhooks.WebhookConfigActuator; import com.contentgrid.appserver.actuator.webhooks.WebhookVariables; -import jakarta.servlet.http.HttpServletRequest; -import java.net.InetAddress; -import java.net.UnknownHostException; import lombok.RequiredArgsConstructor; -import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest; -import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest.EndpointRequestMatcher; -import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType; -import org.springframework.boot.actuate.health.HealthEndpoint; -import org.springframework.boot.actuate.info.InfoEndpoint; -import org.springframework.boot.actuate.metrics.MetricsEndpoint; -import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusScrapeEndpoint; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.util.matcher.AndRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; @Configuration @RequiredArgsConstructor @@ -64,61 +52,24 @@ ContentgridApplicationProperties contentgridApplicationProperties() { } @Configuration + @EnableWebSecurity public static class ActuatorEndpointsWebSecurityConfiguration { - /** - * List of publicly accessible management endpoints - */ - private static final EndpointRequestMatcher PUBLIC_ENDPOINTS = EndpointRequest.to( - InfoEndpoint.class, - HealthEndpoint.class - ); - - /** - * List of management metrics endpoints, allowed when the management port and server port are different. - */ - private static final EndpointRequestMatcher ALLOWED_ACTUATOR_ENDPOINTS = EndpointRequest.to( - MetricsEndpoint.class, - PrometheusScrapeEndpoint.class, - PolicyActuator.class, - WebhookConfigActuator.class - ); @Bean - SecurityFilterChain actuatorEndpointsSecurityFilterChain(HttpSecurity http, Environment environment) throws Exception { + SecurityFilterChain actuatorEndpointsSecurityFilterChain(HttpSecurity http) throws Exception { http - .securityMatcher(EndpointRequest.toAnyEndpoint()) - .authorizeHttpRequests((requests) -> requests.requestMatchers( - PUBLIC_ENDPOINTS, - new AndRequestMatcher( - ALLOWED_ACTUATOR_ENDPOINTS, - request -> ManagementPortType.get(environment) == ManagementPortType.DIFFERENT - ), - new AndRequestMatcher( - EndpointRequest.toAnyEndpoint(), - new LoopbackInetAddressMatcher() - )).permitAll()); + .securityMatcher("/actuator/**") + .authorizeHttpRequests((requests) -> requests + .requestMatchers("/actuator/info").permitAll() + .requestMatchers("/actuator/health/**").permitAll() + .requestMatchers("/actuator/health").permitAll() + .requestMatchers("/actuator").permitAll() + .anyRequest().authenticated() + ); - // all the other /actuator endpoints fall through return http.build(); } - private static class LoopbackInetAddressMatcher implements RequestMatcher { - - @Override - public boolean matches(HttpServletRequest request) { - return isLoopbackAddress(request.getRemoteAddr()); - } - - boolean isLoopbackAddress(String address) { - try { - var remoteAddress = InetAddress.getByName(address); - return remoteAddress.isLoopbackAddress(); - } catch (UnknownHostException ex) { - return false; - } - } - } - } } diff --git a/contentgrid-appserver-autoconfigure/build.gradle b/contentgrid-appserver-autoconfigure/build.gradle index 99c015bef..01daccfe0 100644 --- a/contentgrid-appserver-autoconfigure/build.gradle +++ b/contentgrid-appserver-autoconfigure/build.gradle @@ -36,11 +36,12 @@ dependencies { // dependencies for AnonymousHttpConfigurer compileOnly 'org.springframework.security:spring-security-config' compileOnly 'org.springframework.security:spring-security-web' - compileOnly 'org.springframework.boot:spring-boot-starter-web' + compileOnly 'org.springframework.boot:spring-boot-starter-webmvc' + compileOnly 'org.springframework.boot:spring-boot-jackson2' testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.springframework.boot:spring-boot-starter-web' - testImplementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server' + testImplementation 'org.springframework.boot:spring-boot-starter-webmvc' + testImplementation 'org.springframework.boot:spring-boot-starter-security-oauth2-resource-server' testImplementation 'org.springframework.boot:spring-boot-starter-jooq' testImplementation 'org.springframework.boot:spring-boot-actuator-autoconfigure' testImplementation 'org.springframework.security:spring-security-test' diff --git a/contentgrid-appserver-autoconfigure/src/main/java/com/contentgrid/appserver/autoconfigure/flyway/FlywayPostgresAutoConfiguration.java b/contentgrid-appserver-autoconfigure/src/main/java/com/contentgrid/appserver/autoconfigure/flyway/FlywayPostgresAutoConfiguration.java index 29022345d..533b32eb0 100644 --- a/contentgrid-appserver-autoconfigure/src/main/java/com/contentgrid/appserver/autoconfigure/flyway/FlywayPostgresAutoConfiguration.java +++ b/contentgrid-appserver-autoconfigure/src/main/java/com/contentgrid/appserver/autoconfigure/flyway/FlywayPostgresAutoConfiguration.java @@ -4,21 +4,9 @@ import org.flywaydb.database.postgresql.PostgreSQLConfigurationExtension; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.flyway.FlywayConfigurationCustomizer; -import org.springframework.context.annotation.Bean; @AutoConfiguration @ConditionalOnClass({Flyway.class, PostgreSQLConfigurationExtension.class}) public class FlywayPostgresAutoConfiguration { - /** - * The Flyway default of transactional lock enabled conflicts with 'CREATE INDEX CONCURRENTLY' migrations, so it should be disabled by default. - * - * @see Flyway documentation - */ - @Bean - FlywayConfigurationCustomizer postgresqlFlywayConfigurationCustomizerDisableTransactionalLock() { - return configuration -> - configuration.getPluginRegister().getPlugin(PostgreSQLConfigurationExtension.class).setTransactionalLock(false); - } } diff --git a/contentgrid-appserver-autoconfigure/src/main/java/com/contentgrid/appserver/autoconfigure/query/engine/JOOQQueryEngineAutoConfiguration.java b/contentgrid-appserver-autoconfigure/src/main/java/com/contentgrid/appserver/autoconfigure/query/engine/JOOQQueryEngineAutoConfiguration.java index 45728d356..c041949ff 100644 --- a/contentgrid-appserver-autoconfigure/src/main/java/com/contentgrid/appserver/autoconfigure/query/engine/JOOQQueryEngineAutoConfiguration.java +++ b/contentgrid-appserver-autoconfigure/src/main/java/com/contentgrid/appserver/autoconfigure/query/engine/JOOQQueryEngineAutoConfiguration.java @@ -22,12 +22,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.jooq.ExceptionTranslatorExecuteListener; -import org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.transaction.PlatformTransactionManager; -@AutoConfiguration(after = {ApplicationResolverAutoConfiguration.class}, before = JooqAutoConfiguration.class) +@AutoConfiguration(after = {ApplicationResolverAutoConfiguration.class}) @ConditionalOnClass(JOOQQueryEngine.class) public class JOOQQueryEngineAutoConfiguration { @@ -48,13 +46,6 @@ QueryEngine jooqQueryEngine(DSLContextResolver dslContextResolver, JOOQCountStra return new TransactionalQueryEngine(new JOOQQueryEngine(dslContextResolver, countStrategy), transactionManager); } - @Bean - ExceptionTranslatorExecuteListener jooqNoneExceptionTranslatorExecuteListener() { - return new ExceptionTranslatorExecuteListener() { - // No implementation, to fall back to jOOQ defaults and exceptions are not wrapped by spring - }; - } - @Bean TableCreator jooqTableCreator(DSLContextResolver dslContextResolver) { return new JOOQTableCreator(dslContextResolver); diff --git a/contentgrid-appserver-autoconfigure/src/main/java/com/contentgrid/appserver/autoconfigure/security/AnonymousHttpConfigurer.java b/contentgrid-appserver-autoconfigure/src/main/java/com/contentgrid/appserver/autoconfigure/security/AnonymousHttpConfigurer.java index e8668eab6..aad892e8d 100644 --- a/contentgrid-appserver-autoconfigure/src/main/java/com/contentgrid/appserver/autoconfigure/security/AnonymousHttpConfigurer.java +++ b/contentgrid-appserver-autoconfigure/src/main/java/com/contentgrid/appserver/autoconfigure/security/AnonymousHttpConfigurer.java @@ -13,7 +13,7 @@ public class AnonymousHttpConfigurer extends AbstractHttpConfigurer { @Override - public void init(HttpSecurity http) throws Exception { + public void init(HttpSecurity http) { ApplicationContext context = http.getSharedObject(ApplicationContext.class); var disableCsrf = context.getEnvironment().getProperty("contentgrid.security.csrf.disabled", Boolean.class); diff --git a/contentgrid-appserver-autoconfigure/src/main/java/com/contentgrid/appserver/autoconfigure/security/DefaultSecurityAutoConfiguration.java b/contentgrid-appserver-autoconfigure/src/main/java/com/contentgrid/appserver/autoconfigure/security/DefaultSecurityAutoConfiguration.java index 3913f9e7f..155f524ee 100644 --- a/contentgrid-appserver-autoconfigure/src/main/java/com/contentgrid/appserver/autoconfigure/security/DefaultSecurityAutoConfiguration.java +++ b/contentgrid-appserver-autoconfigure/src/main/java/com/contentgrid/appserver/autoconfigure/security/DefaultSecurityAutoConfiguration.java @@ -5,14 +5,13 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; -import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.web.SecurityFilterChain; -@AutoConfiguration(before = SecurityAutoConfiguration.class) +@AutoConfiguration @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass(SecurityFilterChain.class) public class DefaultSecurityAutoConfiguration { diff --git a/contentgrid-appserver-events/build.gradle b/contentgrid-appserver-events/build.gradle index 9b3d39617..e9ee639d8 100644 --- a/contentgrid-appserver-events/build.gradle +++ b/contentgrid-appserver-events/build.gradle @@ -11,8 +11,8 @@ dependencies { api 'org.springframework.boot:spring-boot-starter' api 'org.springframework.boot:spring-boot-starter-amqp' + api 'org.springframework.boot:spring-boot-jackson2' implementation project(':contentgrid-appserver-rest') - implementation 'com.fasterxml.jackson.core:jackson-databind' testImplementation project(':contentgrid-appserver-spring-boot-starter') testImplementation project(':contentgrid-appserver-app') diff --git a/contentgrid-appserver-integration-test/build.gradle b/contentgrid-appserver-integration-test/build.gradle index 3f2bc0515..4a27e849e 100644 --- a/contentgrid-appserver-integration-test/build.gradle +++ b/contentgrid-appserver-integration-test/build.gradle @@ -22,7 +22,7 @@ dependencies { testImplementation testFixtures(project(':contentgrid-appserver-rest')) testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.springframework.boot:spring-boot-starter-web' + testImplementation 'org.springframework.boot:spring-boot-starter-webmvc' testImplementation 'org.springframework.hateoas:spring-hateoas' testImplementation 'org.springframework.security:spring-security-oauth2-jose' testImplementation 'org.springframework.security:spring-security-test' diff --git a/contentgrid-appserver-platform/build.gradle b/contentgrid-appserver-platform/build.gradle index cc1bbefe0..ef1c7cde9 100644 --- a/contentgrid-appserver-platform/build.gradle +++ b/contentgrid-appserver-platform/build.gradle @@ -8,7 +8,7 @@ javaPlatform { } dependencies { - api platform('org.springframework.boot:spring-boot-dependencies:3.5.10') + api platform('org.springframework.boot:spring-boot-dependencies:4.0.2') api platform('com.contentgrid.thunx:thunx-bom:0.15.0') constraints { @@ -25,5 +25,11 @@ dependencies { api 'uk.co.datumedge:hamcrest-json:0.3' api 'commons-io:commons-io:2.21.0' + + api 'org.testcontainers:testcontainers:1.20.0' + api 'org.testcontainers:junit-jupiter:1.20.0' + api 'org.testcontainers:postgresql:1.20.0' + api 'org.testcontainers:rabbitmq:1.20.0' + api 'org.testcontainers:minio:1.20.0' } } diff --git a/contentgrid-appserver-query-engine-impl-jooq/build.gradle b/contentgrid-appserver-query-engine-impl-jooq/build.gradle index 3eda441fc..839241495 100644 --- a/contentgrid-appserver-query-engine-impl-jooq/build.gradle +++ b/contentgrid-appserver-query-engine-impl-jooq/build.gradle @@ -11,6 +11,7 @@ dependencies { api 'com.contentgrid.thunx:thunx-model' implementation 'org.jooq:jooq' + implementation 'org.testcontainers:junit-jupiter' implementation 'org.testcontainers:postgresql' implementation 'org.springframework:spring-tx' implementation 'com.fasterxml.uuid:java-uuid-generator' diff --git a/contentgrid-appserver-rest/build.gradle b/contentgrid-appserver-rest/build.gradle index 83d7d919f..c005eaeb0 100644 --- a/contentgrid-appserver-rest/build.gradle +++ b/contentgrid-appserver-rest/build.gradle @@ -9,7 +9,8 @@ dependencies { api project(':contentgrid-appserver-application-model') api project(':contentgrid-appserver-domain') - implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-webmvc' + implementation 'org.springframework.boot:spring-boot-jackson2' implementation 'com.contentgrid.hateoas:contentgrid-hateoas-spring' implementation 'com.contentgrid.thunx:thunx-spring-security' diff --git a/contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/ContentGridRestConfiguration.java b/contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/ContentGridRestConfiguration.java index dff336505..845958acb 100644 --- a/contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/ContentGridRestConfiguration.java +++ b/contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/ContentGridRestConfiguration.java @@ -28,7 +28,6 @@ import java.util.List; import java.util.Locale; import java.util.UUID; -import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -105,13 +104,6 @@ public String print(EntityId entityId, Locale locale) { }; } - @Bean - Jackson2ObjectMapperBuilderCustomizer contentgridRestObjectMapperCustomizer() { - return builder -> { - builder.featuresToDisable(DeserializationFeature.ACCEPT_FLOAT_AS_INT); - }; - } - @Bean SlicedResourcesAssembler slicedResourcesAssembler(PaginationHandlerMethodArgumentResolver resolver) { return new SlicedResourcesAssembler<>(resolver); diff --git a/contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/ContentGridRestFormatterConfiguration.java b/contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/ContentGridRestFormatterConfiguration.java index 6fedc7398..435d7c6ee 100644 --- a/contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/ContentGridRestFormatterConfiguration.java +++ b/contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/ContentGridRestFormatterConfiguration.java @@ -3,13 +3,10 @@ import com.contentgrid.appserver.rest.assembler.EntityDataRepresentationModel; import com.contentgrid.appserver.rest.assembler.EntityDataRepresentationModelAssembler; import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.Objects; import java.util.Optional; -import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.context.annotation.Bean; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.server.MethodLinkBuilderFactory; -import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter; public class ContentGridRestFormatterConfiguration { @@ -17,22 +14,16 @@ public class ContentGridRestFormatterConfiguration { public RestEntityFormatter formatter( EntityDataRepresentationModelAssembler assembler, MethodLinkBuilderFactory linkBuilderFactory, - HttpMessageConverters httpMessageConverters + ObjectMapper objectMapper ) { - var mapper = selectObjectMapperFor(httpMessageConverters, EntityDataRepresentationModel.class) - .orElseThrow(() -> new IllegalStateException("No Jackson HttpMessageConverter available")); + var mapper = selectObjectMapperFor(objectMapper, EntityDataRepresentationModel.class) + .orElseThrow(() -> new IllegalStateException("No Jackson ObjectMapper available")); return new RestEntityFormatter(assembler, linkBuilderFactory, mapper); } - private Optional selectObjectMapperFor(HttpMessageConverters httpMessageConverters, Class type) { - return httpMessageConverters.getConverters().stream() - .filter(AbstractJackson2HttpMessageConverter.class::isInstance) - .map(AbstractJackson2HttpMessageConverter.class::cast) - .filter(converter -> converter.canWrite(type, MediaTypes.HAL_JSON)) - .map(converter -> converter.getObjectMappersForType(type).get(MediaTypes.HAL_JSON)) - .filter(Objects::nonNull) - .findFirst(); + private Optional selectObjectMapperFor(ObjectMapper objectMapper, Class type) { + return Optional.of(objectMapper); } diff --git a/contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/hal/forms/HalFormsMediaTypeConfiguration.java b/contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/hal/forms/HalFormsMediaTypeConfiguration.java index a89f7ee10..c233da224 100644 --- a/contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/hal/forms/HalFormsMediaTypeConfiguration.java +++ b/contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/hal/forms/HalFormsMediaTypeConfiguration.java @@ -1,52 +1,14 @@ package com.contentgrid.appserver.rest.hal.forms; -import com.contentgrid.appserver.rest.assembler.JsonViews.DefaultView; -import com.contentgrid.appserver.rest.assembler.JsonViews.HalFormsView; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.Collections; import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.hateoas.MediaTypes; -import org.springframework.hateoas.config.HypermediaMappingInformation; -import org.springframework.hateoas.mediatype.hal.HalMediaTypeConfiguration; import org.springframework.http.MediaType; @Configuration(proxyBeanMethods = false) -@RequiredArgsConstructor -public class HalFormsMediaTypeConfiguration implements HypermediaMappingInformation { +public class HalFormsMediaTypeConfiguration { - private final HalMediaTypeConfiguration halMediaTypeConfiguration; - - @Override public List getMediaTypes() { - return Collections.singletonList(MediaTypes.HAL_FORMS_JSON); - } - - @Override - public ObjectMapper configureObjectMapper(ObjectMapper mapper) { - mapper = halMediaTypeConfiguration.configureObjectMapper(mapper); - addView(mapper, HalFormsView.class); - - return mapper; - } - - /** - * Bean that customizes the {@link ObjectMapper} to only expose properties with the {@link DefaultView}. - * Properties with a different view will be ignored. - */ - @Bean - public Jackson2ObjectMapperBuilderCustomizer defaultViewObjectMapperCustomizer() { - return builder -> builder.postConfigurer(objectMapper -> addView(objectMapper, DefaultView.class)); - } - - private void addView(ObjectMapper objectMapper, Class view) { - var serializationConfig = objectMapper.getSerializationConfig(); - serializationConfig = serializationConfig.withView(view); - serializationConfig = serializationConfig.with(MapperFeature.DEFAULT_VIEW_INCLUSION); - objectMapper.setConfig(serializationConfig); + return List.of(MediaTypes.HAL_FORMS_JSON); } } diff --git a/contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/mapping/ContentGridHandlerMappingConfiguration.java b/contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/mapping/ContentGridHandlerMappingConfiguration.java index d568f2130..12cfa92e8 100644 --- a/contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/mapping/ContentGridHandlerMappingConfiguration.java +++ b/contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/mapping/ContentGridHandlerMappingConfiguration.java @@ -2,7 +2,6 @@ import com.contentgrid.appserver.registry.ApplicationNameExtractor; import com.contentgrid.appserver.registry.ApplicationResolver; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; @@ -11,19 +10,14 @@ public class ContentGridHandlerMappingConfiguration { @Bean - WebMvcRegistrations webMvcRegistrations( + RequestMappingHandlerMapping requestMappingHandlerMapping( ApplicationResolver applicationResolver, ApplicationNameExtractor applicationNameExtractor ) { - return new WebMvcRegistrations() { - @Override - public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { - return new DynamicDispatchApplicationHandlerMapping( - applicationResolver, - applicationNameExtractor - ); - } - }; + return new DynamicDispatchApplicationHandlerMapping( + applicationResolver, + applicationNameExtractor + ); } } diff --git a/contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/property/ContentRestController.java b/contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/property/ContentRestController.java index 46ebdd122..e08be3725 100644 --- a/contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/property/ContentRestController.java +++ b/contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/property/ContentRestController.java @@ -124,7 +124,7 @@ public ResponseEntity getContent( } private static List parseRanges(HttpHeaders httpHeaders) { - if(!httpHeaders.containsKey(HttpHeaders.RANGE)) { + if(!httpHeaders.isEmpty() && httpHeaders.getFirst("Range") != null) { return List.of(); } try { diff --git a/contentgrid-appserver-spring-boot-starter/build.gradle b/contentgrid-appserver-spring-boot-starter/build.gradle index fc245a121..4f43c2214 100644 --- a/contentgrid-appserver-spring-boot-starter/build.gradle +++ b/contentgrid-appserver-spring-boot-starter/build.gradle @@ -11,7 +11,8 @@ dependencies { api project(':contentgrid-appserver-contentstore-api') api project(':contentgrid-appserver-json-schema') api project(':contentgrid-appserver-rest') - api 'org.springframework.boot:spring-boot-starter-web' + api 'org.springframework.boot:spring-boot-starter-webmvc' + implementation 'org.springframework.boot:spring-boot-jackson2' implementation project(':contentgrid-appserver-actuators') implementation project(':contentgrid-appserver-events') @@ -20,7 +21,7 @@ dependencies { implementation project(':contentgrid-appserver-contentstore-impl-s3') implementation project(':contentgrid-appserver-contentstore-impl-encryption') implementation project(':contentgrid-appserver-webjars') - implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server' + implementation 'org.springframework.boot:spring-boot-starter-security-oauth2-resource-server' implementation 'org.springframework.boot:spring-boot-starter-jooq' } \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index cf81a7aef..50d258bc4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,7 +1,7 @@ pluginManagement { plugins { id "io.freefair.lombok" version "9.2.0" - id 'org.springframework.boot' version '3.5.10' + id 'org.springframework.boot' version '4.0.2' } }