diff --git a/README.md b/README.md index 66cfd13..6609ffd 100644 --- a/README.md +++ b/README.md @@ -79,13 +79,13 @@ DB_USERNAME=your-username DB_PASSWORD=your-password ``` -The `application.properties` file uses these environment variables with modern Spring Boot 3.5+ properties: +The `application.properties` file uses these environment variables with modern Spring Boot 4.0+ properties: ```properties # Server configuration server.forward-headers-strategy=framework -# Modern Couchbase configuration (Spring Boot 3.5+) +# Modern Couchbase configuration (Spring Boot 4.0+) spring.couchbase.connection-string=${DB_CONN_STR} spring.couchbase.username=${DB_USERNAME} spring.couchbase.password=${DB_PASSWORD} @@ -178,7 +178,7 @@ public class CouchbaseConfiguration extends AbstractCouchbaseConfiguration { > _from config/CouchbaseConfiguration.java_ -This configuration uses modern Spring Boot 3.5+ properties and automatically loads environment variables from `.env` files for local development. The configuration assumes you have either a locally running Couchbase server or a Couchbase Capella cluster. +This configuration uses modern Spring Boot 4.0+ properties and automatically loads environment variables from `.env` files for local development. The configuration assumes you have either a locally running Couchbase server or a Couchbase Capella cluster. Applications deployed to production or staging environments should use less privileged credentials created using [Role-Based Access Control](https://docs.couchbase.com/go-sdk/current/concept-docs/rbac.html). Please refer to [Managing Connections using the Java SDK with Couchbase Server](https://docs.couchbase.com/java-sdk/current/howtos/managing-connections.html) for more information on Capella and local cluster connections. diff --git a/build.gradle b/build.gradle index c4fdb90..a459330 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'org.springframework.boot' version '3.5.7' + id 'org.springframework.boot' version '4.0.1' id 'io.spring.dependency-management' version '1.1.7' id 'java' } @@ -35,11 +35,13 @@ dependencies { testCompileOnly 'org.projectlombok:lombok:' testAnnotationProcessor 'org.projectlombok:lombok' - // swagger 3 springdoc openapi 2 - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.14' + // swagger 3 springdoc openapi 3 for Spring Boot 4 + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.1' testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test' + testImplementation 'org.springframework.boot:spring-boot-starter-restclient' } tasks.named('test') { diff --git a/src/main/java/org/couchbase/quickstart/springdata/Application.java b/src/main/java/org/couchbase/quickstart/springdata/Application.java index 21dc560..ec8b970 100644 --- a/src/main/java/org/couchbase/quickstart/springdata/Application.java +++ b/src/main/java/org/couchbase/quickstart/springdata/Application.java @@ -7,7 +7,6 @@ import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.web.filter.ForwardedHeaderFilter; @@ -21,7 +20,7 @@ **/ @Slf4j -@SpringBootApplication(exclude = SecurityAutoConfiguration.class, proxyBeanMethods = false) +@SpringBootApplication(proxyBeanMethods = false) @OpenAPIDefinition(info = @Info(title = TITLE, version = VERSION, description = DESCRIPTION)) public class Application implements CommandLineRunner { diff --git a/src/main/java/org/couchbase/quickstart/springdata/models/RestResponsePage.java b/src/main/java/org/couchbase/quickstart/springdata/models/RestResponsePage.java index 4a17518..4512026 100644 --- a/src/main/java/org/couchbase/quickstart/springdata/models/RestResponsePage.java +++ b/src/main/java/org/couchbase/quickstart/springdata/models/RestResponsePage.java @@ -7,7 +7,6 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.JsonNode; public class RestResponsePage extends PageImpl { @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) @@ -15,10 +14,10 @@ public RestResponsePage(@JsonProperty("content") List content, @JsonProperty("number") int number, @JsonProperty("size") int size, @JsonProperty("totalElements") Long totalElements, - @JsonProperty("pageable") JsonNode pageable, + @JsonProperty("pageable") Object pageable, @JsonProperty("last") boolean last, @JsonProperty("totalPages") int totalPages, - @JsonProperty("sort") JsonNode sort, + @JsonProperty("sort") Object sort, @JsonProperty("first") boolean first, @JsonProperty("numberOfElements") int numberOfElements) { diff --git a/src/main/java/org/couchbase/quickstart/springdata/models/RestResponseSlice.java b/src/main/java/org/couchbase/quickstart/springdata/models/RestResponseSlice.java index 6068798..2e4d36d 100644 --- a/src/main/java/org/couchbase/quickstart/springdata/models/RestResponseSlice.java +++ b/src/main/java/org/couchbase/quickstart/springdata/models/RestResponseSlice.java @@ -8,7 +8,6 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.JsonNode; @JsonIgnoreProperties(ignoreUnknown = true) public class RestResponseSlice extends SliceImpl { @@ -16,16 +15,16 @@ public class RestResponseSlice extends SliceImpl { public RestResponseSlice(@JsonProperty("content") List content, @JsonProperty("number") int number, @JsonProperty("size") int size, - @JsonProperty("pageable") JsonNode pageable, + @JsonProperty("pageable") Object pageable, @JsonProperty("last") boolean last, - @JsonProperty("sort") JsonNode sort, + @JsonProperty("sort") Object sort, @JsonProperty("first") boolean first, @JsonProperty("numberOfElements") int numberOfElements, - @JsonProperty("hasNext") boolean hasNext) { + @JsonProperty("hasNext") Boolean hasNext) { // Calculate hasNext from the available information // For Slice, hasNext is typically determined by whether we have more content - super(content, PageRequest.of(number, size), hasNext); + super(content, PageRequest.of(number, size), hasNext != null ? hasNext : !last); } } \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f54c49d..a228890 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,7 +1,7 @@ # Server configuration server.forward-headers-strategy=framework -# Modern Couchbase configuration (Spring Boot 3.5+) +# Modern Couchbase configuration (Spring Boot 4.0+) spring.couchbase.connection-string=${DB_CONN_STR} spring.couchbase.username=${DB_USERNAME} spring.couchbase.password=${DB_PASSWORD} @@ -13,3 +13,7 @@ spring.couchbase.env.timeouts.connect=10000ms # Spring MVC configuration spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER + +# springdoc-openapi 3.x - enable API docs (disabled by default in 3.x) +springdoc.api-docs.enabled=true +springdoc.swagger-ui.enabled=true diff --git a/src/test/java/org/couchbase/quickstart/springdata/controllers/AirlineIntegrationTest.java b/src/test/java/org/couchbase/quickstart/springdata/controllers/AirlineIntegrationTest.java index bb2bce9..26e9fc6 100644 --- a/src/test/java/org/couchbase/quickstart/springdata/controllers/AirlineIntegrationTest.java +++ b/src/test/java/org/couchbase/quickstart/springdata/controllers/AirlineIntegrationTest.java @@ -10,8 +10,9 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.resttestclient.TestRestTemplate; +import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.core.ParameterizedTypeReference; import org.springframework.dao.DataRetrievalFailureException; import org.springframework.http.HttpMethod; @@ -25,6 +26,7 @@ @Slf4j @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureTestRestTemplate class AirlineIntegrationTest { @Value("${local.server.port}") diff --git a/src/test/java/org/couchbase/quickstart/springdata/controllers/AirportIntegrationTest.java b/src/test/java/org/couchbase/quickstart/springdata/controllers/AirportIntegrationTest.java index 90d5705..e92fedb 100644 --- a/src/test/java/org/couchbase/quickstart/springdata/controllers/AirportIntegrationTest.java +++ b/src/test/java/org/couchbase/quickstart/springdata/controllers/AirportIntegrationTest.java @@ -12,8 +12,9 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.resttestclient.TestRestTemplate; +import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.core.ParameterizedTypeReference; import org.springframework.dao.DataRetrievalFailureException; import org.springframework.http.HttpMethod; @@ -27,6 +28,7 @@ @Slf4j @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureTestRestTemplate class AirportIntegrationTest { @Value("${local.server.port}") diff --git a/src/test/java/org/couchbase/quickstart/springdata/controllers/RouteIntegrationTest.java b/src/test/java/org/couchbase/quickstart/springdata/controllers/RouteIntegrationTest.java index 6f3ad9d..01022dd 100644 --- a/src/test/java/org/couchbase/quickstart/springdata/controllers/RouteIntegrationTest.java +++ b/src/test/java/org/couchbase/quickstart/springdata/controllers/RouteIntegrationTest.java @@ -12,8 +12,9 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.resttestclient.TestRestTemplate; +import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.core.ParameterizedTypeReference; import org.springframework.dao.DataRetrievalFailureException; import org.springframework.http.HttpMethod; @@ -27,6 +28,7 @@ @Slf4j @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureTestRestTemplate class RouteIntegrationTest { @Value("${local.server.port}")