diff --git a/.github/workflows/integrationTests.yaml b/.github/workflows/integrationTests.yaml index 53eb236887..63fd278d27 100644 --- a/.github/workflows/integrationTests.yaml +++ b/.github/workflows/integrationTests.yaml @@ -50,26 +50,10 @@ jobs: id: date run: echo "::set-output name=date::$(date +'%Y-%m-%d' --utc)" - uses: actions/checkout@v2 - - name: Setup Java 11 + - name: Setup Java 17 uses: actions/setup-java@v1 - if: matrix.it != 'native' with: - java-version: 11 - - name: Setup GraalVM - uses: DeLaGuardo/setup-graalvm@4.0 - if: matrix.it == 'native' # note that this is no longer in the matrix - with: - graalvm: '21.2.0' - java: 'java11' - arch: 'amd64' - - name: Install GraalVM Native Image - if: matrix.it == 'native' - run: gu install native-image - - uses: actions/cache@v2 - id: mvn-cache - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-unified-${{ steps.date.outputs.date }} + java-version: 17 - name: Setup gcloud uses: google-github-actions/setup-gcloud@v0 with: diff --git a/.github/workflows/sonar.yaml b/.github/workflows/sonar.yaml index 051cf91f00..2c166d3546 100644 --- a/.github/workflows/sonar.yaml +++ b/.github/workflows/sonar.yaml @@ -21,10 +21,10 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v1 with: - java-version: 11 + java-version: 17 - name: Cache SonarCloud packages uses: actions/cache@v2 with: diff --git a/.github/workflows/unitTests.yaml b/.github/workflows/unitTests.yaml index 4b40ce9dcb..e564a80d27 100644 --- a/.github/workflows/unitTests.yaml +++ b/.github/workflows/unitTests.yaml @@ -17,7 +17,7 @@ jobs: strategy: fail-fast: false matrix: - java: [8, 11, 17] + java: [17, 19] steps: - name: Get current date id: date @@ -103,7 +103,7 @@ jobs: - uses: actions/setup-java@v1 with: - java-version: 11 + java-version: 17 - uses: actions/cache@v2 id: mvn-cache diff --git a/.kokoro/publish_javadoc11.sh b/.kokoro/publish_javadoc11.sh index c23187b4bc..4dc47ddd5f 100644 --- a/.kokoro/publish_javadoc11.sh +++ b/.kokoro/publish_javadoc11.sh @@ -79,6 +79,7 @@ sudo pandoc \ docs/src/main/md/kotlin.md \ docs/src/main/md/configuration.md \ docs/src/main/md/migration-guide-1.x.md \ + docs/src/main/md/migration-guide-3.x.md \ -t markdown_github -o docs/src/main/md/documentation.md # copy and replace {project-version} documentation diff --git a/CHANGELOG.md b/CHANGELOG.md index 8151511411..3e32146500 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,96 @@ refer to the [commit history](https://github.com/GoogleCloudPlatform/spring-cloud-gcp/commits/main) on GitHub. +## 4.0.0 + +### Generals +This release officially introduces Spring Boot 3.x compatibility. Note that breaking changes occur in this release. + +All changes within one large [Pull Request](https://github.com/GoogleCloudPlatform/spring-cloud-gcp/pull/1287) as we think split PRs may bring compatibility issues. For the rational of each change, please refer to the description of the PR. + +### Important version upgrades +* Minimum Java version: Java 17 +* Spring Framework libraries upgrade + - [Spring Boot 3.0](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide) + - [Spring Framework 6.0](https://github.com/spring-projects/spring-framework/wiki/Upgrading-to-Spring-Framework-6.x) + - [Spring Cloud 2022.0](https://github.com/spring-cloud/spring-cloud-release/wiki/Spring-Cloud-2022.0-Release-Notes) +* Upgrade plugin versions +* [Enable auto-configuration](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide#auto-configuration-files) +* Fix [#1294](https://github.com/GoogleCloudPlatform/spring-cloud-gcp/issues/1294) + +### Bigquery +* Remove deprecated methods +* Replace `ListenableFuture` and `SettableListenableFuture` with `CompletableFuture` +* Code polish +* Improve code coverage + +### Cloud SQL +* Remove support for `r2dbc-mysql` +* Fix [#1200](https://github.com/GoogleCloudPlatform/spring-cloud-gcp/issues/1200) + +### Datastore +* Remove deprecated methods and constructors +* Replace `ClassTypeInformation` with `TypeInformation` +* Extend inheritance of `DatastoreRepository` +* Change datastore-basic-sample to a web application to remove `spring-shell` dependency +* Fix broken tests +* Code polish + +### Firestore +* Replace `ClassTypeInformation` with `TypeInformation` +* Code polish + +### Kotlin sample +* Code polish + +### Logging +* Replace `javax.servlet` with `jakarta.servlet` +* Remove deprecated methods +* Code polish + +### Native support +* Delete the module + +### Pubsub +* Replace `ListenableFuture` and `SettableListenableFuture` with `CompletableFuture` +* Remove deprecated methods and constructors +* Code polish + +### Pubsub-stream-binder +* Change to functional programming model +* Change `spring-cloud-stream-binder-test` to `spring-cloud-stream-test-support` +* Fix broken tests + +### Secret Manager +* Delete `GcpSecretManagerBootstrapConfiguration` + +### Security +* Replace `javax.servlet` with `jakarta.servlet` + +### Spanner +* Replace `PreferredConstructor` with `InstanceCreatorMetadata` +* Replace `ClassTypeInformation` with `TypeInformation` +* Remove deprecated methods and constructors +* Extend inheritance of `SpannerRepository` +* Fix broken tests +* Code polish + +### Storage +* Remove deprecated methods +* Code polish +* Improve code coverage + +### Trace +* Remove `spring-cloud-sleuth` dependency +* Instrument trace using Observation API +* Remove deprecated methods +* Fix broken tests + +### Vision +* Replace `ListenableFuture` and `SettableListenableFuture` with `CompletableFuture` +* Fix broken tests +* Code polish + ## [3.4.2](https://github.com/GoogleCloudPlatform/spring-cloud-gcp/compare/v3.4.1...v3.4.2) (2023-01-18) @@ -21,8 +111,6 @@ on GitHub. * Fixes docs related to keeping a background user thread - pub/sub ([2802b8e](https://github.com/GoogleCloudPlatform/spring-cloud-gcp/commit/2802b8e4dd22dadb9643efe73bca3615435ac362)) -## 3.5.0-SNAPSHOT - ## 3.4.0 ### General diff --git a/README.adoc b/README.adoc index c86718f74c..9155fcce99 100644 --- a/README.adoc +++ b/README.adoc @@ -36,7 +36,6 @@ Currently, this repository provides support for: ** link:spring-cloud-gcp-starters/spring-cloud-gcp-starter-sql-mysql[Google Cloud SQL MySQL] ** link:spring-cloud-gcp-starters/spring-cloud-gcp-starter-sql-postgresql[Google Cloud SQL PostgreSQL] ** link:spring-cloud-gcp-starters/spring-cloud-gcp-starter-storage[Google Cloud Storage] -** link:spring-cloud-gcp-starters/spring-cloud-gcp-starter-trace[Google Cloud Trace with Spring Cloud Sleuth] ** link:spring-cloud-gcp-starters/spring-cloud-gcp-starter-secretmanager[Google Secret Manager] ** link:spring-cloud-gcp-starters/spring-cloud-gcp-starter-security-firebase[Firebase Authentication] ** link:spring-cloud-gcp-starters/spring-cloud-gcp-starter-security-iap[Google Cloud IAP Authentication] @@ -55,6 +54,7 @@ This project has dependency and transitive dependencies on Spring Projects. The |2.0.x |https://github.com/spring-cloud/spring-cloud-release/wiki/Spring-Cloud-2020.0-Release-Notes[2020.0.x] (3.0/Illford) |2.4.x, 2.5.x|5.3.x| No |3.x | https://github.com/spring-cloud/spring-cloud-release/wiki/Spring-Cloud-2021.0-Release-Notes[2021.0.x] (3.1/Jubilee) |2.6.x, 2.7.x | 5.3.x| Yes +|4.x | https://github.com/spring-cloud/spring-cloud-release/wiki/Spring-Cloud-2022.0-Release-Notes[2022.0.x] (4.0/Kilburn) |https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Release-Notes[3.x]| https://github.com/spring-projects/spring-framework/wiki/What%27s-New-in-Spring-Framework-6.x[6.x]| Yes |=== == Spring Initializr @@ -77,7 +77,7 @@ This will allow you to not specify versions for any of the Maven dependencies an com.google.cloud spring-cloud-gcp-dependencies - 3.4.0 + 4.0.0 pom import diff --git a/docs/pom.xml b/docs/pom.xml index aebf902027..ff3a30a742 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -13,7 +13,7 @@ Spring Framework on Google Cloud Documentation com.google.cloud spring-cloud-gcp-docs - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT pom @@ -23,7 +23,7 @@ none - 3.3.0 + 3.1.0 diff --git a/docs/src/main/asciidoc/bigquery.adoc b/docs/src/main/asciidoc/bigquery.adoc index 79498f9bd0..180f90716a 100644 --- a/docs/src/main/asciidoc/bigquery.adoc +++ b/docs/src/main/asciidoc/bigquery.adoc @@ -84,7 +84,7 @@ Below is a code snippet of how to load a CSV data `InputStream` to a BigQuery ta BigQueryTemplate bigQueryTemplate; public void loadData(InputStream dataInputStream, String tableName) { - ListenableFuture bigQueryJobFuture = + CompletableFuture bigQueryJobFuture = bigQueryTemplate.writeDataToTable( tableName, dataFile.getInputStream(), @@ -111,7 +111,7 @@ BigQueryTemplate bigQueryTemplate; */ public void loadJsonStream(String tableName, InputStream jsonInputStream) throws ExecutionException, InterruptedException { - ListenableFuture writeApFuture = + CompletableFuture writeApFuture = bigQueryTemplate.writeJsonStream(tableName, jsonInputStream); WriteApiResponse apiRes = writeApFuture.get();//get the WriteApiResponse if (!apiRes.isSuccessful()){ @@ -139,7 +139,7 @@ BigQueryTemplate bigQueryTemplate; */ public void createTableAndloadJsonStream(String tableName, InputStream jsonInputStream, Schema tableSchema) throws ExecutionException, InterruptedException { - ListenableFuture writeApFuture = + CompletableFuture writeApFuture = bigQueryTemplate.writeJsonStream(tableName, jsonInputStream, tableSchema);//using the overloaded method which created the table when tableSchema is passed WriteApiResponse apiRes = writeApFuture.get();//get the WriteApiResponse if (!apiRes.isSuccessful()){ @@ -200,12 +200,12 @@ Alternatively, you may omit these headers and explicitly set the table name or f After the `BigQueryFileMessageHandler` processes a message to load data to your BigQuery table, it will respond with a `Job` on the reply channel. The https://googleapis.dev/java/google-cloud-clients/latest/index.html?com/google/cloud/bigquery/package-summary.html[Job object] provides metadata and information about the load file operation. -By default, the `BigQueryFileMessageHandler` is run in asynchronous mode, with `setSync(false)`, and it will reply with a `ListenableFuture` on the reply channel. +By default, the `BigQueryFileMessageHandler` is run in asynchronous mode, with `setSync(false)`, and it will reply with a `CompletableFuture` on the reply channel. The future is tied to the status of the data loading job and will complete when the job completes. If the handler is run in synchronous mode with `setSync(true)`, then the handler will block on the completion of the loading job and block until it is complete. -NOTE: If you decide to use Spring Integration Gateways and you wish to receive `ListenableFuture` as a reply object in the Gateway, you will have to call `.setAsyncExecutor(null)` on your `GatewayProxyFactoryBean`. +NOTE: If you decide to use Spring Integration Gateways and you wish to receive `CompletableFuture` as a reply object in the Gateway, you will have to call `.setAsyncExecutor(null)` on your `GatewayProxyFactoryBean`. This is needed to indicate that you wish to reply on the built-in async support rather than rely on async handling of the gateway. === Sample diff --git a/docs/src/main/asciidoc/first-page.adoc b/docs/src/main/asciidoc/first-page.adoc index f2458873e0..81d4667cd8 100644 --- a/docs/src/main/asciidoc/first-page.adoc +++ b/docs/src/main/asciidoc/first-page.adoc @@ -17,7 +17,7 @@ Spring Framework on Google Cloud lets you leverage the power and simplicity of t - Map objects, relationships, and collections with Spring Data Cloud Spanner, Spring Data Cloud Datastore and Spring Data Reactive Repositories for Cloud Firestore - Write and read from Spring Resources backed up by Google Cloud Storage - Exchange messages with Spring Integration using Google Cloud Pub/Sub on the background -- Trace the execution of your app with Spring Cloud Sleuth and Google Cloud Trace +- Trace the execution of your app with Micrometer and Google Cloud Trace - Configure your app with Spring Cloud Config, backed up by the Google Runtime Configuration API - Consume and produce Google Cloud Storage data via Spring Integration GCS Channel Adapters - Use Spring Security via Google Cloud IAP diff --git a/docs/src/main/asciidoc/index.adoc b/docs/src/main/asciidoc/index.adoc index 394bed7d74..f9e92dc800 100644 --- a/docs/src/main/asciidoc/index.adoc +++ b/docs/src/main/asciidoc/index.adoc @@ -17,7 +17,7 @@ Spring Framework on Google Cloud lets you leverage the power and simplicity of t - Map objects, relationships, and collections with Spring Data Cloud Spanner, Spring Data Cloud Datastore and Spring Data Reactive Repositories for Cloud Firestore - Write and read from Spring Resources backed up by Google Cloud Storage - Exchange messages with Spring Integration using Google Cloud Pub/Sub on the background -- Trace the execution of your app with Spring Cloud Sleuth and Google Cloud Trace +- Trace the execution of your app with Micrometer and Google Cloud Trace - Configure your app with Spring Cloud Config, backed up by the Google Runtime Configuration API - Consume and produce Google Cloud Storage data via Spring Integration GCS Channel Adapters - Use Spring Security via Google Cloud IAP @@ -74,3 +74,5 @@ include::kotlin.adoc[] To see the list of all Google Cloud related configuration properties please check link:appendix.html[the Appendix page]. include::migration-guide-1.x.adoc[] + +include::migration-guide-3.x.adoc[] diff --git a/docs/src/main/asciidoc/logging.adoc b/docs/src/main/asciidoc/logging.adoc index 2dfbaa253f..39a36ed50b 100644 --- a/docs/src/main/asciidoc/logging.adoc +++ b/docs/src/main/asciidoc/logging.adoc @@ -23,8 +23,8 @@ dependencies { https://cloud.google.com/logging/[Cloud Logging] is the managed logging service provided by Google Cloud. This module provides support for associating a web request trace ID with the corresponding log entries. -It does so by retrieving the `X-B3-TraceId` value from the https://logback.qos.ch/manual/mdc.html[Mapped Diagnostic Context (MDC)], which is set by Spring Cloud Sleuth. -If Spring Cloud Sleuth isn't used, the configured `TraceIdExtractor` extracts the desired header value and sets it as the log entry's trace ID. +It does so by retrieving the `X-B3-TraceId` value from the https://logback.qos.ch/manual/mdc.html[Mapped Diagnostic Context (MDC)], which is set by Micrometer. +If Micrometer isn't used, the configured `TraceIdExtractor` extracts the desired header value and sets it as the log entry's trace ID. This allows grouping of log messages by request, for example, in the https://console.cloud.google.com/logs/viewer[Google Cloud Console Logs viewer]. NOTE: Due to the way logging is set up, the Google Cloud project ID and credentials defined in `application.properties` are ignored. @@ -35,12 +35,12 @@ You can do this easily if you're using the https://cloud.google.com/sdk[Google C For use in Web MVC-based applications, `TraceIdLoggingWebMvcInterceptor` is provided that extracts the request trace ID from an HTTP request using a `TraceIdExtractor` and stores it in a thread-local, which can then be used in a logging appender to add the trace ID metadata to log messages. -WARNING: If Spring Framework on Google Cloud Trace is enabled, the logging module disables itself and delegates log correlation to Spring Cloud Sleuth. +WARNING: If Spring Framework on Google Cloud Trace is enabled, the logging module disables itself and delegates log correlation to Micrometer. `LoggingWebMvcConfigurer` configuration class is also provided to help register the `TraceIdLoggingWebMvcInterceptor` in Spring MVC applications. Applications hosted on the Google Cloud include trace IDs under the `x-cloud-trace-context` header, which will be included in log entries. -However, if Sleuth is used the trace ID will be picked up from the MDC. +However, if Micrometer is used the trace ID will be picked up from the MDC. === Logback Support @@ -141,7 +141,7 @@ If `projectId` is not set and cannot be determined, then it'll log `traceId` wit | `includeLevel` | `true` | Should the severity be included | `includeThreadName` | `true` | Should the thread name be included | `includeMDC` | `true` | Should all MDC properties be included. -The MDC properties `X-B3-TraceId`, `X-B3-SpanId` and `X-Span-Export` provided by Spring Sleuth will get excluded as they get handled separately +The MDC properties `X-B3-TraceId`, `X-B3-SpanId` and `X-Span-Export` provided by Micrometer will get excluded as they get handled separately | `includeLoggerName` | `true` | Should the name of the logger be included | `includeFormattedMessage` | `true` | Should the formatted log message be included. | `includeExceptionInMessage` | `true` | Should the stacktrace be appended to the formatted log message. diff --git a/docs/src/main/asciidoc/migration-guide-3.x.adoc b/docs/src/main/asciidoc/migration-guide-3.x.adoc new file mode 100644 index 0000000000..5a1e889f57 --- /dev/null +++ b/docs/src/main/asciidoc/migration-guide-3.x.adoc @@ -0,0 +1,124 @@ +== Migration Guide from Spring Cloud GCP 3.x to 4.x +=== Before you start +==== Upgrade to the 3.x version +This doc assumes you are running with Spring Cloud GCP 3.x. + +If you are currently running with an earlier major version of Spring Cloud GCP, i.e., 1.x or 2.x, we recommend that you upgrade to link:https://github.com/GoogleCloudPlatform/spring-cloud-gcp/releases/tag/v3.0.0[Spring Cloud GCP 3.x] before migrating to Spring Cloud GCP 4.0. + +* link:migration-guide-1.x.adoc[Migration guide from Spring Cloud GCP 1.x to 2.x] +* link:https://github.com/GoogleCloudPlatform/spring-cloud-gcp/releases/tag/v3.0.0[Spring Cloud GCP 3.0 Release Note] + +Note that since Spring Cloud GCP 3.0 has few breaking changes, we only provide a release note. + +==== Review System requirements +Spring Cloud GCP 4.0 is built on Spring Boot 3.0 and Spring Framework 6.0, which requires Java 17 at minimum. +If you are currently on Java 8 or Java 11, you need to upgrade your JDK before you can develop an application based on Spring Cloud GCP 4.0. + +==== Review Deprecations from 3.x +Classes, methods and properties that were deprecated in Spring Cloud GCP 3.x have been removed in this release. +Please ensure that you aren’t calling deprecated methods before upgrading. + +=== Upgrade to Spring Cloud GCP 4.0 +==== Spring Boot 3.0 +Spring Cloud GCP 4.0 builds on Spring Boot 3.0. Review the link:https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide[Spring Boot 3.0 migration guide] before continuing. + +==== Update Bill of Materials (BOM) +If you’re a Maven user, add our BOM to your pom.xml `` section. +This will allow you to not specify versions for any of the Maven dependencies and instead delegate versioning to the BOM. + +[source, xml] +---- + + + + com.google.cloud + spring-cloud-gcp-dependencies + 4.0.0 + pom + import + + + +---- + +===== Review Dependencies +Run `mvn dependency:tree` (`gradlew dependencies` for Gradle projects) to see the dependency tree of your project. +Ensure Spring-related dependencies have matching versions: + +* Spring Boot 3.0 +* Spring Cloud 2022.0 +** For detailed dependency versions, see "2022.0 (Kilburn)" column in link:https://github.com/spring-cloud/spring-cloud-release/wiki/Supported-Versions#supported-releases[Spring Cloud: Supported Versions] table. +* Spring Data 2022.0 +** For detailed dependency versions, see link:https://github.com/spring-projects/spring-data-commons/wiki/Spring-Data-2022.0-%28Turing%29-Release-Notes[Spring Data 2022.0 Release Note]. + +==== Cloud BigQuery +Replaced `ListenableFuture` and `SettableListenableFuture` with `CompletableFuture` as the former two Classes are deprecated in Spring Framework 6.0. + +==== Cloud SQL +MySQL R2DBC is not supported by Spring Cloud GCP 4.0 due to link:https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory/issues/990[compatibility issues] with the Mariadb driver. + +==== Cloud Pub/Sub +Replaced `ListenableFuture` and `SettableListenableFuture` with `CompletableFuture` as the former two Classes are deprecated in Spring Framework 6.0. + +==== Spring Cloud Stream binder to Google Cloud Pub/Sub +Change to functional model as annotation-based model is removed from Spring Cloud Stream 4.0.0. + +==== Secret Manager +To enable the application to fetch a secret from SecretManager, add `spring.config.import=sm://` in the `application.properties` or `application.yml` files and inject the secret into the application by adding `application.secret=${sm://your-secret-name}`. +Do not write secret values in the file. + +Please also remove `spring.cloud.gcp.secretmanager.enabled` and `spring.cloud.gcp.secretmanager.legacy` in `bootstrap.yml` or `bootstrap.properties` files. + +==== Cloud Trace +Annotation `org.springframework.cloud.sleuth.annotation.NewSpan` is deprecated in favor of `io.micrometer.observation.annotation.Observed` since Spring Boot 3.0 builds on Micrometer 1.0 rather than Spring Cloud Sleuth (which is excluded from the Spring Cloud release train). + +If you use `NewSpan` to generate an additional span, replace the annotation with `Observed`. +The default name of the additional span is `#`. +If you want to customize the additional span name, specify the `contextualName` parameter. + +Please also remove `spring.sleuth.enabled` from your `application.properties` or `application.yml` file. +Note that `spring.cloud.gcp.trace.enabled=true` is still required if you want to enable Trace. + +==== Cloud Vision +Replaced `ListenableFuture` and `SettableListenableFuture` with `CompletableFuture` as the former two Classes are deprecated in Spring Framework 6.0. + +=== Configuration Changelog +==== Removed +The following properties are removed from the codebase thus no longer needed in your application. + +* `spring.cloud.gcp.secretmanager.enabled` + +* `spring.cloud.gcp.secretmanager.legacy` + +See the "SecretManager" section. + +* `spring.sleuth.enabled` + +See the "Trace" section + +=== Deprecated Items Removed + +==== Cloud BigQuery +`BigQueryTemplate(BigQuery bigQuery, String datasetName)`:: Use `BigQueryTemplate(BigQuery, BigQueryWriteClient, Map, TaskScheduler)` instead + +`BigQueryTemplate(BigQuery, String, TaskScheduler)`:: Use `BigQueryTemplate(BigQuery, BigQueryWriteClient, Map, TaskScheduler)` instead + +==== Cloud Datastore +`DatastorePersistentPropertyImpl.getPersistentEntityTypes()`:: Use `DatastorePersistentPropertyImpl.getPersistentEntityTypeInformation()` instead + +==== Cloud Pub/Sub +`DefaultSubscriberFactory(GcpProjectIdProvider)`:: Use `DefaultSubscriberFactory(GcpProjectIdProvider, PubSubConfiguration)` instead + +`PubSubConfiguration.computeSubscriberRetrySettings(String, String)`:: Use `PubSubConfiguration.computeSubscriberRetrySettings(ProjectSubscriptionName)` instead + +`PubSubConfiguration.computeSubscriberFlowControlSettings(String, String)`:: Use `PubSubConfiguration.computeSubscriberFlowControlSettings(ProjectSubscriptionName)` instead + +`PubSubConfiguration.getSubscriber(String, String)`:: Use `PubSubConfiguration.getSubscriptionProperties(ProjectSubscriptionName)` instead + +==== Cloud Spanner +`SpannerPersistentEntityImpl(TypeInformation)`:: Use `SpannerPersistentEntityImpl(TypeInformation, SpannerMappingContext, SpannerEntityProcessor)` instead + +`SpannerCompositeKeyProperty.getPersistentEntityTypes()`:: Use `SpannerCompositeKeyProperty.getPersistentEntityTypeInformation()` instead + +==== Cloud Trace +`TracingSubscriberFactory.createSubscriberStub()`:: Use `TracingSubscriberFactory.createSubscriberStub(String)` instead diff --git a/docs/src/main/asciidoc/pubsub.adoc b/docs/src/main/asciidoc/pubsub.adoc index 62a751ac2c..21098139cb 100644 --- a/docs/src/main/asciidoc/pubsub.adoc +++ b/docs/src/main/asciidoc/pubsub.adoc @@ -3,7 +3,7 @@ Spring Framework on Google Cloud provides an abstraction layer to publish to and subscribe from Google Cloud Pub/Sub topics and to create, list or delete Google Cloud Pub/Sub topics and subscriptions. -A Spring Boot starter is provided to auto-configure the various required Pub/Sub components. +A Spring Boot starter is provided to autoconfigure the various required Pub/Sub components. Maven coordinates, using <>: @@ -44,7 +44,7 @@ This section describes options for enabling the integration, specifying the Goog Google Cloud Pub/Sub API, if different from the ones in the <> | No | | `spring.cloud.gcp.pubsub.emulator-host` | The host and port of the local running emulator. -If provided, this will setup the client to connect against a running https://cloud.google.com/pubsub/docs/emulator[Google Cloud Pub/Sub Emulator]. | No | +If provided, this will set up the client to connect against a running https://cloud.google.com/pubsub/docs/emulator[Google Cloud Pub/Sub Emulator]. | No | | `spring.cloud.gcp.pubsub.credentials.encoded-key` | Base64-encoded contents of OAuth2 account private key for authenticating with the Google Cloud Pub/Sub API, if different from the ones in the <> | No | @@ -58,9 +58,9 @@ Pub/Sub credentials | No | https://www.googleapis.com/auth/pubsub This section describes configuration options to customize the behavior of the application's Pub/Sub publishers and subscribers. Subscriber settings can be either global or subscription-specific. -NOTE: A custom configuration (injected through a setter in `DefaultSubscriberFactory` or a custom bean) will take precedence over auto-configuration. +NOTE: A custom configuration (injected through a setter in `DefaultSubscriberFactory` or a custom bean) will take precedence over autoconfiguration. Hence, if one wishes to use per-subscription configuration for a Pub/Sub setting, there must not be a custom bean for that setting. -When using auto-configuration, if both global and per-subscription configurations are provided, then the per-subscription configuration will be used. +When using autoconfiguration, if both global and per-subscription configurations are provided, then the per-subscription configuration will be used. However, if a per-subscription configuration is not set then the global or default configuration will be used. |=== @@ -231,20 +231,20 @@ So, take care not to configure a subscription that has a business impact, or ins If you are using Spring Boot Actuator, you can take advantage of the Cloud Pub/Sub subscription health indicator called `pubsub-subscriber`. The subscription health indicator will verify whether Pub/Sub subscriptions are actively processing messages from the subscription's backlog. -To enable it, you need to add the https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#production-ready[Spring Boot Actuator] to your project and the https://cloud.google.com/monitoring/docs/reference/libraries[Google Cloud Monitoring]. -Also you need to set the following properties `spring.cloud.gcp.pubsub.health.lagThreshold`, `spring.cloud.gcp.pubsub.health.backlogThreshold`. +To enable it, you need to add the https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#production-ready[Spring Boot Actuator] to your project and the https://cloud.google.com/monitoring/docs/reference/libraries[GCP Monitoring]. +Also, you need to set the following properties `spring.cloud.gcp.pubsub.health.lagThreshold`, `spring.cloud.gcp.pubsub.health.backlogThreshold`. The `pubsub-subscriber` indicator will then roll up to the overall application status visible at http://localhost:8080/actuator/health (use the `management.endpoint.health.show-details` property to view per-indicator details). [source,xml] ---- - org.springframework.boot - spring-boot-starter-actuator + org.springframework.boot + spring-boot-starter-actuator - com.google.cloud - google-cloud-monitoring + com.google.cloud + google-cloud-monitoring ---- @@ -255,7 +255,7 @@ If a message has been recently processed in a reasonable time threshold, then th If the backlog of messages for a subscription is big but the subscriber consumes messages then subscriber is still healthy. If there hasn't been any processing of recent messages but the backlog increases, then the subscriber is unhealthy. -NOTE: The health indicator will not behave entirely as expected if Dead Letter Queueing is enabled on the subscription being checked, num_undelivered_messages will drop down by itself after DLQ threshold is reached. +NOTE: The health indicator will not behave entirely as expected if Dead Letter Queueing is enabled on the subscription being checked, num_undelivered_messages will drop by itself after DLQ threshold is reached. |=== | Name | Description | Required | Default value @@ -384,7 +384,7 @@ Using these methods for acknowledging messages in batches is more efficient than . To acknowledge messages individually you can use the `ack()` or `nack()` method on each of them (to acknowledge or negatively acknowledge, correspondingly). -NOTE: All `ack()`, `nack()`, and `modifyAckDeadline()` methods on messages, as well as `PubSubSubscriberTemplate`, are implemented asynchronously, returning a `ListenableFuture` to enable asynchronous processing. +NOTE: All `ack()`, `nack()`, and `modifyAckDeadline()` methods on messages, as well as `PubSubSubscriberTemplate`, are implemented asynchronously, returning a `CompletableFuture` to enable asynchronous processing. ===== Dead Letter Topics @@ -410,7 +410,7 @@ public Subscription newSubscription() { } ---- -Dead letter topics are no different than any other topic, though some https://cloud.google.com/pubsub/docs/dead-letter-topics#granting_forwarding_permissions[additional permissions] are necessary to ensure the Cloud Pub/Sub service can successfully `ack` the original message and re-`publish` it on the dead letter topic. +Dead letter topics are no different from any other topic, though some https://cloud.google.com/pubsub/docs/dead-letter-topics#granting_forwarding_permissions[additional permissions] are necessary to ensure the Cloud Pub/Sub service can successfully `ack` the original message and re-`publish` on the dead letter topic. ==== JSON support @@ -438,7 +438,7 @@ You can serialize objects to JSON on publish automatically: include::{project-root}/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/it/PubSubTemplateDocumentationIntegrationTests.java[tag=json_publish] ---- -And that's how you convert messages to objects on pull: +And that's how you convert messages to object on pull: [source,java,indent=0] ---- include::{project-root}/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/it/PubSubTemplateDocumentationIntegrationTests.java[tag=json_pull] @@ -494,7 +494,7 @@ It allows for the creation, deletion and listing of topics and subscriptions. NOTE: Generally when referring to topics and subscriptions, you can either use the short canonical name within the current project, or the fully-qualified name referring to a topic or subscription in a different project using the `projects/[project_name]/(topics|subscriptions)/` format. -The Spring Boot starter for Spring Framework on Google Cloud Pub/Sub auto-configures a `PubSubAdmin` object using the `GcpProjectIdProvider` and the `CredentialsProvider` auto-configured by the Spring Framework on Google Cloud Core Starter. +The Spring Boot starter for GCP Pub/Sub autoconfigures a `PubSubAdmin` object using the `GcpProjectIdProvider` and the `CredentialsProvider` autoconfigured by the Spring Boot GCP Core starter. ==== Creating a topic @@ -558,6 +558,9 @@ public Subscription createSubscription(String subscriptionName, String topicName public Subscription createSubscription(String subscriptionName, String topicName, Integer ackDeadline) +public Subscription createSubscription( + String subscriptionName, String topicName, String pushEndpoint) + public Subscription createSubscription(String subscriptionName, String topicName, Integer ackDeadline, String pushEndpoint) public Subscription createSubscription(Subscriber.Builder builder) diff --git a/docs/src/main/asciidoc/secretmanager.adoc b/docs/src/main/asciidoc/secretmanager.adoc index 7cf7dbd338..31f180ab04 100644 --- a/docs/src/main/asciidoc/secretmanager.adoc +++ b/docs/src/main/asciidoc/secretmanager.adoc @@ -36,11 +36,8 @@ dependencies { By default, Spring Framework on Google Cloud Secret Manager will authenticate using Application Default Credentials. This can be overridden using the authentication properties. -NOTE: All the below settings must be specified in a https://cloud.spring.io/spring-cloud-commons/multi/multi__spring_cloud_context_application_context_services.html#_the_bootstrap_application_context[`bootstrap.properties`] (or `bootstrap.yaml`) file which is the properties file used to configure settings for bootstrap-phase Spring configuration. - |=== | Name | Description | Required | Default value -| `spring.cloud.gcp.secretmanager.enabled` | Enables the Secret Manager bootstrap property and template configuration. | No | `true` | `spring.cloud.gcp.secretmanager.credentials.location` | OAuth2 credentials for authenticating to the Google Cloud Secret Manager API. | No | By default, infers credentials from https://cloud.google.com/docs/authentication/production[Application Default Credentials]. | `spring.cloud.gcp.secretmanager.credentials.encoded-key` | Base64-encoded contents of OAuth2 account private key for authenticating to the Google Cloud Secret Manager API. | No | By default, infers credentials from https://cloud.google.com/docs/authentication/production[Application Default Credentials]. | `spring.cloud.gcp.secretmanager.project-id` | The default Google Cloud project used to access Secret Manager API for the template and property source. | No | By default, infers the project from https://cloud.google.com/docs/authentication/production[Application Default Credentials]. @@ -80,13 +77,15 @@ sm:// You can use this syntax in the following places: -1. In your `application.properties` or `bootstrap.properties` files: +1. In your `application.properties` file: + [source] ---- # Example of the project-secret long-form syntax. +spring.config.import=sm:// spring.datasource.password=${sm://projects/my-gcp-project/secrets/my-secret} ---- +The former is used to enable https://spring.io/blog/2020/08/14/config-file-processing-in-spring-boot-2-4[Spring Boot's Config Data API]. 2. Access the value using the `@Value` annotation. + @@ -123,20 +122,11 @@ import the actuator starter dependency to your project, spring-boot-starter-actuator ---- -add the following properties to your project's `application.properties`. The latter is used to enable https://spring.io/blog/2020/08/14/config-file-processing-in-spring-boot-2-4[Spring Boot's Config Data API]. +add the following properties to your project's `application.properties`. + [source] ---- management.endpoints.web.exposure.include=refresh -spring.config.import=sm:// ----- -+ -finally, add the following property to your project's `bootstrap.properties` to disable -Secret Manager bootstrap phrase. -+ -[source] ----- -spring.cloud.gcp.secretmanager.legacy=false ---- 2. After running the application, update your secret stored in the Secret Manager. diff --git a/docs/src/main/asciidoc/spring-integration-pubsub.adoc b/docs/src/main/asciidoc/spring-integration-pubsub.adoc index 26448ae0b0..0db9f30edd 100644 --- a/docs/src/main/asciidoc/spring-integration-pubsub.adoc +++ b/docs/src/main/asciidoc/spring-integration-pubsub.adoc @@ -1,7 +1,7 @@ === Channel Adapters for Cloud Pub/Sub The channel adapters for Google Cloud Pub/Sub connect your Spring https://docs.spring.io/spring-integration/reference/html/channel.html[`MessageChannels`] to Google Cloud Pub/Sub topics and subscriptions. -This enables messaging between different processes, applications or micro-services backed up by Google Cloud Pub/Sub. +This enables messaging between different processes, applications or microservices backed up by Google Cloud Pub/Sub. The Spring Integration Channel Adapters for Google Cloud Pub/Sub are included in the `spring-cloud-gcp-pubsub` module and can be autoconfigured by using the `spring-cloud-gcp-starter-pubsub` module in combination with a Spring Integration dependency. @@ -9,14 +9,16 @@ Maven coordinates, using < + + com.google.cloud spring-cloud-gcp-starter-pubsub - - + + org.springframework.integration spring-integration-core - + + ---- Gradle coordinates: @@ -169,7 +171,7 @@ public void pubsubErrorHandler(Message message) { } ---- -If you would prefer to manually ack or nack the message, you can do it by retrieving the header of the exception payload: +If you preferred to manually ack or nack the message, you can do it by retrieving the header of the exception payload: [source,java] ---- @@ -271,7 +273,6 @@ If none is provided, the adapter waits indefinitely for a response. It is possible to set user-defined callbacks for the `publish()` call in `PubSubMessageHandler` through the `setSuccessCallback()` and `setFailureCallback()` methods (either one or both may be set). These give access to the Pub/Sub publish message ID in case of success, or the root cause exception in case of error. Both callbacks include the original message as the second argument. -The old `setPublishCallback()` method that only gave access to message ID or root cause exception is deprecated and will be removed in a future release. [source,java,indent=0] ---- diff --git a/docs/src/main/asciidoc/spring-stream.adoc b/docs/src/main/asciidoc/spring-stream.adoc index 74e7deef3e..814d34cf12 100644 --- a/docs/src/main/asciidoc/spring-stream.adoc +++ b/docs/src/main/asciidoc/spring-stream.adoc @@ -33,11 +33,13 @@ NOTE: Partitioning is currently not supported by this binder. === Configuration You can configure the Spring Cloud Stream Binder for Google Cloud Pub/Sub to automatically generate the underlying resources, like the Google Cloud Pub/Sub topics and subscriptions for producers and consumers. -For that, you can use the `spring.cloud.stream.gcp.pubsub.bindings...auto-create-resources` property, which is turned ON by default. +For that, you can use the `spring.cloud.stream.gcp.pubsub.bindings...auto-create-resources` property, which is turned ON by default. + +NOTE: For more info about consumer/producer naming convention, please refer to https://docs.spring.io/spring-cloud-stream/docs/current/reference/html/spring-cloud-stream.html#_functional_binding_names[Functional binding names]. Starting with version 1.1, these and other binder properties can be configured globally for all the bindings, e.g. `spring.cloud.stream.gcp.pubsub.default.consumer.auto-create-resources`. -If you are using Pub/Sub auto-configuration from the Spring Framework on Google Cloud Pub/Sub Starter, you should refer to the <> section for other Pub/Sub parameters. +If you are using Pub/Sub autoconfiguration from the Spring Framework on Google Cloud Pub/Sub Starter, you should refer to the <> section for other Pub/Sub parameters. NOTE: To use this binder with a https://cloud.google.com/pubsub/docs/emulator[running emulator], configure its host and port via `spring.cloud.gcp.pubsub.emulator-host`. @@ -54,8 +56,8 @@ For example, for the following configuration, a topic called `myEvents` would be .application.properties [source] ---- -spring.cloud.stream.bindings.events.destination=myEvents -spring.cloud.stream.gcp.pubsub.bindings.events.producer.auto-create-resources=true +spring.cloud.stream.bindings.{PRODUCER_NAME}.destination=myEvents +spring.cloud.stream.gcp.pubsub.bindings.{PRODUCER_NAME}.producer.auto-create-resources=true ---- ==== Consumer Destination Configuration @@ -86,8 +88,8 @@ For example, with this configuration: .application.properties [source] ---- -spring.cloud.stream.bindings.events.destination=myEvents -spring.cloud.stream.gcp.pubsub.bindings.events.consumer.auto-create-resources=false +spring.cloud.stream.bindings.{CONSUMER_NAME}.destination=myEvents +spring.cloud.stream.gcp.pubsub.bindings.{CONSUMER_NAME}.consumer.auto-create-resources=false ---- Only an anonymous subscription named `anonymous.myEvents.a6d83782-c5a3-4861-ac38-e6e2af15a7be` is created and later cleaned up. @@ -96,11 +98,11 @@ In another example, with the following configuration: .application.properties [source] ---- -spring.cloud.stream.bindings.events.destination=myEvents -spring.cloud.stream.gcp.pubsub.bindings.events.consumer.auto-create-resources=true +spring.cloud.stream.bindings.{CONSUMER_NAME}.destination=myEvents +spring.cloud.stream.gcp.pubsub.bindings.{CONSUMER_NAME}.consumer.auto-create-resources=true # specify consumer group, and avoid anonymous consumer group generation -spring.cloud.stream.bindings.events.group=consumerGroup1 +spring.cloud.stream.bindings.{CONSUMER_NAME}.group=consumerGroup1 ---- These resources will be created: @@ -113,9 +115,9 @@ For example, for a consumer to allow only two headers, provide a comma separated .application.properties ---- -spring.cloud.stream.gcp.pubsub.bindings.-in-0.consumer.allowedHeaders=allowed1, allowed2 +spring.cloud.stream.gcp.pubsub.bindings.{CONSUMER_NAME}.consumer.allowedHeaders=allowed1, allowed2 ---- -Where should be replaced by the method which is consuming/reading messages from Cloud Pub/Sub and allowed1, allowed2 is the comma separated list of headers that the user wants to keep. +Where `CONSUMER_NAME` should be replaced by the method which is consuming/reading messages from Cloud Pub/Sub and allowed1, allowed2 is the comma separated list of headers that the user wants to keep. @@ -123,9 +125,9 @@ A similar style is applicable for producers as well. For example: .application.properties ---- -spring.cloud.stream.gcp.pubsub.bindings.-out-0.producer.allowedHeaders=allowed3,allowed4 +spring.cloud.stream.gcp.pubsub.bindings.{PRODUCER_NAME}.producer.allowedHeaders=allowed3,allowed4 ---- -Where should be replaced by the method which is producing/sending messages to Cloud Pub/Sub and allowed3, allowed4 is the comma separated list of headers that user wants to map. All other headers will be removed before the message is sent to Cloud Pub/Sub. +Where `PRODUCER_NAME` should be replaced by the method which is producing/sending messages to Cloud Pub/Sub and allowed3, allowed4 is the comma separated list of headers that user wants to map. All other headers will be removed before the message is sent to Cloud Pub/Sub. @@ -174,84 +176,8 @@ Supplier> generateUserMessages() { A processor application works similarly to a source application, except it is triggered by presence of a `Function` bean. - -=== Binding with Annotations - -NOTE: As of version 3.0, annotation binding is considered legacy. - -To set up a sink application in this style, you would associate a class with a binding interface, such as the built-in `Sink` interface. - -``` -@EnableBinding(Sink.class) -public class SinkExample { - - @StreamListener(Sink.INPUT) - public void handleMessage(UserMessage userMessage) { - // process message - } -} -``` - -To set up a source application, you would similarly associate a class with a built-in `Source` interface, and inject an instance of it provided by Spring Cloud Stream. - -``` -@EnableBinding(Source.class) -public class SourceExample { - - @Autowired - private Source source; - - public void sendMessage() { - this.source.output().send(new GenericMessage<>(/* your object here */)); - } -} -``` - - -=== Streaming vs. Polled Input - -Many Spring Cloud Stream applications will use the built-in `Sink` binding, which triggers the _streaming_ input binder creation. -Messages can then be consumed with an input handler marked by `@StreamListener(Sink.INPUT)` annotation, at whatever rate Pub/Sub sends them. - -For more control over the rate of message arrival, a polled input binder can be set up by defining a custom binding interface with an `@Input`-annotated method returning `PollableMessageSource`. - -[source,java] ----- -public interface PollableSink { - - @Input("input") - PollableMessageSource input(); -} ----- - -The `PollableMessageSource` can then be injected and queried, as needed. - -[source,java] ----- -@EnableBinding(PollableSink.class) -public class SinkExample { - - @Autowired - PollableMessageSource destIn; - - @Bean - public ApplicationRunner singlePollRunner() { - return args -> { - // This will poll only once. - // Add a loop or a scheduler to get more messages. - destIn.poll(message -> System.out.println("Message retrieved: " + message)); - }; - } -} ----- - -By default, the polling will only get 1 message at a time. -Use the `spring.cloud.stream.gcp.pubsub.default.consumer.maxFetchSize` property to fetch additional messages per network roundtrip. - === Sample Sample applications are available: -* For https://github.com/GoogleCloudPlatform/spring-cloud-gcp/tree/main/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample[streaming input, annotation-based]. * For https://github.com/GoogleCloudPlatform/spring-cloud-gcp/tree/main/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-functional-sample[streaming input, functional style]. -* For https://github.com/GoogleCloudPlatform/spring-cloud-gcp/tree/main/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample[polled input]. diff --git a/docs/src/main/asciidoc/sql.adoc b/docs/src/main/asciidoc/sql.adoc index bdb1495538..94edde0983 100644 --- a/docs/src/main/asciidoc/sql.adoc +++ b/docs/src/main/asciidoc/sql.adoc @@ -2,7 +2,7 @@ == Cloud SQL Spring Framework on Google Cloud adds integrations with -https://docs.spring.io/spring/docs/current/spring-framework-reference/html/jdbc.html[Spring JDBC] and https://docs.spring.io/spring-data/r2dbc/docs/current/reference/html/#r2dbc.core[Spring R2DBC] so you can run your MySQL or PostgreSQL databases in https://cloud.google.com/sql[Google Cloud SQL] using Spring JDBC and other libraries that depend on it like Spring Data JPA or Spring Data R2DBC. +https://docs.spring.io/spring/docs/current/spring-framework-reference/html/jdbc.html[Spring JDBC] and https://docs.spring.io/spring-data/r2dbc/docs/current/reference/html/#r2dbc.core[Spring R2DBC], so you can run your MySQL or PostgreSQL databases in https://cloud.google.com/sql[Google Cloud SQL] using Spring JDBC and other libraries that depend on it like Spring Data JPA or Spring Data R2DBC. The Cloud SQL support is provided by Spring Framework on Google Cloud in the form of two Spring Boot starters, one for MySQL and another one for PostgreSQL. The role of the starters is to read configuration from properties and assume default settings so that user experience connecting to MySQL and PostgreSQL is as simple as possible. @@ -53,7 +53,7 @@ To do that, go to the https://console.cloud.google.com/apis/library[API library ==== Spring Boot Starter for Google Cloud SQL -The Spring Boot Starters for Google Cloud SQL provide an auto-configured https://docs.oracle.com/javase/7/docs/api/javax/sql/DataSource.html[`DataSource`] object. +The Spring Boot Starters for Google Cloud SQL provide an autoconfigured https://docs.oracle.com/javase/7/docs/api/javax/sql/DataSource.html[`DataSource`] object. Coupled with Spring JDBC, it provides a https://docs.spring.io/spring/docs/current/spring-framework-reference/html/jdbc.html#jdbc-JdbcTemplate[`JdbcTemplate`] object bean that allows for operations such as querying and modifying a database. @@ -65,7 +65,7 @@ public List> listUsers() { ---- You can rely on -https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html#boot-features-connect-to-production-database[Spring Boot data source auto-configuration] to configure a `DataSource` bean. +https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html#boot-features-connect-to-production-database[Spring Boot data source autoconfiguration] to configure a `DataSource` bean. In other words, properties like the SQL username, `spring.datasource.username`, and password, `spring.datasource.password` can be used. There is also some configuration specific to Google Cloud SQL (see "Cloud SQL Configuration Properties" section below). @@ -76,7 +76,7 @@ There is also some configuration specific to Google Cloud SQL (see "Cloud SQL Co | `spring.datasource.driver-class-name` | JDBC driver to use. | No | MySQL: `com.mysql.cj.jdbc.Driver`; PostgreSQL: `org.postgresql.Driver` |=== -NOTE: If you provide your own `spring.datasource.url`, it will be ignored, unless you disable Cloud SQL auto configuration with `spring.cloud.gcp.sql.enabled=false` or `spring.cloud.gcp.sql.jdbc.enabled=false`. +NOTE: If you provide your own `spring.datasource.url`, it will be ignored, unless you disable Cloud SQL autoconfiguration with `spring.cloud.gcp.sql.enabled=false` or `spring.cloud.gcp.sql.jdbc.enabled=false`. ===== `DataSource` creation flow @@ -94,59 +94,12 @@ You can connect to your database with as little as a database and instance names Maven and Gradle coordinates, using <>: -To use MySQL: - -[source,xml] ----- - - com.google.cloud - spring-cloud-gcp-starter-sql-mysql-r2dbc - - ----- - -[source,subs="normal"] ----- -dependencies { - implementation("com.google.cloud:spring-cloud-gcp-starter-sql-mysql-r2dbc") -} ----- - -To use PostgreSQL with Spring Boot 2.6: - -[source,xml] ----- - - com.google.cloud - spring-cloud-gcp-starter-sql-postgres-r2dbc - ----- - -[source,subs="normal"] ----- -dependencies { - implementation("com.google.cloud:spring-cloud-gcp-starter-sql-postgres-r2dbc") -} ----- - -To use PostgreSQL with Spring Boot 2.7 (the latest version of the Postgres R2DBC driver changed its Maven coordinates): +To use PostgreSQL: ``` xml - com.google.cloud - spring-cloud-gcp-starter-sql-postgres-r2dbc - - - io.r2dbc - r2dbc-postgresql - - - - - - org.postgresql - r2dbc-postgresql - 0.9.1.RELEASE + com.google.cloud + spring-cloud-gcp-starter-sql-postgres-r2dbc ``` @@ -178,11 +131,11 @@ There is also some configuration specific to Google Cloud SQL (see "Cloud SQL Co |=== | Property name | Description | Required | Default value -| `spring.r2dbc.username` | Database username | No | MySQL: `root`; PostgreSQL: `postgres` +| `spring.r2dbc.username` | Database username | No | `postgres` | `spring.r2dbc.password` | Database password | No | `null` |=== -NOTE: If you provide your own `spring.r2dbc.url`, it will be ignored, unless you disable Cloud SQL auto-configuration for R2DBC with `spring.cloud.gcp.sql.enabled=false` or `spring.cloud.gcp.sql.r2dbc.enabled=false` . +NOTE: If you provide your own `spring.r2dbc.url`, it will be ignored, unless you disable Cloud SQL autoconfiguration for R2DBC with `spring.cloud.gcp.sql.enabled=false` or `spring.cloud.gcp.sql.r2dbc.enabled=false` . ===== `ConnectionFactory` creation flow @@ -276,5 +229,4 @@ Available sample applications and codelabs: - https://github.com/GoogleCloudPlatform/spring-cloud-gcp/tree/main/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-sample[Spring Framework on Google Cloud PostgreSQL] - https://github.com/GoogleCloudPlatform/spring-cloud-gcp/tree/main/spring-cloud-gcp-samples/spring-cloud-gcp-data-jpa-sample[Spring Data JPA with Spring Framework on Google Cloud SQL] - Codelab: https://codelabs.developers.google.com/codelabs/cloud-spring-petclinic-cloudsql/index.html[Spring Pet Clinic using Cloud SQL] -- https://github.com/GoogleCloudPlatform/spring-cloud-gcp/tree/main/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample[R2DBC: Spring Framework on Google Cloud MySQL] -- https://github.com/GoogleCloudPlatform/spring-cloud-gcp/tree/main/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-r2dbc-sample[R2DBC: Spring Framework on Google Cloud PostgreSQL] \ No newline at end of file +- https://github.com/GoogleCloudPlatform/spring-cloud-gcp/tree/main/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-r2dbc-sample[R2DBC: Spring Framework on Google Cloud PostgreSQL] diff --git a/docs/src/main/asciidoc/trace.adoc b/docs/src/main/asciidoc/trace.adoc index b95cee0280..48b34e88a1 100644 --- a/docs/src/main/asciidoc/trace.adoc +++ b/docs/src/main/asciidoc/trace.adoc @@ -1,12 +1,12 @@ [#cloud-trace] == Cloud Trace -Google Cloud provides a managed distributed tracing service called https://cloud.google.com/trace/[Cloud Trace], and https://cloud.spring.io/spring-cloud-sleuth/[Spring Cloud Sleuth] can be used with it to easily instrument Spring Boot applications for observability. +Google Cloud provides a managed distributed tracing service called https://cloud.google.com/trace/[Cloud Trace], and https://micrometer.io/[Micrometer] can be used with it to easily instrument Spring Boot applications for observability. -Typically, Spring Cloud Sleuth captures trace information and forwards traces to services like Zipkin for storage and analysis. +Typically, Micrometer captures trace information and forwards traces to service like Zipkin for storage and analysis. However, on Google Cloud, instead of running and maintaining your own Zipkin instance and storage, you can use Cloud Trace to store traces, view trace details, generate latency distributions graphs, and generate performance regression reports. -This Spring Framework on Google Cloud starter can forward Spring Cloud Sleuth traces to Cloud Trace without an intermediary Zipkin server. +This Spring Framework on Google Cloud starter can forward Micrometer traces to Cloud Trace without an intermediary Zipkin server. Maven coordinates, using <>: @@ -37,7 +37,7 @@ If you are already using a Zipkin server capturing trace information from multip === Tracing -Spring Cloud Sleuth uses the https://github.com/openzipkin/brave[Brave tracer] to generate traces. +Micrometer uses the https://github.com/openzipkin/brave[Brave tracer] to generate traces. This integration enables Brave to use the https://github.com/openzipkin/zipkin-gcp/tree/main/propagation-stackdriver[`StackdriverTracePropagation`] propagation. A propagation is responsible for extracting trace context from an entity (e.g., an HTTP servlet request) and injecting trace context into an entity. @@ -46,7 +46,7 @@ A canonical example of the propagation usage is a web server that receives an HT In the case of `StackdriverTracePropagation`, first it looks for trace context in the https://github.com/openzipkin/b3-propagation[X-B3 headers] (`X-B3-TraceId`, `X-B3-SpanId`). If those are not found, `StackdriverTracePropagation` will fall back on `x-cloud-trace-context` key (e.g., an HTTP request header). -If you need different propagation behavior (e.g. relying primarily on `x-cloud-trace-context` in a mixed Spring / non-Spring application environment), Spring Cloud Sleuth allows such customization through the https://docs.spring.io/spring-cloud-sleuth/docs/current/reference/html/howto.html#how-to-change-context-propagation[`CUSTOM` propagation type]. +If you need different propagation behavior (e.g. relying primarily on `x-cloud-trace-context` in a mixed Spring / non-Spring application environment), Micrometer allows such customization through the https://micrometer.io/docs/contextPropagation[`CUSTOM` propagation type]. [TIP] ==== @@ -62,19 +62,19 @@ The value of the `x-cloud-trace-context` key can be formatted in three different Since Cloud Trace doesn't support span joins, a new span ID is always generated, regardless of the one specified in `x-cloud-trace-context`. `TRACE_TRUE` can either be `0` if the entity should be untraced, or `1` if it should be traced. -This field forces the decision of whether or not to trace the request; if omitted then the decision is deferred to the sampler. +This field forces the decision of whether to trace the request; if omitted then the decision is deferred to the sampler. ==== === Spring Boot Starter for Cloud Trace -Spring Boot Starter for Cloud Trace uses Spring Cloud Sleuth and auto-configures a https://github.com/openzipkin/zipkin-gcp/blob/main/sender-stackdriver/src/main/java/zipkin2/reporter/stackdriver/StackdriverSender.java[StackdriverSender] that sends the Sleuth’s trace information to Cloud Trace. +Spring Boot Starter for Cloud Trace uses Micrometer and autoconfigures a https://github.com/openzipkin/zipkin-gcp/blob/main/sender-stackdriver/src/main/java/zipkin2/reporter/stackdriver/StackdriverSender.java[StackdriverSender] that sends the Micrometer’s trace information to Cloud Trace. All configurations are optional: |=== | Name | Description | Required | Default value -| `spring.cloud.gcp.trace.enabled` | Auto-configure Spring Cloud Sleuth to send traces to Cloud Trace. | No | `true` +| `spring.cloud.gcp.trace.enabled` | Autoconfigure Micrometer to send traces to Cloud Trace. | No | `true` | `spring.cloud.gcp.trace.project-id` | Overrides the project ID from the <> | No | | `spring.cloud.gcp.trace.credentials.location` | Overrides the credentials location from the <> | No | | `spring.cloud.gcp.trace.credentials.encoded-key` | Overrides the credentials encoded key from the <> | No | @@ -91,33 +91,26 @@ All configurations are optional: | `spring.cloud.gcp.trace.pubsub.enabled` | (Experimental) Auto-configure Pub/Sub instrumentation for Trace. | No | `false` |=== -You can use core Spring Cloud Sleuth properties to control Sleuth’s sampling rate, etc. -Read https://cloud.spring.io/spring-cloud-sleuth/[Sleuth documentation] for more information on Sleuth configurations. +You can use core Micrometer properties to control Micrometer’s sampling rate, etc. +Read https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator.micrometer-tracing[Spring Boot Tracing documentation] for more information on Micrometer configurations. For example, when you are testing to see the traces are going through, you can set the sampling rate to 100%. [source] ---- -spring.sleuth.sampler.probability=1 # Send 100% of the request traces to Cloud Trace. -spring.sleuth.web.skipPattern=(^cleanup.*|.+favicon.*) # Ignore some URL paths. -spring.sleuth.scheduled.enabled=false # disable executor 'async' traces +management.tracing.sampling.probability=1.0 # Send 100% of the request traces to Cloud Trace. ---- -WARNING: By default, Spring Cloud Sleuth auto-configuration instruments executor beans, which may cause recurring traces with the name `async` to appear in Cloud Trace if your application or one of its dependencies introduces scheduler beans into Spring application context. To avoid this noise, please disable automatic instrumentation of executors via `spring.sleuth.scheduled.enabled=false` in your application configuration. - -Spring Framework on Google Cloud Trace does override some Sleuth configurations: +Spring Framework on Google Cloud Trace does override some Micrometer configurations: - Always uses 128-bit Trace IDs. This is required by Cloud Trace. - Does not use Span joins. Span joins will share the span ID between the client and server Spans. Cloud Trace requires that every Span ID within a Trace to be unique, so Span joins are not supported. -- Uses `StackdriverHttpRequestParser` by default to populate Stackdriver related fields. - -=== Overriding the auto-configuration -Spring Cloud Sleuth supports sending traces to multiple tracing systems as of version 2.1.0. -In order to get this to work, every tracing system needs to have a `Reporter` and `Sender`. +=== Overriding the autoconfiguration +You can send traces to multiple tracing systems. In order to get this to work, every tracing system needs to have a `Reporter` and `Sender`. If you want to override the provided beans you need to give them a specific name. To do this you can use respectively `StackdriverTraceAutoConfiguration.REPORTER_BEAN_NAME` and `StackdriverTraceAutoConfiguration.SENDER_BEAN_NAME`. @@ -171,9 +164,6 @@ It's set to `false` by default, but when set to `true`, trace spans will be crea ---- # Enable Pub/Sub tracing using this property spring.cloud.gcp.trace.pubsub.enabled=true - -# You should disable Spring Integration instrumentation by Sleuth as it's unnecessary when Pub/Sub tracing is enabled -spring.sleuth.integration.enabled=false ---- === Sample diff --git a/docs/src/main/asciidoc/vision.adoc b/docs/src/main/asciidoc/vision.adoc index 58fd120fd8..51860e11b8 100644 --- a/docs/src/main/asciidoc/vision.adoc +++ b/docs/src/main/asciidoc/vision.adoc @@ -188,13 +188,13 @@ When OCR is run on a document, the Cloud Vision APIs will output a collection of The `DocumentOcrTemplate` provides the following method for running OCR on a document saved in Google Cloud Storage: -`ListenableFuture runOcrForDocument(GoogleStorageLocation document, GoogleStorageLocation outputFilePathPrefix)` +`CompletableFuture runOcrForDocument(GoogleStorageLocation document, GoogleStorageLocation outputFilePathPrefix)` The method allows you to specify the location of the document and the output location for where all the JSON output files will be saved in Google Cloud Storage. -It returns a `ListenableFuture` containing `DocumentOcrResultSet` which contains the OCR content of the document. +It returns a `CompletableFuture` containing `DocumentOcrResultSet` which contains the OCR content of the document. NOTE: Running OCR on a document is an operation that can take between several minutes to several hours depending on how large the document is. -It is recommended to register callbacks to the returned ListenableFuture or ignore it and process the JSON output files at a later point in time using `readOcrOutputFile` or `readOcrOutputFileSet`. +It is recommended to register callbacks to the returned CompletableFuture or ignore it and process the JSON output files at a later point in time using `readOcrOutputFile` or `readOcrOutputFileSet`. ==== Running OCR Example @@ -210,7 +210,7 @@ public void runOcrOnDocument() { GoogleStorageLocation outputLocationPrefix = GoogleStorageLocation.forFolder( "your-bucket", "output_folder/test.pdf/"); - ListenableFuture result = + CompletableFuture result = this.documentOcrTemplate.runOcrForDocument( document, outputLocationPrefix); @@ -229,7 +229,7 @@ In some use-cases, you may need to directly read OCR output files stored in Goog - `readOcrOutputFileSet(GoogleStorageLocation jsonOutputFilePathPrefix)`: Reads a collection of OCR output files under a file path prefix and returns the parsed contents. -All of the files under the path should correspond to the same document. +All the files under the path should correspond to the same document. - `readOcrOutputFile(GoogleStorageLocation jsonFile)`: Reads a single OCR output file and returns the parsed contents. diff --git a/docs/src/main/md/bigquery.md b/docs/src/main/md/bigquery.md index 812b45e4ec..d09535a20e 100644 --- a/docs/src/main/md/bigquery.md +++ b/docs/src/main/md/bigquery.md @@ -92,7 +92,7 @@ BigQuery table. BigQueryTemplate bigQueryTemplate; public void loadData(InputStream dataInputStream, String tableName) { - ListenableFuture bigQueryJobFuture = + CompletableFuture bigQueryJobFuture = bigQueryTemplate.writeDataToTable( tableName, dataFile.getInputStream(), @@ -118,7 +118,7 @@ BigQueryTemplate bigQueryTemplate; */ public void loadJsonStream(String tableName, InputStream jsonInputStream) throws ExecutionException, InterruptedException { - ListenableFuture writeApFuture = + CompletableFuture writeApFuture = bigQueryTemplate.writeJsonStream(tableName, jsonInputStream); WriteApiResponse apiRes = writeApFuture.get();//get the WriteApiResponse if (!apiRes.isSuccessful()){ @@ -144,7 +144,7 @@ BigQueryTemplate bigQueryTemplate; */ public void createTableAndloadJsonStream(String tableName, InputStream jsonInputStream, Schema tableSchema) throws ExecutionException, InterruptedException { - ListenableFuture writeApFuture = + CompletableFuture writeApFuture = bigQueryTemplate.writeJsonStream(tableName, jsonInputStream, tableSchema);//using the overloaded method which created the table when tableSchema is passed WriteApiResponse apiRes = writeApFuture.get();//get the WriteApiResponse if (!apiRes.isSuccessful()){ @@ -218,7 +218,7 @@ provides metadata and information about the load file operation. By default, the `BigQueryFileMessageHandler` is run in asynchronous mode, with `setSync(false)`, and it will reply with a -`ListenableFuture` on the reply channel. The future is tied to the +`CompletableFuture` on the reply channel. The future is tied to the status of the data loading job and will complete when the job completes. If the handler is run in synchronous mode with `setSync(true)`, then the @@ -228,7 +228,7 @@ it is complete.
If you decide to use Spring Integration Gateways and you wish to receive -`ListenableFuture` as a reply object in the Gateway, you will have +`CompletableFuture` as a reply object in the Gateway, you will have to call `.setAsyncExecutor(null)` on your `GatewayProxyFactoryBean`. This is needed to indicate that you wish to reply on the built-in async support rather than rely on async handling of the gateway. diff --git a/docs/src/main/md/convert-from-ascii.sh b/docs/src/main/md/convert-from-ascii.sh index 62041c1c35..91c23ce82b 100755 --- a/docs/src/main/md/convert-from-ascii.sh +++ b/docs/src/main/md/convert-from-ascii.sh @@ -39,6 +39,7 @@ convertascii kotlin convertascii configuration convertascii migration-guide-1.x + convertascii migration-guide-3.x convertascii appendix convertascii first-page diff --git a/docs/src/main/md/first-page.md b/docs/src/main/md/first-page.md index c0079509de..253bde2259 100644 --- a/docs/src/main/md/first-page.md +++ b/docs/src/main/md/first-page.md @@ -24,7 +24,7 @@ Spring Framework to: - Exchange messages with Spring Integration using Google Cloud Pub/Sub on the background - - Trace the execution of your app with Spring Cloud Sleuth and Google + - Trace the execution of your app with Micrometer and Google Cloud Trace - Configure your app with Spring Cloud Config, backed up by the Google diff --git a/docs/src/main/md/logging.md b/docs/src/main/md/logging.md index 5413190184..0870261c59 100644 --- a/docs/src/main/md/logging.md +++ b/docs/src/main/md/logging.md @@ -22,8 +22,7 @@ logging service provided by Google Cloud. This module provides support for associating a web request trace ID with the corresponding log entries. It does so by retrieving the `X-B3-TraceId` value from the [Mapped Diagnostic Context -(MDC)](https://logback.qos.ch/manual/mdc.html), which is set by Spring -Cloud Sleuth. If Spring Cloud Sleuth isn’t used, the configured +(MDC)](https://logback.qos.ch/manual/mdc.html), which is set by Micrometer. If Spring Cloud Micrometer isn’t used, the configured `TraceIdExtractor` extracts the desired header value and sets it as the log entry’s trace ID. This allows grouping of log messages by request, for example, in the [Google Cloud Console Logs @@ -53,7 +52,7 @@ messages.
If Spring Framework on Google Cloud Trace is enabled, the logging module disables itself -and delegates log correlation to Spring Cloud Sleuth. +and delegates log correlation to Micrometer.
@@ -63,7 +62,7 @@ applications. Applications hosted on the Google Cloud include trace IDs under the `x-cloud-trace-context` header, which will be included in log -entries. However, if Sleuth is used the trace ID will be picked up from +entries. However, if Micrometer is used the trace ID will be picked up from the MDC. ### Logback Support @@ -212,7 +211,7 @@ configure the appender. The following properties are available:

includeMDC

true

-

Should all MDC properties be included. The MDC properties X-B3-TraceId, X-B3-SpanId and X-Span-Export provided by Spring Sleuth will get excluded as they get handled separately

+

Should all MDC properties be included. The MDC properties X-B3-TraceId, X-B3-SpanId and X-Span-Export provided by Micrometer will get excluded as they get handled separately

includeLoggerName

diff --git a/docs/src/main/md/migration-guide-3.x.md b/docs/src/main/md/migration-guide-3.x.md new file mode 100644 index 0000000000..f66f1a1035 --- /dev/null +++ b/docs/src/main/md/migration-guide-3.x.md @@ -0,0 +1,135 @@ +## Migration Guide from Spring Cloud GCP 3.x to 4.x + +### Before you start + +#### Upgrade to the 3.x version +This doc assumes you are running with Spring Cloud GCP 3.x. + +If you are currently running with an earlier major version of Spring Cloud GCP, i.e., 1.x or 2.x, we recommend that you upgrade to [Spring Cloud GCP 3.x](https://github.com/GoogleCloudPlatform/spring-cloud-gcp/releases/tag/v3.0.0) before migrating to Spring Cloud GCP 4.0. + +- [Migration guide from Spring Cloud GCP 1.x to 2.x](./migration-guide-1.x.md) +- [Spring Cloud GCP 3.0 Release Note](https://github.com/GoogleCloudPlatform/spring-cloud-gcp/releases/tag/v3.0.0) + +Note that since Spring Cloud GCP 3.0 has few breaking changes, we only provide a release note. + +#### Review System requirements +Spring Cloud GCP 4.0 is built on Spring Boot 3.0 and Spring Framework 6.0, which requires Java 17 at minimum. +If you are currently on Java 8 or Java 11, you need to upgrade your JDK before you can develop an application based on Spring Cloud GCP 4.0. + +#### Review Deprecations from 3.x +Classes, methods and properties that were deprecated in Spring Cloud GCP 3.x have been removed in this release. +Please ensure that you aren’t calling deprecated methods before upgrading. + +### Upgrade to Spring Cloud GCP 4.0 +#### Spring Boot 3.0 +Spring Cloud GCP 4.0 builds on Spring Boot 3.0. Review the [Spring Boot 3.0 migration guide](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide) before continuing. + +#### Update Bill of Materials (BOM) +If you’re a Maven user, add our BOM to your pom.xml `` section. +This will allow you to not specify versions for any of the Maven dependencies and instead delegate versioning to the BOM. + +```xml + + + + com.google.cloud + spring-cloud-gcp-dependencies + 4.0.0 + pom + import + + + +``` + +##### Review Dependencies +Run `mvn dependency:tree` (`gradlew dependencies` for Gradle projects) to see the dependency tree of your project. +Ensure Spring-related dependencies have matching versions: + +- Spring Boot 3.0 +- Spring Cloud 2022.0 + - For detailed dependency versions, see "2022.0 (Kilburn)" column in [Spring Cloud: Supported Versions](https://github.com/spring-cloud/spring-cloud-release/wiki/Supported-Versions#supported-releases) table. +- Spring Data 2022.0 + - For detailed dependency versions, see [Spring Data 2022.0 Release Note](https://github.com/spring-projects/spring-data-commons/wiki/Spring-Data-2022.0-%28Turing%29-Release-Notes). + +#### Cloud BigQuery +Replaced `ListenableFuture` and `SettableListenableFuture` with `CompletableFuture` as the former two Classes are deprecated in Spring Framework 6.0. + +#### Cloud SQL +MySQL R2DBC is not supported by Spring Cloud GCP 4.0 due to [compatibility issues](https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory/issues/990) with the Mariadb driver. + +#### Cloud Pub/Sub +Replaced `ListenableFuture` and `SettableListenableFuture` with `CompletableFuture` as the former two Classes are deprecated in Spring Framework 6.0. + +#### Spring Cloud Stream binder to Google Cloud Pub/Sub +Change to functional model as annotation-based model is removed from Spring Cloud Stream 4.0.0. + +#### Secret Manager +To enable the application to fetch a secret from SecretManager, add `spring.config.import=sm://` in the `application.properties` or `application.yml` files and inject the secret into the application by adding `application.secret=${sm://your-secret-name}`. +Do not write secret values in the file. + +Please also remove `spring.cloud.gcp.secretmanager.enabled` and `spring.cloud.gcp.secretmanager.legacy` in `bootstrap.yml` or `bootstrap.properties` files. + +#### Cloud Trace +Annotation `org.springframework.cloud.sleuth.annotation.NewSpan` is deprecated in favor of `io.micrometer.observation.annotation.Observed` since Spring Boot 3.0 builds on Micrometer 1.0 rather than Spring Cloud Sleuth (which is excluded from the Spring Cloud release train). + +If you use `NewSpan` to generate an additional span, replace the annotation with `Observed`. +The default name of the additional span is `#`. +If you want to customize the additional span name, specify the `contextualName` parameter. + +Please also remove `spring.sleuth.enabled` from your `application.properties` or `application.yml` file. +Note that `spring.cloud.gcp.trace.enabled=true` is still required if you want to enable Trace. + +#### Cloud Vision +Replaced `ListenableFuture` and `SettableListenableFuture` with `CompletableFuture` as the former two Classes are deprecated in Spring Framework 6.0. + +### Configuration Changelog +#### Removed +The following properties are removed from the codebase thus no longer needed in your application. + +- `spring.cloud.gcp.secretmanager.enabled` + +- `spring.cloud.gcp.secretmanager.legacy` + +See the "SecretManager" section. + +- `spring.sleuth.enabled` + +See the "Trace" section + +### Deprecated Items Removed + +#### Cloud BigQuery +- `BigQueryTemplate(BigQuery bigQuery, String datasetName)` + Use `BigQueryTemplate(BigQuery, BigQueryWriteClient, Map, TaskScheduler)` instead + +- `BigQueryTemplate(BigQuery, String, TaskScheduler)` + Use `BigQueryTemplate(BigQuery, BigQueryWriteClient, Map, TaskScheduler)` instead + +#### Cloud Datastore +- `DatastorePersistentPropertyImpl.getPersistentEntityTypes()` + Use `DatastorePersistentPropertyImpl.getPersistentEntityTypeInformation()` instead + +#### Cloud Pub/Sub +- `DefaultSubscriberFactory(GcpProjectIdProvider)` + Use `DefaultSubscriberFactory(GcpProjectIdProvider, PubSubConfiguration)` instead + +- `PubSubConfiguration.computeSubscriberRetrySettings(String, String)` + Use `PubSubConfiguration.computeSubscriberRetrySettings(ProjectSubscriptionName)` instead + +- `PubSubConfiguration.computeSubscriberFlowControlSettings(String, String)` + Use `PubSubConfiguration.computeSubscriberFlowControlSettings(ProjectSubscriptionName)` instead + +- `PubSubConfiguration.getSubscriber(String, String)` + Use `PubSubConfiguration.getSubscriptionProperties(ProjectSubscriptionName)` instead + +#### Cloud Spanner +- `SpannerPersistentEntityImpl(TypeInformation)` + Use `SpannerPersistentEntityImpl(TypeInformation, SpannerMappingContext, SpannerEntityProcessor)` instead + +- `SpannerCompositeKeyProperty.getPersistentEntityTypes()` + Use `SpannerCompositeKeyProperty.getPersistentEntityTypeInformation()` instead + +#### Cloud Trace +- `TracingSubscriberFactory.createSubscriberStub()` + Use `TracingSubscriberFactory.createSubscriberStub(String)` instead diff --git a/docs/src/main/md/pubsub.md b/docs/src/main/md/pubsub.md index a478711191..8d63c27577 100644 --- a/docs/src/main/md/pubsub.md +++ b/docs/src/main/md/pubsub.md @@ -4,7 +4,7 @@ Spring Framework on Google Cloud provides an abstraction layer to publish to and subscribe from Google Cloud Pub/Sub topics and to create, list or delete Google Cloud Pub/Sub topics and subscriptions. -A Spring Boot starter is provided to auto-configure the various required +A Spring Boot starter is provided to autoconfigure the various required Pub/Sub components. Maven coordinates, @@ -57,9 +57,9 @@ settings can be either global or subscription-specific. A custom configuration (injected through a setter in `DefaultSubscriberFactory` or a custom bean) will take precedence over -auto-configuration. Hence, if one wishes to use per-subscription +autoconfiguration. Hence, if one wishes to use per-subscription configuration for a Pub/Sub setting, there must not be a custom bean for -that setting. When using auto-configuration, if both global and +that setting. When using autoconfiguration, if both global and per-subscription configurations are provided, then the per-subscription configuration will be used. However, if a per-subscription configuration is not set then the global or default configuration will be used. @@ -235,7 +235,7 @@ backlog. To enable it, you need to add the [Spring Boot Actuator](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#production-ready) to your project and the [GCP Monitoring](https://cloud.google.com/monitoring/docs/reference/libraries). -Also you need to set the following properties +Also, you need to set the following properties `spring.cloud.gcp.pubsub.health.lagThreshold`, `spring.cloud.gcp.pubsub.health.backlogThreshold`. @@ -271,7 +271,7 @@ but the backlog increases, then the subscriber is unhealthy. The health indicator will not behave entirely as expected if Dead Letter Queueing is enabled on the subscription being checked, -num\_undelivered\_messages will drop down by itself after DLQ threshold +num\_undelivered\_messages will drop by itself after DLQ threshold is reached.
@@ -458,7 +458,7 @@ There are two ways to acknowledge messages. All `ack()`, `nack()`, and `modifyAckDeadline()` methods on messages, as well as `PubSubSubscriberTemplate`, are implemented asynchronously, -returning a `ListenableFuture` to enable asynchronous processing. +returning a `CompletableFuture` to enable asynchronous processing. @@ -488,11 +488,11 @@ public Subscription newSubscription() { } ``` -Dead letter topics are no different than any other topic, though some +Dead letter topics are no different from any other topic, though some [additional permissions](https://cloud.google.com/pubsub/docs/dead-letter-topics#granting_forwarding_permissions) are necessary to ensure the Cloud Pub/Sub service can successfully `ack` -the original message and re-`publish` it on the dead letter topic. +the original message and re-`publish` on the dead letter topic. #### JSON support @@ -555,7 +555,7 @@ user.setPassword("password"); pubSubTemplate.publish(topicName, user); ``` -And that’s how you convert messages to objects on pull: +And that’s how you convert messages to object on pull: ``` java int maxMessages = 1; @@ -640,7 +640,7 @@ project using the The Spring Boot starter for Spring Framework on Google Cloud Pub/Sub auto-configures a `PubSubAdmin` object using the `GcpProjectIdProvider` and the `CredentialsProvider` -auto-configured by the Spring Framework on Google Cloud Core Starter. +autoconfigured by the Spring Framework on Google Cloud Core Starter. #### Creating a topic @@ -687,7 +687,7 @@ in a project: ``` java List topics = - pubSubAdmin.listTopics().stream().map(Topic::getName).collect(Collectors.toList()); + pubSubAdmin.listTopics().stream().map(Topic::getName).toList(); ``` #### Creating a subscription @@ -700,6 +700,8 @@ public Subscription createSubscription(String subscriptionName, String topicName public Subscription createSubscription(String subscriptionName, String topicName, Integer ackDeadline) +public Subscription createSubscription(String subscriptionName, String topicName, String pushEndpoint) + public Subscription createSubscription(String subscriptionName, String topicName, Integer ackDeadline, String pushEndpoint) public Subscription createSubscription(Subscriber.Builder builder) @@ -746,9 +748,9 @@ Here is an example of how to list every subscription name in a project: ``` java List subscriptions = - pubSubAdmin.listSubscriptions().stream() - .map(Subscription::getName) - .collect(Collectors.toList()); + pubSubAdmin.listSubscriptions().stream() + .map(Subscription::getName) + .toList(); ``` ### Sample diff --git a/docs/src/main/md/secretmanager.md b/docs/src/main/md/secretmanager.md index fd418ab95c..d339935812 100644 --- a/docs/src/main/md/secretmanager.md +++ b/docs/src/main/md/secretmanager.md @@ -43,19 +43,9 @@ By default, Spring Framework on Google Cloud Secret Manager will authenticate us Application Default Credentials. This can be overridden using the authentication properties. -
- -All of the below settings must be specified in a -[`bootstrap.properties`](https://cloud.spring.io/spring-cloud-commons/multi/multi__spring_cloud_context_application_context_services.html#_the_bootstrap_application_context) -(or `bootstrap.yaml`) file which is the properties file used to -configure settings for bootstrap-phase Spring configuration. - -
- | | | | | |-----------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -------- | ------------------------------------------------------------------------------------------------------------------------------- | | Name | Description | Required | Default value | -| `spring.cloud.gcp.secretmanager.enabled` | Enables the Secret Manager bootstrap property and template configuration. | No | `true` | | `spring.cloud.gcp.secretmanager.credentials.location` | OAuth2 credentials for authenticating to the Google Cloud Secret Manager API. | No | By default, infers credentials from [Application Default Credentials](https://cloud.google.com/docs/authentication/production). | | `spring.cloud.gcp.secretmanager.credentials.encoded-key` | Base64-encoded contents of OAuth2 account private key for authenticating to the Google Cloud Secret Manager API. | No | By default, infers credentials from [Application Default Credentials](https://cloud.google.com/docs/authentication/production). | | `spring.cloud.gcp.secretmanager.project-id` | The default Google Cloud project used to access Secret Manager API for the template and property source. | No | By default, infers the project from [Application Default Credentials](https://cloud.google.com/docs/authentication/production). | @@ -96,12 +86,14 @@ secrets: You can use this syntax in the following places: -1. In your `application.properties` or `bootstrap.properties` files: +1. In your `application.properties` file: # Example of the project-secret long-form syntax. + spring.config.import=sm:// spring.datasource.password=${sm://projects/my-gcp-project/secrets/my-secret} + The former is used to enable [Spring Boot's Config Data API](https://spring.io/blog/2020/08/14/config-file-processing-in-spring-boot-2-4). -2. Access the value using the `@Value` annotation. +3. Access the value using the `@Value` annotation. // Example of using shortest form syntax. @Value("${sm://my-secret}") @@ -135,10 +127,9 @@ template. spring-boot-starter-actuator -- add the following property to your project's `application.properties`. The latter is used to enable [Spring Boot's Config Data API](https://spring.io/blog/2020/08/14/config-file-processing-in-spring-boot-2-4). +- add the following property to your project's `application.properties`. management.endpoints.web.exposure.include=refresh - spring.config.import=sm:// - finally, add the following property to your project's `bootstrap.properties` to disable Secret Manager bootstrap phrase. diff --git a/docs/src/main/md/spring-integration.md b/docs/src/main/md/spring-integration.md index 958a291194..c314c7aacc 100644 --- a/docs/src/main/md/spring-integration.md +++ b/docs/src/main/md/spring-integration.md @@ -9,7 +9,7 @@ Cloud services. The channel adapters for Google Cloud Pub/Sub connect your Spring [`MessageChannels`](https://docs.spring.io/spring-integration/reference/html/channel.html) to Google Cloud Pub/Sub topics and subscriptions. This enables messaging -between different processes, applications or micro-services backed up by +between different processes, applications or microservices backed up by Google Cloud Pub/Sub. The Spring Integration Channel Adapters for Google Cloud Pub/Sub are @@ -206,7 +206,7 @@ public void pubsubErrorHandler(Message message) { } ``` -If you would prefer to manually ack or nack the message, you can do it +If you preferred to manually ack or nack the message, you can do it by retrieving the header of the exception payload: ``` java @@ -334,19 +334,11 @@ It is possible to set user-defined callbacks for the `publish()` call in `setFailureCallback()` methods (either one or both may be set). These give access to the Pub/Sub publish message ID in case of success, or the root cause exception in case of error. Both callbacks include the -original message as the second argument. The old `setPublishCallback()` -method that only gave access to message ID or root cause exception is -deprecated and will be removed in a future release. +original message as the second argument. ``` java -adapter.setPublishCallback( - new ListenableFutureCallback() { - @Override - public void onFailure(Throwable ex) {} - - @Override - public void onSuccess(String result) {} - }); +adapter.setSuccessCallback((ackId, message) -> {}); +adapter.setFailureCallback((cause, message) -> {}); ``` To override the default topic you can use the `GcpPubSubHeaders.TOPIC` diff --git a/docs/src/main/md/spring-stream.md b/docs/src/main/md/spring-stream.md index c01fc32165..dbd32b9e06 100644 --- a/docs/src/main/md/spring-stream.md +++ b/docs/src/main/md/spring-stream.md @@ -41,14 +41,21 @@ You can configure the Spring Cloud Stream Binder for Google Cloud Pub/Sub to automatically generate the underlying resources, like the Google Cloud Pub/Sub topics and subscriptions for producers and consumers. For that, you can use the -`spring.cloud.stream.gcp.pubsub.bindings...auto-create-resources` +`spring.cloud.stream.gcp.pubsub.bindings...auto-create-resources` property, which is turned ON by default. +
+ +For more info about consumer/producer naming convention, please refer to [Functional binding names](https://docs.spring.io/spring-cloud-stream/docs/current/reference/html/spring-cloud-stream.html#_functional_binding_names). + +
+ Starting with version 1.1, these and other binder properties can be configured globally for all the bindings, e.g. `spring.cloud.stream.gcp.pubsub.default.consumer.auto-create-resources`. -If you are using Pub/Sub auto-configuration from the Spring Framework on Google Cloud +If you are using Pub/Sub autoconfiguration from the Spring Framework on Google Cloud + Pub/Sub Starter, you should refer to the [configuration](#pubsub-configuration) section for other Pub/Sub parameters. @@ -79,8 +86,8 @@ would be created. **application.properties.** - spring.cloud.stream.bindings.events.destination=myEvents - spring.cloud.stream.gcp.pubsub.bindings.events.producer.auto-create-resources=true + spring.cloud.stream.bindings.{PRODUCER_NAME}.destination=myEvents + spring.cloud.stream.gcp.pubsub.bindings.{PRODUCER_NAME}.producer.auto-create-resources=true #### Consumer Destination Configuration @@ -119,8 +126,8 @@ For example, with this configuration: **application.properties.** - spring.cloud.stream.bindings.events.destination=myEvents - spring.cloud.stream.gcp.pubsub.bindings.events.consumer.auto-create-resources=false + spring.cloud.stream.bindings.{CONSUMER_NAME}.destination=myEvents + spring.cloud.stream.gcp.pubsub.bindings.{CONSUMER_NAME}.consumer.auto-create-resources=false Only an anonymous subscription named `anonymous.myEvents.a6d83782-c5a3-4861-ac38-e6e2af15a7be` is created and @@ -130,11 +137,11 @@ In another example, with the following configuration: **application.properties.** - spring.cloud.stream.bindings.events.destination=myEvents - spring.cloud.stream.gcp.pubsub.bindings.events.consumer.auto-create-resources=true + spring.cloud.stream.bindings.{CONSUMER_NAME}.destination=myEvents + spring.cloud.stream.gcp.pubsub.bindings.{CONSUMER_NAME}.consumer.auto-create-resources=true # specify consumer group, and avoid anonymous consumer group generation - spring.cloud.stream.bindings.events.group=consumerGroup1 + spring.cloud.stream.bindings.{CONSUMER_NAME}.group=consumerGroup1 These resources will be created: @@ -150,9 +157,9 @@ a comma separated list like this: **application.properties.** - spring.cloud.stream.gcp.pubsub.bindings.-in-0.consumer.allowedHeaders=allowed1, allowed2 + spring.cloud.stream.gcp.pubsub.bindings.{CONSUMER_NAME}.consumer.allowedHeaders=allowed1, allowed2 -Where \ should be replaced by the method which is +Where `CONSUMER_NAME` should be replaced by the method which is consuming/reading messages from Cloud Pub/Sub and allowed1, allowed2 is the comma separated list of headers that the user wants to keep. @@ -160,9 +167,9 @@ A similar style is applicable for producers as well. For example: **application.properties.** - spring.cloud.stream.gcp.pubsub.bindings.-out-0.producer.allowedHeaders=allowed3,allowed4 + spring.cloud.stream.gcp.pubsub.bindings.{PRODUCER_NAME}.producer.allowedHeaders=allowed3,allowed4 -Where \ should be replaced by the method which is +Where `PRODUCER_NAME` should be replaced by the method which is producing/sending messages to Cloud Pub/Sub and allowed3, allowed4 is the comma separated list of headers that user wants to map. All other headers will be removed before the message is sent to Cloud Pub/Sub. @@ -215,94 +222,9 @@ stream, which will be used as is. A processor application works similarly to a source application, except it is triggered by presence of a `Function` bean. -### Binding with Annotations - -
- -As of version 3.0, annotation binding is considered legacy. - -
- -To set up a sink application in this style, you would associate a class -with a binding interface, such as the built-in `Sink` interface. - - @EnableBinding(Sink.class) - public class SinkExample { - - @StreamListener(Sink.INPUT) - public void handleMessage(UserMessage userMessage) { - // process message - } - } - -To set up a source application, you would similarly associate a class -with a built-in `Source` interface, and inject an instance of it -provided by Spring Cloud Stream. - - @EnableBinding(Source.class) - public class SourceExample { - - @Autowired - private Source source; - - public void sendMessage() { - this.source.output().send(new GenericMessage<>(/* your object here */)); - } - } - -### Streaming vs. Polled Input - -Many Spring Cloud Stream applications will use the built-in `Sink` -binding, which triggers the *streaming* input binder creation. Messages -can then be consumed with an input handler marked by -`@StreamListener(Sink.INPUT)` annotation, at whatever rate Pub/Sub sends -them. - -For more control over the rate of message arrival, a polled input binder -can be set up by defining a custom binding interface with an -`@Input`-annotated method returning `PollableMessageSource`. - -``` java -public interface PollableSink { - - @Input("input") - PollableMessageSource input(); -} -``` - -The `PollableMessageSource` can then be injected and queried, as needed. - -``` java -@EnableBinding(PollableSink.class) -public class SinkExample { - - @Autowired - PollableMessageSource destIn; - - @Bean - public ApplicationRunner singlePollRunner() { - return args -> { - // This will poll only once. - // Add a loop or a scheduler to get more messages. - destIn.poll(message -> System.out.println("Message retrieved: " + message)); - }; - } -} -``` - -By default, the polling will only get 1 message at a time. Use the -`spring.cloud.stream.gcp.pubsub.default.consumer.maxFetchSize` property -to fetch additional messages per network roundtrip. - ### Sample Sample applications are available: - - For [streaming input, - annotation-based](https://github.com/GoogleCloudPlatform/spring-cloud-gcp/tree/main/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample). - - For [streaming input, functional style](https://github.com/GoogleCloudPlatform/spring-cloud-gcp/tree/main/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-functional-sample). - - - For [polled - input](https://github.com/GoogleCloudPlatform/spring-cloud-gcp/tree/main/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample). diff --git a/docs/src/main/md/sql.md b/docs/src/main/md/sql.md index d126c16934..dfc9c50f2e 100644 --- a/docs/src/main/md/sql.md +++ b/docs/src/main/md/sql.md @@ -6,7 +6,7 @@ and [Spring R2DBC](https://docs.spring.io/spring-data/r2dbc/docs/current/reference/html/#r2dbc.core) so you can run your MySQL or PostgreSQL databases in [Google Cloud SQL](https://cloud.google.com/sql) using Spring JDBC and other libraries -that depend on it like Spring Data JPA or Spring Data R2DBC. +that depend on it like Spring Data JPA or Spring Data R2DBC (only R2DBC PostgreSQL is supported). The Cloud SQL support is provided by Spring Framework on Google Cloud in the form of two Spring Boot starters, one for MySQL and another one for PostgreSQL. The @@ -57,7 +57,7 @@ Console, search for "Cloud SQL API" and enable the option that is called #### Spring Boot Starter for Google Cloud SQL -The Spring Boot Starters for Google Cloud SQL provide an auto-configured +The Spring Boot Starters for Google Cloud SQL provide an autoconfigured [`DataSource`](https://docs.oracle.com/javase/7/docs/api/javax/sql/DataSource.html) object. Coupled with Spring JDBC, it provides a [`JdbcTemplate`](https://docs.spring.io/spring/docs/current/spring-framework-reference/html/jdbc.html#jdbc-JdbcTemplate) @@ -71,7 +71,7 @@ public List> listUsers() { ``` You can rely on [Spring Boot data source -auto-configuration](https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html#boot-features-connect-to-production-database) +autoconfiguration](https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html#boot-features-connect-to-production-database) to configure a `DataSource` bean. In other words, properties like the SQL username, `spring.datasource.username`, and password, `spring.datasource.password` can be used. There is also some @@ -88,7 +88,7 @@ Properties" section below).
If you provide your own `spring.datasource.url`, it will be ignored, -unless you disable Cloud SQL auto configuration with +unless you disable Cloud SQL autoconfiguration with `spring.cloud.gcp.sql.enabled=false` or `spring.cloud.gcp.sql.jdbc.enabled=false`. @@ -121,50 +121,12 @@ database with as little as a database and instance names. Maven and Gradle coordinates, using [Spring Framework on Google Cloud BOM](getting-started.xml#bill-of-materials): -To use MySQL: - -``` xml - - com.google.cloud - spring-cloud-gcp-starter-sql-mysql-r2dbc - -``` - - dependencies { - implementation("com.google.cloud:spring-cloud-gcp-starter-sql-mysql-r2dbc") - } - -To use PostgreSQL with Spring Boot 2.6: - -``` xml - - com.google.cloud - spring-cloud-gcp-starter-sql-postgres-r2dbc - -``` - - dependencies { - implementation("com.google.cloud:spring-cloud-gcp-starter-sql-postgres-r2dbc") - } - -To use PostgreSQL with Spring Boot 2.7 (the latest version of the Postgres R2DBC driver changed its Maven coordinates): +To use PostgreSQL: ``` xml - com.google.cloud - spring-cloud-gcp-starter-sql-postgres-r2dbc - - - io.r2dbc - r2dbc-postgresql - - - - - - org.postgresql - r2dbc-postgresql - 0.9.1.RELEASE + com.google.cloud + spring-cloud-gcp-starter-sql-postgres-r2dbc ``` @@ -205,16 +167,16 @@ Standard R2DBC properties like the SQL username, used. There is also some configuration specific to Google Cloud SQL (see "Cloud SQL Configuration Properties" section below). -| | | | | -| ----------------------- | ----------------- | -------- | ------------------------------------- | -| Property name | Description | Required | Default value | -| `spring.r2dbc.username` | Database username | No | MySQL: `root`; PostgreSQL: `postgres` | -| `spring.r2dbc.password` | Database password | No | `null` | +| | | | | +| ----------------------- | ----------------- | -------- | ------------------------------------ | +| Property name | Description | Required | Default value | +| `spring.r2dbc.username` | Database username | No | `postgres` | +| `spring.r2dbc.password` | Database password | No | `null` |
If you provide your own `spring.r2dbc.url`, it will be ignored, unless -you disable Cloud SQL auto-configuration for R2DBC with +you disable Cloud SQL autoconfiguration for R2DBC with `spring.cloud.gcp.sql.enabled=false` or `spring.cloud.gcp.sql.r2dbc.enabled=false` . @@ -342,8 +304,5 @@ Available sample applications and codelabs: - Codelab: [Spring Pet Clinic using Cloud SQL](https://codelabs.developers.google.com/codelabs/cloud-spring-petclinic-cloudsql/index.html) - - [R2DBC: Spring Framework on Google Cloud - MySQL](https://github.com/GoogleCloudPlatform/spring-cloud-gcp/tree/main/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample) - - [R2DBC: Spring Framework on Google Cloud PostgreSQL](https://github.com/GoogleCloudPlatform/spring-cloud-gcp/tree/main/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-r2dbc-sample) diff --git a/docs/src/main/md/trace.md b/docs/src/main/md/trace.md index 18ab267374..a3cf57bf41 100644 --- a/docs/src/main/md/trace.md +++ b/docs/src/main/md/trace.md @@ -1,18 +1,17 @@ ## Cloud Trace Google Cloud provides a managed distributed tracing service -called [Cloud Trace](https://cloud.google.com/trace/), and [Spring Cloud -Sleuth](https://cloud.spring.io/spring-cloud-sleuth/) can be used with +called [Cloud Trace](https://cloud.google.com/trace/), and [Micrometer](https://micrometer.io/) can be used with it to easily instrument Spring Boot applications for observability. -Typically, Spring Cloud Sleuth captures trace information and forwards -traces to services like Zipkin for storage and analysis. However, on +Typically, Micrometer captures trace information and forwards +traces to service like Zipkin for storage and analysis. However, on Google Cloud, instead of running and maintaining your own Zipkin instance and storage, you can use Cloud Trace to store traces, view trace details, generate latency distributions graphs, and generate performance regression reports. -This Spring Framework on Google Cloud starter can forward Spring Cloud Sleuth traces to +This Spring Framework on Google Cloud starter can forward Micrometer traces to Cloud Trace without an intermediary Zipkin server. Maven coordinates, @@ -47,7 +46,7 @@ those traces to Cloud Trace without modifying existing applications. ### Tracing -Spring Cloud Sleuth uses the [Brave +Micrometer uses the [Brave tracer](https://github.com/openzipkin/brave) to generate traces. This integration enables Brave to use the [`StackdriverTracePropagation`](https://github.com/openzipkin/zipkin-gcp/tree/main/propagation-stackdriver) @@ -72,12 +71,12 @@ in three different ways: `TRACE_ID` is a 32-character hexadecimal value that encodes a 128-bit number. -`SPAN_ID` is an unsigned long. Since Cloud Trace doesn’t support span +`SPAN_ID` is an unsigned long. Since Cloud Trace doesn't support span joins, a new span ID is always generated, regardless of the one specified in `x-cloud-trace-context`. `TRACE_TRUE` can either be `0` if the entity should be untraced, or `1` -if it should be traced. This field forces the decision of whether or not +if it should be traced. This field forces the decision of whether to trace the request; if omitted then the decision is deferred to the sampler. @@ -87,17 +86,17 @@ headers](https://github.com/openzipkin/b3-propagation). ### Spring Boot Starter for Cloud Trace -Spring Boot Starter for Cloud Trace uses Spring Cloud Sleuth and -auto-configures a +Spring Boot Starter for Cloud Trace uses Micrometer and +autoconfigures a [StackdriverSender](https://github.com/openzipkin/zipkin-gcp/blob/main/sender-stackdriver/src/main/java/zipkin2/reporter/stackdriver/StackdriverSender.java) -that sends the Sleuth’s trace information to Cloud Trace. +that sends the Micrometer’s trace information to Cloud Trace. All configurations are optional: | | | | | | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | | Name | Description | Required | Default value | -| `spring.cloud.gcp.trace.enabled` | Auto-configure Spring Cloud Sleuth to send traces to Cloud Trace. | No | `true` | +| `spring.cloud.gcp.trace.enabled` | Auto-configure Micrometer to send traces to Cloud Trace. | No | `true` | | `spring.cloud.gcp.trace.project-id` | Overrides the project ID from the [Spring Framework on Google Cloud Module](#spring-framework-on-google-cloud-core) | No | | | `spring.cloud.gcp.trace.credentials.location` | Overrides the credentials location from the [Spring Framework on Google Cloud Module](#spring-framework-on-google-cloud-core) | No | | | `spring.cloud.gcp.trace.credentials.encoded-key` | Overrides the credentials encoded key from the [Spring Framework on Google Cloud Module](#spring-framework-on-google-cloud-core) | No | | @@ -113,31 +112,16 @@ All configurations are optional: | `spring.cloud.gcp.trace.server-response-timeout-ms` | Server response timeout in millis. | No | `5000` | | `spring.cloud.gcp.trace.pubsub.enabled` | (Experimental) Auto-configure Pub/Sub instrumentation for Trace. | No | `false` | -You can use core Spring Cloud Sleuth properties to control Sleuth’s -sampling rate, etc. Read [Sleuth -documentation](https://cloud.spring.io/spring-cloud-sleuth/) for more -information on Sleuth configurations. +You can use core Micrometer properties to control Micrometer’s +sampling rate, etc. Read [Spring Boot Tracing documentation](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator.micrometer-tracing) for more +information on Micrometer configurations. For example, when you are testing to see the traces are going through, you can set the sampling rate to 100%. - spring.sleuth.sampler.probability=1 # Send 100% of the request traces to Cloud Trace. - spring.sleuth.web.skipPattern=(^cleanup.*|.+favicon.*) # Ignore some URL paths. - spring.sleuth.scheduled.enabled=false # disable executor 'async' traces + management.tracing.sampling.probability=1.0 # Send 100% of the request traces to Cloud Trace. -
- -By default, Spring Cloud Sleuth auto-configuration instruments executor -beans, which may cause recurring traces with the name `async` to appear -in Cloud Trace if your application or one of its dependencies introduces -scheduler beans into Spring application context. To avoid this noise, -please disable automatic instrumentation of executors via -`spring.sleuth.scheduled.enabled=false` in your application -configuration. - -
- -Spring Framework on Google Cloud Trace does override some Sleuth configurations: +Spring Framework on Google Cloud Trace does override some Micrometer configurations: - Always uses 128-bit Trace IDs. This is required by Cloud Trace. @@ -145,13 +129,9 @@ Spring Framework on Google Cloud Trace does override some Sleuth configurations: the client and server Spans. Cloud Trace requires that every Span ID within a Trace to be unique, so Span joins are not supported. - - Uses `StackdriverHttpRequestParser` by default to populate - Stackdriver related fields. - -### Overriding the auto-configuration +### Overriding the autoconfiguration -Spring Cloud Sleuth supports sending traces to multiple tracing systems -as of version 2.1.0. In order to get this to work, every tracing system +You can send traces to multiple tracing systems. In order to get this to work, every tracing system needs to have a `Reporter` and `Sender`. If you want to override the provided beans you need to give them a specific name. To do this you can use respectively @@ -217,9 +197,6 @@ Spring Integration channel adapters, and the Spring Cloud Stream Binder. # Enable Pub/Sub tracing using this property spring.cloud.gcp.trace.pubsub.enabled=true - - # You should disable Spring Integration instrumentation by Sleuth as it's unnecessary when Pub/Sub tracing is enabled - spring.sleuth.integration.enabled=false ### Sample diff --git a/docs/src/main/md/vision.md b/docs/src/main/md/vision.md index bb830f0fa0..77ffb38fec 100644 --- a/docs/src/main/md/vision.md +++ b/docs/src/main/md/vision.md @@ -251,20 +251,20 @@ the document. The `DocumentOcrTemplate` provides the following method for running OCR on a document saved in Google Cloud Storage: -`ListenableFuture +`CompletableFuture runOcrForDocument(GoogleStorageLocation document, GoogleStorageLocation outputFilePathPrefix)` The method allows you to specify the location of the document and the output location for where all the JSON output files will be saved in -Google Cloud Storage. It returns a `ListenableFuture` containing +Google Cloud Storage. It returns a `CompletableFuture` containing `DocumentOcrResultSet` which contains the OCR content of the document.
Running OCR on a document is an operation that can take between several minutes to several hours depending on how large the document is. It is -recommended to register callbacks to the returned ListenableFuture or +recommended to register callbacks to the returned CompletableFuture or ignore it and process the JSON output files at a later point in time using `readOcrOutputFile` or `readOcrOutputFileSet`. @@ -285,7 +285,7 @@ document. GoogleStorageLocation outputLocationPrefix = GoogleStorageLocation.forFolder( "your-bucket", "output_folder/test.pdf/"); - ListenableFuture result = + CompletableFuture result = this.documentOcrTemplate.runOcrForDocument( document, outputLocationPrefix); diff --git a/pom.xml b/pom.xml index 6d285001b7..b9ea93d413 100644 --- a/pom.xml +++ b/pom.xml @@ -1,13 +1,11 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" + child.project.url.inherit.append.path="false"> 4.0.0 - com.google.cloud spring-cloud-gcp - 3.4.3-SNAPSHOT - + 4.0.0-SNAPSHOT Spring Framework on Google Cloud Spring Framework on Google Cloud https://spring.io/projects/spring-cloud-gcp @@ -37,34 +35,32 @@ ${project.version} - - 2021.0.5 + 2022.0.0 + 3.0.0 ${project.parent.version} - 2.7.7 1.0.4 2.4.1 - 0.10.5 + 1.0.0 - 3.8.1 + 3.10.1 2.8.2 - 2.22.1 + 3.0.0-M7 1.3.0 3.0.1 3.3.0 3.4.1 3.2.1 2.22.2 - 1.8 - 1.8 + 17 + 17 1.6.13 3.2.1 9.3 2.0.0 2.18.0 - ${skipTests} ${skipTests} @@ -149,16 +145,15 @@ java-cfenv-test-support ${java-cfenv.version} + - org.springframework.experimental - spring-native-configuration - ${spring-native.version} - - - org.springframework.experimental - spring-aot - ${spring-native.version} + io.micrometer + micrometer-tracing-bom + ${micrometer-tracing.verison} + pom + import + @@ -374,7 +369,6 @@ spring-cloud-gcp-security-firebase spring-cloud-gcp-vision spring-cloud-gcp-kms - spring-cloud-gcp-native-support spring-cloud-previews @@ -392,7 +386,7 @@ docFX - 11 + 17 @@ -401,7 +395,7 @@ spring-framework-on-google-cloud - 8 + 17 @@ -515,18 +509,18 @@ - + - errorprone_java_9_and_up + errorprone_java_17_and_up - [9,] + [17,] org.apache.maven.plugins - maven-compiler-plugin + ${maven-compiler-plugin.version} 3.10.1 ${maven.compiler.source} @@ -535,7 +529,11 @@ true -XDcompilePolicy=simple - -Xplugin:ErrorProne -Xep:UnicodeInCode:OFF + + -Xplugin:ErrorProne \ + -Xep:UnicodeInCode:OFF \ + -Xep:CanIgnoreReturnValue:OFF + -parameters -J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED @@ -563,41 +561,6 @@ - - errorprone_java8 - - 1.8 - - - 9+181-r4173-1 - - - - - - maven-compiler-plugin - 3.10.1 - - true - - -J-Xbootclasspath/p:${settings.localRepository}/com/google/errorprone/javac/${javac.version}/javac-${javac.version}.jar - - -parameters - - - - - com.google.errorprone - error_prone_core - ${errorprone.version} - - - - - - - - release @@ -704,7 +667,7 @@ - 8 + 17 com.example, com.example.*, @@ -792,7 +755,7 @@ Apache License, Version 2.0 https://www.apache.org/licenses/LICENSE-2.0 - Copyright 2015-2020 the original author or authors. + Copyright 2015-2022 the original author or authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/spring-cloud-gcp-autoconfigure/pom.xml b/spring-cloud-gcp-autoconfigure/pom.xml index 3d5cefc211..80b0cf6d51 100644 --- a/spring-cloud-gcp-autoconfigure/pom.xml +++ b/spring-cloud-gcp-autoconfigure/pom.xml @@ -5,7 +5,7 @@ spring-cloud-gcp com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 @@ -42,8 +42,8 @@ true - javax.servlet - javax.servlet-api + jakarta.servlet + jakarta.servlet-api provided @@ -117,27 +117,15 @@ test - - - com.google.cloud.sql - cloud-sql-connector-r2dbc-mysql - true - - - dev.miku - r2dbc-mysql - true - - - - + com.google.cloud.sql cloud-sql-connector-r2dbc-postgres true + - io.r2dbc + org.postgresql r2dbc-postgresql true @@ -183,19 +171,20 @@ - org.springframework.cloud - spring-cloud-sleuth-autoconfigure + io.micrometer + micrometer-tracing-bridge-brave true + - io.zipkin.reporter2 - zipkin-reporter-brave + io.zipkin.brave + brave-instrumentation-messaging true - org.springframework.cloud - spring-cloud-sleuth-brave + io.zipkin.reporter2 + zipkin-reporter-brave true diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/bigquery/GcpBigQueryAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/bigquery/GcpBigQueryAutoConfiguration.java index f9d11d06a5..f23dd4f79e 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/bigquery/GcpBigQueryAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/bigquery/GcpBigQueryAutoConfiguration.java @@ -30,17 +30,17 @@ import java.util.HashMap; import java.util.Map; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; /** Provides client objects for interfacing with BigQuery. */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @AutoConfigureAfter(GcpContextAutoConfiguration.class) @ConditionalOnProperty(value = "spring.cloud.gcp.bigquery.enabled", matchIfMissing = true) @ConditionalOnClass({BigQuery.class, BigQueryTemplate.class}) diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/config/GcpConfigBootstrapConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/config/GcpConfigBootstrapConfiguration.java index 537531be39..9158372cb2 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/config/GcpConfigBootstrapConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/config/GcpConfigBootstrapConfiguration.java @@ -19,18 +19,18 @@ import com.google.cloud.spring.core.DefaultCredentialsProvider; import com.google.cloud.spring.core.DefaultGcpProjectIdProvider; import java.io.IOException; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; /** * Bootstrap auto configuration for Google Cloud Runtime Configurator Starter. * * @since 1.1 */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @ConditionalOnProperty(prefix = "spring.cloud.gcp.config", name = "enabled", havingValue = "true") @EnableConfigurationProperties(GcpConfigProperties.class) public class GcpConfigBootstrapConfiguration { diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/core/GcpContextAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/core/GcpContextAutoConfiguration.java index c1fd6607b4..8c93ec46a1 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/core/GcpContextAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/core/GcpContextAutoConfiguration.java @@ -25,17 +25,17 @@ import java.io.IOException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; /** * Base starter for Google Cloud Projects. Provides defaults for {@link * com.google.auth.oauth2.GoogleCredentials}. Binds properties from {@link GcpProperties}. */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @ConditionalOnProperty( name = "spring.cloud.gcp.core.enabled", havingValue = "true", diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/datastore/DatastoreRepositoriesAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/datastore/DatastoreRepositoriesAutoConfiguration.java index bb6e0dc18a..8d7d7c30b8 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/datastore/DatastoreRepositoriesAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/datastore/DatastoreRepositoriesAutoConfiguration.java @@ -19,11 +19,11 @@ import com.google.cloud.spring.data.datastore.repository.DatastoreRepository; import com.google.cloud.spring.data.datastore.repository.config.DatastoreRepositoryConfigurationExtension; import com.google.cloud.spring.data.datastore.repository.support.DatastoreRepositoryFactoryBean; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; /** @@ -32,7 +32,7 @@ * * @since 1.1 */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @ConditionalOnClass(DatastoreRepository.class) @ConditionalOnMissingBean({ DatastoreRepositoryFactoryBean.class, diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/datastore/DatastoreTransactionManagerAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/datastore/DatastoreTransactionManagerAutoConfiguration.java index 41d1c980c9..83f990649a 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/datastore/DatastoreTransactionManagerAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/datastore/DatastoreTransactionManagerAutoConfiguration.java @@ -18,6 +18,7 @@ import com.google.cloud.spring.data.datastore.core.DatastoreTransactionManager; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -25,7 +26,6 @@ import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.transaction.PlatformTransactionManager; /** @@ -33,14 +33,18 @@ * * @since 1.1 */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @ConditionalOnClass(DatastoreTransactionManager.class) @ConditionalOnProperty(value = "spring.cloud.gcp.datastore.enabled", matchIfMissing = true) @AutoConfigureBefore(TransactionAutoConfiguration.class) public class DatastoreTransactionManagerAutoConfiguration { + private DatastoreTransactionManagerAutoConfiguration() { + + } + /** Configuration class. */ - @Configuration(proxyBeanMethods = false) + @AutoConfiguration static class DatastoreTransactionManagerConfiguration { private final DatastoreProvider datastore; diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/datastore/GcpDatastoreAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/datastore/GcpDatastoreAutoConfiguration.java index 1f3b36e6b3..512914998b 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/datastore/GcpDatastoreAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/datastore/GcpDatastoreAutoConfiguration.java @@ -43,13 +43,13 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.data.rest.webmvc.spi.BackendIdConverter; /** @@ -57,7 +57,7 @@ * * @since 1.1 */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @AutoConfigureAfter(GcpContextAutoConfiguration.class) @ConditionalOnProperty(value = "spring.cloud.gcp.datastore.enabled", matchIfMissing = true) @ConditionalOnClass({DatastoreOperations.class, Datastore.class}) diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/datastore/GcpDatastoreEmulatorAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/datastore/GcpDatastoreEmulatorAutoConfiguration.java index 6bcac6eaa2..041cb65cc1 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/datastore/GcpDatastoreEmulatorAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/datastore/GcpDatastoreEmulatorAutoConfiguration.java @@ -21,13 +21,13 @@ import java.util.concurrent.TimeoutException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.SmartLifecycle; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; /** * If spring.cloud.gcp.datastore.emulator.enabled is set to true the emulator will be started as a @@ -35,7 +35,7 @@ * * @since 1.2 */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @ConditionalOnProperty("spring.cloud.gcp.datastore.emulator.enabled") @AutoConfigureBefore(GcpDatastoreAutoConfiguration.class) @EnableConfigurationProperties(GcpDatastoreProperties.class) diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/datastore/health/DatastoreHealthIndicatorAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/datastore/health/DatastoreHealthIndicatorAutoConfiguration.java index e2c801c8d9..2f57773f8b 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/datastore/health/DatastoreHealthIndicatorAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/datastore/health/DatastoreHealthIndicatorAutoConfiguration.java @@ -23,13 +23,13 @@ import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator; import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; /** * {@link HealthContributorAutoConfiguration Auto-configuration} for {@link @@ -37,7 +37,7 @@ * * @since 1.2 */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @ConditionalOnClass({Datastore.class, HealthIndicator.class}) @ConditionalOnBean(value = Datastore.class, parameterizedContainer = Supplier.class) @ConditionalOnEnabledHealthIndicator("datastore") diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/firestore/FirestoreRepositoriesAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/firestore/FirestoreRepositoriesAutoConfiguration.java index f445d3fe6f..6f2443d92e 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/firestore/FirestoreRepositoriesAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/firestore/FirestoreRepositoriesAutoConfiguration.java @@ -19,11 +19,11 @@ import com.google.cloud.spring.data.firestore.FirestoreReactiveRepository; import com.google.cloud.spring.data.firestore.repository.config.FirestoreRepositoryConfigurationExtension; import com.google.cloud.spring.data.firestore.repository.support.FirestoreRepositoryFactoryBean; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; /** @@ -31,7 +31,7 @@ * * @since 1.2 */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @ConditionalOnClass(FirestoreReactiveRepository.class) @ConditionalOnMissingBean({ FirestoreRepositoryFactoryBean.class, diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/firestore/FirestoreTransactionManagerAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/firestore/FirestoreTransactionManagerAutoConfiguration.java index ef7c509f94..8b2f6dd2fc 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/firestore/FirestoreTransactionManagerAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/firestore/FirestoreTransactionManagerAutoConfiguration.java @@ -20,6 +20,7 @@ import com.google.cloud.spring.data.firestore.mapping.FirestoreClassMapper; import com.google.cloud.spring.data.firestore.transaction.ReactiveFirestoreTransactionManager; import com.google.firestore.v1.FirestoreGrpc; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -27,7 +28,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import reactor.core.publisher.Flux; /** @@ -35,7 +35,7 @@ * * @since 2.0.5 */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @ConditionalOnClass({ ReactiveFirestoreTransactionManager.class, FirestoreGrpc.FirestoreStub.class, diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/firestore/GcpFirestoreAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/firestore/GcpFirestoreAutoConfiguration.java index 15a615583c..bfbc1a230e 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/firestore/GcpFirestoreAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/firestore/GcpFirestoreAutoConfiguration.java @@ -35,13 +35,13 @@ import io.grpc.auth.MoreCallCredentials; import java.io.IOException; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import reactor.core.publisher.Flux; /** @@ -49,7 +49,7 @@ * * @since 1.2 */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @AutoConfigureAfter(GcpContextAutoConfiguration.class) @ConditionalOnProperty(value = "spring.cloud.gcp.firestore.enabled", matchIfMissing = true) @ConditionalOnClass({Firestore.class}) diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/firestore/GcpFirestoreEmulatorAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/firestore/GcpFirestoreEmulatorAutoConfiguration.java index 9921a67444..9d8cb7c23e 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/firestore/GcpFirestoreEmulatorAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/firestore/GcpFirestoreEmulatorAutoConfiguration.java @@ -33,13 +33,13 @@ import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import reactor.core.publisher.Flux; /** @@ -47,7 +47,7 @@ * * @since 1.2.3 */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @ConditionalOnProperty("spring.cloud.gcp.firestore.emulator.enabled") @AutoConfigureBefore(GcpFirestoreAutoConfiguration.class) @EnableConfigurationProperties(GcpFirestoreProperties.class) diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/kms/GcpKmsAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/kms/GcpKmsAutoConfiguration.java index b266fb3ac9..8c45e90f27 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/kms/GcpKmsAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/kms/GcpKmsAutoConfiguration.java @@ -24,15 +24,15 @@ import com.google.cloud.spring.core.UserAgentHeaderProvider; import com.google.cloud.spring.kms.KmsTemplate; import java.io.IOException; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; /** Autoconfiguration for GCP KMS which enables data encryption and decryption. */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @EnableConfigurationProperties(GcpKmsProperties.class) @ConditionalOnClass({KeyManagementServiceClient.class, KmsTemplate.class}) @ConditionalOnProperty(value = "spring.cloud.gcp.kms.enabled", matchIfMissing = true) diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/logging/StackdriverLoggingAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/logging/StackdriverLoggingAutoConfiguration.java index a983d93226..b4e7f857f4 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/logging/StackdriverLoggingAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/logging/StackdriverLoggingAutoConfiguration.java @@ -22,6 +22,7 @@ import com.google.cloud.spring.logging.TraceIdLoggingWebMvcInterceptor; import com.google.cloud.spring.logging.extractors.CloudTraceIdExtractor; import com.google.cloud.spring.logging.extractors.TraceIdExtractor; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -29,7 +30,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.web.servlet.HandlerInterceptor; @@ -38,7 +38,7 @@ * configuration is turned on only if Trace support is not used and Web MVC is used. Otherwise, the * MDC context will be used by the Logback appenders. */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @ConditionalOnClass({HandlerInterceptor.class, LoggingAppender.class, TraceIdExtractor.class}) @ConditionalOnMissingBean(name = "stackdriverTracingCustomizer") @AutoConfigureAfter(StackdriverTraceAutoConfiguration.class) diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/metrics/GcpStackdriverMetricsAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/metrics/GcpStackdriverMetricsAutoConfiguration.java index 9f6fe30c61..9904318985 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/metrics/GcpStackdriverMetricsAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/metrics/GcpStackdriverMetricsAutoConfiguration.java @@ -29,6 +29,7 @@ import org.springframework.boot.actuate.autoconfigure.metrics.export.ConditionalOnEnabledMetricsExport; import org.springframework.boot.actuate.autoconfigure.metrics.export.stackdriver.StackdriverMetricsExportAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.export.stackdriver.StackdriverProperties; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -37,14 +38,13 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; /** * Provides auto-detection for `project-id` and `credentials`. * * @since 1.2.4 */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @AutoConfigureBefore(StackdriverMetricsExportAutoConfiguration.class) @AutoConfigureAfter(MetricsAutoConfiguration.class) @ConditionalOnClass(StackdriverMeterRegistry.class) diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/GcpPubSubAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/GcpPubSubAutoConfiguration.java index b2280476ce..4274c947b8 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/GcpPubSubAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/GcpPubSubAutoConfiguration.java @@ -67,6 +67,7 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -74,14 +75,13 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.context.support.GenericApplicationContext; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.threeten.bp.Duration; /** Auto-config for Pub/Sub. */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @AutoConfigureAfter(GcpContextAutoConfiguration.class) @ConditionalOnProperty(value = "spring.cloud.gcp.pubsub.enabled", matchIfMissing = true) @ConditionalOnClass(PubSubTemplate.class) diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/GcpPubSubEmulatorAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/GcpPubSubEmulatorAutoConfiguration.java index 1dd0c17d0f..580cebc6eb 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/GcpPubSubEmulatorAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/GcpPubSubEmulatorAutoConfiguration.java @@ -23,19 +23,19 @@ import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import javax.annotation.PreDestroy; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; /** * If spring.cloud.gcp.pubsub.emulator-host is set, spring stream will connect to a * running pub/sub emulator. */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @ConditionalOnClass({ManagedChannel.class, PubSubTemplate.class}) @ConditionalOnProperty(prefix = "spring.cloud.gcp.pubsub", name = "enabled", matchIfMissing = true) @AutoConfigureBefore(GcpPubSubAutoConfiguration.class) diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/GcpPubSubReactiveAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/GcpPubSubReactiveAutoConfiguration.java index 0f05d07f85..419e33d62f 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/GcpPubSubReactiveAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/GcpPubSubReactiveAutoConfiguration.java @@ -20,12 +20,12 @@ import com.google.cloud.spring.pubsub.reactive.PubSubReactiveFactory; import java.util.Optional; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import reactor.core.publisher.Flux; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; @@ -35,7 +35,7 @@ * * @since 1.2 */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @AutoConfigureAfter(GcpPubSubAutoConfiguration.class) @ConditionalOnClass({Flux.class, PubSubSubscriberTemplate.class}) @ConditionalOnProperty( diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/health/PubSubHealthIndicator.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/health/PubSubHealthIndicator.java index 6b3f426944..c887ed89da 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/health/PubSubHealthIndicator.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/health/PubSubHealthIndicator.java @@ -23,6 +23,7 @@ import com.google.cloud.spring.pubsub.support.AcknowledgeablePubsubMessage; import java.util.List; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -31,7 +32,6 @@ import org.springframework.boot.actuate.health.Health; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import org.springframework.util.concurrent.ListenableFuture; /** * Default implementation of {@link org.springframework.boot.actuate.health.HealthIndicator} for @@ -117,7 +117,7 @@ private void doHealthCheck(Runnable up, Consumer down, Consumer> future = + CompletableFuture> future = pubSubTemplate.pullAsync(this.subscription, 1, true); List messages = future.get(timeoutMillis, TimeUnit.MILLISECONDS); if (this.acknowledgeMessages) { @@ -131,8 +131,7 @@ boolean isHealthyException(ExecutionException e) { private boolean isHealthyResponseForUnspecifiedSubscription(ExecutionException e) { Throwable t = e.getCause(); - if (t instanceof ApiException) { - ApiException aex = (ApiException) t; + if (t instanceof ApiException aex) { Code errorCode = aex.getStatusCode().getCode(); return errorCode == StatusCode.Code.NOT_FOUND || errorCode == Code.PERMISSION_DENIED; } diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/health/PubSubHealthIndicatorAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/health/PubSubHealthIndicatorAutoConfiguration.java index 47bb339c90..31cd6b81af 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/health/PubSubHealthIndicatorAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/health/PubSubHealthIndicatorAutoConfiguration.java @@ -24,6 +24,7 @@ import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; import org.springframework.boot.actuate.health.HealthContributor; import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -31,7 +32,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.util.Assert; /** @@ -39,7 +39,7 @@ * * @since 1.2.2 */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @ConditionalOnClass({HealthIndicator.class, PubSubTemplate.class}) @ConditionalOnBean(PubSubTemplate.class) @ConditionalOnEnabledHealthIndicator("pubsub") diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/health/PubSubSubscriptionHealthIndicatorAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/health/PubSubSubscriptionHealthIndicatorAutoConfiguration.java index 6c3ec6b4eb..95f4243ccf 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/health/PubSubSubscriptionHealthIndicatorAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/pubsub/health/PubSubSubscriptionHealthIndicatorAutoConfiguration.java @@ -30,19 +30,19 @@ import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration; import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator; import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; /** * @since 2.0.6 */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @ConditionalOnClass({HealthIndicator.class, MetricServiceClient.class}) @ConditionalOnEnabledHealthIndicator("pubsub-subscriber") @ConditionalOnProperty({ diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/secretmanager/GcpSecretManagerBootstrapConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/secretmanager/GcpSecretManagerBootstrapConfiguration.java deleted file mode 100644 index 5bc23118c4..0000000000 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/secretmanager/GcpSecretManagerBootstrapConfiguration.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2017-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.cloud.spring.autoconfigure.secretmanager; - -import com.google.api.gax.core.CredentialsProvider; -import com.google.cloud.secretmanager.v1.SecretManagerServiceClient; -import com.google.cloud.secretmanager.v1.SecretManagerServiceSettings; -import com.google.cloud.spring.core.DefaultCredentialsProvider; -import com.google.cloud.spring.core.DefaultGcpProjectIdProvider; -import com.google.cloud.spring.core.GcpProjectIdProvider; -import com.google.cloud.spring.core.UserAgentHeaderProvider; -import com.google.cloud.spring.secretmanager.SecretManagerPropertySourceLocator; -import com.google.cloud.spring.secretmanager.SecretManagerTemplate; -import java.io.IOException; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.ConfigurableEnvironment; - -/** - * Bootstrap Autoconfiguration for GCP Secret Manager which enables loading secrets as properties - * into the application {@link org.springframework.core.env.Environment}. - * - * @since 1.2.2 - * @deprecated since external resources should be using Spring Boot's Config Data API, more info in - * here. - */ -@Deprecated -@Configuration(proxyBeanMethods = false) -@EnableConfigurationProperties(GcpSecretManagerProperties.class) -@ConditionalOnClass({SecretManagerServiceClient.class, SecretManagerTemplate.class}) -@ConditionalOnProperty(value = "spring.cloud.gcp.secretmanager.enabled", matchIfMissing = true) -public class GcpSecretManagerBootstrapConfiguration { - - private final GcpProjectIdProvider gcpProjectIdProvider; - private final GcpSecretManagerProperties properties; - - public GcpSecretManagerBootstrapConfiguration( - GcpSecretManagerProperties properties, ConfigurableEnvironment configurableEnvironment) { - - this.properties = properties; - this.gcpProjectIdProvider = - properties.getProjectId() != null - ? properties::getProjectId - : new DefaultGcpProjectIdProvider(); - } - - @Bean - @ConditionalOnMissingBean - public CredentialsProvider googleCredentials(GcpSecretManagerProperties secretManagerProperties) - throws IOException { - return new DefaultCredentialsProvider(secretManagerProperties); - } - - @Bean - @ConditionalOnMissingBean - public SecretManagerServiceClient secretManagerClient(CredentialsProvider googleCredentials) - throws IOException { - SecretManagerServiceSettings settings = - SecretManagerServiceSettings.newBuilder() - .setCredentialsProvider(googleCredentials) - .setHeaderProvider( - new UserAgentHeaderProvider(GcpSecretManagerBootstrapConfiguration.class)) - .build(); - - return SecretManagerServiceClient.create(settings); - } - - @Bean - @ConditionalOnMissingBean - public SecretManagerTemplate secretManagerTemplate(SecretManagerServiceClient client) { - return new SecretManagerTemplate(client, this.gcpProjectIdProvider) - .setAllowDefaultSecretValue(this.properties.isAllowDefaultSecret()); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(value = "spring.cloud.gcp.secretmanager.legacy", matchIfMissing = true) - public SecretManagerPropertySourceLocator secretManagerPropertySourceLocator( - SecretManagerTemplate secretManagerTemplate) { - return new SecretManagerPropertySourceLocator(secretManagerTemplate, this.gcpProjectIdProvider); - } -} diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/security/FirebaseAuthenticationAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/security/FirebaseAuthenticationAutoConfiguration.java index 46b2144295..da689572de 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/security/FirebaseAuthenticationAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/security/FirebaseAuthenticationAutoConfiguration.java @@ -22,6 +22,7 @@ import com.google.cloud.spring.security.firebase.FirebaseTokenValidator; import java.util.ArrayList; import java.util.List; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -30,7 +31,6 @@ import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidator; @@ -44,7 +44,7 @@ /** * @since 1.2.2 */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @ConditionalOnClass(FirebaseTokenValidator.class) @ConditionalOnProperty(value = "spring.cloud.gcp.security.firebase.enabled", matchIfMissing = true) @AutoConfigureBefore(OAuth2ResourceServerAutoConfiguration.class) diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/security/IapAuthenticationAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/security/IapAuthenticationAutoConfiguration.java index 08719bf134..fc8c7751b9 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/security/IapAuthenticationAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/security/IapAuthenticationAutoConfiguration.java @@ -28,6 +28,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -36,8 +37,6 @@ import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; @@ -67,15 +66,9 @@ * * * - *

If a custom {@link WebSecurityConfigurerAdapter} is present, it must add {@code - * .oauth2ResourceServer().jwt()} customization to {@link - * org.springframework.security.config.annotation.web.builders.HttpSecurity} object. If no custom - * {@link WebSecurityConfigurerAdapter} is found, Spring Boot's default {@code - * OAuth2ResourceServerWebSecurityConfiguration} will add this customization. - * * @since 1.1 */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @ConditionalOnProperty(value = "spring.cloud.gcp.security.iap.enabled", matchIfMissing = true) @ConditionalOnClass({AudienceValidator.class}) @AutoConfigureBefore(OAuth2ResourceServerAutoConfiguration.class) diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/spanner/GcpSpannerAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/spanner/GcpSpannerAutoConfiguration.java index f91748f455..eb2648b0dd 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/spanner/GcpSpannerAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/spanner/GcpSpannerAutoConfiguration.java @@ -44,18 +44,18 @@ import java.io.IOException; import java.util.Optional; import java.util.function.Supplier; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import org.springframework.data.rest.webmvc.spi.BackendIdConverter; /** Provides Spring Data classes to use with Cloud Spanner. */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @AutoConfigureAfter(GcpContextAutoConfiguration.class) @ConditionalOnProperty(value = "spring.cloud.gcp.spanner.enabled", matchIfMissing = true) @ConditionalOnClass({ diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/spanner/GcpSpannerEmulatorAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/spanner/GcpSpannerEmulatorAutoConfiguration.java index e27cbde2a1..ba554ab537 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/spanner/GcpSpannerEmulatorAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/spanner/GcpSpannerEmulatorAutoConfiguration.java @@ -23,12 +23,12 @@ import com.google.cloud.spring.core.GcpProjectIdProvider; import java.io.IOException; import java.util.Optional; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.util.Assert; /** @@ -36,7 +36,7 @@ * * @since 1.2.3 */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @AutoConfigureBefore({GcpSpannerAutoConfiguration.class, GcpContextAutoConfiguration.class}) @EnableConfigurationProperties(GcpSpannerProperties.class) @ConditionalOnProperty( diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/spanner/SpannerRepositoriesAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/spanner/SpannerRepositoriesAutoConfiguration.java index 9b302b4bad..c3a1fa9e43 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/spanner/SpannerRepositoriesAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/spanner/SpannerRepositoriesAutoConfiguration.java @@ -19,18 +19,18 @@ import com.google.cloud.spring.data.spanner.repository.SpannerRepository; import com.google.cloud.spring.data.spanner.repository.config.SpannerRepositoryConfigurationExtension; import com.google.cloud.spring.data.spanner.repository.support.SpannerRepositoryFactoryBean; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; /** * Enables autoconfiguration for {@link * com.google.cloud.spring.data.spanner.repository.config.EnableSpannerRepositories}. */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @ConditionalOnClass(SpannerRepository.class) @ConditionalOnMissingBean({ SpannerRepositoryFactoryBean.class, diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/spanner/SpannerTransactionManagerAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/spanner/SpannerTransactionManagerAutoConfiguration.java index a38e3a0b5b..380fb419da 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/spanner/SpannerTransactionManagerAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/spanner/SpannerTransactionManagerAutoConfiguration.java @@ -20,6 +20,7 @@ import com.google.cloud.spring.data.spanner.core.SpannerTransactionManager; import java.util.function.Supplier; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -27,7 +28,6 @@ import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.transaction.PlatformTransactionManager; /** @@ -35,14 +35,18 @@ * * @since 1.1 */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @ConditionalOnClass(SpannerTransactionManager.class) @ConditionalOnProperty(value = "spring.cloud.gcp.spanner.enabled", matchIfMissing = true) @AutoConfigureBefore(TransactionAutoConfiguration.class) public class SpannerTransactionManagerAutoConfiguration { + private SpannerTransactionManagerAutoConfiguration() { + + } + /** Config settings. */ - @Configuration(proxyBeanMethods = false) + @AutoConfiguration static class DatabaseClientTransactionManagerConfiguration { private final Supplier databaseClientProvider; diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/spanner/health/SpannerHealthIndicatorAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/spanner/health/SpannerHealthIndicatorAutoConfiguration.java index a4ab723539..f3e9df35d9 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/spanner/health/SpannerHealthIndicatorAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/spanner/health/SpannerHealthIndicatorAutoConfiguration.java @@ -24,6 +24,7 @@ import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; import org.springframework.boot.actuate.health.HealthContributor; import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -31,7 +32,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.util.Assert; /** @@ -39,7 +39,7 @@ * * @since 2.0.6 */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @ConditionalOnClass({SpannerTemplate.class, HealthIndicator.class}) @ConditionalOnBean(SpannerTemplate.class) @ConditionalOnEnabledHealthIndicator("spanner") diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/R2dbcCloudSqlEnvironmentPostProcessor.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/R2dbcCloudSqlEnvironmentPostProcessor.java index 4028e39ddf..45a293416b 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/R2dbcCloudSqlEnvironmentPostProcessor.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/sql/R2dbcCloudSqlEnvironmentPostProcessor.java @@ -84,10 +84,10 @@ String createUrl(DatabaseType databaseType, GcpCloudSqlProperties sqlProperties) } /** - * Returns {@link DatabaseType} constant based on whether mySQL or postgreSQL R2DBC driver and + * Returns {@link DatabaseType} constant based on whether postgresSQL R2DBC driver and * connector dependencies are present on the classpath. Returns null if Cloud SQL is not enabled * in Spring Cloud GCP, CredentialFactory is not present or ConnectionFactory (which is used to - * enable Spring R2DBC auto-configuration) is not present. + * enable Spring R2DBC autoconfiguration) is not present. * * @param environment environment to post-process * @return database type @@ -95,14 +95,10 @@ String createUrl(DatabaseType databaseType, GcpCloudSqlProperties sqlProperties) DatabaseType getEnabledDatabaseType(ConfigurableEnvironment environment) { if (isR2dbcEnabled(environment) && isOnClasspath("com.google.cloud.sql.CredentialFactory") - && isOnClasspath("io.r2dbc.spi.ConnectionFactory")) { - if (isOnClasspath("com.google.cloud.sql.core.GcpConnectionFactoryProviderMysql") - && isOnClasspath("dev.miku.r2dbc.mysql.MySqlConnectionFactoryProvider")) { - return DatabaseType.MYSQL; - } else if (isOnClasspath("com.google.cloud.sql.core.GcpConnectionFactoryProviderPostgres") - && isOnClasspath("io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider")) { - return DatabaseType.POSTGRESQL; - } + && isOnClasspath("io.r2dbc.spi.ConnectionFactory") + && isOnClasspath("com.google.cloud.sql.core.GcpConnectionFactoryProviderPostgres") + && isOnClasspath("io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider")) { + return DatabaseType.POSTGRESQL; } return null; } diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/storage/GcpStorageAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/storage/GcpStorageAutoConfiguration.java index b4df1f886c..7bd6f36fda 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/storage/GcpStorageAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/storage/GcpStorageAutoConfiguration.java @@ -26,12 +26,12 @@ import com.google.cloud.storage.Storage; import com.google.cloud.storage.StorageOptions; import java.io.IOException; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; /** @@ -41,7 +41,7 @@ * * @see GoogleStorageProtocolResolver */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @ConditionalOnClass({GoogleStorageProtocolResolverSettings.class, Storage.class}) @ConditionalOnProperty(value = "spring.cloud.gcp.storage.enabled", matchIfMissing = true) @EnableConfigurationProperties({GcpProperties.class, GcpStorageProperties.class}) diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/trace/StackdriverTraceAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/trace/StackdriverTraceAutoConfiguration.java index 12ba3f6f57..18ad5f892f 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/trace/StackdriverTraceAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/trace/StackdriverTraceAutoConfiguration.java @@ -19,15 +19,12 @@ import brave.TracingCustomizer; import brave.baggage.BaggagePropagation; import brave.handler.SpanHandler; -import brave.http.HttpRequestParser; -import brave.http.HttpTracingCustomizer; import brave.propagation.B3Propagation; import brave.propagation.Propagation; import brave.propagation.stackdriver.StackdriverTracePropagation; import com.google.api.gax.core.CredentialsProvider; import com.google.api.gax.core.ExecutorProvider; import com.google.api.gax.core.FixedExecutorProvider; -import com.google.cloud.spring.autoconfigure.trace.sleuth.StackdriverHttpRequestParser; import com.google.cloud.spring.core.DefaultCredentialsProvider; import com.google.cloud.spring.core.GcpProjectIdProvider; import com.google.cloud.spring.core.UserAgentHeaderProvider; @@ -42,15 +39,14 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.actuate.autoconfigure.tracing.BraveAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration; -import org.springframework.cloud.sleuth.autoconfig.brave.instrument.web.BraveHttpConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import zipkin2.CheckResult; import zipkin2.Span; @@ -64,10 +60,10 @@ import zipkin2.reporter.stackdriver.StackdriverSender.Builder; /** Config for Stackdriver Trace. */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @EnableConfigurationProperties({GcpTraceProperties.class}) @ConditionalOnProperty( - value = {"spring.sleuth.enabled", "spring.cloud.gcp.trace.enabled"}, + value = {"spring.cloud.gcp.trace.enabled"}, matchIfMissing = true) @ConditionalOnClass(StackdriverSender.class) @AutoConfigureBefore(BraveAutoConfiguration.class) @@ -98,11 +94,11 @@ public class StackdriverTraceAutoConfiguration { */ public static final String CUSTOMIZER_BEAN_NAME = "stackdriverTracingCustomizer"; - private GcpProjectIdProvider finalProjectIdProvider; + private final GcpProjectIdProvider finalProjectIdProvider; - private CredentialsProvider finalCredentialsProvider; + private final CredentialsProvider finalCredentialsProvider; - private UserAgentHeaderProvider headerProvider = new UserAgentHeaderProvider(this.getClass()); + private final UserAgentHeaderProvider headerProvider = new UserAgentHeaderProvider(this.getClass()); private ThreadPoolTaskScheduler defaultTraceSenderThreadPool; @@ -137,7 +133,7 @@ public SpanHandler stackdriverSpanHandler( @Bean @ConditionalOnMissingBean - ReporterMetrics sleuthReporterMetrics() { + ReporterMetrics reporterMetrics() { return ReporterMetrics.NOOP_METRICS; } @@ -260,34 +256,4 @@ public void closeScheduler() { this.defaultTraceSenderThreadPool.shutdown(); } } - - /** Configuration for Sleuth. */ - @Configuration(proxyBeanMethods = false) - @ConditionalOnProperty( - name = "spring.sleuth.http.enabled", - havingValue = "true", - matchIfMissing = true) - @AutoConfigureBefore(BraveHttpConfiguration.class) - public static class StackdriverTraceHttpAutoconfiguration { - @Bean - @ConditionalOnProperty( - name = "spring.sleuth.http.legacy.enabled", - havingValue = "false", - matchIfMissing = true) - @ConditionalOnMissingBean - HttpRequestParser stackdriverHttpRequestParser() { - return new StackdriverHttpRequestParser(); - } - - @Bean - @ConditionalOnProperty( - name = "spring.sleuth.http.legacy.enabled", - havingValue = "false", - matchIfMissing = true) - @ConditionalOnMissingBean - HttpTracingCustomizer stackdriverHttpTracingCustomizer( - HttpRequestParser stackdriverHttpRequestParser) { - return builder -> builder.clientRequestParser(stackdriverHttpRequestParser); - } - } } diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/trace/pubsub/TracePubSubAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/trace/pubsub/TracePubSubAutoConfiguration.java index 81b676ce53..a5b16aa97f 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/trace/pubsub/TracePubSubAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/trace/pubsub/TracePubSubAutoConfiguration.java @@ -22,27 +22,26 @@ import com.google.cloud.spring.autoconfigure.pubsub.GcpPubSubAutoConfiguration; import com.google.cloud.spring.pubsub.core.publisher.PublisherCustomizer; import com.google.cloud.spring.pubsub.support.PublisherFactory; +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.aop.ObservedAspect; import org.springframework.beans.factory.BeanFactory; +import org.springframework.boot.actuate.autoconfigure.tracing.BraveAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; 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.condition.ConditionalOnProperty; -import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration; -import org.springframework.cloud.sleuth.autoconfig.brave.instrument.messaging.BraveMessagingAutoConfiguration; -import org.springframework.cloud.sleuth.brave.instrument.messaging.ConditionalOnMessagingEnabled; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; -@Configuration(proxyBeanMethods = false) -@ConditionalOnMessagingEnabled +@AutoConfiguration @ConditionalOnBean(Tracing.class) -@ConditionalOnProperty(value = "spring.cloud.gcp.trace.pubsub.enabled", matchIfMissing = false) +@ConditionalOnProperty(value = "spring.cloud.gcp.trace.pubsub.enabled") @ConditionalOnClass({PublisherFactory.class, MessagingTracing.class}) -@AutoConfigureAfter({BraveAutoConfiguration.class, BraveMessagingAutoConfiguration.class}) +@AutoConfigureAfter({BraveAutoConfiguration.class}) @AutoConfigureBefore(GcpPubSubAutoConfiguration.class) class TracePubSubAutoConfiguration { @@ -63,8 +62,22 @@ PubSubTracing pubSubTracing(MessagingTracing messagingTracing) { PublisherCustomizer tracePublisherCustomizer(PubSubTracing pubSubTracing) { TraceHelper helper = new TraceHelper(pubSubTracing); - return (Publisher.Builder publisherBuilder, String topic) -> { - publisherBuilder.setTransform(msg -> helper.instrumentMessage(msg, topic)); - }; + return (Publisher.Builder publisherBuilder, String topic) -> + publisherBuilder.setTransform(msg -> helper.instrumentMessage(msg, topic)); + } + + @Bean + @ConditionalOnMissingBean + public MessagingTracing messagingTracing(Tracing tracing) { + return MessagingTracing.create(tracing); + } + + // To have the @Observed support we need to register this aspect + // Refers to https://spring.io/blog/2022/10/12/observability-with-spring-boot-3 + // for more info. + @Bean + @ConditionalOnMissingBean + ObservedAspect observedAspect(ObservationRegistry observationRegistry) { + return new ObservedAspect(observationRegistry); } } diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/trace/pubsub/TracingSubscriberFactory.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/trace/pubsub/TracingSubscriberFactory.java index 6179f28843..e30e526424 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/trace/pubsub/TracingSubscriberFactory.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/trace/pubsub/TracingSubscriberFactory.java @@ -49,11 +49,6 @@ public PullRequest createPullRequest( return delegate.createPullRequest(subscriptionName, maxMessages, returnImmediately); } - @Override - public SubscriberStub createSubscriberStub() { - return pubSubTracing.subscriberStub(delegate.createSubscriberStub()); - } - @Override public SubscriberStub createSubscriberStub(String subscriptionName) { return pubSubTracing.subscriberStub(delegate.createSubscriberStub(subscriptionName)); diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/trace/sleuth/StackdriverHttpRequestParser.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/trace/sleuth/StackdriverHttpRequestParser.java deleted file mode 100644 index 9b12f3ac3d..0000000000 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/trace/sleuth/StackdriverHttpRequestParser.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2017-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.cloud.spring.autoconfigure.trace.sleuth; - -import brave.SpanCustomizer; -import brave.http.HttpRequest; -import brave.http.HttpRequestParser; -import brave.http.HttpTags; -import brave.propagation.TraceContext; -import java.net.URI; - -/** - * An {@link HttpRequestParser} that fills information for Stackdriver Trace. - * - *

Based on {@code org.springframework.cloud.sleuth.instrument.web.SleuthHttpClientParser}. - */ -public class StackdriverHttpRequestParser implements HttpRequestParser { - - @Override - public void parse(HttpRequest request, TraceContext context, SpanCustomizer customizer) { - HttpRequestParser.DEFAULT.parse(request, context, customizer); - HttpTags.URL.tag(request, context, customizer); - HttpTags.ROUTE.tag(request, context, customizer); - - String url = request.url(); - URI uri = URI.create(url); - if (uri.getHost() != null) { - customizer.tag("http.host", uri.getHost()); - } - } -} diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/trace/sleuth/package-info.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/trace/sleuth/package-info.java deleted file mode 100644 index f03ab1791a..0000000000 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/trace/sleuth/package-info.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2018-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Auto-configuration for Spring Cloud GCP Trace module related to Sleuth and Stackdriver - * integration. - */ -package com.google.cloud.spring.autoconfigure.trace.sleuth; diff --git a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/vision/CloudVisionAutoConfiguration.java b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/vision/CloudVisionAutoConfiguration.java index c4f2f200b0..d280928533 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/vision/CloudVisionAutoConfiguration.java +++ b/spring-cloud-gcp-autoconfigure/src/main/java/com/google/cloud/spring/autoconfigure/vision/CloudVisionAutoConfiguration.java @@ -27,12 +27,12 @@ import java.io.IOException; import java.util.concurrent.Executor; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; /** @@ -40,7 +40,7 @@ * * @since 1.1 */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @EnableConfigurationProperties(CloudVisionProperties.class) @ConditionalOnClass(CloudVisionTemplate.class) @ConditionalOnProperty(value = "spring.cloud.gcp.vision.enabled", matchIfMissing = true) diff --git a/spring-cloud-gcp-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-cloud-gcp-autoconfigure/src/main/resources/META-INF/spring.factories index a3ff19996e..170b2869c6 100644 --- a/spring-cloud-gcp-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-gcp-autoconfigure/src/main/resources/META-INF/spring.factories @@ -1,37 +1,5 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -com.google.cloud.spring.autoconfigure.pubsub.GcpPubSubEmulatorAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.core.GcpContextAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.logging.StackdriverLoggingAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.pubsub.GcpPubSubAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.pubsub.GcpPubSubReactiveAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.spanner.GcpSpannerAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.spanner.GcpSpannerEmulatorAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.spanner.health.SpannerHealthIndicatorAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.spanner.SpannerTransactionManagerAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.datastore.GcpDatastoreAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.firestore.GcpFirestoreAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.firestore.FirestoreTransactionManagerAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.firestore.GcpFirestoreEmulatorAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.datastore.health.DatastoreHealthIndicatorAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.storage.GcpStorageAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.trace.StackdriverTraceAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.trace.pubsub.TracePubSubAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.datastore.DatastoreRepositoriesAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.spanner.SpannerRepositoriesAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.security.IapAuthenticationAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.security.FirebaseAuthenticationAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.vision.CloudVisionAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.datastore.GcpDatastoreEmulatorAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.bigquery.GcpBigQueryAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.datastore.DatastoreTransactionManagerAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.firestore.FirestoreRepositoriesAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.pubsub.health.PubSubSubscriptionHealthIndicatorAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.pubsub.health.PubSubHealthIndicatorAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.metrics.GcpStackdriverMetricsAutoConfiguration,\ -com.google.cloud.spring.autoconfigure.kms.GcpKmsAutoConfiguration org.springframework.cloud.bootstrap.BootstrapConfiguration=\ -com.google.cloud.spring.autoconfigure.config.GcpConfigBootstrapConfiguration,\ -com.google.cloud.spring.autoconfigure.secretmanager.GcpSecretManagerBootstrapConfiguration +com.google.cloud.spring.autoconfigure.config.GcpConfigBootstrapConfiguration org.springframework.boot.env.EnvironmentPostProcessor=\ com.google.cloud.spring.autoconfigure.sql.CloudSqlEnvironmentPostProcessor,\ com.google.cloud.spring.autoconfigure.sql.R2dbcCloudSqlEnvironmentPostProcessor,\ diff --git a/spring-cloud-gcp-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-cloud-gcp-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..99c6d6dffd --- /dev/null +++ b/spring-cloud-gcp-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,30 @@ +com.google.cloud.spring.autoconfigure.bigquery.GcpBigQueryAutoConfiguration +com.google.cloud.spring.autoconfigure.core.GcpContextAutoConfiguration +com.google.cloud.spring.autoconfigure.datastore.DatastoreRepositoriesAutoConfiguration +com.google.cloud.spring.autoconfigure.datastore.GcpDatastoreAutoConfiguration +com.google.cloud.spring.autoconfigure.datastore.GcpDatastoreEmulatorAutoConfiguration +com.google.cloud.spring.autoconfigure.datastore.DatastoreTransactionManagerAutoConfiguration +com.google.cloud.spring.autoconfigure.datastore.health.DatastoreHealthIndicatorAutoConfiguration +com.google.cloud.spring.autoconfigure.firestore.FirestoreTransactionManagerAutoConfiguration +com.google.cloud.spring.autoconfigure.firestore.GcpFirestoreAutoConfiguration +com.google.cloud.spring.autoconfigure.firestore.GcpFirestoreEmulatorAutoConfiguration +com.google.cloud.spring.autoconfigure.firestore.FirestoreRepositoriesAutoConfiguration +com.google.cloud.spring.autoconfigure.kms.GcpKmsAutoConfiguration +com.google.cloud.spring.autoconfigure.metrics.GcpStackdriverMetricsAutoConfiguration +com.google.cloud.spring.autoconfigure.pubsub.GcpPubSubAutoConfiguration +com.google.cloud.spring.autoconfigure.pubsub.GcpPubSubEmulatorAutoConfiguration +com.google.cloud.spring.autoconfigure.pubsub.GcpPubSubReactiveAutoConfiguration +com.google.cloud.spring.autoconfigure.pubsub.health.PubSubHealthIndicatorAutoConfiguration +com.google.cloud.spring.autoconfigure.pubsub.health.PubSubSubscriptionHealthIndicatorAutoConfiguration +com.google.cloud.spring.autoconfigure.logging.StackdriverLoggingAutoConfiguration +com.google.cloud.spring.autoconfigure.security.FirebaseAuthenticationAutoConfiguration +com.google.cloud.spring.autoconfigure.security.IapAuthenticationAutoConfiguration +com.google.cloud.spring.autoconfigure.spanner.GcpSpannerAutoConfiguration +com.google.cloud.spring.autoconfigure.spanner.GcpSpannerEmulatorAutoConfiguration +com.google.cloud.spring.autoconfigure.spanner.SpannerRepositoriesAutoConfiguration +com.google.cloud.spring.autoconfigure.spanner.SpannerTransactionManagerAutoConfiguration +com.google.cloud.spring.autoconfigure.spanner.health.SpannerHealthIndicatorAutoConfiguration +com.google.cloud.spring.autoconfigure.storage.GcpStorageAutoConfiguration +com.google.cloud.spring.autoconfigure.trace.StackdriverTraceAutoConfiguration +com.google.cloud.spring.autoconfigure.trace.pubsub.TracePubSubAutoConfiguration +com.google.cloud.spring.autoconfigure.vision.CloudVisionAutoConfiguration diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/datastore/TestRepository.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/datastore/TestRepository.java index 0607e35984..ac873e3515 100644 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/datastore/TestRepository.java +++ b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/datastore/TestRepository.java @@ -19,4 +19,4 @@ import com.google.cloud.spring.data.datastore.repository.DatastoreRepository; /** A repository for testing instantiation. */ -public interface TestRepository extends DatastoreRepository {} +public interface TestRepository extends DatastoreRepository {} diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/logging/StackdriverLoggingAutoConfigurationTests.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/logging/StackdriverLoggingAutoConfigurationTests.java index 3cd6785b76..f23921b224 100644 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/logging/StackdriverLoggingAutoConfigurationTests.java +++ b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/logging/StackdriverLoggingAutoConfigurationTests.java @@ -24,24 +24,28 @@ import com.google.cloud.spring.autoconfigure.core.GcpContextAutoConfiguration; import com.google.cloud.spring.autoconfigure.trace.StackdriverTraceAutoConfiguration; import com.google.cloud.spring.logging.TraceIdLoggingWebMvcInterceptor; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; -import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration; import org.springframework.context.annotation.Bean; import zipkin2.reporter.Reporter; /** Tests for auto-config. */ class StackdriverLoggingAutoConfigurationTests { - private WebApplicationContextRunner contextRunner = - new WebApplicationContextRunner() - .withUserConfiguration(TestConfiguration.class) - .withConfiguration( - AutoConfigurations.of( - StackdriverLoggingAutoConfiguration.class, GcpContextAutoConfiguration.class)); + private WebApplicationContextRunner contextRunner; + + @BeforeEach + void init() { + contextRunner = new WebApplicationContextRunner() + .withUserConfiguration(TestConfiguration.class) + .withConfiguration( + AutoConfigurations.of( + StackdriverLoggingAutoConfiguration.class, GcpContextAutoConfiguration.class)); + } @Test void testDisabledConfiguration() { @@ -87,11 +91,9 @@ void testRegularConfiguration() { } @Test - void testWithSleuth() { + void testWithStackdriverTraceAutoConfiguration() { this.contextRunner - .withConfiguration( - AutoConfigurations.of( - StackdriverTraceAutoConfiguration.class, BraveAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(StackdriverTraceAutoConfiguration.class)) .withPropertyValues("spring.cloud.gcp.project-id=pop-1") .run( context -> diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/GcpPubSubAutoConfigurationTests.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/GcpPubSubAutoConfigurationTests.java index 61440be6e1..3f18d1e72c 100644 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/GcpPubSubAutoConfigurationTests.java +++ b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/GcpPubSubAutoConfigurationTests.java @@ -38,6 +38,7 @@ import com.google.cloud.spring.pubsub.support.CachingPublisherFactory; import com.google.cloud.spring.pubsub.support.DefaultPublisherFactory; import com.google.cloud.spring.pubsub.support.DefaultSubscriberFactory; +import com.google.cloud.spring.pubsub.support.PubSubSubscriptionUtils; import com.google.cloud.spring.pubsub.support.PublisherFactory; import com.google.pubsub.v1.ProjectSubscriptionName; import java.util.List; @@ -774,7 +775,7 @@ void retrySettings_globalConfigurationSet() { GcpProjectIdProvider projectIdProvider = ctx.getBean(GcpProjectIdProvider.class); PubSubConfiguration.Retry retrySettings = gcpPubSubProperties.computeSubscriberRetrySettings( - "subscription-name", projectIdProvider.getProjectId()); + ProjectSubscriptionName.of(projectIdProvider.getProjectId(), "subscription-name")); assertThat(retrySettings.getTotalTimeoutSeconds()).isEqualTo(1L); assertThat(retrySettings.getInitialRetryDelaySeconds()).isEqualTo(2L); assertThat(retrySettings.getRetryDelayMultiplier()).isEqualTo(3); @@ -824,8 +825,7 @@ void retrySettings_selectiveConfigurationSet() { GcpProjectIdProvider projectIdProvider = ctx.getBean(GcpProjectIdProvider.class); PubSubConfiguration.Retry retrySettings = gcpPubSubProperties.computeSubscriberRetrySettings( - "subscription-name", projectIdProvider.getProjectId()); - + ProjectSubscriptionName.of(projectIdProvider.getProjectId(), "subscription-name")); assertThat(retrySettings.getTotalTimeoutSeconds()).isEqualTo(1L); assertThat(retrySettings.getInitialRetryDelaySeconds()).isEqualTo(2L); assertThat(retrySettings.getRetryDelayMultiplier()).isEqualTo(3); @@ -891,7 +891,7 @@ void retrySettings_globalAndSelectiveConfigurationSet_selectiveTakesPrecedence() // property set PubSubConfiguration.Retry retrySettings = gcpPubSubProperties.computeSubscriberRetrySettings( - "subscription-name", projectIdProvider.getProjectId()); + ProjectSubscriptionName.of(projectIdProvider.getProjectId(), "subscription-name")); assertThat(gcpPubSubProperties.getFullyQualifiedSubscriberProperties()) .containsKey(ProjectSubscriptionName.parse( "projects/fake project/subscriptions/subscription-name")); @@ -909,7 +909,8 @@ void retrySettings_globalAndSelectiveConfigurationSet_selectiveTakesPrecedence() // property set PubSubConfiguration.Retry retrySettingsForOtherSubscriber = gcpPubSubProperties - .getSubscriber("other", projectIdProvider.getProjectId()) + .getSubscriptionProperties(PubSubSubscriptionUtils + .toProjectSubscriptionName("other", projectIdProvider.getProjectId())) .getRetry(); assertThat(retrySettingsForOtherSubscriber.getTotalTimeoutSeconds()).isEqualTo(10L); assertThat(retrySettingsForOtherSubscriber.getInitialRetryDelaySeconds()).isEqualTo( @@ -985,8 +986,7 @@ void retrySettings_globalAndDifferentSelectiveConfigurationSet_pickGlobal() { PubSubConfiguration.Retry retrySettings = gcpPubSubProperties.computeSubscriberRetrySettings( - "subscription-name", projectIdProvider.getProjectId()); - + ProjectSubscriptionName.of(projectIdProvider.getProjectId(), "subscription-name")); assertThat(retrySettings.getTotalTimeoutSeconds()).isEqualTo(10L); assertThat(retrySettings.getInitialRetryDelaySeconds()).isEqualTo(10L); assertThat(retrySettings.getRetryDelayMultiplier()).isEqualTo(10); @@ -1035,7 +1035,7 @@ void retrySettings_subsetOfProperties_pickGlobalWhenSelectiveNotSpecified() { GcpProjectIdProvider projectIdProvider = ctx.getBean(GcpProjectIdProvider.class); PubSubConfiguration.Retry retry = gcpPubSubProperties.computeSubscriberRetrySettings( - "subscription-name", projectIdProvider.getProjectId()); + ProjectSubscriptionName.of(projectIdProvider.getProjectId(), "subscription-name")); assertThat(retry.getTotalTimeoutSeconds()).isEqualTo(10L); assertThat(retry.getInitialRetryDelaySeconds()).isEqualTo(2L); assertThat(retry.getRetryDelayMultiplier()).isEqualTo(3); @@ -1140,7 +1140,9 @@ void flowControlSettings_selectiveConfigurationSet() { PubSubConfiguration.FlowControl flowControl = gcpPubSubProperties - .getSubscriber("subscription-name", projectIdProvider.getProjectId()) + .getSubscriptionProperties( + PubSubSubscriptionUtils + .toProjectSubscriptionName("subscription-name", projectIdProvider.getProjectId())) .getFlowControl(); assertThat(flowControl.getMaxOutstandingElementCount()).isEqualTo(11L); assertThat(flowControl.getMaxOutstandingRequestBytes()).isEqualTo(12L); diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/GcpPubSubReactiveAutoConfigurationTest.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/GcpPubSubReactiveAutoConfigurationTest.java index f1d7e93d20..52f9802b62 100644 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/GcpPubSubReactiveAutoConfigurationTest.java +++ b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/GcpPubSubReactiveAutoConfigurationTest.java @@ -28,6 +28,7 @@ import com.google.cloud.spring.pubsub.reactive.PubSubReactiveFactory; import com.google.cloud.spring.pubsub.support.AcknowledgeablePubsubMessage; import java.util.Arrays; +import java.util.concurrent.CompletableFuture; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -41,7 +42,6 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.AsyncResult; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; import reactor.test.StepVerifier; @@ -130,7 +130,7 @@ private void setUpThreadPrefixVerification(String threadPrefix) { arg -> { assertThat(Thread.currentThread().getName()).startsWith(threadPrefix); - return AsyncResult.forValue(Arrays.asList(mockMessage, mockMessage, mockMessage)); + return CompletableFuture.completedFuture(Arrays.asList(mockMessage, mockMessage, mockMessage)); }); } diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/health/PubSubHealthIndicatorAutoConfigurationTests.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/health/PubSubHealthIndicatorAutoConfigurationTests.java index 2616428fa1..97504d05f5 100644 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/health/PubSubHealthIndicatorAutoConfigurationTests.java +++ b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/health/PubSubHealthIndicatorAutoConfigurationTests.java @@ -17,21 +17,16 @@ package com.google.cloud.spring.autoconfigure.pubsub.health; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.google.api.gax.core.CredentialsProvider; -import com.google.api.gax.grpc.GrpcStatusCode; -import com.google.api.gax.rpc.ApiException; import com.google.auth.Credentials; import com.google.cloud.spring.autoconfigure.pubsub.GcpPubSubAutoConfiguration; import com.google.cloud.spring.core.GcpProjectIdProvider; @@ -39,17 +34,14 @@ import com.google.cloud.spring.pubsub.support.AcknowledgeablePubsubMessage; import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.CompletableFuture; import java.util.regex.Pattern; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.BeanInitializationException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.actuate.health.CompositeHealthContributor; +import org.springframework.boot.actuate.health.NamedContributor; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.util.concurrent.ListenableFuture; /** Tests for Pub/Sub Health Indicator autoconfiguration. */ class PubSubHealthIndicatorAutoConfigurationTests { @@ -57,7 +49,7 @@ class PubSubHealthIndicatorAutoConfigurationTests { private static final Pattern UUID_PATTERN = Pattern.compile("spring-cloud-gcp-healthcheck-[a-f0-9]{8}(-[a-f0-9]{4}){4}[a-f0-9]{8}"); - private ApplicationContextRunner baseContextRunner = + private final ApplicationContextRunner baseContextRunner = new ApplicationContextRunner() .withConfiguration( AutoConfigurations.of( @@ -111,7 +103,7 @@ void healthIndicatorPresent_customConfig() { void compositeHealthIndicatorPresentMultiplePubSubTemplate() throws Exception { PubSubTemplate mockPubSubTemplate1 = mock(PubSubTemplate.class); PubSubTemplate mockPubSubTemplate2 = mock(PubSubTemplate.class); - ListenableFuture> future = mock(ListenableFuture.class); + CompletableFuture> future = mock(CompletableFuture.class); when(future.get(anyLong(), any())).thenReturn(Collections.emptyList()); when(mockPubSubTemplate1.pullAsync(anyString(), anyInt(), anyBoolean())).thenReturn(future); @@ -134,7 +126,7 @@ void compositeHealthIndicatorPresentMultiplePubSubTemplate() throws Exception { ctx.getBean("pubSubHealthContributor", CompositeHealthContributor.class); assertThat(healthContributor).isNotNull(); assertThat(healthContributor.stream()).hasSize(2); - assertThat(healthContributor.stream().map(c -> c.getName())) + assertThat(healthContributor.stream().map(NamedContributor::getName)) .containsExactlyInAnyOrder("pubSubTemplate1", "pubSubTemplate2"); }); } diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/health/PubSubHealthIndicatorTests.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/health/PubSubHealthIndicatorTests.java index f6b6ce238d..24ee9821cb 100644 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/health/PubSubHealthIndicatorTests.java +++ b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/health/PubSubHealthIndicatorTests.java @@ -17,7 +17,6 @@ package com.google.cloud.spring.autoconfigure.pubsub.health; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -34,9 +33,9 @@ import com.google.cloud.spring.pubsub.core.PubSubTemplate; import com.google.cloud.spring.pubsub.support.AcknowledgeablePubsubMessage; import io.grpc.Status.Code; -import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; import org.junit.jupiter.api.Test; @@ -45,9 +44,7 @@ import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.beans.factory.BeanInitializationException; import org.springframework.boot.actuate.health.Status; -import org.springframework.util.concurrent.ListenableFuture; /** Tests for the Pub/Sub Health Indicator. */ @ExtendWith(MockitoExtension.class) @@ -55,7 +52,8 @@ class PubSubHealthIndicatorTests { @Mock private PubSubTemplate pubSubTemplate; - @Mock ListenableFuture> future; + @Mock + CompletableFuture> future; @Test void healthUp_customSubscription() throws Exception { @@ -70,7 +68,7 @@ void healthUp_customSubscription() throws Exception { @Test void acknowledgeEnabled() throws Exception { AcknowledgeablePubsubMessage msg = mock(AcknowledgeablePubsubMessage.class); - when(future.get(anyLong(), any())).thenReturn(Arrays.asList(msg)); + when(future.get(anyLong(), any())).thenReturn(List.of(msg)); when(pubSubTemplate.pullAsync(anyString(), anyInt(), anyBoolean())).thenReturn(future); PubSubHealthIndicator healthIndicator = @@ -82,7 +80,7 @@ void acknowledgeEnabled() throws Exception { @Test void acknowledgeDisabled() throws Exception { AcknowledgeablePubsubMessage msg = mock(AcknowledgeablePubsubMessage.class); - when(future.get(anyLong(), any())).thenReturn(Arrays.asList(msg)); + when(future.get(anyLong(), any())).thenReturn(List.of(msg)); when(pubSubTemplate.pullAsync(anyString(), anyInt(), anyBoolean())).thenReturn(future); PubSubHealthIndicator healthIndicator = diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/it/PubSubAutoConfigurationIntegrationTests.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/it/PubSubAutoConfigurationIntegrationTests.java index c070fc82c0..017d518a47 100644 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/it/PubSubAutoConfigurationIntegrationTests.java +++ b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/it/PubSubAutoConfigurationIntegrationTests.java @@ -50,10 +50,10 @@ class PubSubAutoConfigurationIntegrationTests { private static GcpProjectIdProvider projectIdProvider; - private String fullSubscriptionNameSub1 = "projects/" + projectIdProvider.getProjectId() + "/subscriptions/test-sub-1"; - private String fullSubscriptionNameSub2 = "projects/" + projectIdProvider.getProjectId() + "/subscriptions/test-sub-2"; + private final String fullSubscriptionNameSub1 = "projects/" + projectIdProvider.getProjectId() + "/subscriptions/test-sub-1"; + private final String fullSubscriptionNameSub2 = "projects/" + projectIdProvider.getProjectId() + "/subscriptions/test-sub-2"; - private ApplicationContextRunner contextRunner = + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues( "spring.cloud.gcp.pubsub.subscriber.retryableCodes=INTERNAL", @@ -125,7 +125,7 @@ void testPull() { .build(); PubSubConfiguration.Retry retry = gcpPubSubProperties.computeSubscriberRetrySettings( - subscriptionName, projectId); + ProjectSubscriptionName.of(projectId, subscriptionName)); assertThat(retry.getTotalTimeoutSeconds()).isEqualTo(600L); assertThat(retry.getInitialRetryDelaySeconds()).isEqualTo(100L); assertThat(retry.getRetryDelayMultiplier()).isEqualTo(1.3); diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/it/PubSubChannelAdaptersIntegrationTests.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/it/PubSubChannelAdaptersIntegrationTests.java index 7456333e24..05c0591af0 100644 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/it/PubSubChannelAdaptersIntegrationTests.java +++ b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/it/PubSubChannelAdaptersIntegrationTests.java @@ -18,7 +18,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.times; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import com.google.cloud.spring.autoconfigure.core.GcpContextAutoConfiguration; @@ -31,6 +32,7 @@ import com.google.cloud.spring.pubsub.integration.AckMode; import com.google.cloud.spring.pubsub.integration.inbound.PubSubInboundChannelAdapter; import com.google.cloud.spring.pubsub.integration.outbound.PubSubMessageHandler; +import com.google.cloud.spring.pubsub.integration.outbound.PubSubMessageHandler.SuccessCallback; import com.google.cloud.spring.pubsub.support.BasicAcknowledgeablePubsubMessage; import com.google.cloud.spring.pubsub.support.GcpPubSubHeaders; import java.io.IOException; @@ -45,7 +47,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfSystemProperty; -import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -61,7 +62,6 @@ import org.springframework.messaging.PollableChannel; import org.springframework.messaging.SubscribableChannel; import org.springframework.messaging.support.MessageBuilder; -import org.springframework.util.concurrent.ListenableFutureCallback; /** Tests for Pub/Sub channel adapters. */ @EnabledIfSystemProperty(named = "it.pubsub", matches = "true") @@ -82,7 +82,7 @@ static void enableTests() throws IOException { pubSubAdmin = new PubSubAdmin( new DefaultGcpProjectIdProvider(), - new DefaultCredentialsProvider(() -> new Credentials())); + new DefaultCredentialsProvider(Credentials::new)); } @BeforeEach @@ -120,7 +120,7 @@ void sendAndReceiveMessageAsString() { .run( context -> { Map headers = new HashMap<>(); - // Only String values for now.. + // Only String values for now. headers.put("storm", "lift your skinny fists"); headers.put("static", "lift your skinny fists"); headers.put("sleep", "lift your skinny fists"); @@ -141,9 +141,9 @@ void sendAndReceiveMessageAsString() { assertThat(payload).isEqualTo("I am a message (sendAndReceiveMessageAsString)."); assertThat(message.getHeaders()).hasSize(6); - assertThat(message.getHeaders().get("storm")).isEqualTo("lift your skinny fists"); - assertThat(message.getHeaders().get("static")).isEqualTo("lift your skinny fists"); - assertThat(message.getHeaders().get("sleep")).isEqualTo("lift your skinny fists"); + assertThat(message.getHeaders()).containsEntry("storm", "lift your skinny fists"); + assertThat(message.getHeaders()).containsEntry("static", "lift your skinny fists"); + assertThat(message.getHeaders()).containsEntry("sleep", "lift your skinny fists"); assertThat(message.getHeaders().get(GcpPubSubHeaders.ORIGINAL_MESSAGE)).isNotNull(); }); } @@ -259,7 +259,6 @@ void sendAndReceiveMessageAutoAckWithFailure() { } @Test - @SuppressWarnings("deprecation") void sendAndReceiveMessageManualAckThroughAcknowledgementHeader() { this.contextRunner .withUserConfiguration(PollableConfiguration.class, CommonConfiguration.class) @@ -295,16 +294,8 @@ void sendAndReceiveMessagePublishCallback() { .withUserConfiguration(PollableConfiguration.class, CommonConfiguration.class) .run( context -> { - ListenableFutureCallback callbackSpy = - Mockito.spy( - new ListenableFutureCallback() { - @Override - public void onFailure(Throwable ex) {} - - @Override - public void onSuccess(String result) {} - }); - context.getBean(PubSubMessageHandler.class).setPublishCallback(callbackSpy); + SuccessCallback successCallback = mock(SuccessCallback.class); + context.getBean(PubSubMessageHandler.class).setSuccessCallback(successCallback); context .getBean("inputChannel", MessageChannel.class) .send( @@ -317,9 +308,11 @@ public void onSuccess(String result) {} .getBean("outputChannel", PollableChannel.class) .receive(RECEIVE_TIMEOUT_MS); assertThat(message).isNotNull(); + Awaitility.await() .atMost(1, TimeUnit.SECONDS) - .untilAsserted(() -> verify(callbackSpy, times(1)).onSuccess(any())); + .untilAsserted(() -> + verify(successCallback).onSuccess(anyString(), any())); }); } diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/it/PubSubTemplateDocumentationIntegrationTests.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/it/PubSubTemplateDocumentationIntegrationTests.java index 0a46535ea4..165331ab7b 100644 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/it/PubSubTemplateDocumentationIntegrationTests.java +++ b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/it/PubSubTemplateDocumentationIntegrationTests.java @@ -43,7 +43,6 @@ import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; import org.awaitility.Awaitility; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfSystemProperty; @@ -53,13 +52,12 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.support.GenericMessage; -import org.springframework.util.concurrent.ListenableFutureCallback; /** Documentation tests for Pub/Sub. */ @EnabledIfSystemProperty(named = "it.pubsub-docs", matches = "true") class PubSubTemplateDocumentationIntegrationTests { - private ApplicationContextRunner contextRunner = + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues( "spring.cloud.gcp.pubsub.subscriber.max-ack-extension-period=0", @@ -179,12 +177,12 @@ private void pubSubTest(PubSubTest pubSubTest, Class... configClass) { List subscriptions = pubSubAdmin.listSubscriptions().stream() .map(Subscription::getName) - .collect(Collectors.toList()); + .toList(); // end::list_subscriptions[] // tag::list_topics[] List topics = - pubSubAdmin.listTopics().stream().map(Topic::getName).collect(Collectors.toList()); + pubSubAdmin.listTopics().stream().map(Topic::getName).toList(); // end::list_topics[] pubSubAdmin.deleteSubscription(subscriptionName); @@ -372,14 +370,8 @@ public PubSubMessageHandler pubSubMessageHandler(PubSubTemplate pubSubTemplate) // end::message_router[] // tag::adapter_callback[] - adapter.setPublishCallback( - new ListenableFutureCallback() { - @Override - public void onFailure(Throwable ex) {} - - @Override - public void onSuccess(String result) {} - }); + adapter.setSuccessCallback((ackId, message) -> {}); + adapter.setFailureCallback((cause, message) -> {}); // end::adapter_callback[] return adapter; @@ -421,7 +413,7 @@ void run( throws ExecutionException, InterruptedException; } - class Logger { + static class Logger { List messages = new ArrayList<>(); void info(String message) { diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/it/PubSubTemplateIntegrationTests.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/it/PubSubTemplateIntegrationTests.java index 141ce1c784..91cf6d73db 100644 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/it/PubSubTemplateIntegrationTests.java +++ b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/pubsub/it/PubSubTemplateIntegrationTests.java @@ -50,7 +50,6 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.awaitility.Awaitility; @@ -181,10 +180,10 @@ void testPullAndAck() { messagesSet.addAll( newMessages.stream() .map(message -> message.getPubsubMessage().getData().toStringUtf8()) - .collect(Collectors.toList())); + .toList()); } - assertThat(messagesSet.size()).as("check that we received all the messages").isEqualTo(3); + assertThat(messagesSet).as("check that we received all the messages").hasSize(3); ackableMessages.forEach( message -> { diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/secretmanager/SecretManagerBootstrapConfigurationTests.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/secretmanager/SecretManagerBootstrapConfigurationTests.java deleted file mode 100644 index bebf08db07..0000000000 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/secretmanager/SecretManagerBootstrapConfigurationTests.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright 2020-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.cloud.spring.autoconfigure.secretmanager; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import com.google.api.gax.core.CredentialsProvider; -import com.google.api.gax.rpc.NotFoundException; -import com.google.auth.Credentials; -import com.google.cloud.secretmanager.v1.AccessSecretVersionResponse; -import com.google.cloud.secretmanager.v1.SecretManagerServiceClient; -import com.google.cloud.secretmanager.v1.SecretPayload; -import com.google.cloud.secretmanager.v1.SecretVersionName; -import com.google.protobuf.ByteString; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.WebApplicationType; -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.cloud.bootstrap.config.PropertySourceLocator; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** Unit tests for {@link GcpSecretManagerBootstrapConfiguration}. */ -class SecretManagerBootstrapConfigurationTests { - - private static final String PROJECT_NAME = "hollow-light-of-the-sealed-land"; - - private SpringApplicationBuilder applicationBuilder = - new SpringApplicationBuilder( - TestBootstrapConfiguration.class, GcpSecretManagerBootstrapConfiguration.class) - .properties( - "spring.cloud.gcp.secretmanager.project-id=" + PROJECT_NAME, - "spring.cloud.bootstrap.enabled=true", - "spring.cloud.gcp.sql.enabled=false") - .web(WebApplicationType.NONE); - - @Test - void testGetProperty() { - try (ConfigurableApplicationContext c = applicationBuilder.run()) { - String secret = c.getEnvironment().getProperty("sm://my-secret"); - assertThat(secret).isEqualTo("hello"); - } - } - - @Test - void testGetProperty_otherVersion() { - try (ConfigurableApplicationContext c = applicationBuilder.run()) { - String secret = c.getEnvironment().getProperty("sm://my-secret/1"); - assertThat(secret).isEqualTo("hello v1"); - } - } - - @Test - void testGetProperty_otherProject() { - try (ConfigurableApplicationContext c = applicationBuilder.run()) { - String secret = - c.getEnvironment() - .getProperty("sm://projects/other-project/secrets/other-secret/versions/3"); - assertThat(secret).isEqualTo("goodbye"); - } - } - - @Test - void testValueAnnotation() { - try (ConfigurableApplicationContext c = applicationBuilder.run()) { - String secret = c.getBean("secret", String.class); - assertThat(secret).isEqualTo("hello"); - - secret = c.getBean("fullyQualifiedSecret", String.class); - assertThat(secret).isEqualTo("hello"); - } - } - - @Test - void configurationDisabled() { - SpringApplicationBuilder disabledConfigurationApp = - new SpringApplicationBuilder( - TestBootstrapConfiguration.class, GcpSecretManagerBootstrapConfiguration.class) - .properties("spring.cloud.gcp.secretmanager.project-id=" + PROJECT_NAME) - .properties("spring.cloud.gcp.secretmanager.enabled=false") - .properties("spring.cloud.gcp.sql.enabled=false") - .web(WebApplicationType.NONE); - - try (ConfigurableApplicationContext c = disabledConfigurationApp.run()) { - String secret = c.getEnvironment().getProperty("sm://my-secret"); - assertThat(secret).isNull(); - } - } - - @Configuration - static class TestBootstrapConfiguration { - - @Value("${sm://my-secret}") - private String secret; - - @Value("${sm://" + PROJECT_NAME + "/my-secret/latest}") - private String fullyQualifiedSecret; - - @Bean - public String secret() { - return secret; - } - - @Bean - public String fullyQualifiedSecret() { - return secret; - } - - @Bean - public static SecretManagerServiceClient secretManagerClient() { - SecretManagerServiceClient client = mock(SecretManagerServiceClient.class); - - SecretVersionName secretVersionName = - SecretVersionName.newBuilder() - .setProject(PROJECT_NAME) - .setSecret("my-secret") - .setSecretVersion("latest") - .build(); - - when(client.accessSecretVersion(secretVersionName)) - .thenReturn( - AccessSecretVersionResponse.newBuilder() - .setPayload(SecretPayload.newBuilder().setData(ByteString.copyFromUtf8("hello"))) - .build()); - - secretVersionName = - SecretVersionName.newBuilder() - .setProject(PROJECT_NAME) - .setSecret("fake-secret") - .setSecretVersion("latest") - .build(); - - when(client.accessSecretVersion(secretVersionName)) - .thenThrow(NotFoundException.class); - - secretVersionName = - SecretVersionName.newBuilder() - .setProject(PROJECT_NAME) - .setSecret("my-secret") - .setSecretVersion("1") - .build(); - - when(client.accessSecretVersion(secretVersionName)) - .thenReturn( - AccessSecretVersionResponse.newBuilder() - .setPayload( - SecretPayload.newBuilder().setData(ByteString.copyFromUtf8("hello v1"))) - .build()); - - secretVersionName = - SecretVersionName.newBuilder() - .setProject("other-project") - .setSecret("other-secret") - .setSecretVersion("3") - .build(); - - when(client.accessSecretVersion(secretVersionName)) - .thenReturn( - AccessSecretVersionResponse.newBuilder() - .setPayload( - SecretPayload.newBuilder().setData(ByteString.copyFromUtf8("goodbye"))) - .build()); - - return client; - } - - @Bean - public static CredentialsProvider googleCredentials() { - return () -> mock(Credentials.class); - } - - // This is added in here to verify that the Secret Manager property source locator bean - // still gets created even if another PropertySourceLocator bean exists in the environment. - @Bean - public static PropertySourceLocator defaultPropertySourceLocator() { - return locatorEnvironment -> null; - } - } -} diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/secretmanager/SecretManagerCompatibilityTests.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/secretmanager/SecretManagerCompatibilityTests.java index b5aaa3748c..bd807f5600 100644 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/secretmanager/SecretManagerCompatibilityTests.java +++ b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/secretmanager/SecretManagerCompatibilityTests.java @@ -10,8 +10,6 @@ import com.google.cloud.secretmanager.v1.SecretManagerServiceClient; import com.google.cloud.secretmanager.v1.SecretPayload; import com.google.cloud.secretmanager.v1.SecretVersionName; -import com.google.cloud.spring.autoconfigure.secretmanager.SecretManagerBootstrapConfigurationTests.TestBootstrapConfiguration; -import com.google.cloud.spring.secretmanager.SecretManagerTemplate; import com.google.protobuf.ByteString; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -32,7 +30,7 @@ class SecretManagerCompatibilityTests { @BeforeEach void init() { - application = new SpringApplicationBuilder(GcpSecretManagerBootstrapConfiguration.class) + application = new SpringApplicationBuilder(SecretManagerCompatibilityTests.class) .web(WebApplicationType.NONE) .properties( "spring.cloud.gcp.secretmanager.project-id=" + PROJECT_NAME, @@ -61,44 +59,14 @@ void init() { .thenThrow(NotFoundException.class); } - /** - * Users with the legacy configuration (i.e., bootstrap phrase) should get {@link - * SecretManagerTemplate} autoconfiguration and properties resolved. Note that {@link - * TestBootstrapConfiguration} is automatically picked to create a mock {@link - * SecretManagerServiceClient} object as it is specified in spring.factories. - */ - @Test - void testLegacyConfigurationWhenDefaultSecretIsNotAllowed() { - application.properties("spring.cloud.bootstrap.enabled=true"); - try (ConfigurableApplicationContext applicationContext = application.run()) { - ConfigurableEnvironment environment = applicationContext.getEnvironment(); - assertThat(environment.getProperty("sm://my-secret")).isEqualTo("hello"); - assertThatThrownBy(() -> environment.getProperty("sm://fake-secret")) - .isExactlyInstanceOf(NotFoundException.class); - } - } - - @Test - void testLegacyConfigurationWhenDefaultSecretIsAllowed() { - application.properties( - "spring.cloud.bootstrap.enabled=true", - "spring.cloud.gcp.secretmanager.allow-default-secret=true"); - try (ConfigurableApplicationContext applicationContext = application.run()) { - ConfigurableEnvironment environment = applicationContext.getEnvironment(); - assertThat(environment.getProperty("sm://my-secret")).isEqualTo("hello"); - assertThat(environment.getProperty("sm://fake-secret")).isNull(); - } - } - /** * Users with the new configuration (i.e., using `spring.config.import`) should get {@link * com.google.cloud.spring.secretmanager.SecretManagerTemplate} autoconfiguration and properties * resolved. */ @Test - void testNewConfigurationWhenDefaultSecretIsNotAllowed() { + void testConfigurationWhenDefaultSecretIsNotAllowed() { application.properties( - "spring.cloud.gcp.secretmanager.legacy=false", "spring.config.import=sm://") .addBootstrapRegistryInitializer( (registry) -> registry.registerIfAbsent( @@ -115,9 +83,8 @@ void testNewConfigurationWhenDefaultSecretIsNotAllowed() { } @Test - void testNewConfigurationWhenDefaultSecretIsAllowed() { + void testConfigurationWhenDefaultSecretIsAllowed() { application.properties( - "spring.cloud.gcp.secretmanager.legacy=false", "spring.cloud.gcp.secretmanager.allow-default-secret=true", "spring.config.import=sm://") .addBootstrapRegistryInitializer( diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/security/IapAuthenticationAutoConfigurationTests.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/security/IapAuthenticationAutoConfigurationTests.java index 75b33965b5..511e30df4a 100644 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/security/IapAuthenticationAutoConfigurationTests.java +++ b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/security/IapAuthenticationAutoConfigurationTests.java @@ -29,9 +29,9 @@ import com.google.cloud.spring.security.iap.AppEngineAudienceProvider; import com.google.cloud.spring.security.iap.AudienceProvider; import com.google.cloud.spring.security.iap.AudienceValidator; +import jakarta.servlet.http.HttpServletRequest; import java.time.Instant; import java.util.Collections; -import javax.servlet.http.HttpServletRequest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/spanner/TestRepository.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/spanner/TestRepository.java index 8666f59f15..47ee3929d2 100644 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/spanner/TestRepository.java +++ b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/spanner/TestRepository.java @@ -21,4 +21,4 @@ /** A repository for testing instantiation. */ @Repository -public interface TestRepository extends SpannerRepository {} +public interface TestRepository extends SpannerRepository {} diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/R2dbcCloudSqlEnvironmentPostProcessorTests.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/R2dbcCloudSqlEnvironmentPostProcessorTests.java index 44a34dbd76..288496f5d7 100644 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/R2dbcCloudSqlEnvironmentPostProcessorTests.java +++ b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/sql/R2dbcCloudSqlEnvironmentPostProcessorTests.java @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Set; import org.junit.jupiter.api.Test; import org.springframework.boot.SpringApplication; @@ -29,7 +30,7 @@ /** Tests for {@link R2dbcCloudSqlEnvironmentPostProcessor}. */ class R2dbcCloudSqlEnvironmentPostProcessorTests { - private R2dbcCloudSqlEnvironmentPostProcessor r2dbcPostProcessor = + private final R2dbcCloudSqlEnvironmentPostProcessor r2dbcPostProcessor = new R2dbcCloudSqlEnvironmentPostProcessor(); ApplicationContextRunner contextRunner = @@ -39,15 +40,6 @@ class R2dbcCloudSqlEnvironmentPostProcessorTests { r2dbcPostProcessor.postProcessEnvironment( configurableApplicationContext.getEnvironment(), new SpringApplication())); - @Test - void testCreateUrl_mySql() { - GcpCloudSqlProperties properties = new GcpCloudSqlProperties(); - properties.setDatabaseName("my-database"); - properties.setInstanceConnectionName("my-instance-connection-name"); - String r2dbcUrl = r2dbcPostProcessor.createUrl(DatabaseType.MYSQL, properties); - assertThat(r2dbcUrl).isEqualTo("r2dbc:gcp:mysql://my-instance-connection-name/my-database"); - } - @Test void testCreateUrl_postgres() { GcpCloudSqlProperties properties = new GcpCloudSqlProperties(); @@ -57,65 +49,14 @@ void testCreateUrl_postgres() { assertThat(r2dbcUrl).isEqualTo("r2dbc:gcp:postgres://my-instance-connection-name/my-database"); } - @Test - void testSetR2dbcProperty_mySql_defaultUsername() { - this.contextRunner - .withPropertyValues( - "spring.cloud.gcp.sql.databaseName=my-database", - "spring.cloud.gcp.sql.instanceConnectionName=my-project:region:my-instance") - .run( - context -> { - assertThat(context.getEnvironment().getProperty("spring.r2dbc.url")) - .isEqualTo("r2dbc:gcp:mysql://my-project:region:my-instance/my-database"); - assertThat(context.getEnvironment().getProperty("spring.r2dbc.username")) - .isEqualTo("root"); - }); - } - - @Test - void testSetR2dbcProperty_mySql_usernameProvided() { - this.contextRunner - .withPropertyValues( - "spring.cloud.gcp.sql.databaseName=my-database", - "spring.cloud.gcp.sql.instanceConnectionName=my-project:region:my-instance", - "spring.r2dbc.username=my-username") - .run( - context -> { - assertThat(context.getEnvironment().getProperty("spring.r2dbc.url")) - .isEqualTo("r2dbc:gcp:mysql://my-project:region:my-instance/my-database"); - assertThat(context.getEnvironment().getProperty("spring.r2dbc.username")) - .isEqualTo("my-username"); - }); - } - - @Test - void testSetR2dbcProperty_mySql_urlProvidedByUserIgnored() { - this.contextRunner - .withPropertyValues( - "spring.cloud.gcp.sql.databaseName=my-database", - "spring.cloud.gcp.sql.instanceConnectionName=my-project:region:my-instance", - "spring.r2dbc.url=ignored") - .run( - context -> { - assertThat(context.getEnvironment().getProperty("spring.r2dbc.url")) - .isEqualTo("r2dbc:gcp:mysql://my-project:region:my-instance/my-database"); - }); - } - @Test void testSetR2dbcProperty_postgres() { - verifyThatCorrectUrlAndUsernameSet(new String[] {"io.r2dbc.postgresql"}, + verifyThatCorrectUrlAndUsernameSet( + new String[] {"io.r2dbc.postgresql"}, "postgres", "r2dbc:gcp:postgres://my-project:region:my-instance/my-database"); } - @Test - void testSetR2dbcProperty_mysql() { - verifyThatCorrectUrlAndUsernameSet(new String[] {"dev.miku.r2dbc.mysql"}, - "root", - "r2dbc:gcp:mysql://my-project:region:my-instance/my-database"); - } - /** * Verifies that correct database properties got injected into context, given a passed-in list of * packages to retain on the classpath. @@ -125,14 +66,13 @@ void testSetR2dbcProperty_mysql() { * @param url expected {@code spring.r2dbc.username} value to verify */ private void verifyThatCorrectUrlAndUsernameSet( - String[] driverPackagesToInclude, String username, String url) { + String[] driverPackagesToInclude, + String username, + String url) { // Because `FilteredClassLoader` accepts a list of packages to remove from classpath, // `driverPackagesToInclude` is used to calculate the inverse list of packages to _exclude_. - Set driverPackagesToExclude = new HashSet<>(Arrays.asList( - "dev.miku.r2dbc.mysql", - "io.r2dbc.postgresql" - )); - driverPackagesToExclude.removeAll(Arrays.asList(driverPackagesToInclude)); + Set driverPackagesToExclude = new HashSet<>(List.of("io.r2dbc.postgresql")); + Arrays.asList(driverPackagesToInclude).forEach(driverPackagesToExclude::remove); this.contextRunner .withPropertyValues( diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/trace/StackdriverTraceAutoConfigurationTests.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/trace/StackdriverTraceAutoConfigurationTests.java index 7abf8c6ccf..f18b93949a 100644 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/trace/StackdriverTraceAutoConfigurationTests.java +++ b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/trace/StackdriverTraceAutoConfigurationTests.java @@ -23,12 +23,14 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import brave.Tracer; import brave.TracingCustomizer; import brave.handler.SpanHandler; -import brave.http.HttpRequestParser; -import brave.http.HttpTracingCustomizer; +import brave.propagation.TraceContextOrSamplingFlags; import com.google.api.gax.core.ExecutorProvider; import com.google.cloud.spring.autoconfigure.core.GcpContextAutoConfiguration; +import com.google.cloud.spring.autoconfigure.trace.StackdriverTraceAutoConfigurationTests.MultipleSpanHandlersConfig.GcpTraceService; +import com.google.cloud.spring.autoconfigure.trace.StackdriverTraceAutoConfigurationTests.MultipleSpanHandlersConfig.OtherSender; import com.google.devtools.cloudtrace.v2.BatchWriteSpansRequest; import com.google.devtools.cloudtrace.v2.Span; import com.google.devtools.cloudtrace.v2.TraceServiceGrpc; @@ -47,12 +49,12 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.reflect.FieldUtils; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.tracing.BraveAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import zipkin2.Call; @@ -67,17 +69,21 @@ /** Tests for auto-config. */ class StackdriverTraceAutoConfigurationTests { - private ApplicationContextRunner contextRunner = - new ApplicationContextRunner() - .withConfiguration( - AutoConfigurations.of( - StackdriverTraceAutoConfiguration.class, - GcpContextAutoConfiguration.class, - BraveAutoConfiguration.class, - RefreshAutoConfiguration.class)) - .withUserConfiguration(MockConfiguration.class) - .withPropertyValues( - "spring.cloud.gcp.project-id=proj", "spring.sleuth.sampler.probability=1.0"); + private ApplicationContextRunner contextRunner; + + @BeforeEach + void init() { + contextRunner = new ApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of( + StackdriverTraceAutoConfiguration.class, + GcpContextAutoConfiguration.class, + BraveAutoConfiguration.class, + RefreshAutoConfiguration.class)) + .withUserConfiguration(MockConfiguration.class) + .withPropertyValues( + "spring.cloud.gcp.project-id=proj"); + } @Test void test() { @@ -88,8 +94,6 @@ void test() { () -> SpanHandler.NOOP) .run( context -> { - assertThat(context.getBean(HttpRequestParser.class)).isNotNull(); - assertThat(context.getBean(HttpTracingCustomizer.class)).isNotNull(); assertThat( context.getBean( StackdriverTraceAutoConfiguration.SENDER_BEAN_NAME, Sender.class)) @@ -165,8 +169,6 @@ void supportsMultipleReporters() { .withUserConfiguration(MultipleSpanHandlersConfig.class) .run( context -> { - assertThat(context.getBean(HttpRequestParser.class)).isNotNull(); - assertThat(context.getBean(HttpTracingCustomizer.class)).isNotNull(); assertThat(context.getBean(ManagedChannel.class)).isNotNull(); assertThat(context.getBeansOfType(Sender.class)).hasSize(2); assertThat(context.getBeansOfType(Sender.class)) @@ -174,13 +176,18 @@ void supportsMultipleReporters() { assertThat(context.getBeansOfType(SpanHandler.class)) .containsKeys("stackdriverSpanHandler", "otherSpanHandler"); - org.springframework.cloud.sleuth.Span span = - context.getBean(Tracer.class).nextSpan().name("foo").tag("foo", "bar").start(); - span.end(); - String spanId = span.context().spanId(); + brave.Span span = context + .getBean(Tracer.class) + // always send the trace + .nextSpan(TraceContextOrSamplingFlags.SAMPLED) + .name("foo") + .tag("foo", "bar") + .start(); + span.finish(); + String spanId = span.context().spanIdString(); + GcpTraceService gcpTraceService = + context.getBean(GcpTraceService.class); - MultipleSpanHandlersConfig.GcpTraceService gcpTraceService = - context.getBean(MultipleSpanHandlersConfig.GcpTraceService.class); await() .atMost(10, TimeUnit.SECONDS) .pollInterval(Duration.ofSeconds(1)) @@ -202,8 +209,8 @@ void supportsMultipleReporters() { .isEqualTo("bar"); }); - MultipleSpanHandlersConfig.OtherSender sender = - (MultipleSpanHandlersConfig.OtherSender) context.getBean("otherSender"); + OtherSender sender = + (OtherSender) context.getBean("otherSender"); await() .atMost(10, TimeUnit.SECONDS) .untilAsserted(() -> assertThat(sender.isSpanSent()).isTrue()); @@ -299,9 +306,8 @@ TracingCustomizer otherTracingCustomizer(SpanHandler otherSpanHandler) { @Bean SpanHandler otherSpanHandler(OtherSender otherSender) { - AsyncReporter reporter = AsyncReporter.create(otherSender); - SpanHandler spanHandler = AsyncZipkinSpanHandler.create(reporter); - return spanHandler; + AsyncReporter reporter = AsyncReporter.create(otherSender); + return AsyncZipkinSpanHandler.create(reporter); } @Bean @@ -343,7 +349,7 @@ public Call sendSpans(List encodedSpans) { /** Used as implementation on the in-process gRPC server for verification. */ static class GcpTraceService extends TraceServiceGrpc.TraceServiceImplBase { - private Map traces = new HashMap<>(); + private final Map traces = new HashMap<>(); boolean hasSpan(String spanId) { return this.traces.containsKey(spanId); diff --git a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/trace/pubsub/TracePubSubAutoConfigurationTest.java b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/trace/pubsub/TracePubSubAutoConfigurationTest.java index 8955eac4e2..a09079cb77 100644 --- a/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/trace/pubsub/TracePubSubAutoConfigurationTest.java +++ b/spring-cloud-gcp-autoconfigure/src/test/java/com/google/cloud/spring/autoconfigure/trace/pubsub/TracePubSubAutoConfigurationTest.java @@ -23,9 +23,9 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import brave.Tracing; import brave.handler.SpanHandler; -import brave.http.HttpRequestParser; -import brave.http.HttpTracingCustomizer; +import brave.messaging.MessagingTracing; import com.google.api.core.ApiFunction; import com.google.cloud.pubsub.v1.Publisher; import com.google.cloud.spring.autoconfigure.core.GcpContextAutoConfiguration; @@ -33,47 +33,50 @@ import com.google.cloud.spring.autoconfigure.trace.StackdriverTraceAutoConfiguration; import com.google.cloud.spring.pubsub.core.publisher.PublisherCustomizer; import io.grpc.ManagedChannel; +import io.micrometer.observation.aop.ObservedAspect; import java.util.List; import java.util.stream.Collectors; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; -import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration; -import org.springframework.cloud.sleuth.autoconfig.brave.instrument.messaging.BraveMessagingAutoConfiguration; import zipkin2.reporter.Reporter; import zipkin2.reporter.Sender; /** Tests for Trace Pub/Sub auto-config. */ class TracePubSubAutoConfigurationTest { - private ApplicationContextRunner contextRunner = - new ApplicationContextRunner() - .withConfiguration( - AutoConfigurations.of( - TracePubSubAutoConfiguration.class, - StackdriverTraceAutoConfiguration.class, - GcpContextAutoConfiguration.class, - BraveAutoConfiguration.class, - BraveMessagingAutoConfiguration.class, - RefreshAutoConfiguration.class)) - .withUserConfiguration(MockConfiguration.class) - .withBean( - StackdriverTraceAutoConfiguration.SPAN_HANDLER_BEAN_NAME, - SpanHandler.class, - () -> SpanHandler.NOOP) - // Prevent healthcheck from triggering a real call to Trace. - .withBean(REPORTER_BEAN_NAME, Reporter.class, () -> mock(Reporter.class)) - .withPropertyValues( - "spring.cloud.gcp.project-id=proj", "spring.sleuth.sampler.probability=1.0"); + private ApplicationContextRunner contextRunner; + + @BeforeEach + void init() { + contextRunner = + new ApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of( + TracePubSubAutoConfiguration.class, + StackdriverTraceAutoConfiguration.class, + GcpContextAutoConfiguration.class, + RefreshAutoConfiguration.class, + ObservationAutoConfiguration.class)) + .withUserConfiguration(MockConfiguration.class) + .withBean( + StackdriverTraceAutoConfiguration.SPAN_HANDLER_BEAN_NAME, + SpanHandler.class, + () -> SpanHandler.NOOP) + // Prevent health-check from triggering a real call to Trace. + .withBean(REPORTER_BEAN_NAME, Reporter.class, () -> mock(Reporter.class)) + .withPropertyValues( + "spring.cloud.gcp.project-id=proj"); + } @Test void test() { this.contextRunner.run( context -> { - assertThat(context.getBean(HttpRequestParser.class)).isNotNull(); - assertThat(context.getBean(HttpTracingCustomizer.class)).isNotNull(); assertThat( context.getBean(StackdriverTraceAutoConfiguration.SENDER_BEAN_NAME, Sender.class)) .isNotNull(); @@ -87,25 +90,34 @@ void testPubSubTracingDisabledByDefault() { context -> { assertThat(context.getBeansOfType(TracePubSubBeanPostProcessor.class)).isEmpty(); assertThat(context.getBeansOfType(PubSubTracing.class)).isEmpty(); + assertThat(context.getBeansOfType(MessagingTracing.class)).isEmpty(); + assertThat(context.getBeansOfType(ObservedAspect.class)).isEmpty(); }); } @Test void testPubSubTracingEnabled() { + Tracing tracing = Tracing.newBuilder().build(); this.contextRunner .withPropertyValues("spring.cloud.gcp.trace.pubsub.enabled=true") + .withBean(Tracing.class, () -> tracing) .run( context -> { assertThat(context.getBean(TracePubSubBeanPostProcessor.class)).isNotNull(); assertThat(context.getBean(PubSubTracing.class)).isNotNull(); + assertThat(context.getBean(MessagingTracing.class)).isNotNull(); + assertThat(context.getBean(ObservedAspect.class)).isNotNull(); }); } @Test void tracePubSubCustomizerAppliedLast() { PublisherCustomizer noopCustomizer = (pb, t) -> {}; + Tracing tracing = Tracing.newBuilder().build(); this.contextRunner .withPropertyValues("spring.cloud.gcp.trace.pubsub.enabled=true") + .withBean(Tracing.class, () -> tracing) + .withBean(MessagingTracing.class, () -> MessagingTracing.newBuilder(tracing).build()) .withBean(PublisherCustomizer.class, () -> noopCustomizer) .run(context -> { ObjectProvider customizersProvider = @@ -114,7 +126,7 @@ void tracePubSubCustomizerAppliedLast() { customizersProvider.orderedStream().collect(Collectors.toList()); assertThat(customizers).hasSize(2); - // Object provider lists highest priority first, so default priority `noopCustomizer` + // Object provider lists the highest priority first, so default priority `noopCustomizer` // will be second assertThat(customizers.get(1)).isSameAs(noopCustomizer); diff --git a/spring-cloud-gcp-bigquery/pom.xml b/spring-cloud-gcp-bigquery/pom.xml index a779228b02..51eea2797c 100644 --- a/spring-cloud-gcp-bigquery/pom.xml +++ b/spring-cloud-gcp-bigquery/pom.xml @@ -7,7 +7,7 @@ spring-cloud-gcp com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT spring-cloud-gcp-bigquery diff --git a/spring-cloud-gcp-bigquery/src/main/java/com/google/cloud/spring/bigquery/core/BigQueryOperations.java b/spring-cloud-gcp-bigquery/src/main/java/com/google/cloud/spring/bigquery/core/BigQueryOperations.java index 2ec50410d8..5b1c1d7af9 100644 --- a/spring-cloud-gcp-bigquery/src/main/java/com/google/cloud/spring/bigquery/core/BigQueryOperations.java +++ b/spring-cloud-gcp-bigquery/src/main/java/com/google/cloud/spring/bigquery/core/BigQueryOperations.java @@ -20,7 +20,7 @@ import com.google.cloud.bigquery.Job; import com.google.cloud.bigquery.Schema; import java.io.InputStream; -import org.springframework.util.concurrent.ListenableFuture; +import java.util.concurrent.CompletableFuture; /** * Defines operations for use with BigQuery. @@ -35,10 +35,10 @@ public interface BigQueryOperations { * @param tableName name of the table to write to * @param inputStream input stream of the table data to write * @param dataFormatOptions the format of the data to write - * @return {@link ListenableFuture} containing the BigQuery Job indicating completion of operation + * @return {@link CompletableFuture} containing the BigQuery Job indicating completion of operation * @throws BigQueryException if errors occur when loading data to the BigQuery table */ - ListenableFuture writeDataToTable( + CompletableFuture writeDataToTable( String tableName, InputStream inputStream, FormatOptions dataFormatOptions); /** @@ -53,7 +53,7 @@ ListenableFuture writeDataToTable( * Field.of("County", StandardSQLTypeName.STRING) * ); * - * ListenableFuture bigQueryJobFuture = + * CompletableFuture bigQueryJobFuture = * bigQueryTemplate.writeDataToTable( * TABLE_NAME, dataFile.getInputStream(), FormatOptions.csv(), schema); * } @@ -62,10 +62,10 @@ ListenableFuture writeDataToTable( * @param inputStream input stream of the table data to write * @param dataFormatOptions the format of the data to write * @param schema the schema of the table being loaded - * @return {@link ListenableFuture} containing the BigQuery Job indicating completion of operation + * @return {@link CompletableFuture} containing the BigQuery Job indicating completion of operation * @throws BigQueryException if errors occur when loading data to the BigQuery table */ - ListenableFuture writeDataToTable( + CompletableFuture writeDataToTable( String tableName, InputStream inputStream, FormatOptions dataFormatOptions, Schema schema); /** @@ -75,10 +75,10 @@ ListenableFuture writeDataToTable( * * @param tableName name of the table to write to * @param jsonInputStream input stream of the json file to be written - * @return {@link ListenableFuture} containing the WriteApiResponse indicating completion of + * @return {@link CompletableFuture} containing the WriteApiResponse indicating completion of * operation */ - ListenableFuture writeJsonStream(String tableName, InputStream jsonInputStream); + CompletableFuture writeJsonStream(String tableName, InputStream jsonInputStream); /** * This method uses BigQuery Storage Write API to write new line delimited JSON file to the @@ -86,9 +86,9 @@ ListenableFuture writeDataToTable( * * @param tableName name of the table to write to * @param jsonInputStream input stream of the json file to be written - * @return {@link ListenableFuture} containing the WriteApiResponse indicating completion of + * @return {@link CompletableFuture} containing the WriteApiResponse indicating completion of * operation */ - ListenableFuture writeJsonStream( + CompletableFuture writeJsonStream( String tableName, InputStream jsonInputStream, Schema schema); } diff --git a/spring-cloud-gcp-bigquery/src/main/java/com/google/cloud/spring/bigquery/core/BigQueryTemplate.java b/spring-cloud-gcp-bigquery/src/main/java/com/google/cloud/spring/bigquery/core/BigQueryTemplate.java index d8fb8dc681..fe685ba460 100644 --- a/spring-cloud-gcp-bigquery/src/main/java/com/google/cloud/spring/bigquery/core/BigQueryTemplate.java +++ b/spring-cloud-gcp-bigquery/src/main/java/com/google/cloud/spring/bigquery/core/BigQueryTemplate.java @@ -45,17 +45,15 @@ import java.nio.channels.Channels; import java.time.Duration; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ScheduledFuture; import org.json.JSONArray; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.TaskScheduler; -import org.springframework.scheduling.concurrent.DefaultManagedTaskScheduler; import org.springframework.util.Assert; import org.springframework.util.StreamUtils; -import org.springframework.util.concurrent.ListenableFuture; -import org.springframework.util.concurrent.SettableListenableFuture; /** * Helper class which simplifies common operations done in BigQuery. @@ -87,43 +85,7 @@ public class BigQueryTemplate implements BigQueryOperations { private final Logger logger = LoggerFactory.getLogger(BigQueryTemplate.class); - private int jsonWriterBatchSize; - - /** - * Creates the {@link BigQuery} template. - * - * @param bigQuery the underlying client object used to interface with BigQuery - * @param datasetName the name of the dataset in which all operations will take place - * @deprecated As of release 3.3.1, use - * BigQueryTemplate(BigQuery,BigQueryWriteClient,Map,TaskScheduler) instead - */ - @Deprecated - public BigQueryTemplate(BigQuery bigQuery, String datasetName) { - this(bigQuery, datasetName, new DefaultManagedTaskScheduler()); - } - - /** - * Creates the {@link BigQuery} template. - * - * @param bigQuery the underlying client object used to interface with BigQuery - * @param datasetName the name of the dataset in which all operations will take place - * @param taskScheduler the {@link TaskScheduler} used to poll for the status of long-running - * BigQuery operations - * @deprecated As of release 3.3.1, use - * BigQueryTemplate(BigQuery,BigQueryWriteClient,Map,TaskScheduler) instead - */ - @Deprecated - public BigQueryTemplate(BigQuery bigQuery, String datasetName, TaskScheduler taskScheduler) { - Assert.notNull(bigQuery, "BigQuery client object must not be null."); - Assert.notNull(datasetName, "Dataset name must not be null"); - Assert.notNull(taskScheduler, "TaskScheduler must not be null"); - - this.bigQuery = bigQuery; - this.datasetName = datasetName; - this.taskScheduler = taskScheduler; - this.bigQueryWriteClient = - null; // This constructor is Deprecated. We cannot use BigQueryWriteClient with this - } + private final int jsonWriterBatchSize; /** * A Full constructor which creates the {@link BigQuery} template. @@ -203,13 +165,13 @@ public void setJobPollInterval(Duration jobPollInterval) { } @Override - public ListenableFuture writeDataToTable( + public CompletableFuture writeDataToTable( String tableName, InputStream inputStream, FormatOptions dataFormatOptions) { return this.writeDataToTable(tableName, inputStream, dataFormatOptions, null); } @Override - public ListenableFuture writeDataToTable( + public CompletableFuture writeDataToTable( String tableName, InputStream inputStream, FormatOptions dataFormatOptions, Schema schema) { TableId tableId = TableId.of(datasetName, tableName); @@ -247,11 +209,11 @@ public ListenableFuture writeDataToTable( * * @param tableName name of the table to write to * @param jsonInputStream input stream of the json file to be written - * @return {@link ListenableFuture} containing the WriteApiResponse indicating completion of + * @return {@link CompletableFuture} containing the WriteApiResponse indicating completion of * operation */ @Override - public ListenableFuture writeJsonStream( + public CompletableFuture writeJsonStream( String tableName, InputStream jsonInputStream, Schema schema) { createTable(tableName, schema); // create table if it's not already created return writeJsonStream(tableName, jsonInputStream); @@ -278,32 +240,29 @@ public Table createTable( * * @param tableName name of the table to write to * @param jsonInputStream input stream of the json file to be written - * @return {@link ListenableFuture} containing the WriteApiResponse indicating completion of + * @return {@link CompletableFuture} containing the WriteApiResponse indicating completion of * operation */ @Override - public ListenableFuture writeJsonStream( + public CompletableFuture writeJsonStream( String tableName, InputStream jsonInputStream) { - SettableListenableFuture writeApiFutureResponse = - new SettableListenableFuture<>(); + CompletableFuture writeApiFutureResponse = + new CompletableFuture<>(); Thread asyncTask = new Thread( () -> { try { WriteApiResponse apiResponse = getWriteApiResponse(tableName, jsonInputStream); - writeApiFutureResponse.set(apiResponse); + writeApiFutureResponse.complete(apiResponse); } catch (DescriptorValidationException | IOException e) { - writeApiFutureResponse.setException(e); + writeApiFutureResponse.completeExceptionally(e); logger.warn(String.format("Error: %s %n", e.getMessage()), e); - } catch (InterruptedException e) { - writeApiFutureResponse.setException(e); + } catch (Exception e) { + writeApiFutureResponse.completeExceptionally(e); // Restore interrupted state in case of an InterruptedException Thread.currentThread().interrupt(); - } catch (Throwable t) { - writeApiFutureResponse.setException(t); - Thread.currentThread().interrupt(); } }); asyncTask @@ -311,13 +270,16 @@ public ListenableFuture writeJsonStream( // thread can be run in the ExecutorService when it has been wired-in // register success and failure callback - writeApiFutureResponse.addCallback( - res -> logger.info("Data successfully written"), - res -> { - asyncTask.interrupt(); // interrupt the thread as the developer might have cancelled the - // Future. - // This can be replaced with interrupting the ExecutorService when it has been wired-in - logger.info("asyncTask interrupted"); + writeApiFutureResponse.whenComplete( + (writeApiResponse, exception) -> { + if (exception != null) { + asyncTask.interrupt(); // interrupt the thread as the developer might have cancelled the + // Future. + // This can be replaced with interrupting the ExecutorService when it has been wired-in + logger.info("asyncTask interrupted"); + return; + } + logger.info("Data successfully written"); }); return writeApiFutureResponse; @@ -370,7 +332,7 @@ public WriteApiResponse getWriteApiResponse(String tableName, InputStream jsonIn throw new BigQueryException("Failed to append records. \n" + e); } - // Finalize the stream before commiting it + // Finalize the stream before committing it writer.finalizeWriteStream(); BatchCommitWriteStreamsResponse commitResponse = getCommitResponse(parentTable, writer); @@ -381,7 +343,7 @@ public WriteApiResponse getWriteApiResponse(String tableName, InputStream jsonIn } } - // set isSucccessful flag to true of there were no errors + // set isSuccessful flag to true of there were no errors if (apiResponse.getErrors().isEmpty()) { apiResponse.setSuccessful(true); } @@ -423,9 +385,9 @@ public int getJsonWriterBatchSize() { return this.jsonWriterBatchSize; } - private SettableListenableFuture createJobFuture(Job pendingJob) { - // Prepare the polling task for the ListenableFuture result returned to end-user - SettableListenableFuture result = new SettableListenableFuture<>(); + private CompletableFuture createJobFuture(Job pendingJob) { + // Prepare the polling task for the CompletableFuture result returned to end-user + CompletableFuture result = new CompletableFuture<>(); ScheduledFuture scheduledFuture = taskScheduler.scheduleAtFixedRate( () -> { @@ -433,22 +395,25 @@ private SettableListenableFuture createJobFuture(Job pendingJob) { Job job = pendingJob.reload(); if (State.DONE.equals(job.getStatus().getState())) { if (job.getStatus().getError() != null) { - result.setException( + result.completeExceptionally( new BigQueryException(job.getStatus().getError().getMessage())); } else { - result.set(job); + result.complete(job); } } } catch (Exception e) { - result.setException(new BigQueryException(e.getMessage())); + result.completeExceptionally(new BigQueryException(e.getMessage())); } }, this.jobPollInterval); - result.addCallback( - response -> scheduledFuture.cancel(true), - response -> { - pendingJob.cancel(); + result.whenComplete( + (response, exception) -> { + if (exception != null) { + pendingJob.cancel(); + scheduledFuture.cancel(true); + return; + } scheduledFuture.cancel(true); }); diff --git a/spring-cloud-gcp-bigquery/src/main/java/com/google/cloud/spring/bigquery/integration/outbound/BigQueryFileMessageHandler.java b/spring-cloud-gcp-bigquery/src/main/java/com/google/cloud/spring/bigquery/integration/outbound/BigQueryFileMessageHandler.java index 70084d8cb4..f2c5e2d080 100644 --- a/spring-cloud-gcp-bigquery/src/main/java/com/google/cloud/spring/bigquery/integration/outbound/BigQueryFileMessageHandler.java +++ b/spring-cloud-gcp-bigquery/src/main/java/com/google/cloud/spring/bigquery/integration/outbound/BigQueryFileMessageHandler.java @@ -29,6 +29,7 @@ import java.io.IOException; import java.io.InputStream; import java.time.Duration; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -43,7 +44,6 @@ import org.springframework.messaging.Message; import org.springframework.messaging.MessageHandlingException; import org.springframework.util.Assert; -import org.springframework.util.concurrent.ListenableFuture; /** * A {@link org.springframework.messaging.MessageHandler} which handles sending and loading files to @@ -166,7 +166,7 @@ public void setTimeout(Duration timeout) { * *

If set to true, the handler runs synchronously and returns {@link Job} for message * responses. If set to false, the handler will return {@link - * org.springframework.util.concurrent.ListenableFuture} of the Job as the response for each + * CompletableFuture} of the Job as the response for each * message. * * @param sync whether {@link BigQueryFileMessageHandler} should wait synchronously for jobs to @@ -189,7 +189,7 @@ protected Object handleRequestMessage(Message message) { Assert.notNull(formatOptions, "Data file formatOptions must not be null."); try (InputStream inputStream = convertToInputStream(message.getPayload())) { - ListenableFuture jobFuture = + CompletableFuture jobFuture = this.bigQueryTemplate.writeDataToTable(tableName, inputStream, formatOptions, schema); if (this.sync) { @@ -216,14 +216,14 @@ protected Object handleRequestMessage(Message message) { private static InputStream convertToInputStream(Object payload) throws IOException { InputStream result; - if (payload instanceof File) { - result = new BufferedInputStream(new FileInputStream((File) payload)); - } else if (payload instanceof byte[]) { - result = new ByteArrayInputStream((byte[]) payload); - } else if (payload instanceof InputStream) { - result = (InputStream) payload; - } else if (payload instanceof Resource) { - result = ((Resource) payload).getInputStream(); + if (payload instanceof File file) { + result = new BufferedInputStream(new FileInputStream(file)); + } else if (payload instanceof byte[] bytes) { + result = new ByteArrayInputStream(bytes); + } else if (payload instanceof InputStream inputStream) { + result = inputStream; + } else if (payload instanceof Resource resource) { + result = resource.getInputStream(); } else { throw new IllegalArgumentException( String.format( diff --git a/spring-cloud-gcp-bigquery/src/test/java/com/google/cloud/spring/bigquery/BigQueryTemplateTest.java b/spring-cloud-gcp-bigquery/src/test/java/com/google/cloud/spring/bigquery/BigQueryTemplateTest.java index 5ef86e4a15..59ba2c4a80 100644 --- a/spring-cloud-gcp-bigquery/src/test/java/com/google/cloud/spring/bigquery/BigQueryTemplateTest.java +++ b/spring-cloud-gcp-bigquery/src/test/java/com/google/cloud/spring/bigquery/BigQueryTemplateTest.java @@ -16,9 +16,15 @@ package com.google.cloud.spring.bigquery; +import static com.google.cloud.bigquery.JobInfo.CreateDisposition.CREATE_IF_NEEDED; +import static com.google.cloud.bigquery.JobInfo.CreateDisposition.CREATE_NEVER; +import static com.google.cloud.bigquery.JobInfo.WriteDisposition.WRITE_APPEND; +import static com.google.cloud.bigquery.JobInfo.WriteDisposition.WRITE_EMPTY; +import static com.google.cloud.bigquery.JobInfo.WriteDisposition.WRITE_TRUNCATE; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; @@ -47,6 +53,7 @@ import java.time.Duration; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -54,13 +61,12 @@ import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.scheduling.concurrent.DefaultManagedTaskScheduler; -import org.springframework.util.concurrent.ListenableFuture; @ExtendWith(MockitoExtension.class) class BigQueryTemplateTest { private BigQueryWriteClient bigQueryWriteClientMock; - private String newLineSeperatedJson = + private final String newLineSeperatedJson = "{\"CompanyName\":\"TALES\",\"Description\":\"mark\",\"SerialNumber\":97,\"Leave\":0,\"EmpName\":\"Mark\"}\n" + "{\"CompanyName\":\"1Q84\",\"Description\":\"ark\",\"SerialNumber\":978,\"Leave\":0,\"EmpName\":\"HARUKI\"}"; private static final String PROJECT = "project"; @@ -72,7 +78,7 @@ class BigQueryTemplateTest { private BigQueryRpc bigqueryRpcMock; private BigQuery bigquery; private BigQueryOptions options; - private Map bqInitSettings = new HashMap<>(); + private final Map bqInitSettings = new HashMap<>(); BigQueryTemplate bqTemplateSpy; private Schema getDefaultSchema() { @@ -92,7 +98,7 @@ public void setUp() { rpcFactoryMock = mock(BigQueryRpcFactory.class); bigqueryRpcMock = mock(BigQueryRpc.class); when(rpcFactoryMock.create(any(BigQueryOptions.class))).thenReturn(bigqueryRpcMock); - options = createBigQueryOptionsForProject(PROJECT, rpcFactoryMock); + options = createBigQueryOptionsForProject(rpcFactoryMock); bigQueryWriteClientMock = mock(BigQueryWriteClient.class); bigquery = options.getService(); bqInitSettings.put("DATASET_NAME", DATASET); @@ -105,19 +111,82 @@ public void setUp() { } private BigQueryOptions createBigQueryOptionsForProject( - String project, BigQueryRpcFactory rpcFactory) { + BigQueryRpcFactory rpcFactory) { return BigQueryOptions.newBuilder() - .setProjectId(project) + .setProjectId(BigQueryTemplateTest.PROJECT) .setServiceRpcFactory(rpcFactory) .setRetrySettings(ServiceOptions.getNoRetrySettings()) .build(); } + @Test + void getDatasetNameTest() { + assertThat(bqTemplateSpy.getDatasetName()).isEqualTo(DATASET); + } + + @Test + void getJsonWriterBatchSizeTest() { + assertThat(bqTemplateSpy.getJsonWriterBatchSize()).isEqualTo(JSON_WRITER_BATCH_SIZE); + } + + @Test + void setAutoDetectSchemaTest() { + assertThatCode(() -> bqTemplateSpy.setAutoDetectSchema(true)) + .doesNotThrowAnyException(); + assertThatCode(() -> bqTemplateSpy.setAutoDetectSchema(false)) + .doesNotThrowAnyException(); + } + + @Test + void setWriteDispositionTest() { + assertThatCode(() -> bqTemplateSpy.setWriteDisposition(WRITE_TRUNCATE)) + .doesNotThrowAnyException(); + assertThatCode(() -> bqTemplateSpy.setWriteDisposition(WRITE_APPEND)) + .doesNotThrowAnyException(); + assertThatCode(() -> bqTemplateSpy.setWriteDisposition(WRITE_EMPTY)) + .doesNotThrowAnyException(); + } + + @Test + void setWriteDispositionThrowsExceptionTest() { + assertThatCode(() -> bqTemplateSpy.setWriteDisposition(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasStackTraceContaining("BigQuery write disposition must not be null."); + } + + @Test + void setCreateDispositionTest() { + assertThatCode(() -> bqTemplateSpy.setCreateDisposition(CREATE_IF_NEEDED)) + .doesNotThrowAnyException(); + assertThatCode(() -> bqTemplateSpy.setCreateDisposition(CREATE_NEVER)) + .doesNotThrowAnyException(); + } + + @Test + void setCreateDispositionThrowsExceptionTest() { + assertThatCode(() -> bqTemplateSpy.setCreateDisposition(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasStackTraceContaining("BigQuery create disposition must not be null."); + } + + @Test + void setJobPollIntervalTest() { + assertThatCode(() -> bqTemplateSpy.setJobPollInterval(Duration.ofSeconds(1L))) + .doesNotThrowAnyException(); + } + + @Test + void setJobPollIntervalThrowsExceptionTest() { + assertThatCode(() -> bqTemplateSpy.setJobPollInterval(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasStackTraceContaining("BigQuery job polling interval must not be null"); + } + @Test void getWriteApiResponseTest() throws DescriptorValidationException, IOException, InterruptedException { - InputStream jsoninputStream = new ByteArrayInputStream(newLineSeperatedJson.getBytes()); + InputStream jsonInputStream = new ByteArrayInputStream(newLineSeperatedJson.getBytes()); doReturn(mock(BigQueryJsonDataWriter.class)) .when(bqTemplateSpy) @@ -130,7 +199,7 @@ void getWriteApiResponseTest() .when(bqTemplateSpy) .getCommitResponse(any(TableName.class), any(BigQueryJsonDataWriter.class)); - WriteApiResponse apiRes = bqTemplateSpy.getWriteApiResponse(TABLE, jsoninputStream); + WriteApiResponse apiRes = bqTemplateSpy.getWriteApiResponse(TABLE, jsonInputStream); assertTrue(apiRes.isSuccessful()); assertEquals(0, apiRes.getErrors().size()); @@ -140,15 +209,15 @@ void getWriteApiResponseTest() void writeJsonStreamTest() throws DescriptorValidationException, IOException, InterruptedException, ExecutionException { - InputStream jsoninputStream = new ByteArrayInputStream(newLineSeperatedJson.getBytes()); + InputStream jsonInputStream = new ByteArrayInputStream(newLineSeperatedJson.getBytes()); WriteApiResponse apiResponse = new WriteApiResponse(); apiResponse.setSuccessful(true); doReturn(apiResponse) .when(bqTemplateSpy) .getWriteApiResponse(any(String.class), any(InputStream.class)); - ListenableFuture futRes = - bqTemplateSpy.writeJsonStream(TABLE, jsoninputStream); + CompletableFuture futRes = + bqTemplateSpy.writeJsonStream(TABLE, jsonInputStream); WriteApiResponse apiRes = futRes.get(); assertTrue(apiRes.isSuccessful()); assertEquals(0, apiRes.getErrors().size()); @@ -158,7 +227,7 @@ void writeJsonStreamTest() void writeJsonStreamWithSchemaTest() throws DescriptorValidationException, IOException, InterruptedException, ExecutionException { - InputStream jsoninputStream = new ByteArrayInputStream(newLineSeperatedJson.getBytes()); + InputStream jsonInputStream = new ByteArrayInputStream(newLineSeperatedJson.getBytes()); WriteApiResponse apiResponse = new WriteApiResponse(); apiResponse.setSuccessful(true); doReturn(apiResponse) @@ -169,8 +238,8 @@ void writeJsonStreamWithSchemaTest() .when(bqTemplateSpy) .createTable(any(String.class), any(Schema.class)); - ListenableFuture futRes = - bqTemplateSpy.writeJsonStream(TABLE, jsoninputStream, getDefaultSchema()); + CompletableFuture futRes = + bqTemplateSpy.writeJsonStream(TABLE, jsonInputStream, getDefaultSchema()); WriteApiResponse apiRes = futRes.get(); assertTrue(apiRes.isSuccessful()); assertEquals(0, apiRes.getErrors().size()); @@ -179,9 +248,9 @@ void writeJsonStreamWithSchemaTest() @Test void writeJsonStreamFailsOnGenericWritingException() - throws DescriptorValidationException, IOException, InterruptedException, ExecutionException { + throws DescriptorValidationException, IOException, InterruptedException { - InputStream jsoninputStream = new ByteArrayInputStream(newLineSeperatedJson.getBytes()); + InputStream jsonInputStream = new ByteArrayInputStream(newLineSeperatedJson.getBytes()); doReturn(mock(Table.class)) .when(bqTemplateSpy) .createTable(any(String.class), any(Schema.class)); @@ -190,8 +259,8 @@ void writeJsonStreamFailsOnGenericWritingException() .when(bqTemplateSpy) .getWriteApiResponse(any(String.class), any(InputStream.class)); - ListenableFuture futRes = - bqTemplateSpy.writeJsonStream(TABLE, jsoninputStream, getDefaultSchema()); + CompletableFuture futRes = + bqTemplateSpy.writeJsonStream(TABLE, jsonInputStream, getDefaultSchema()); assertThat(futRes) .withFailMessage("boom!") .failsWithin(Duration.ofSeconds(1)); diff --git a/spring-cloud-gcp-bigquery/src/test/java/com/google/cloud/spring/bigquery/core/BigQueryTemplateIntegrationTests.java b/spring-cloud-gcp-bigquery/src/test/java/com/google/cloud/spring/bigquery/core/BigQueryTemplateIntegrationTests.java index 7eddc83f89..5b64e905f5 100644 --- a/spring-cloud-gcp-bigquery/src/test/java/com/google/cloud/spring/bigquery/core/BigQueryTemplateIntegrationTests.java +++ b/spring-cloud-gcp-bigquery/src/test/java/com/google/cloud/spring/bigquery/core/BigQueryTemplateIntegrationTests.java @@ -32,10 +32,9 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; @@ -46,7 +45,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.core.io.Resource; import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.util.concurrent.ListenableFuture; /** * Integration tests for BigQuery. @@ -102,7 +100,7 @@ void testLoadFileWithSchema() throws Exception { Field.of("State", StandardSQLTypeName.STRING), Field.of("County", StandardSQLTypeName.STRING)); - ListenableFuture bigQueryJobFuture = + CompletableFuture bigQueryJobFuture = bigQueryTemplate.writeDataToTable( tableName, dataFile.getInputStream(), FormatOptions.csv(), schema); @@ -128,7 +126,7 @@ void testJsonFileLoadWithSchema() throws Exception { Field.of("Leave", StandardSQLTypeName.NUMERIC), Field.of("EmpName", StandardSQLTypeName.STRING)); - ListenableFuture writeApiFuture = + CompletableFuture writeApiFuture = bigQueryTemplate.writeJsonStream(tableName, jsonDataFile.getInputStream(), schema); WriteApiResponse writeApiResponse = @@ -146,7 +144,7 @@ void testJsonFileLoadWithSchema() throws Exception { @Test void testLoadFile() throws IOException, ExecutionException, InterruptedException { - ListenableFuture bigQueryJobFuture = + CompletableFuture bigQueryJobFuture = bigQueryTemplate.writeDataToTable( this.tableName, dataFile.getInputStream(), FormatOptions.csv()); @@ -167,7 +165,7 @@ void testLoadBytes() throws ExecutionException, InterruptedException { byte[] byteArray = "CountyId,State,County\n1001,Alabama,Autauga County\n".getBytes(); ByteArrayInputStream byteStream = new ByteArrayInputStream(byteArray); - ListenableFuture bigQueryJobFuture = + CompletableFuture bigQueryJobFuture = bigQueryTemplate.writeDataToTable(this.tableName, byteStream, FormatOptions.csv()); Job job = bigQueryJobFuture.get(); diff --git a/spring-cloud-gcp-bigquery/src/test/java/com/google/cloud/spring/bigquery/integration/outbound/BigQueryFileMessageHandlerIntegrationTests.java b/spring-cloud-gcp-bigquery/src/test/java/com/google/cloud/spring/bigquery/integration/outbound/BigQueryFileMessageHandlerIntegrationTests.java index 7a22c7d4d9..3a0d886122 100644 --- a/spring-cloud-gcp-bigquery/src/test/java/com/google/cloud/spring/bigquery/integration/outbound/BigQueryFileMessageHandlerIntegrationTests.java +++ b/spring-cloud-gcp-bigquery/src/test/java/com/google/cloud/spring/bigquery/integration/outbound/BigQueryFileMessageHandlerIntegrationTests.java @@ -35,6 +35,7 @@ import java.io.File; import java.time.Duration; import java.util.HashMap; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.AfterEach; @@ -51,7 +52,6 @@ import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.util.concurrent.ListenableFuture; @EnabledIfSystemProperty(named = "it.bigquery", matches = "true") @ExtendWith(SpringExtension.class) @@ -91,8 +91,8 @@ void testLoadFileWithSchema() throws InterruptedException, ExecutionException { MessageBuilder.createMessage( new File("src/test/resources/data.csv"), new MessageHeaders(messageHeaders)); - ListenableFuture jobFuture = - (ListenableFuture) this.messageHandler.handleRequestMessage(message); + CompletableFuture jobFuture = + (CompletableFuture) this.messageHandler.handleRequestMessage(message); // Assert that a BigQuery polling task is scheduled successfully. await() @@ -126,8 +126,8 @@ void testLoadFile() throws InterruptedException, ExecutionException { MessageBuilder.createMessage( new File("src/test/resources/data.csv"), new MessageHeaders(messageHeaders)); - ListenableFuture jobFuture = - (ListenableFuture) this.messageHandler.handleRequestMessage(message); + CompletableFuture jobFuture = + (CompletableFuture) this.messageHandler.handleRequestMessage(message); // Assert that a BigQuery polling task is scheduled successfully. await() @@ -181,8 +181,8 @@ void testLoadFile_cancel() { MessageBuilder.createMessage( new File("src/test/resources/data.csv"), new MessageHeaders(messageHeaders)); - ListenableFuture jobFuture = - (ListenableFuture) this.messageHandler.handleRequestMessage(message); + CompletableFuture jobFuture = + (CompletableFuture) this.messageHandler.handleRequestMessage(message); assertThat(jobFuture).isNotNull(); jobFuture.cancel(true); diff --git a/spring-cloud-gcp-bigquery/src/test/java/com/google/cloud/spring/bigquery/integration/outbound/BigQueryFileMessageHandlerTests.java b/spring-cloud-gcp-bigquery/src/test/java/com/google/cloud/spring/bigquery/integration/outbound/BigQueryFileMessageHandlerTests.java index 0d235bd4e4..29a2fb52f1 100644 --- a/spring-cloud-gcp-bigquery/src/test/java/com/google/cloud/spring/bigquery/integration/outbound/BigQueryFileMessageHandlerTests.java +++ b/spring-cloud-gcp-bigquery/src/test/java/com/google/cloud/spring/bigquery/integration/outbound/BigQueryFileMessageHandlerTests.java @@ -17,6 +17,7 @@ package com.google.cloud.spring.bigquery.integration.outbound; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -26,16 +27,24 @@ import com.google.cloud.bigquery.Job; import com.google.cloud.bigquery.Schema; import com.google.cloud.spring.bigquery.core.BigQueryTemplate; +import java.io.IOException; import java.io.InputStream; +import java.math.BigInteger; +import java.time.Duration; import java.util.Collections; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.core.io.Resource; +import org.springframework.expression.Expression; import org.springframework.integration.expression.ValueExpression; import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHandlingException; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.support.MessageBuilder; -import org.springframework.util.concurrent.ListenableFuture; -import org.springframework.util.concurrent.SettableListenableFuture; class BigQueryFileMessageHandlerTests { @@ -43,23 +52,48 @@ class BigQueryFileMessageHandlerTests { private BigQueryFileMessageHandler messageHandler; + private final CompletableFuture completableFuture = new CompletableFuture<>(); + @BeforeEach void setup() { bigQueryTemplate = mock(BigQueryTemplate.class); - SettableListenableFuture result = new SettableListenableFuture<>(); - result.set(mock(Job.class)); - when(bigQueryTemplate.writeDataToTable(any(), any(), any(), any())).thenReturn(result); - + completableFuture.complete(mock(Job.class)); + when(bigQueryTemplate.writeDataToTable(any(), any(), any(), any())).thenReturn( + completableFuture); messageHandler = new BigQueryFileMessageHandler(bigQueryTemplate); - } - - @Test - void testHandleMessage_async() { messageHandler.setTableName("testTable"); messageHandler.setFormatOptions(FormatOptions.csv()); messageHandler.setSync(false); messageHandler.setTableSchema(Schema.of()); + } + + @Test + void testHandleResourceMessage() throws IOException { + Resource payload = mock(Resource.class); + Message message = + MessageBuilder.createMessage(payload, new MessageHeaders(Collections.emptyMap())); + + Object result = messageHandler.handleRequestMessage(message); + verify(bigQueryTemplate) + .writeDataToTable("testTable", payload.getInputStream(), FormatOptions.csv(), Schema.of()); + assertThat(result).isNotNull().isInstanceOf(CompletableFuture.class); + } + + @Test + void testHandleResourceMessageThrowsException() throws IOException { + Resource payload = mock(Resource.class); + when(payload.getInputStream()).thenThrow(IOException.class); + Message message = + MessageBuilder.createMessage(payload, new MessageHeaders(Collections.emptyMap())); + + assertThatCode(() -> messageHandler.handleRequestMessage(message)) + .isInstanceOf(MessageHandlingException.class) + .hasStackTraceContaining("Failed to write data to BigQuery tables in message handler"); + } + + @Test + void testHandleInputStreamMessage_async() { InputStream payload = mock(InputStream.class); Message message = MessageBuilder.createMessage(payload, new MessageHeaders(Collections.emptyMap())); @@ -68,13 +102,11 @@ void testHandleMessage_async() { verify(bigQueryTemplate) .writeDataToTable("testTable", payload, FormatOptions.csv(), Schema.of()); - assertThat(result).isNotNull().isInstanceOf(ListenableFuture.class); + assertThat(result).isNotNull().isInstanceOf(CompletableFuture.class); } @Test void testHandleMessage_sync() { - messageHandler.setTableName("testTable"); - messageHandler.setFormatOptions(FormatOptions.csv()); messageHandler.setSync(true); messageHandler.setTableSchemaExpression(new ValueExpression<>(Schema.of())); @@ -88,4 +120,101 @@ void testHandleMessage_sync() { .writeDataToTable("testTable", payload, FormatOptions.csv(), Schema.of()); assertThat(result).isNotNull().isInstanceOf(Job.class); } + + @Test + void testHandleMessage_ThrowsExecutionExceptionTest() + throws ExecutionException, InterruptedException, TimeoutException { + CompletableFuture mockCompletableFuture = mock(CompletableFuture.class); + when(bigQueryTemplate.writeDataToTable(any(), any(), any(), any())).thenReturn( + mockCompletableFuture); + when(mockCompletableFuture.get(1L, TimeUnit.SECONDS)) + .thenThrow(ExecutionException.class); + messageHandler.setSync(true); + messageHandler.setTableSchemaExpression(new ValueExpression<>(Schema.of())); + messageHandler.setTimeout(Duration.ofSeconds(1)); + InputStream payload = mock(InputStream.class); + Message message = + MessageBuilder.createMessage(payload, new MessageHeaders(Collections.emptyMap())); + + assertThatCode(() -> messageHandler.handleRequestMessage(message)) + .isInstanceOf(MessageHandlingException.class) + .hasStackTraceContaining("Failed to wait for BigQuery Job to complete in message handler"); + } + + @Test + void testHandleMessage_ThrowsInterruptedExceptionTest() + throws ExecutionException, InterruptedException, TimeoutException { + CompletableFuture mockCompletableFuture = mock(CompletableFuture.class); + when(bigQueryTemplate.writeDataToTable(any(), any(), any(), any())).thenReturn( + mockCompletableFuture); + when(mockCompletableFuture.get(1L, TimeUnit.SECONDS)) + .thenThrow(InterruptedException.class); + messageHandler.setSync(true); + messageHandler.setTableSchemaExpression(new ValueExpression<>(Schema.of())); + messageHandler.setTimeout(Duration.ofSeconds(1)); + InputStream payload = mock(InputStream.class); + Message message = + MessageBuilder.createMessage(payload, new MessageHeaders(Collections.emptyMap())); + + assertThatCode(() -> messageHandler.handleRequestMessage(message)) + .isInstanceOf(MessageHandlingException.class) + .hasStackTraceContaining("Failed to wait for BigQuery Job (interrupted) in message handler"); + } + + @Test + void testHandleMessageWithIllegalArgumentTest() { + messageHandler.setSync(true); + messageHandler.setTableSchemaExpression(new ValueExpression<>(Schema.of())); + + BigInteger payload = mock(BigInteger.class); + Message message = + MessageBuilder.createMessage(payload, new MessageHeaders(Collections.emptyMap())); + + assertThatCode(() -> messageHandler.handleRequestMessage(message)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(String.format( + "Unsupported message payload type: %s. The supported payload types " + + "are: java.io.File, byte[], org.springframework.core.io.Resource, " + + "and java.io.InputStream.", + payload.getClass().getName())); + } + + @Test + void setTableNameExpressionThrowsExceptionTest() { + assertThatCode(() -> messageHandler.setTableNameExpression(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Table name expression must not be null."); + } + + @Test + void setTableNameExpressionTest() { + assertThatCode(() -> messageHandler.setTableNameExpression(mock(Expression.class))) + .doesNotThrowAnyException(); + } + + @Test + void setFormatOptionsExpressionThrowsExceptionTest() { + assertThatCode(() -> messageHandler.setFormatOptionsExpression(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Format options expression cannot be null."); + } + + @Test + void setFormatOptionsExpressionTest() { + assertThatCode(() -> messageHandler.setFormatOptionsExpression(mock(Expression.class))) + .doesNotThrowAnyException(); + } + + @Test + void setTimeOutThrowsExceptionTest() { + assertThatCode(() -> messageHandler.setTimeout(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Timeout duration must not be null."); + } + + @Test + void setTimeOutTest() { + assertThatCode(() -> messageHandler.setTimeout(Duration.ZERO)) + .doesNotThrowAnyException(); + } } diff --git a/spring-cloud-gcp-cloudfoundry/pom.xml b/spring-cloud-gcp-cloudfoundry/pom.xml index 2550c3bdce..82aecb23f9 100644 --- a/spring-cloud-gcp-cloudfoundry/pom.xml +++ b/spring-cloud-gcp-cloudfoundry/pom.xml @@ -7,7 +7,7 @@ spring-cloud-gcp com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT spring-cloud-gcp-cloudfoundry diff --git a/spring-cloud-gcp-core/pom.xml b/spring-cloud-gcp-core/pom.xml index 58897e6960..30b64e8758 100644 --- a/spring-cloud-gcp-core/pom.xml +++ b/spring-cloud-gcp-core/pom.xml @@ -6,7 +6,7 @@ spring-cloud-gcp com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT spring-cloud-gcp-core diff --git a/spring-cloud-gcp-data-datastore/pom.xml b/spring-cloud-gcp-data-datastore/pom.xml index b7c698dc56..6fa3dafb67 100644 --- a/spring-cloud-gcp-data-datastore/pom.xml +++ b/spring-cloud-gcp-data-datastore/pom.xml @@ -7,9 +7,8 @@ spring-cloud-gcp com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT - com.google.cloud spring-cloud-gcp-data-datastore Spring Framework on Google Cloud Module - Datastore Spring Framework on Google Cloud Datastore Module diff --git a/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/DatastoreTemplate.java b/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/DatastoreTemplate.java index f5e4cf06de..662985815b 100644 --- a/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/DatastoreTemplate.java +++ b/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/DatastoreTemplate.java @@ -84,7 +84,7 @@ import org.springframework.data.mapping.AssociationHandler; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyAccessor; -import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.transaction.support.TransactionSynchronizationManager; @@ -249,7 +249,7 @@ private Collection performFindByKey(Iterable ids, Class entityClass } private List findAllById(Set keys, Class entityClass, ReadContext context) { - List missingKeys = keys.stream().filter(context::notCached).collect(Collectors.toList()); + List missingKeys = keys.stream().filter(context::notCached).toList(); if (!missingKeys.isEmpty()) { List entities = getDatastoreReadWriter().fetch(missingKeys.toArray(new Key[] {})); @@ -305,8 +305,8 @@ private StructuredQuery applyPageable(StructuredQuery query, Pageable pageable) return query; } Cursor cursor = null; - if (pageable instanceof DatastorePageable) { - cursor = ((DatastorePageable) pageable).toCursor(); + if (pageable instanceof DatastorePageable datastorePageable) { + cursor = datastorePageable.toCursor(); } StructuredQuery.Builder builder = query.toBuilder(); if (cursor != null) { @@ -470,7 +470,7 @@ public Map findByIdAsMap(Key key, Class valueType) { Entity entity = getDatastoreReadWriter().get(key); return this.datastoreEntityConverter.readAsMap( - String.class, ClassTypeInformation.from(valueType), entity); + String.class, TypeInformation.of(valueType), entity); } @Override @@ -561,7 +561,7 @@ private List getReferenceEntitiesForSave( List keyValues = StreamSupport.stream((iterableVal).spliterator(), false) .map(o -> KeyValue.of(this.getKey(o, false))) - .collect(Collectors.toList()); + .toList(); value = ListValue.of(keyValues); } else { @@ -656,7 +656,7 @@ private List convertEntitiesForRead( return keys.stream() .map(key -> convertEntityResolveDescendantsAndReferences(entityClass, key, context)) .filter(Objects::nonNull) - .collect(Collectors.toList()); + .toList(); } private T convertEntityResolveDescendantsAndReferences( diff --git a/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/convert/DatastoreCustomConversions.java b/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/convert/DatastoreCustomConversions.java index d6d078cd1f..fd2e886664 100644 --- a/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/convert/DatastoreCustomConversions.java +++ b/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/convert/DatastoreCustomConversions.java @@ -24,9 +24,8 @@ import java.util.List; import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.CustomConversions; -import org.springframework.data.convert.JodaTimeConverters; import org.springframework.data.convert.Jsr310Converters; -import org.springframework.data.convert.ThreeTenBackPortConverters; +import org.springframework.lang.NonNull; /** * Value object to capture custom conversion. {@link DatastoreCustomConversions} @@ -41,17 +40,15 @@ public class DatastoreCustomConversions extends CustomConversions { static { ArrayList> converters = new ArrayList<>(); - converters.addAll(JodaTimeConverters.getConvertersToRegister()); converters.addAll(Jsr310Converters.getConvertersToRegister()); - converters.addAll(ThreeTenBackPortConverters.getConvertersToRegister()); converters.add( new Converter() { @Override - public Long convert(BaseKey key) { + public Long convert(@NonNull BaseKey baseKey) { Long id = null; // embedded entities have IncompleteKey, and have no inner value - if (key instanceof Key) { - id = ((Key) key).getId(); + if (baseKey instanceof Key key) { + id = key.getId(); if (id == null) { throw new DatastoreDataException( "The given key doesn't have a numeric ID but a conversion" @@ -65,11 +62,11 @@ public Long convert(BaseKey key) { converters.add( new Converter() { @Override - public String convert(BaseKey key) { + public String convert(@NonNull BaseKey baseKey) { String name = null; // embedded entities have IncompleteKey, and have no inner value - if (key instanceof Key) { - name = ((Key) key).getName(); + if (baseKey instanceof Key key) { + name = key.getName(); if (name == null) { throw new DatastoreDataException( "The given key doesn't have a String name value but " diff --git a/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/convert/DefaultDatastoreEntityConverter.java b/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/convert/DefaultDatastoreEntityConverter.java index 1717655e40..8f9f18c2e7 100644 --- a/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/convert/DefaultDatastoreEntityConverter.java +++ b/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/convert/DefaultDatastoreEntityConverter.java @@ -36,14 +36,13 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.model.EntityInstantiator; import org.springframework.data.mapping.model.EntityInstantiators; import org.springframework.data.mapping.model.ParameterValueProvider; import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider; -import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.NonNull; import org.springframework.util.Assert; /** @@ -52,7 +51,7 @@ * @since 1.1 */ public class DefaultDatastoreEntityConverter implements DatastoreEntityConverter { - private DatastoreMappingContext mappingContext; + private final DatastoreMappingContext mappingContext; private final EntityInstantiators instantiators = new EntityInstantiators(); @@ -123,7 +122,7 @@ public Map readAsMap( if (entity == null) { return null; } - return readAsMap(entity, ClassTypeInformation.from(HashMap.class)); + return readAsMap(entity, TypeInformation.of(HashMap.class)); } public DatastorePersistentEntity getDiscriminationPersistentEntity( @@ -172,7 +171,7 @@ public R read(Class clazz, BaseEntity entity) { persistentEntity.doWithColumnBackedProperties( datastorePersistentProperty -> { // if a property is a constructor argument, it was already computed on instantiation - if (!persistentEntity.isConstructorArgument(datastorePersistentProperty)) { + if (!persistentEntity.isCreatorArgument(datastorePersistentProperty)) { Object value = propertyValueProvider.getPropertyValue(datastorePersistentProperty); if (value != null) { accessor.setProperty(datastorePersistentProperty, value); @@ -213,13 +212,13 @@ private boolean isDiscriminationFieldMatch( propertyValueProvider.getPropertyValue( entity.getDiscriminationFieldName(), NOT_EMBEDDED, - ClassTypeInformation.from(String[].class))) + TypeInformation.of(String[].class))) [0].equals(entity.getDiscriminatorValue()); } @Override @SuppressWarnings("unchecked") - public void write(Object source, BaseEntity.Builder sink) { + public void write(Object source, @NonNull BaseEntity.Builder sink) { DatastorePersistentEntity persistentEntity = this.mappingContext.getDatastorePersistentEntity(source.getClass()); @@ -228,7 +227,7 @@ public void write(Object source, BaseEntity.Builder sink) { if (!discriminationValues.isEmpty() || discriminationFieldName != null) { sink.set( discriminationFieldName, - discriminationValues.stream().map(StringValue::of).collect(Collectors.toList())); + discriminationValues.stream().map(StringValue::of).toList()); } PersistentPropertyAccessor accessor = persistentEntity.getPropertyAccessor(source); persistentEntity.doWithColumnBackedProperties( @@ -271,7 +270,7 @@ private Value setExcludeFromIndexes(Value convertedVal) { return ListValue.of( (List) ((ListValue) convertedVal) - .get().stream().map(this::setExcludeFromIndexes).collect(Collectors.toList())); + .get().stream().map(this::setExcludeFromIndexes).toList()); } else { return convertedVal.toBuilder().setExcludeFromIndexes(true).build(); } diff --git a/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/convert/TwoStepsConversions.java b/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/convert/TwoStepsConversions.java index 09eee40c10..144f1c5ff6 100644 --- a/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/convert/TwoStepsConversions.java +++ b/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/convert/TwoStepsConversions.java @@ -34,12 +34,12 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; -import java.util.stream.Collectors; import java.util.stream.StreamSupport; import org.springframework.core.convert.ConversionException; import org.springframework.core.convert.converter.Converter; @@ -47,7 +47,6 @@ import org.springframework.core.convert.support.GenericConversionService; import org.springframework.data.convert.CustomConversions; import org.springframework.data.mapping.PersistentEntity; -import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -62,7 +61,7 @@ */ public class TwoStepsConversions implements ReadWriteConversions { private static final Converter BLOB_TO_BYTE_ARRAY_CONVERTER = - new Converter() { + new Converter<>() { @Override public byte[] convert(Blob source) { return source.toByteArray(); @@ -70,7 +69,7 @@ public byte[] convert(Blob source) { }; private static final Converter BYTE_ARRAY_TO_BLOB_CONVERTER = - new Converter() { + new Converter<>() { @Override public Blob convert(byte[] source) { return Blob.copyFrom(source); @@ -106,14 +105,14 @@ public TwoStepsConversions( this.internalConversionService.addConverter(BLOB_TO_BYTE_ARRAY_CONVERTER); } + @SuppressWarnings("unchecked") @Override public T convertOnRead(Object val, Class targetCollectionType, Class targetComponentType) { - return (T) - convertOnRead( - val, - EmbeddedType.NOT_EMBEDDED, - targetCollectionType, - ClassTypeInformation.from(targetComponentType)); + return convertOnRead( + val, + EmbeddedType.NOT_EMBEDDED, + targetCollectionType, + TypeInformation.of(targetComponentType)); } @Override @@ -130,34 +129,25 @@ public T convertOnRead( return convertOnRead(val, embeddedType, collectionType, componentTypeInformation); } + @SuppressWarnings("unchecked") private T convertOnRead( Object val, EmbeddedType embeddedType, Class targetCollectionType, - TypeInformation targetComponentType) { + TypeInformation targetComponentType) { if (val == null) { return null; } - BiFunction, ?> readConverter; - switch (embeddedType) { - case EMBEDDED_MAP: - readConverter = - (x, typeInformation) -> - convertOnReadSingleEmbeddedMap( - x, - typeInformation.getComponentType().getType(), - typeInformation.getMapValueType(), - targetComponentType); - break; - case EMBEDDED_ENTITY: - readConverter = this::convertOnReadSingleEmbedded; - break; - case NOT_EMBEDDED: - readConverter = this::convertOnReadSingle; - break; - default: - throw new DatastoreDataException("Unexpected property embedded type: " + embeddedType); - } + BiFunction, ?> readConverter = switch (embeddedType) { + case EMBEDDED_MAP -> (x, typeInformation) -> + convertOnReadSingleEmbeddedMap( + x, + Objects.requireNonNull(typeInformation.getComponentType()).getType(), + typeInformation.getMapValueType(), + targetComponentType); + case EMBEDDED_ENTITY -> this::convertOnReadSingleEmbedded; + case NOT_EMBEDDED -> this::convertOnReadSingle; + }; if (ValueUtil.isCollectionLike(val.getClass()) && targetCollectionType != null @@ -166,15 +156,15 @@ private T convertOnRead( try { Assert.isInstanceOf( Iterable.class, val, "Value passed to convertOnRead expected to be Iterable"); - List elements = + List elements = StreamSupport.stream(((Iterable) val).spliterator(), false) .map( v -> { - Object o = (v instanceof Value) ? ((Value) v).get() : v; + Object o = (v instanceof Value) ? ((Value) v).get() : v; return readConverter.apply(o, targetComponentType); }) - .collect(Collectors.toList()); - return (T) convertCollection(elements, targetCollectionType); + .toList(); + return convertCollection(elements, targetCollectionType); } catch (ConversionException | DatastoreDataException ex) { throw new DatastoreDataException("Unable process elements of a collection", ex); } @@ -187,10 +177,10 @@ private Map convertOnReadSingleEmbeddedMap( Object value, Class keyType, TypeInformation targetComponentType, - TypeInformation componentType) { + TypeInformation componentType) { Assert.notNull(value, "Cannot convert a null value."); - if (value instanceof BaseEntity) { - return this.datastoreEntityConverter.readAsMap((BaseEntity) value, componentType); + if (value instanceof BaseEntity baseEntity) { + return this.datastoreEntityConverter.readAsMap(baseEntity, componentType); } throw new DatastoreDataException( "Embedded entity was expected, but " + value.getClass() + " found"); @@ -200,9 +190,9 @@ private Map convertOnReadSingleEmbeddedMap( private T convertOnReadSingleEmbedded( Object value, TypeInformation targetTypeInformation) { Assert.notNull(value, "Cannot convert a null value."); - if (value instanceof BaseEntity) { + if (value instanceof BaseEntity baseEntity) { return (T) - this.datastoreEntityConverter.read(targetTypeInformation.getType(), (BaseEntity) value); + this.datastoreEntityConverter.read(targetTypeInformation.getType(), baseEntity); } throw new DatastoreDataException( "Embedded entity was expected, but " + value.getClass() + " found"); @@ -213,8 +203,8 @@ private T convertOnReadSingle(Object val, TypeInformation targetTypeInfor if (val == null) { return null; } - Class targetType = boxIfNeeded(targetTypeInformation.getType()); - Class sourceType = val.getClass(); + Class targetType = boxIfNeeded(targetTypeInformation.getType()); + Class sourceType = val.getClass(); Object result = null; TypeTargets typeTargets = computeTypeTargets(targetType); @@ -271,27 +261,19 @@ private Value convertOnWrite( Function writeConverter = this::convertOnWriteSingle; if (proppertyVal != null) { - switch (embeddedType) { - case EMBEDDED_MAP: - writeConverter = - x -> convertOnWriteSingleEmbeddedMap(x, fieldName, typeInformation.getMapValueType()); - break; - case EMBEDDED_ENTITY: - writeConverter = x -> convertOnWriteSingleEmbedded(x, fieldName); - break; - case NOT_EMBEDDED: - writeConverter = this::convertOnWriteSingle; - break; - default: - throw new DatastoreDataException("Unexpected property embedded type: " + embeddedType); - } + writeConverter = switch (embeddedType) { + case EMBEDDED_MAP -> x -> convertOnWriteSingleEmbeddedMap(x, fieldName, + typeInformation.getMapValueType()); + case EMBEDDED_ENTITY -> x -> convertOnWriteSingleEmbedded(x, fieldName); + case NOT_EMBEDDED -> this::convertOnWriteSingle; + }; } val = ValueUtil.toListIfArray(val); - if (val instanceof Iterable) { + if (val instanceof Iterable iterable) { List> values = new ArrayList<>(); - for (Object propEltValue : (Iterable) val) { + for (Object propEltValue : iterable) { values.add(writeConverter.apply(propEltValue)); } return ListValue.of(values); @@ -315,8 +297,8 @@ private EntityValue applyEntityValueBuilder( .map(PersistentEntity::getIdProperty) .map( id -> - this.datastoreMappingContext - .getPersistentEntity(val.getClass()) + Objects.requireNonNull(this.datastoreMappingContext + .getPersistentEntity(val.getClass())) .getPropertyAccessor(val) .getProperty(id)); @@ -340,7 +322,7 @@ private EntityValue convertOnWriteSingleEmbeddedMap( String field = convertOnReadSingle( convertOnWriteSingle(e.getKey()).get(), - ClassTypeInformation.from(String.class)); + TypeInformation.of(String.class)); builder.set( field, convertOnWrite( @@ -430,15 +412,8 @@ public void registerEntityConverter(DatastoreEntityConverter datastoreEntityConv this.datastoreEntityConverter = datastoreEntityConverter; } - private static class TypeTargets { - private Class firstStepTarget; - - private Class secondStepTarget; - - TypeTargets(Class firstStepTarget, Class secondStepTarget) { - this.firstStepTarget = firstStepTarget; - this.secondStepTarget = secondStepTarget; - } + private record TypeTargets(Class firstStepTarget, + Class secondStepTarget) { Class getFirstStepTarget() { return this.firstStepTarget; diff --git a/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/mapping/DatastorePersistentPropertyImpl.java b/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/mapping/DatastorePersistentPropertyImpl.java index a63e708539..a6338b6171 100644 --- a/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/mapping/DatastorePersistentPropertyImpl.java +++ b/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/core/mapping/DatastorePersistentPropertyImpl.java @@ -16,7 +16,6 @@ package com.google.cloud.spring.data.datastore.core.mapping; -import java.util.stream.Collectors; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; @@ -24,8 +23,6 @@ import org.springframework.data.mapping.model.Property; import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy; import org.springframework.data.mapping.model.SimpleTypeHolder; -import org.springframework.data.util.StreamUtils; -import org.springframework.data.util.TypeInformation; import org.springframework.util.StringUtils; /** @@ -130,13 +127,6 @@ private String getAnnotatedFieldName() { return null; } - @Override - public Iterable> getPersistentEntityTypes() { - return StreamUtils.createStreamFromIterator(super.getPersistentEntityTypes().iterator()) - .filter(typeInfo -> typeInfo.getType().isAnnotationPresent(Entity.class)) - .collect(Collectors.toList()); - } - @Override public boolean isLazyLoaded() { return findAnnotation(LazyReference.class) != null; diff --git a/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/repository/DatastoreRepository.java b/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/repository/DatastoreRepository.java index bfafab46d0..0389f0475e 100644 --- a/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/repository/DatastoreRepository.java +++ b/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/repository/DatastoreRepository.java @@ -17,6 +17,7 @@ package com.google.cloud.spring.data.datastore.repository; import java.util.function.Function; +import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.QueryByExampleExecutor; @@ -28,7 +29,7 @@ * @since 1.1 */ public interface DatastoreRepository - extends PagingAndSortingRepository, QueryByExampleExecutor { + extends PagingAndSortingRepository, QueryByExampleExecutor, CrudRepository { /** * Performs multiple read and write operations in a single transaction. diff --git a/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/repository/config/DatastoreAuditingRegistrar.java b/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/repository/config/DatastoreAuditingRegistrar.java index 45803d1ca3..617e96c941 100644 --- a/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/repository/config/DatastoreAuditingRegistrar.java +++ b/spring-cloud-gcp-data-datastore/src/main/java/com/google/cloud/spring/data/datastore/repository/config/DatastoreAuditingRegistrar.java @@ -16,6 +16,7 @@ package com.google.cloud.spring.data.datastore.repository.config; +import com.google.cloud.spring.data.datastore.core.mapping.DatastoreMappingContext; import com.google.cloud.spring.data.datastore.repository.support.DatastoreAuditingEventListener; import java.lang.annotation.Annotation; import org.springframework.beans.factory.config.BeanDefinition; @@ -24,6 +25,7 @@ import org.springframework.data.auditing.AuditingHandler; import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport; import org.springframework.data.auditing.config.AuditingConfiguration; +import org.springframework.data.mapping.context.PersistentEntities; /** * Registers the annotations and classes for providing auditing support in Spring Data Cloud @@ -35,8 +37,6 @@ public class DatastoreAuditingRegistrar extends AuditingBeanDefinitionRegistrarS private static final String AUDITING_HANDLER_BEAN_NAME = "datastoreAuditingHandler"; - private static final String MAPPING_CONTEXT_BEAN_NAME = "datastoreMappingContext"; - @Override protected Class getAnnotation() { return EnableDatastoreAuditing.class; @@ -57,10 +57,9 @@ protected void registerAuditListenerBeanDefinition( @Override protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder( AuditingConfiguration configuration) { - BeanDefinitionBuilder builder = - configureDefaultAuditHandlerAttributes( - configuration, BeanDefinitionBuilder.rootBeanDefinition(AuditingHandler.class)); - return builder.addConstructorArgReference(MAPPING_CONTEXT_BEAN_NAME); + return configureDefaultAuditHandlerAttributes( + configuration, BeanDefinitionBuilder.rootBeanDefinition(AuditingHandler.class)) + .addConstructorArgValue(PersistentEntities.of(new DatastoreMappingContext())); } @Override diff --git a/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/DatastoreTemplateAuditingTests.java b/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/DatastoreTemplateAuditingTests.java index 7c102dc377..497c7eaad6 100644 --- a/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/DatastoreTemplateAuditingTests.java +++ b/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/DatastoreTemplateAuditingTests.java @@ -31,8 +31,7 @@ import com.google.cloud.spring.data.datastore.core.mapping.DatastoreMappingContext; import com.google.cloud.spring.data.datastore.core.mapping.Entity; import com.google.cloud.spring.data.datastore.repository.config.EnableDatastoreAuditing; -import java.time.LocalDate; -import java.time.LocalDateTime; +import java.time.Instant; import java.util.Collections; import java.util.Optional; import org.junit.jupiter.api.Test; @@ -52,7 +51,7 @@ @ContextConfiguration class DatastoreTemplateAuditingTests { - private static final LocalDateTime LONG_AGO = LocalDate.parse("2000-01-01").atStartOfDay(); + private static final Instant LONG_AGO = Instant.parse("2000-01-01T00:00:00.00Z"); @Autowired DatastoreTemplate datastoreTemplate; @Autowired Datastore datastore; @@ -134,7 +133,7 @@ public DatastoreTemplate datastoreTemplate( FullEntity testEntity = invocation.getArgument(0); assertThat(testEntity.getTimestamp("lastTouched")).isNotNull(); assertThat(testEntity.getTimestamp("lastTouched")) - .isGreaterThan(Timestamp.of(java.sql.Timestamp.valueOf(LONG_AGO))); + .isGreaterThan(Timestamp.of(java.sql.Timestamp.from(LONG_AGO))); assertThat(testEntity.getString("lastUser")).isEqualTo("test_user"); return null; }); @@ -154,6 +153,7 @@ private static class TestEntity { @LastModifiedBy String lastUser; - @LastModifiedDate LocalDateTime lastTouched; + @LastModifiedDate + Instant lastTouched; } } diff --git a/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/DatastoreTemplateTests.java b/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/DatastoreTemplateTests.java index 5f35fd7ce6..25f717d56b 100644 --- a/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/DatastoreTemplateTests.java +++ b/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/DatastoreTemplateTests.java @@ -95,7 +95,7 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; -import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; /** Tests for the Datastore Template. */ class DatastoreTemplateTests { @@ -1407,7 +1407,7 @@ void findByIdAsMapTest() { this.datastoreTemplate.findByIdAsMap(keyForMap, Long.class); verify(this.datastoreEntityConverter, times(1)) - .readAsMap(String.class, ClassTypeInformation.from(Long.class), datastoreEntity); + .readAsMap(String.class, TypeInformation.of(Long.class), datastoreEntity); } @Test diff --git a/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/DatastoreTransactionManagerTests.java b/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/DatastoreTransactionManagerTests.java index 65b4390d6f..f357a3eb50 100644 --- a/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/DatastoreTransactionManagerTests.java +++ b/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/DatastoreTransactionManagerTests.java @@ -101,15 +101,15 @@ void testDoCommit() { @Test void testDoCommitFailure() { - + DatastoreException exception = new DatastoreException(0, "", ""); when(this.transaction.isActive()).thenReturn(true); - when(this.transaction.commit()).thenThrow(new DatastoreException(0, "", "")); + when(this.transaction.commit()).thenThrow(exception); this.tx.setTransaction(this.transaction); assertThatThrownBy(() -> this.manager.doCommit(this.status)) .isInstanceOf(TransactionSystemException.class) - .hasMessage("Cloud Datastore transaction failed to commit.; " - + "nested exception is com.google.cloud.datastore.DatastoreException: "); + .hasMessage("Cloud Datastore transaction failed to commit.") + .hasCause(exception); } @Test @@ -130,15 +130,15 @@ void testDoRollback() { @Test void testDoRollbackFailure() { - + DatastoreException exception = new DatastoreException(0, "", ""); when(this.transaction.isActive()).thenReturn(true); - doThrow(new DatastoreException(0, "", "")).when(this.transaction).rollback(); + doThrow(exception).when(this.transaction).rollback(); this.tx.setTransaction(this.transaction); assertThatThrownBy(() -> this.manager.doRollback(this.status)) .isInstanceOf(TransactionSystemException.class) - .hasMessage("Cloud Datastore transaction failed to rollback.; " - + "nested exception is com.google.cloud.datastore.DatastoreException: "); + .hasMessage("Cloud Datastore transaction failed to rollback.") + .hasCause(exception); } @Test diff --git a/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/convert/DefaultDatastoreEntityConverterTests.java b/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/convert/DefaultDatastoreEntityConverterTests.java index 8a67579865..a6d14b2906 100644 --- a/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/convert/DefaultDatastoreEntityConverterTests.java +++ b/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/convert/DefaultDatastoreEntityConverterTests.java @@ -230,8 +230,8 @@ void testWrongTypeReadException() { .isInstanceOf(DatastoreDataException.class) .hasMessageContaining("Unable to read " + "com.google.cloud.spring.data.datastore.core.convert.TestDatastoreItem entity") - .hasMessageContaining("Unable to read property boolField") - .hasMessageContaining("Unable to convert class java.lang.Long to class java.lang.Boolean"); + .hasStackTraceContaining("Unable to read property boolField") + .hasStackTraceContaining("Unable to convert class java.lang.Long to class java.lang.Boolean"); } @Test @@ -258,8 +258,8 @@ void testWrongTypeReadExceptionList() { .isInstanceOf(DatastoreDataException.class) .hasMessageContaining("Unable to read " + "com.google.cloud.spring.data.datastore.core.convert.TestDatastoreItem entity") - .hasMessageContaining("Unable to read property boolField") - .hasMessageContaining("Unable to convert class " + .hasStackTraceContaining("Unable to read property boolField") + .hasStackTraceContaining("Unable to convert class " + "com.google.common.collect.SingletonImmutableList to class java.lang.Boolean"); } @@ -365,7 +365,7 @@ void testUnsupportedTypeWriteException() { assertThatThrownBy(() -> ENTITY_CONVERTER.write(item, builder)) .isInstanceOf(DatastoreDataException.class) .hasMessageContaining("Unable to write testItemUnsupportedFields.unsupportedField") - .hasMessageContaining("Unable to convert class " + .hasStackTraceContaining("Unable to convert class " + "com.google.cloud.spring.data.datastore.core.convert." + "TestItemUnsupportedFields$NewType to Datastore supported type."); } @@ -402,7 +402,7 @@ void testUnsupportedTypeWrite() { @Test void testCollectionFieldsUnsupportedCollection() { - ComparableBeanContextSupport comparableBeanContextSupport = new ComparableBeanContextSupport(); + ComparableBeanContextSupport comparableBeanContextSupport = new ComparableBeanContextSupport<>(); comparableBeanContextSupport.add("this implementation of Collection"); comparableBeanContextSupport.add("is unsupported out of the box!"); @@ -423,10 +423,10 @@ void testCollectionFieldsUnsupportedCollection() { .isInstanceOf(DatastoreDataException.class) .hasMessageContaining("Unable to read" + " com.google.cloud.spring.data.datastore.core.convert.TestDatastoreItemCollections" - + " entity;") - .hasMessageContaining("Unable to read property beanContext;") - .hasMessageContaining("Failed to convert from type [java.util.ArrayList] to type" - + " [com.google.cloud.spring.data.datastore.core.convert.TestDatastoreItemCollections$ComparableBeanContextSupport]"); + + " entity") + .hasStackTraceContaining("Unable to read property beanContext") + .hasStackTraceContaining("Failed to convert from type [java.util.ImmutableCollections$ListN] to type" + + " [com.google.cloud.spring.data.datastore.core.convert.TestDatastoreItemCollections$ComparableBeanContextSupport]"); } @Test @@ -550,7 +550,7 @@ void testCollectionFieldsUnsupported() { assertThatThrownBy(() -> ENTITY_CONVERTER.write(item, builder)) .isInstanceOf(DatastoreDataException.class) .hasMessageContaining("Unable to write collectionOfUnsupportedTypes.unsupportedElts") - .hasMessageContaining("Unable to convert " + .hasStackTraceContaining("Unable to convert " + "class com.google.cloud.spring.data.datastore.core.convert." + "TestItemUnsupportedFields$NewType to Datastore supported type."); } @@ -600,12 +600,12 @@ void testCollectionFieldsUnsupportedWriteReadException() { Class parameter = TestItemUnsupportedFields.CollectionOfUnsupportedTypes.class; assertThatThrownBy(() -> entityConverter.read(parameter, entity)) - .hasMessageContaining("Unable to read property unsupportedElts") - .hasMessageContaining("Unable process elements of a collection") - .hasMessageContaining("No converter found capable of converting from type [java.lang.Integer] " + .hasStackTraceContaining("Unable to read property unsupportedElts") + .hasStackTraceContaining("Unable process elements of a collection") + .hasStackTraceContaining("No converter found capable of converting from type [java.lang.Integer] " + "to type [com.google.cloud.spring.data.datastore.core.convert." + "TestItemUnsupportedFields$NewType]") - .hasMessageContaining("Unable to read com.google.cloud.spring.data.datastore.core.convert." + .hasStackTraceContaining("Unable to read com.google.cloud.spring.data.datastore.core.convert." + "TestItemUnsupportedFields$CollectionOfUnsupportedTypes entity") .isInstanceOf(DatastoreDataException.class); } @@ -882,7 +882,7 @@ void privateCustomMapExceptionTest() { entityConverter.read(ServiceConfigurationPrivateCustomMap.class, entity); }) .isInstanceOf(DatastoreDataException.class) - .hasMessageContaining( + .hasStackTraceContaining( "Unable to create an instance of a custom map type: " + "class com.google.cloud.spring.data.datastore.core.convert." + "DefaultDatastoreEntityConverterTests$PrivateCustomMap " @@ -896,7 +896,7 @@ void testMismatchedStringIdLongProperty() { assertThatThrownBy(() -> ENTITY_CONVERTER.read(LongIdEntity.class, testEntity)) .isInstanceOf(ConversionFailedException.class) - .hasMessageContaining("The given key doesn't have a numeric ID but a conversion to Long was attempted"); + .hasStackTraceContaining("The given key doesn't have a numeric ID but a conversion to Long was attempted"); } @Test @@ -906,7 +906,7 @@ void testMismatchedLongIdStringProperty() { assertThatThrownBy(() -> ENTITY_CONVERTER.read(StringIdEntity.class, testEntity)) .isInstanceOf(ConversionFailedException.class) - .hasMessageContaining("The given key doesn't have a String name value but a conversion to String was attempted"); + .hasStackTraceContaining("The given key doesn't have a String name value but a conversion to String was attempted"); } private Entity.Builder getEntityBuilder() { diff --git a/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/convert/EntityPropertyValueProviderTests.java b/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/convert/EntityPropertyValueProviderTests.java index ce57c28ee7..19c91d5cc8 100644 --- a/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/convert/EntityPropertyValueProviderTests.java +++ b/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/convert/EntityPropertyValueProviderTests.java @@ -127,8 +127,7 @@ void testException() { assertThatThrownBy(() -> provider.getPropertyValue(testDpe)) .isInstanceOf(DatastoreDataException.class) - .hasMessage("Unable to read property boolField; nested exception is " - + "com.google.cloud.spring.data.datastore.core.mapping.DatastoreDataException: " - + "Unable to convert class java.lang.Long to class java.lang.Boolean"); + .hasMessageContaining("Unable to read property boolField") + .hasStackTraceContaining("Unable to convert class java.lang.Long to class java.lang.Boolean"); } } diff --git a/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/convert/TestDatastoreItemCollections.java b/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/convert/TestDatastoreItemCollections.java index 4e8a609335..c627ed5e60 100644 --- a/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/convert/TestDatastoreItemCollections.java +++ b/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/convert/TestDatastoreItemCollections.java @@ -41,7 +41,7 @@ public class TestDatastoreItemCollections { public TestDatastoreItemCollections( List intList, - ComparableBeanContextSupport beanContext, + ComparableBeanContextSupport beanContext, String[] stringArray, boolean[] boolArray, byte[][] bytes, @@ -133,7 +133,7 @@ private boolean equal(Object a, Object b) { * BeanContextSupport does not provide an equals() implementation. This subclass overrides {@code * equals/hashCode} and keeps a simple list of values to enable test verification. */ - static class ComparableBeanContextSupport extends BeanContextSupport { + static class ComparableBeanContextSupport extends BeanContextSupport { private Set values = new HashSet<>(); @Override diff --git a/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/mapping/DatastoreMappingContextTests.java b/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/mapping/DatastoreMappingContextTests.java index 85cf212693..c8540b9294 100644 --- a/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/mapping/DatastoreMappingContextTests.java +++ b/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/mapping/DatastoreMappingContextTests.java @@ -25,7 +25,6 @@ import com.google.cloud.Timestamp; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; -import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; /** Tests for the `DatastoreMappingContext`. */ @@ -37,7 +36,7 @@ void testApplicationContextPassing() { ApplicationContext applicationContext = mock(ApplicationContext.class); context.setApplicationContext(applicationContext); - context.createPersistentEntity(ClassTypeInformation.from(Object.class)); + context.createPersistentEntity(TypeInformation.of(Object.class)); verify(mockEntity, times(1)).setApplicationContext(applicationContext); } @@ -47,7 +46,7 @@ void testApplicationContextIsNotSet() { DatastorePersistentEntityImpl mockEntity = mock(DatastorePersistentEntityImpl.class); DatastoreMappingContext context = createDatastoreMappingContextWith(mockEntity); - context.createPersistentEntity(ClassTypeInformation.from(Object.class)); + context.createPersistentEntity(TypeInformation.of(Object.class)); verifyNoMoreInteractions(mockEntity); } diff --git a/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/mapping/DatastorePersistentEntityImplTests.java b/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/mapping/DatastorePersistentEntityImplTests.java index 2d17b21c9c..b1a20fd466 100644 --- a/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/mapping/DatastorePersistentEntityImplTests.java +++ b/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/core/mapping/DatastorePersistentEntityImplTests.java @@ -28,6 +28,7 @@ import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.SimplePropertyHandler; import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; import org.springframework.expression.spel.SpelEvaluationException; /** Tests for the Datastore Persistent Entity. */ @@ -38,7 +39,7 @@ class DatastorePersistentEntityImplTests { @Test void testTableName() { DatastorePersistentEntityImpl entity = - new DatastorePersistentEntityImpl<>(ClassTypeInformation.from(TestEntity.class), null); + new DatastorePersistentEntityImpl<>(TypeInformation.of(TestEntity.class), null); assertThat(entity.kindName()).isEqualTo("custom_test_kind"); } @@ -46,7 +47,7 @@ void testTableName() { void testRawTableName() { DatastorePersistentEntityImpl entity = new DatastorePersistentEntityImpl<>( - ClassTypeInformation.from(EntityNoCustomName.class), null); + TypeInformation.of(EntityNoCustomName.class), null); assertThat(entity.kindName()).isEqualTo("entityNoCustomName"); } @@ -55,7 +56,7 @@ void testRawTableName() { void testEmptyCustomTableName() { DatastorePersistentEntityImpl entity = new DatastorePersistentEntityImpl<>( - ClassTypeInformation.from(EntityEmptyCustomName.class), null); + TypeInformation.of(EntityEmptyCustomName.class), null); assertThat(entity.kindName()).isEqualTo("entityEmptyCustomName"); } @@ -64,9 +65,9 @@ void testEmptyCustomTableName() { void testExpressionResolutionWithoutApplicationContext() { DatastorePersistentEntityImpl entity = new DatastorePersistentEntityImpl<>( - ClassTypeInformation.from(EntityWithExpression.class), null); + TypeInformation.of(EntityWithExpression.class), null); - assertThatThrownBy(() -> entity.kindName()) + assertThatThrownBy(entity::kindName) .isInstanceOf(SpelEvaluationException.class) .hasMessageContaining("Property or field 'kindPostfix' cannot be found on null"); } @@ -75,7 +76,7 @@ void testExpressionResolutionWithoutApplicationContext() { void testExpressionResolutionFromApplicationContext() { DatastorePersistentEntityImpl entity = new DatastorePersistentEntityImpl<>( - ClassTypeInformation.from(EntityWithExpression.class), null); + TypeInformation.of(EntityWithExpression.class), null); ApplicationContext applicationContext = mock(ApplicationContext.class); when(applicationContext.getBean("kindPostfix")).thenReturn("something"); @@ -103,7 +104,7 @@ void testGetIdPropertyOrFail() { DatastorePersistentEntity testEntity = new DatastoreMappingContext().getPersistentEntity(EntityWithNoId.class); - assertThatThrownBy(() -> testEntity.getIdPropertyOrFail()) + assertThatThrownBy(testEntity::getIdPropertyOrFail) .isInstanceOf(DatastoreDataException.class) .hasMessage("An ID property was required but does not exist for the type: " + "class com.google.cloud.spring.data.datastore.core.mapping." diff --git a/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/repository/query/PartTreeDatastoreQueryTests.java b/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/repository/query/PartTreeDatastoreQueryTests.java index 29d18390b4..5bd326fc47 100644 --- a/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/repository/query/PartTreeDatastoreQueryTests.java +++ b/spring-cloud-gcp-data-datastore/src/test/java/com/google/cloud/spring/data/datastore/repository/query/PartTreeDatastoreQueryTests.java @@ -73,7 +73,7 @@ import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.repository.query.DefaultParameters; -import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; /** Tests for Part-Tree Datastore Query Methods. */ @@ -1062,7 +1062,7 @@ private void queryWithMockResult( if (mockOptionalNullable) { DefaultRepositoryMetadata mockMetadata = mock(DefaultRepositoryMetadata.class); doReturn(m.getReturnType()).when(mockMetadata).getReturnedDomainClass(m); - doReturn(ClassTypeInformation.fromReturnTypeOf(m)).when(mockMetadata).getReturnType(m); + doReturn(TypeInformation.fromReturnTypeOf(m)).when(mockMetadata).getReturnType(m); DatastoreQueryMethod datastoreQueryMethod = new DatastoreQueryMethod(m, mockMetadata, mock(SpelAwareProxyProjectionFactory.class)); doReturn(datastoreQueryMethod.isOptionalReturnType()) diff --git a/spring-cloud-gcp-data-firestore/pom.xml b/spring-cloud-gcp-data-firestore/pom.xml index 32d55217b9..179a1437c5 100644 --- a/spring-cloud-gcp-data-firestore/pom.xml +++ b/spring-cloud-gcp-data-firestore/pom.xml @@ -7,7 +7,7 @@ spring-cloud-gcp com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT spring-cloud-gcp-data-firestore diff --git a/spring-cloud-gcp-data-firestore/src/test/java/com/google/cloud/spring/data/firestore/FirestoreTemplateTests.java b/spring-cloud-gcp-data-firestore/src/test/java/com/google/cloud/spring/data/firestore/FirestoreTemplateTests.java index 448b653737..4e388b01ca 100644 --- a/spring-cloud-gcp-data-firestore/src/test/java/com/google/cloud/spring/data/firestore/FirestoreTemplateTests.java +++ b/spring-cloud-gcp-data-firestore/src/test/java/com/google/cloud/spring/data/firestore/FirestoreTemplateTests.java @@ -69,7 +69,7 @@ void setup() { this.firestoreTemplate = new FirestoreTemplate( this.firestoreStub, - this.parent, + parent, new FirestoreDefaultClassMapper(mappingContext), mappingContext); } @@ -91,7 +91,7 @@ void findAllTest() { .build(); RunQueryRequest request = RunQueryRequest.newBuilder() - .setParent(this.parent) + .setParent(parent) .setStructuredQuery(structuredQuery) .build(); @@ -311,7 +311,7 @@ void findByIdTest() { .verifyComplete(); GetDocumentRequest request = - GetDocumentRequest.newBuilder().setName(this.parent + "/testEntities/" + "e1").build(); + GetDocumentRequest.newBuilder().setName(parent + "/testEntities/" + "e1").build(); verify(this.firestoreStub, times(1)).getDocument(eq(request), any()); } @@ -332,12 +332,12 @@ void findByIdErrorTest() { .expectErrorMatches( e -> e instanceof FirestoreDataException - && e.getMessage().contains("Firestore error") + && e.getCause().getMessage().contains("Firestore error") && e.getMessage().contains("Error while reading entries by id")) .verify(); GetDocumentRequest request = - GetDocumentRequest.newBuilder().setName(this.parent + "/testEntities/" + "e1").build(); + GetDocumentRequest.newBuilder().setName(parent + "/testEntities/" + "e1").build(); verify(this.firestoreStub, times(1)).getDocument(eq(request), any()); } @@ -360,7 +360,7 @@ void findByIdNotFoundTest() { .verifyComplete(); GetDocumentRequest request = - GetDocumentRequest.newBuilder().setName(this.parent + "/testEntities/" + "e1").build(); + GetDocumentRequest.newBuilder().setName(parent + "/testEntities/" + "e1").build(); verify(this.firestoreStub, times(1)).getDocument(eq(request), any()); } @@ -368,13 +368,13 @@ void findByIdNotFoundTest() { @Test void findAllByIdTest() { GetDocumentRequest request1 = - GetDocumentRequest.newBuilder().setName(this.parent + "/testEntities/e1").build(); + GetDocumentRequest.newBuilder().setName(parent + "/testEntities/e1").build(); GetDocumentRequest request2 = - GetDocumentRequest.newBuilder().setName(this.parent + "/testEntities/e2").build(); + GetDocumentRequest.newBuilder().setName(parent + "/testEntities/e2").build(); GetDocumentRequest request3 = - GetDocumentRequest.newBuilder().setName(this.parent + "/testEntities/e3").build(); + GetDocumentRequest.newBuilder().setName(parent + "/testEntities/e3").build(); doAnswer( invocation -> { @@ -443,7 +443,7 @@ void countTest() { .build(); RunQueryRequest request = RunQueryRequest.newBuilder() - .setParent(this.parent) + .setParent(parent) .setStructuredQuery(structuredQuery) .build(); @@ -479,7 +479,7 @@ void countWithQueryTest() { RunQueryRequest request = RunQueryRequest.newBuilder() - .setParent(this.parent) + .setParent(parent) .setStructuredQuery(expectedBuilder) .build(); @@ -507,7 +507,7 @@ private void addWhere(StructuredQuery.Builder builder) { void existsByIdTest() { GetDocumentRequest request = GetDocumentRequest.newBuilder() - .setName(this.parent + "/testEntities/" + "e1") + .setName(parent + "/testEntities/" + "e1") .setMask(DocumentMask.newBuilder().addFieldPaths("__name__").build()) .build(); @@ -535,7 +535,7 @@ void existsByIdTest() { void existsByIdNotFoundTest() { GetDocumentRequest request = GetDocumentRequest.newBuilder() - .setName(this.parent + "/testEntities/" + "e1") + .setName(parent + "/testEntities/" + "e1") .setMask(DocumentMask.newBuilder().addFieldPaths("__name__").build()) .build(); @@ -579,7 +579,7 @@ void withParentTest_entityReference() { GetDocumentRequest request = GetDocumentRequest.newBuilder() - .setName(this.parent + "/testEntities/parent/testEntities/child") + .setName(parent + "/testEntities/parent/testEntities/child") .build(); verify(this.firestoreStub, times(1)).getDocument(eq(request), any()); @@ -605,7 +605,7 @@ void withParentTest_idClassReference() { GetDocumentRequest request = GetDocumentRequest.newBuilder() - .setName(this.parent + "/testEntities/parent/testEntities/child") + .setName(parent + "/testEntities/parent/testEntities/child") .build(); verify(this.firestoreStub, times(1)).getDocument(eq(request), any()); @@ -704,7 +704,7 @@ public int hashCode() { } @Document(collectionName = "testEntities") - class TestEntityUpdateTimeVersion { + static class TestEntityUpdateTimeVersion { @DocumentId public String id; @UpdateTime(version = true) @@ -724,12 +724,10 @@ public boolean equals(Object o) { if (this == o) { return true; } - if (!(o instanceof TestEntityUpdateTimeVersion)) { + if (!(o instanceof TestEntityUpdateTimeVersion that)) { return false; } - TestEntityUpdateTimeVersion that = (TestEntityUpdateTimeVersion) o; - if (!Objects.equals(id, that.id)) { return false; } @@ -745,7 +743,7 @@ public int hashCode() { } @Document(collectionName = "testEntities") - class TestEntityUpdateTime { + static class TestEntityUpdateTime { @DocumentId public String id; @UpdateTime public Timestamp updateTime; @@ -764,12 +762,10 @@ public boolean equals(Object o) { if (this == o) { return true; } - if (!(o instanceof TestEntityUpdateTime)) { + if (!(o instanceof TestEntityUpdateTime that)) { return false; } - TestEntityUpdateTime that = (TestEntityUpdateTime) o; - if (!Objects.equals(id, that.id)) { return false; } diff --git a/spring-cloud-gcp-data-firestore/src/test/java/com/google/cloud/spring/data/firestore/mapping/FirestorePersistentEntityImplTests.java b/spring-cloud-gcp-data-firestore/src/test/java/com/google/cloud/spring/data/firestore/mapping/FirestorePersistentEntityImplTests.java index 53e3daef4c..1ea8d1b49f 100644 --- a/spring-cloud-gcp-data-firestore/src/test/java/com/google/cloud/spring/data/firestore/mapping/FirestorePersistentEntityImplTests.java +++ b/spring-cloud-gcp-data-firestore/src/test/java/com/google/cloud/spring/data/firestore/mapping/FirestorePersistentEntityImplTests.java @@ -20,7 +20,7 @@ import com.google.cloud.spring.data.firestore.Document; import org.junit.jupiter.api.Test; -import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; /** Tests for {@link FirestorePersistentEntityImpl}. */ class FirestorePersistentEntityImplTests { @@ -28,14 +28,14 @@ class FirestorePersistentEntityImplTests { @Test void testSetCollectionName() { FirestorePersistentEntity firestorePersistentEntity = - new FirestorePersistentEntityImpl<>(ClassTypeInformation.from(Student.class)); + new FirestorePersistentEntityImpl<>(TypeInformation.of(Student.class)); assertThat(firestorePersistentEntity.collectionName()).isEqualTo("student"); } @Test void testInferCollectionName() { FirestorePersistentEntity firestorePersistentEntity = - new FirestorePersistentEntityImpl<>(ClassTypeInformation.from(Employee.class)); + new FirestorePersistentEntityImpl<>(TypeInformation.of(Employee.class)); assertThat(firestorePersistentEntity.collectionName()).isEqualTo("employee_table"); } diff --git a/spring-cloud-gcp-data-firestore/src/test/java/com/google/cloud/spring/data/firestore/mapping/FirestorePersistentPropertyImplTest.java b/spring-cloud-gcp-data-firestore/src/test/java/com/google/cloud/spring/data/firestore/mapping/FirestorePersistentPropertyImplTest.java index 9de04f9dde..e45f52b307 100644 --- a/spring-cloud-gcp-data-firestore/src/test/java/com/google/cloud/spring/data/firestore/mapping/FirestorePersistentPropertyImplTest.java +++ b/spring-cloud-gcp-data-firestore/src/test/java/com/google/cloud/spring/data/firestore/mapping/FirestorePersistentPropertyImplTest.java @@ -28,7 +28,7 @@ import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.model.Property; import org.springframework.data.mapping.model.SimpleTypeHolder; -import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; import org.springframework.test.context.junit.jupiter.SpringExtension; /** Tests for {@link FirestorePersistentPropertyImpl}. */ @@ -46,7 +46,7 @@ void testGetFieldName_isIdProperty() throws NoSuchFieldException { when(mockProperty.getName()).thenReturn("id"); when(mockProperty.getField()).thenReturn(Optional.of(TestEntity.class.getField("id"))); when(mockPersistentEntity.getTypeInformation()) - .thenReturn(ClassTypeInformation.from(TestEntity.class)); + .thenReturn(TypeInformation.of(TestEntity.class)); when(mockPersistentEntity.getType()).thenReturn(TestEntity.class); FirestorePersistentPropertyImpl firestorePersistentProperty = diff --git a/spring-cloud-gcp-data-spanner/pom.xml b/spring-cloud-gcp-data-spanner/pom.xml index d2773fc5f7..a02c377b45 100644 --- a/spring-cloud-gcp-data-spanner/pom.xml +++ b/spring-cloud-gcp-data-spanner/pom.xml @@ -7,9 +7,8 @@ spring-cloud-gcp com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT - com.google.cloud spring-cloud-gcp-data-spanner Spring Framework on Google Cloud Module - Cloud Spanner Spring Framework on Google Cloud Cloud Spanner Module diff --git a/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/core/convert/ConverterAwareMappingSpannerEntityReader.java b/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/core/convert/ConverterAwareMappingSpannerEntityReader.java index c171bf6988..ed42f46fd4 100644 --- a/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/core/convert/ConverterAwareMappingSpannerEntityReader.java +++ b/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/core/convert/ConverterAwareMappingSpannerEntityReader.java @@ -22,8 +22,8 @@ import com.google.cloud.spring.data.spanner.core.mapping.SpannerPersistentEntity; import com.google.cloud.spring.data.spanner.core.mapping.SpannerPersistentProperty; import java.util.Set; +import org.springframework.data.mapping.InstanceCreatorMetadata; import org.springframework.data.mapping.PersistentPropertyAccessor; -import org.springframework.data.mapping.PreferredConstructor; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.model.EntityInstantiator; import org.springframework.data.mapping.model.EntityInstantiators; @@ -39,9 +39,9 @@ class ConverterAwareMappingSpannerEntityReader implements SpannerEntityReader { private final SpannerMappingContext spannerMappingContext; - private EntityInstantiators instantiators; + private final EntityInstantiators instantiators; - private SpannerReadConverter converter; + private final SpannerReadConverter converter; ConverterAwareMappingSpannerEntityReader( SpannerMappingContext spannerMappingContext, SpannerReadConverter spannerReadConverter) { @@ -75,8 +75,8 @@ public R read( StructPropertyValueProvider propertyValueProvider = new StructPropertyValueProvider(structAccessor, this.converter, this, allowMissingColumns); - PreferredConstructor persistenceConstructor = - persistentEntity.getPersistenceConstructor(); + InstanceCreatorMetadata instanceCreatorMetadata = + persistentEntity.getInstanceCreatorMetadata(); // @formatter:off ParameterValueProvider parameterValueProvider = @@ -105,7 +105,7 @@ public R read( includeColumns, readAllColumns, allowMissingColumns, - persistenceConstructor)) { + instanceCreatorMetadata)) { Object value = propertyValueProvider.getPropertyValue(spannerPersistentProperty); accessor.setProperty(spannerPersistentProperty, value); @@ -122,7 +122,7 @@ private boolean shouldSkipProperty( Set includeColumns, boolean readAllColumns, boolean allowMissingColumns, - PreferredConstructor persistenceConstructor) { + InstanceCreatorMetadata persistenceConstructor) { String columnName = spannerPersistentProperty.getColumnName(); boolean notRequiredByPartialRead = !readAllColumns && !includeColumns.contains(columnName); @@ -130,7 +130,7 @@ private boolean shouldSkipProperty( || notRequiredByPartialRead || isMissingColumn(struct, allowMissingColumns, columnName) || struct.isNull(columnName) - || persistenceConstructor.isConstructorParameter(spannerPersistentProperty); + || persistenceConstructor.isCreatorParameter(spannerPersistentProperty); } private boolean isMissingColumn( diff --git a/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerCompositeKeyProperty.java b/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerCompositeKeyProperty.java index d15e5b9bf4..a0c85b22a3 100644 --- a/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerCompositeKeyProperty.java +++ b/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerCompositeKeyProperty.java @@ -30,7 +30,6 @@ import org.springframework.data.mapping.Association; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentPropertyAccessor; -import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -176,12 +175,7 @@ public Class getType() { @Override public TypeInformation getTypeInformation() { - return ClassTypeInformation.from(getType()); - } - - @Override - public Iterable> getPersistentEntityTypes() { - return Collections.emptySet(); + return TypeInformation.of(getType()); } @Nullable diff --git a/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerPersistentEntityImpl.java b/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerPersistentEntityImpl.java index 5e4293e555..df20f72ea3 100644 --- a/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerPersistentEntityImpl.java +++ b/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerPersistentEntityImpl.java @@ -19,7 +19,6 @@ import com.google.cloud.spanner.Key; import com.google.cloud.spanner.Type; import com.google.cloud.spring.data.spanner.core.convert.ConversionUtils; -import com.google.cloud.spring.data.spanner.core.convert.ConverterAwareMappingSpannerEntityProcessor; import com.google.cloud.spring.data.spanner.core.convert.SpannerEntityProcessor; import com.google.cloud.spring.data.spanner.core.convert.SpannerEntityWriter; import java.util.ArrayList; @@ -79,7 +78,7 @@ public class SpannerPersistentEntityImpl private final SpannerEntityProcessor spannerEntityProcessor; - private StandardEvaluationContext context; + private final StandardEvaluationContext context; private SpannerCompositeKeyProperty idProperty; @@ -91,22 +90,6 @@ public class SpannerPersistentEntityImpl private final Set> jsonProperties = new HashSet<>(); - /** - * Creates a {@link SpannerPersistentEntityImpl}. - * - * @param information type information about the underlying entity type. - * @deprecated remove on next major release. use - * {@link #SpannerPersistentEntityImpl(TypeInformation, - * SpannerMappingContext, SpannerEntityProcessor)} instead. - */ - @Deprecated - public SpannerPersistentEntityImpl(TypeInformation information) { - this( - information, - new SpannerMappingContext(), - new ConverterAwareMappingSpannerEntityProcessor(new SpannerMappingContext())); - } - /** * Creates a {@link SpannerPersistentEntityImpl}. * @@ -467,8 +450,7 @@ public void setProperty(PersistentProperty property, @Nullable Object value) SpannerPersistentProperty[] primaryKeyProperties = owner.getPrimaryKeyProperties(); Iterator partsIterator; - if (value instanceof Key) { - Key keyValue = (Key) value; + if (value instanceof Key keyValue) { if (keyValue.size() != primaryKeyProperties.length) { throw new SpannerDataException( "The number of key parts is not equal to the number of primary key properties"); diff --git a/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerPersistentPropertyImpl.java b/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerPersistentPropertyImpl.java index a619135258..8de60840ab 100644 --- a/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerPersistentPropertyImpl.java +++ b/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerPersistentPropertyImpl.java @@ -19,9 +19,9 @@ import com.google.cloud.spanner.Type.Code; import com.google.spanner.v1.TypeCode; import java.util.List; +import java.util.Objects; import java.util.OptionalInt; import java.util.OptionalLong; -import java.util.stream.Collectors; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PersistentEntity; @@ -32,6 +32,7 @@ import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.util.StreamUtils; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.NonNull; import org.springframework.util.StringUtils; /** @@ -44,7 +45,7 @@ public class SpannerPersistentPropertyImpl extends AnnotationBasedPersistentProperty implements SpannerPersistentProperty { - private FieldNamingStrategy fieldNamingStrategy; + private final FieldNamingStrategy fieldNamingStrategy; /** * Creates a new {@link SpannerPersistentPropertyImpl}. @@ -68,13 +69,15 @@ public class SpannerPersistentPropertyImpl /** Only provides types that are also annotated with {@link Table}. */ @Override - public Iterable> getPersistentEntityTypes() { - return StreamUtils.createStreamFromIterator(super.getPersistentEntityTypes().iterator()) + @NonNull + public Iterable> getPersistentEntityTypeInformation() { + return StreamUtils.createStreamFromIterator(super.getPersistentEntityTypeInformation().iterator()) .filter(typeInfo -> typeInfo.getType().isAnnotationPresent(Table.class)) - .collect(Collectors.toList()); + .toList(); } @Override + @NonNull protected Association createAssociation() { return new Association<>(this, null); } @@ -213,4 +216,23 @@ private String getAnnotatedColumnName() { return null; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof SpannerPersistentPropertyImpl that)) { + return false; + } + if (!super.equals(o)) { + return false; + } + return fieldNamingStrategy.equals(that.fieldNamingStrategy); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), fieldNamingStrategy); + } } diff --git a/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/repository/SpannerRepository.java b/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/repository/SpannerRepository.java index 47211ae0bd..1bf6c4aaaa 100644 --- a/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/repository/SpannerRepository.java +++ b/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/repository/SpannerRepository.java @@ -18,6 +18,7 @@ import com.google.cloud.spring.data.spanner.core.SpannerOperations; import java.util.function.Function; +import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.PagingAndSortingRepository; /** @@ -27,7 +28,8 @@ * @param the id type of the entity * @since 1.1 */ -public interface SpannerRepository extends PagingAndSortingRepository { +public interface SpannerRepository extends PagingAndSortingRepository, + CrudRepository { /** * Gets a {@link SpannerOperations}, which allows more-direct access to Google Cloud Spanner diff --git a/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/repository/config/SpannerAuditingRegistrar.java b/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/repository/config/SpannerAuditingRegistrar.java index a4c2e40f11..1ce60ddb0b 100644 --- a/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/repository/config/SpannerAuditingRegistrar.java +++ b/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/repository/config/SpannerAuditingRegistrar.java @@ -16,6 +16,7 @@ package com.google.cloud.spring.data.spanner.repository.config; +import com.google.cloud.spring.data.spanner.core.mapping.SpannerMappingContext; import com.google.cloud.spring.data.spanner.repository.support.SpannerAuditingEventListener; import java.lang.annotation.Annotation; import org.springframework.beans.factory.config.BeanDefinition; @@ -24,6 +25,7 @@ import org.springframework.data.auditing.AuditingHandler; import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport; import org.springframework.data.auditing.config.AuditingConfiguration; +import org.springframework.data.mapping.context.PersistentEntities; /** * Registers the annotations and classes for providing auditing support in Spring Data Cloud @@ -35,8 +37,6 @@ public class SpannerAuditingRegistrar extends AuditingBeanDefinitionRegistrarSup private static final String AUDITING_HANDLER_BEAN_NAME = "spannerAuditingHandler"; - private static final String MAPPING_CONTEXT_BEAN_NAME = "spannerMappingContext"; - @Override protected Class getAnnotation() { return EnableSpannerAuditing.class; @@ -57,10 +57,9 @@ protected void registerAuditListenerBeanDefinition( @Override protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder( AuditingConfiguration configuration) { - BeanDefinitionBuilder builder = - configureDefaultAuditHandlerAttributes( - configuration, BeanDefinitionBuilder.rootBeanDefinition(AuditingHandler.class)); - return builder.addConstructorArgReference(MAPPING_CONTEXT_BEAN_NAME); + return configureDefaultAuditHandlerAttributes( + configuration, BeanDefinitionBuilder.rootBeanDefinition(AuditingHandler.class)) + .addConstructorArgValue(PersistentEntities.of(new SpannerMappingContext())); } @Override diff --git a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/SpannerTemplateAuditingTests.java b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/SpannerTemplateAuditingTests.java index ae876eb0d3..fca1abfb63 100644 --- a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/SpannerTemplateAuditingTests.java +++ b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/SpannerTemplateAuditingTests.java @@ -28,9 +28,7 @@ import com.google.cloud.spring.data.spanner.core.mapping.SpannerMappingContext; import com.google.cloud.spring.data.spanner.core.mapping.Table; import com.google.cloud.spring.data.spanner.repository.config.EnableSpannerAuditing; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.Arrays; +import java.time.Instant; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.Test; @@ -51,9 +49,9 @@ class SpannerTemplateAuditingTests { private static final List UPSERT_MUTATION = - Arrays.asList(Mutation.newInsertOrUpdateBuilder("custom_test_table").build()); + List.of(Mutation.newInsertOrUpdateBuilder("custom_test_table").build()); - private static final LocalDateTime LONG_AGO = LocalDate.parse("2000-01-01").atStartOfDay(); + private static final Instant LONG_AGO = Instant.parse("2000-01-01T00:00:00.00Z"); @Autowired SpannerTemplate spannerTemplate; @@ -133,6 +131,6 @@ private static class TestEntity { @LastModifiedBy String lastUser; - @LastModifiedDate LocalDateTime lastTouched; + @LastModifiedDate Instant lastTouched; } } diff --git a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/SpannerTransactionManagerTests.java b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/SpannerTransactionManagerTests.java index c2c658d0b6..1f4786a001 100644 --- a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/SpannerTransactionManagerTests.java +++ b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/SpannerTransactionManagerTests.java @@ -184,9 +184,9 @@ void testDoCommitRollbackExceptions() { tx.transactionManager = transactionManager; assertThatThrownBy(() -> manager.doCommit(status)) - .isInstanceOf(UnexpectedRollbackException.class) - .hasMessage("Transaction Got Rolled Back; " - + "nested exception is com.google.cloud.spanner.AbortedException"); + .isInstanceOf(UnexpectedRollbackException.class) + .hasMessage("Transaction Got Rolled Back") + .hasCauseExactlyInstanceOf(AbortedException.class); } @Test @@ -202,9 +202,9 @@ void testDoCommitDupeException() { tx.transactionManager = transactionManager; assertThatThrownBy(() -> manager.doCommit(status)) - .isInstanceOf(DuplicateKeyException.class) - .hasMessage("ALREADY_EXISTS; nested exception is " - + "com.google.cloud.spanner.SpannerException: ALREADY_EXISTS: this is from a test"); + .isInstanceOf(DuplicateKeyException.class) + .hasMessage("ALREADY_EXISTS") + .hasStackTraceContaining("ALREADY_EXISTS: this is from a test"); } @Test diff --git a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/convert/ConverterAwareMappingSpannerEntityReaderTests.java b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/convert/ConverterAwareMappingSpannerEntityReaderTests.java index cd21907ad7..c1853127ab 100644 --- a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/convert/ConverterAwareMappingSpannerEntityReaderTests.java +++ b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/convert/ConverterAwareMappingSpannerEntityReaderTests.java @@ -17,6 +17,7 @@ package com.google.cloud.spring.data.spanner.core.convert; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -41,6 +42,7 @@ import com.google.cloud.spring.data.spanner.core.mapping.SpannerMappingContext; import com.google.gson.Gson; import java.util.Arrays; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.core.convert.ConversionFailedException; @@ -72,7 +74,7 @@ void readNestedStructTest() { .to(Value.string("key1")) .set("innerTestEntities") .toStructArray( - Type.struct(StructField.of("value", Type.string())), Arrays.asList(innerStruct)) + Type.struct(StructField.of("value", Type.string())), List.of(innerStruct)) .build(); OuterTestEntity result = @@ -143,18 +145,18 @@ void readSingularArrayMismatchTest() { .to(Value.string("key1")) .set("innerLengths") .toStructArray( - Type.struct(StructField.of("string_col", Type.string())), Arrays.asList(colStruct)) + Type.struct(StructField.of("string_col", Type.string())), List.of(colStruct)) .build(); ConverterAwareMappingSpannerEntityReader testReader = new ConverterAwareMappingSpannerEntityReader(new SpannerMappingContext(), new SpannerReadConverter( - Arrays.asList( - new Converter() { - @Nullable - @Override - public Integer convert(Struct source) { - return source.getString("string_col").length(); - } - }))); + List.of( + new Converter() { + @Nullable + @Override + public Integer convert(Struct source) { + return source.getString("string_col").length(); + } + }))); assertThatThrownBy(() -> testReader.read(OuterTestEntityFlatFaulty.class, rowStruct)) .isInstanceOf(SpannerDataException.class) .hasMessage("The value in column with name innerLengths could not be converted to the corresponding" @@ -178,7 +180,7 @@ void readConvertedNestedStructTest() { new ConverterAwareMappingSpannerEntityReader( new SpannerMappingContext(), new SpannerReadConverter( - Arrays.asList( + List.of( new Converter() { @Nullable @Override @@ -255,10 +257,11 @@ void readUnconvertableValueTest() { assertThatThrownBy(() -> this.spannerEntityReader.read(TestEntity.class, struct)) - .isInstanceOf(ConversionFailedException.class) - .hasMessage("Failed to convert from type [java.lang.String] to type " - + "[java.lang.Double] for value 'UNCONVERTABLE VALUE'; nested exception is " - + "java.lang.NumberFormatException: For input string: \"UNCONVERTABLEVALUE\""); + .isInstanceOf(ConversionFailedException.class) + .hasMessage("Failed to convert from type [java.lang.String] to type " + + "[java.lang.Double] for value 'UNCONVERTABLE VALUE'") + .hasStackTraceContaining( + "java.lang.NumberFormatException: For input string: \"UNCONVERTABLEVALUE\""); } @Test @@ -289,7 +292,7 @@ void readNestedStructWithConstructor() { .to(Value.string("key1")) .set("innerTestEntities") .toStructArray( - Type.struct(StructField.of("value", Type.string())), Arrays.asList(innerStruct)) + Type.struct(StructField.of("value", Type.string())), List.of(innerStruct)) .build(); TestEntities.OuterTestEntityWithConstructor result = @@ -327,7 +330,7 @@ void ensureConstructorArgsAreReadOnce() { Struct row = mock(Struct.class); when(row.getString("id")).thenReturn("1234"); when(row.getType()) - .thenReturn(Type.struct(Arrays.asList(Type.StructField.of("id", Type.string())))); + .thenReturn(Type.struct(List.of(StructField.of("id", Type.string())))); when(row.getColumnType("id")).thenReturn(Type.string()); TestEntities.SimpleConstructorTester result = @@ -357,18 +360,18 @@ void testPartialConstructorWithNotEnoughArgs() { } @Test - void zeroArgsListShouldThrowError() { + void zeroArgsListShouldNotThrowError() { Struct struct = Struct.newBuilder() .set("zeroArgsListOfObjects") - .to(Value.stringArray(Arrays.asList("hello", "world"))) + .to(Value.stringArray(List.of("hello", "world"))) .build(); - - assertThatThrownBy(() -> this.spannerEntityReader.read(TestEntities.TestEntityWithListWithZeroTypeArgs.class, struct)) - .isInstanceOf(SpannerDataException.class) - .hasMessage("in field 'zeroArgsListOfObjects': Unsupported number of type parameters found: 0 Only" - + " collections of exactly 1 type parameter are supported."); + // Starting from Spring 3.0, Collection types without generics can be resolved to type with wildcard + // generics (i.e., "?"). For example, "zeroArgsListOfObjects" will be resolved to List, rather + // than List. + assertThatNoException() + .isThrownBy(() -> this.spannerEntityReader.read(TestEntities.TestEntityWithListWithZeroTypeArgs.class, struct)); } @Test diff --git a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerKeyPropertyTests.java b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerKeyPropertyTests.java index a78d20c557..8d94ebe2dd 100644 --- a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerKeyPropertyTests.java +++ b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerKeyPropertyTests.java @@ -24,7 +24,7 @@ import java.lang.annotation.Annotation; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; /** Tests for the Spanner custom key property. */ class SpannerKeyPropertyTests { @@ -91,13 +91,7 @@ void getTypeTest() { @Test void getTypeInformationTest() { assertThat(this.spannerKeyProperty.getTypeInformation()) - .isEqualTo(ClassTypeInformation.from(Key.class)); - } - - @Test - @SuppressWarnings("deprecation") - void getPersistentEntityTypeTest() { - assertThat(this.spannerKeyProperty.getPersistentEntityTypes().iterator().hasNext()).isFalse(); + .isEqualTo(TypeInformation.of(Key.class)); } @Test diff --git a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerMappingContextTests.java b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerMappingContextTests.java index 703c145b85..5798eb9432 100644 --- a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerMappingContextTests.java +++ b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerMappingContextTests.java @@ -28,7 +28,6 @@ import org.springframework.context.ApplicationContext; import org.springframework.data.mapping.model.FieldNamingStrategy; import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy; -import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -60,7 +59,7 @@ void testApplicationContextPassing() { ApplicationContext applicationContext = mock(ApplicationContext.class); context.setApplicationContext(applicationContext); - context.createPersistentEntity(ClassTypeInformation.from(Object.class)); + context.createPersistentEntity(TypeInformation.of(Object.class)); verify(mockEntity, times(1)).setApplicationContext(applicationContext); } @@ -70,7 +69,7 @@ void testApplicationContextIsNotSet() { SpannerPersistentEntityImpl mockEntity = mock(SpannerPersistentEntityImpl.class); SpannerMappingContext context = createSpannerMappingContextWith(mockEntity); - context.createPersistentEntity(ClassTypeInformation.from(Object.class)); + context.createPersistentEntity(TypeInformation.of(Object.class)); verifyNoMoreInteractions(mockEntity); } diff --git a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerPersistentEntityImplTests.java b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerPersistentEntityImplTests.java index f7278b309d..207a39f784 100644 --- a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerPersistentEntityImplTests.java +++ b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/mapping/SpannerPersistentEntityImplTests.java @@ -36,7 +36,7 @@ import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.SimplePropertyHandler; -import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; /** Tests for the Spanner persistent entity. */ class SpannerPersistentEntityImplTests { @@ -54,7 +54,7 @@ class SpannerPersistentEntityImplTests { @Test void testTableName() { SpannerPersistentEntityImpl entity = - new SpannerPersistentEntityImpl<>(ClassTypeInformation.from(TestEntity.class), + new SpannerPersistentEntityImpl<>(TypeInformation.of(TestEntity.class), this.spannerMappingContext, this.spannerEntityProcessor); assertThat(entity.tableName()).isEqualTo("custom_test_table"); @@ -63,7 +63,7 @@ void testTableName() { @Test void testRawTableName() { SpannerPersistentEntityImpl entity = - new SpannerPersistentEntityImpl<>(ClassTypeInformation.from(EntityNoCustomName.class), + new SpannerPersistentEntityImpl<>(TypeInformation.of(EntityNoCustomName.class), this.spannerMappingContext, this.spannerEntityProcessor); assertThat(entity.tableName()).isEqualTo("entityNoCustomName"); @@ -72,7 +72,7 @@ void testRawTableName() { @Test void testEmptyCustomTableName() { SpannerPersistentEntityImpl entity = - new SpannerPersistentEntityImpl<>(ClassTypeInformation.from(EntityEmptyCustomName.class), + new SpannerPersistentEntityImpl<>(TypeInformation.of(EntityEmptyCustomName.class), this.spannerMappingContext, this.spannerEntityProcessor); assertThat(entity.tableName()).isEqualTo("entityEmptyCustomName"); @@ -88,17 +88,18 @@ void testColumns() { void testExpressionResolutionWithoutApplicationContext() { SpannerPersistentEntityImpl entity = - new SpannerPersistentEntityImpl<>(ClassTypeInformation.from(EntityWithExpression.class), + new SpannerPersistentEntityImpl<>(TypeInformation.of(EntityWithExpression.class), this.spannerMappingContext, this.spannerEntityProcessor); - assertThatThrownBy(() -> entity.tableName()) - .isInstanceOf(SpannerDataException.class) - .hasMessage("Error getting table name for EntityWithExpression; nested exception is org.springframework.expression.spel.SpelEvaluationException: EL1007E: Property or field 'tablePostfix' cannot be found on null"); + assertThatThrownBy(entity::tableName) + .isInstanceOf(SpannerDataException.class) + .hasMessage("Error getting table name for EntityWithExpression") + .hasStackTraceContaining("EL1007E: Property or field 'tablePostfix' cannot be found on null"); } @Test void testExpressionResolutionFromApplicationContext() { SpannerPersistentEntityImpl entity = - new SpannerPersistentEntityImpl<>(ClassTypeInformation.from(EntityWithExpression.class), + new SpannerPersistentEntityImpl<>(TypeInformation.of(EntityWithExpression.class), this.spannerMappingContext, this.spannerEntityProcessor); ApplicationContext applicationContext = mock(ApplicationContext.class); @@ -215,21 +216,21 @@ void testIgnoredProperty() { void testInvalidTableName() { SpannerPersistentEntityImpl entity = - new SpannerPersistentEntityImpl<>(ClassTypeInformation.from(EntityBadName.class), + new SpannerPersistentEntityImpl<>(TypeInformation.of(EntityBadName.class), this.spannerMappingContext, this.spannerEntityProcessor); - assertThatThrownBy(() -> entity.tableName()) - .isInstanceOf(SpannerDataException.class) - .hasMessage("Error getting table name for EntityBadName; nested exception is com.google.cloud.spring.data.spanner.core.mapping.SpannerDataException: Only" - + " letters, numbers, and underscores are allowed in table names: ;DROP TABLE" - + " your_table;"); + assertThatThrownBy(entity::tableName) + .isInstanceOf(SpannerDataException.class) + .hasMessage("Error getting table name for EntityBadName") + .hasStackTraceContaining( + "Only letters, numbers, and underscores are allowed in table names: ;DROP TABLE your_table;"); } @Test void testSpelInvalidName() { SpannerPersistentEntityImpl entity = - new SpannerPersistentEntityImpl<>(ClassTypeInformation.from(EntityWithExpression.class), + new SpannerPersistentEntityImpl<>(TypeInformation.of(EntityWithExpression.class), this.spannerMappingContext, this.spannerEntityProcessor); ApplicationContext applicationContext = mock(ApplicationContext.class); @@ -238,12 +239,12 @@ void testSpelInvalidName() { entity.setApplicationContext(applicationContext); - assertThatThrownBy(() -> entity.tableName()) - .isInstanceOf(SpannerDataException.class) - .hasMessage("Error getting table name for EntityWithExpression; nested exception is " - + "com.google.cloud.spring.data.spanner.core.mapping.SpannerDataException: " - + "Only letters, numbers, and underscores are allowed in table names: " - + "table_; DROP TABLE your_table;"); + assertThatThrownBy(entity::tableName) + .isInstanceOf(SpannerDataException.class) + .hasMessage("Error getting table name for EntityWithExpression") + .hasStackTraceContaining( + "Only letters, numbers, and underscores are allowed in table names: " + + "table_; DROP TABLE your_table;"); } diff --git a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/repository/query/SpannerQueryMethodTests.java b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/repository/query/SpannerQueryMethodTests.java index a970e63f08..5f256691b4 100644 --- a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/repository/query/SpannerQueryMethodTests.java +++ b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/repository/query/SpannerQueryMethodTests.java @@ -19,15 +19,15 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import java.lang.reflect.Method; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.RepositoryMetadata; -import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; class SpannerQueryMethodTests { @@ -38,9 +38,8 @@ class SpannerQueryMethodTests { void setUp() throws Exception { this.mockMetadata = mock(RepositoryMetadata.class); this.mockProjectionFactory = mock(ProjectionFactory.class); - when(mockMetadata.getReturnType(any())) - .thenReturn( - ClassTypeInformation.fromReturnTypeOf(Example.class.getMethod("someAnnotatedMethod"))); + doReturn(TypeInformation.fromReturnTypeOf(Example.class.getMethod("someAnnotatedMethod"))) + .when(mockMetadata).getReturnType(any()); doAnswer(a -> String.class).when(mockMetadata).getReturnedDomainClass(any()); } diff --git a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/test/domain/SingerRepository.java b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/test/domain/SingerRepository.java index 55cca1255e..4c0a1b1498 100644 --- a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/test/domain/SingerRepository.java +++ b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/test/domain/SingerRepository.java @@ -18,6 +18,7 @@ import com.google.cloud.spring.data.spanner.repository.SpannerRepository; import com.google.cloud.spring.data.spanner.repository.query.Query; +import org.springframework.data.repository.query.Param; public interface SingerRepository extends SpannerRepository { @Query( @@ -25,5 +26,5 @@ public interface SingerRepository extends SpannerRepository { value = "INSERT INTO singers_list (singerId, firstName, lastName) VALUES (@singerId, @firstName," + " @lastName)") - void insert(Integer singerId, String firstName, String lastName); + void insert(@Param("singerId") Integer singerId, @Param("firstName") String firstName, @Param("lastName") String lastName); } diff --git a/spring-cloud-gcp-dependencies/pom.xml b/spring-cloud-gcp-dependencies/pom.xml index 3b8ed63dd0..46fb2f0b57 100644 --- a/spring-cloud-gcp-dependencies/pom.xml +++ b/spring-cloud-gcp-dependencies/pom.xml @@ -2,17 +2,9 @@ 4.0.0 - - org.springframework.cloud - spring-cloud-dependencies-parent - 3.1.4 - - - com.google.cloud spring-cloud-gcp-dependencies - 3.4.3-SNAPSHOT - + 4.0.0-SNAPSHOT Spring Framework on Google Cloud Dependencies Spring Framework on Google Cloud Dependencies pom @@ -32,22 +24,11 @@ 26.4.0 1.8.2 31.1-jre - 0.8.2.RELEASE - 0.8.13.RELEASE + 1.0.0.RELEASE - - - - org.springframework.cloud - spring-cloud-function-dependencies - 3.2.7 - pom - import - - com.google.guava @@ -269,35 +250,14 @@ ${cloud-sql-socket-factory.version} - - - com.google.cloud.sql - cloud-sql-connector-r2dbc-mysql - ${cloud-sql-socket-factory.version} - - - - - dev.miku - r2dbc-mysql - ${r2dbc-mysql-driver.version} - - com.google.cloud.sql cloud-sql-connector-r2dbc-postgres ${cloud-sql-socket-factory.version} - - - io.r2dbc + org.postgresql r2dbc-postgresql ${r2dbc-postgres-driver.version} @@ -399,4 +359,35 @@ + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0 + + Copyright 2015-2022 the original author or authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + See the License for the specific language governing permissions and + limitations under the License. + + + + + + + Google + http://cloud.google.com + + + diff --git a/spring-cloud-gcp-kms/pom.xml b/spring-cloud-gcp-kms/pom.xml index e75f957500..a1c04372dd 100644 --- a/spring-cloud-gcp-kms/pom.xml +++ b/spring-cloud-gcp-kms/pom.xml @@ -4,7 +4,7 @@ com.google.cloud spring-cloud-gcp - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 diff --git a/spring-cloud-gcp-logging/pom.xml b/spring-cloud-gcp-logging/pom.xml index 3f8492c38c..7abb16f1c5 100644 --- a/spring-cloud-gcp-logging/pom.xml +++ b/spring-cloud-gcp-logging/pom.xml @@ -5,7 +5,7 @@ com.google.cloud spring-cloud-gcp - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT @@ -29,10 +29,9 @@ com.google.cloud google-cloud-logging-logback - - javax.servlet - javax.servlet-api + jakarta.servlet + jakarta.servlet-api provided diff --git a/spring-cloud-gcp-logging/src/main/java/com/google/cloud/spring/logging/StackdriverTraceConstants.java b/spring-cloud-gcp-logging/src/main/java/com/google/cloud/spring/logging/StackdriverTraceConstants.java index 8b3545fabb..d9c8e35ace 100644 --- a/spring-cloud-gcp-logging/src/main/java/com/google/cloud/spring/logging/StackdriverTraceConstants.java +++ b/spring-cloud-gcp-logging/src/main/java/com/google/cloud/spring/logging/StackdriverTraceConstants.java @@ -36,13 +36,13 @@ public interface StackdriverTraceConstants { /** The JSON field name for the trace-id. */ String TRACE_ID_ATTRIBUTE = "logging.googleapis.com/trace"; - /** The name of the MDC parameter Spring Sleuth is storing the trace id at. */ + /** The name of the MDC parameter Micrometer is storing the trace id at. */ String MDC_FIELD_TRACE_ID = "traceId"; - /** The name of the MDC parameter Spring Sleuth is storing the span id at. */ + /** The name of the MDC parameter Micrometer is storing the span id at. */ String MDC_FIELD_SPAN_ID = "spanId"; - /** The name of the MDC parameter Spring Sleuth is storing the span export information at. */ + /** The name of the MDC parameter Micrometer is storing the span export information at. */ String MDC_FIELD_SPAN_EXPORT = "X-Span-Export"; /** diff --git a/spring-cloud-gcp-logging/src/main/java/com/google/cloud/spring/logging/TraceIdLoggingWebMvcInterceptor.java b/spring-cloud-gcp-logging/src/main/java/com/google/cloud/spring/logging/TraceIdLoggingWebMvcInterceptor.java index f56ee20caa..234c5937f1 100644 --- a/spring-cloud-gcp-logging/src/main/java/com/google/cloud/spring/logging/TraceIdLoggingWebMvcInterceptor.java +++ b/spring-cloud-gcp-logging/src/main/java/com/google/cloud/spring/logging/TraceIdLoggingWebMvcInterceptor.java @@ -17,8 +17,8 @@ package com.google.cloud.spring.logging; import com.google.cloud.spring.logging.extractors.TraceIdExtractor; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.springframework.util.Assert; import org.springframework.web.servlet.HandlerInterceptor; diff --git a/spring-cloud-gcp-logging/src/main/java/com/google/cloud/spring/logging/extensions/LogstashLoggingEventEnhancer.java b/spring-cloud-gcp-logging/src/main/java/com/google/cloud/spring/logging/extensions/LogstashLoggingEventEnhancer.java index f0d01a9361..ca59c65c5d 100644 --- a/spring-cloud-gcp-logging/src/main/java/com/google/cloud/spring/logging/extensions/LogstashLoggingEventEnhancer.java +++ b/spring-cloud-gcp-logging/src/main/java/com/google/cloud/spring/logging/extensions/LogstashLoggingEventEnhancer.java @@ -21,6 +21,7 @@ import com.google.cloud.logging.logback.LoggingEventEnhancer; import com.google.cloud.spring.logging.JsonLoggingEventEnhancer; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.function.Consumer; import net.logstash.logback.marker.ObjectAppendingMarker; @@ -41,14 +42,19 @@ public class LogstashLoggingEventEnhancer @Override public void enhanceLogEntry(LogEntry.Builder builder, ILoggingEvent event) { addLogstashMarkerIfNecessary( - event.getMarker(), + getFirstMarker(event.getMarkerList()), marker -> builder.addLabel(marker.getFieldName(), marker.getFieldValue().toString())); } @Override public void enhanceJsonLogEntry(Map jsonMap, ILoggingEvent event) { addLogstashMarkerIfNecessary( - event.getMarker(), marker -> jsonMap.put(marker.getFieldName(), marker.getFieldValue())); + getFirstMarker(event.getMarkerList()), + marker -> jsonMap.put(marker.getFieldName(), marker.getFieldValue())); + } + + private Marker getFirstMarker(List markers) { + return markers == null || markers.isEmpty() ? null : markers.get(0); } private void addLogstashMarkerIfNecessary( @@ -57,8 +63,7 @@ private void addLogstashMarkerIfNecessary( return; } - if (marker instanceof ObjectAppendingMarker) { - ObjectAppendingMarker objectAppendingMarker = (ObjectAppendingMarker) marker; + if (marker instanceof ObjectAppendingMarker objectAppendingMarker) { markerAdderFunction.accept(objectAppendingMarker); } diff --git a/spring-cloud-gcp-logging/src/main/java/com/google/cloud/spring/logging/extractors/CloudTraceIdExtractor.java b/spring-cloud-gcp-logging/src/main/java/com/google/cloud/spring/logging/extractors/CloudTraceIdExtractor.java index 240ce52bd2..0f7f5e9c16 100644 --- a/spring-cloud-gcp-logging/src/main/java/com/google/cloud/spring/logging/extractors/CloudTraceIdExtractor.java +++ b/spring-cloud-gcp-logging/src/main/java/com/google/cloud/spring/logging/extractors/CloudTraceIdExtractor.java @@ -16,7 +16,7 @@ package com.google.cloud.spring.logging.extractors; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; /** Extracts trace IDs from HTTP requests using the x-cloud-trace-context header. */ public class CloudTraceIdExtractor implements TraceIdExtractor { diff --git a/spring-cloud-gcp-logging/src/main/java/com/google/cloud/spring/logging/extractors/TraceIdExtractor.java b/spring-cloud-gcp-logging/src/main/java/com/google/cloud/spring/logging/extractors/TraceIdExtractor.java index 1342a4491e..f21a5af866 100644 --- a/spring-cloud-gcp-logging/src/main/java/com/google/cloud/spring/logging/extractors/TraceIdExtractor.java +++ b/spring-cloud-gcp-logging/src/main/java/com/google/cloud/spring/logging/extractors/TraceIdExtractor.java @@ -16,7 +16,7 @@ package com.google.cloud.spring.logging.extractors; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; /** An extractor that can provide a trace ID from an HTTP request. */ public interface TraceIdExtractor { diff --git a/spring-cloud-gcp-logging/src/test/java/com/google/cloud/spring/logging/extensions/LogstashLoggingEventEnhancerTests.java b/spring-cloud-gcp-logging/src/test/java/com/google/cloud/spring/logging/extensions/LogstashLoggingEventEnhancerTests.java index 22a9507013..968e159e7d 100644 --- a/spring-cloud-gcp-logging/src/test/java/com/google/cloud/spring/logging/extensions/LogstashLoggingEventEnhancerTests.java +++ b/spring-cloud-gcp-logging/src/test/java/com/google/cloud/spring/logging/extensions/LogstashLoggingEventEnhancerTests.java @@ -23,6 +23,7 @@ import com.google.cloud.logging.LogEntry; import com.google.cloud.logging.Payload; import java.util.HashMap; +import java.util.List; import java.util.Map; import net.logstash.logback.marker.Markers; import org.junit.jupiter.api.BeforeEach; @@ -41,8 +42,8 @@ void setup() { enhancer = new LogstashLoggingEventEnhancer(); loggingEvent = Mockito.mock(ILoggingEvent.class); - when(loggingEvent.getMarker()) - .thenReturn(Markers.append("k1", "v1").and(Markers.append("k2", "v2"))); + when(loggingEvent.getMarkerList()) + .thenReturn(List.of(Markers.append("k1", "v1").and(Markers.append("k2", "v2")))); } @Test diff --git a/spring-cloud-gcp-native-support/src/main/java/com/google/cloud/spring/nativex/firestore/FirestoreDocumentComponentProcessor.java b/spring-cloud-gcp-native-support/src/main/java/com/google/cloud/spring/nativex/firestore/FirestoreDocumentComponentProcessor.java deleted file mode 100644 index 6fc8fc5a2f..0000000000 --- a/spring-cloud-gcp-native-support/src/main/java/com/google/cloud/spring/nativex/firestore/FirestoreDocumentComponentProcessor.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2017-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.cloud.spring.nativex.firestore; - -import java.util.List; -import org.springframework.nativex.type.ComponentProcessor; -import org.springframework.nativex.type.NativeContext; -import org.springframework.nativex.type.Type; -import org.springframework.nativex.type.TypeProcessor; - -/** - * Native Component processor adding reflection support for classes annotated with {@link - * com.google.cloud.spring.data.firestore.Document}. These classes will be found by spring aot if - * spring-context-indexer was used to index such classes. - * - * @see JpaComponentProcessor - */ -public class FirestoreDocumentComponentProcessor implements ComponentProcessor { - - private static final String FIRESTORE_DOCUMENT_FQN = - "com.google.cloud.spring.data.firestore.Document"; - - private final TypeProcessor typeProcessor = - TypeProcessor.namedProcessor("FirestoreDocumentComponentProcessor"); - - @Override - public boolean handle( - NativeContext imageContext, String componentType, List classifiers) { - if (classifiers.contains(FIRESTORE_DOCUMENT_FQN)) { - return true; - } - Type type = imageContext.getTypeSystem().resolveName(componentType); - return type.getAnnotations().stream() - .anyMatch(tag -> tag.getDottedName().equals(FIRESTORE_DOCUMENT_FQN)); - } - - @Override - public void process(NativeContext imageContext, String componentType, List classifiers) { - Type domainType = imageContext.getTypeSystem().resolveName(componentType); - typeProcessor.use(imageContext).toProcessType(domainType); - } -} diff --git a/spring-cloud-gcp-native-support/src/main/java/com/google/cloud/spring/nativex/firestore/FirestoreNativeConfig.java b/spring-cloud-gcp-native-support/src/main/java/com/google/cloud/spring/nativex/firestore/FirestoreNativeConfig.java deleted file mode 100644 index ed9b325731..0000000000 --- a/spring-cloud-gcp-native-support/src/main/java/com/google/cloud/spring/nativex/firestore/FirestoreNativeConfig.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2017-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.cloud.spring.nativex.firestore; - -import com.google.cloud.spring.autoconfigure.datastore.GcpDatastoreEmulatorAutoConfiguration; -import com.google.cloud.spring.autoconfigure.firestore.FirestoreRepositoriesAutoConfiguration; -import com.google.cloud.spring.data.firestore.SimpleFirestoreReactiveRepository; -import com.google.cloud.spring.data.firestore.repository.support.FirestoreRepositoryFactoryBean; -import org.springframework.nativex.hint.AccessBits; -import org.springframework.nativex.hint.JdkProxyHint; -import org.springframework.nativex.hint.NativeHint; -import org.springframework.nativex.hint.TypeHint; -import org.springframework.nativex.type.NativeConfiguration; - -/** - * Native hints for {@link FirestoreRepositoriesAutoConfiguration}. Inspired by - * org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesHints - * - * @see JpaRepositoriesHints - */ -@NativeHint( - trigger = FirestoreRepositoriesAutoConfiguration.class, - types = - @TypeHint( - types = { - FirestoreRepositoryFactoryBean.class, - SimpleFirestoreReactiveRepository.class, - GcpDatastoreEmulatorAutoConfiguration.class - }, - typeNames = {"com.google.cloud.spring.data.firestore.mapping.FirestoreMappingContext"}, - access = - AccessBits.CLASS - | AccessBits.DECLARED_METHODS - | AccessBits.DECLARED_CONSTRUCTORS - | AccessBits.RESOURCE), - jdkProxies = - @JdkProxyHint( - typeNames = { - "com.google.cloud.spring.data.firestore.FirestoreReactiveRepository", - "org.springframework.aop.SpringProxy", - "org.springframework.aop.framework.Advised", - "org.springframework.core.DecoratingProxy" - })) -public class FirestoreNativeConfig implements NativeConfiguration {} diff --git a/spring-cloud-gcp-native-support/src/main/java/com/google/cloud/spring/nativex/firestore/FirestoreRepositoryComponentProcessor.java b/spring-cloud-gcp-native-support/src/main/java/com/google/cloud/spring/nativex/firestore/FirestoreRepositoryComponentProcessor.java deleted file mode 100644 index db11871c51..0000000000 --- a/spring-cloud-gcp-native-support/src/main/java/com/google/cloud/spring/nativex/firestore/FirestoreRepositoryComponentProcessor.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2017-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.cloud.spring.nativex.firestore; - -import java.util.Collections; -import java.util.Set; -import org.springframework.data.SpringDataComponentProcessor; - -/** - * Extend {@link SpringDataComponentProcessor} to add required native support for Firestore - * Repositories. - */ -public class FirestoreRepositoryComponentProcessor extends SpringDataComponentProcessor { - @Override - protected Set storeSpecificRepositoryDeclarationNames() { - return Collections.singleton( - "com.google.cloud.spring.data.firestore.FirestoreReactiveRepository"); - } -} diff --git a/spring-cloud-gcp-native-support/src/main/resources/META-INF/services/org.springframework.nativex.type.ComponentProcessor b/spring-cloud-gcp-native-support/src/main/resources/META-INF/services/org.springframework.nativex.type.ComponentProcessor deleted file mode 100644 index 0a232b3b56..0000000000 --- a/spring-cloud-gcp-native-support/src/main/resources/META-INF/services/org.springframework.nativex.type.ComponentProcessor +++ /dev/null @@ -1,2 +0,0 @@ -com.google.cloud.spring.nativex.firestore.FirestoreDocumentComponentProcessor -com.google.cloud.spring.nativex.firestore.FirestoreRepositoryComponentProcessor diff --git a/spring-cloud-gcp-native-support/src/main/resources/META-INF/services/org.springframework.nativex.type.NativeConfiguration b/spring-cloud-gcp-native-support/src/main/resources/META-INF/services/org.springframework.nativex.type.NativeConfiguration deleted file mode 100644 index 300920bcc6..0000000000 --- a/spring-cloud-gcp-native-support/src/main/resources/META-INF/services/org.springframework.nativex.type.NativeConfiguration +++ /dev/null @@ -1 +0,0 @@ -com.google.cloud.spring.nativex.firestore.FirestoreNativeConfig diff --git a/spring-cloud-gcp-native-support/src/test/java/com/google/cloud/spring/nativex/firestore/NativeSupportTest.java b/spring-cloud-gcp-native-support/src/test/java/com/google/cloud/spring/nativex/firestore/NativeSupportTest.java deleted file mode 100644 index 81cb4dbdc0..0000000000 --- a/spring-cloud-gcp-native-support/src/test/java/com/google/cloud/spring/nativex/firestore/NativeSupportTest.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2017-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.cloud.spring.nativex.firestore; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import com.google.cloud.spring.data.firestore.Document; -import java.io.File; -import java.util.Arrays; -import java.util.Collections; -import java.util.ServiceLoader; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.data.repository.Repository; -import org.springframework.nativex.type.ComponentProcessor; -import org.springframework.nativex.type.NativeConfiguration; -import org.springframework.nativex.type.NativeContext; -import org.springframework.nativex.type.TypeSystem; - -class NativeSupportTest { - - private NativeContext nativeContext; - private TypeSystem typeSystem; - - private final FirestoreDocumentComponentProcessor documentsComponentProcessor = - new FirestoreDocumentComponentProcessor(); - private final FirestoreRepositoryComponentProcessor repositoryComponentProcessor = - new FirestoreRepositoryComponentProcessor(); - - @BeforeEach - void setup() { - nativeContext = mock(NativeContext.class); - typeSystem = - new TypeSystem( - Arrays.asList( - new File("./target/classes").toString(), - new File("./target/test-classes").toString())); - - when(nativeContext.getTypeSystem()).thenReturn(typeSystem); - } - - @Test - void shouldConfigureComponentProcessorJavaSpi() { - assertThat(ServiceLoader.load(ComponentProcessor.class)) - .anyMatch(FirestoreDocumentComponentProcessor.class::isInstance) - .anyMatch(FirestoreRepositoryComponentProcessor.class::isInstance); - } - - @Test - void shouldConfigureNativeConfigurationJavaSpi() { - assertThat(ServiceLoader.load(NativeConfiguration.class)) - .anyMatch(FirestoreNativeConfig.class::isInstance); - } - - @Test - void shouldHandleComponentIndexedFirestoreDocuments() { - assertThat( - documentsComponentProcessor.handle( - nativeContext, - typeSystem.resolve(TestDocument.class).getDottedName(), - Collections.singletonList(typeSystem.resolve(Document.class).getDottedName()))) - .isTrue(); - } - - @Test - void shouldHandleNoneIndexedFirestoreDocuments() { - assertThat( - documentsComponentProcessor.handle( - nativeContext, - typeSystem.resolve(TestDocument.class).getDottedName(), - Collections.emptyList())) - .isTrue(); - } - - @Test - void shouldHandleFirestoreRepositories() { - assertThat( - repositoryComponentProcessor.handle( - nativeContext, - typeSystem.resolve(TestDocumentRepository.class).getDottedName(), - Collections.singletonList(typeSystem.resolve(Repository.class).getDottedName()))) - .isTrue(); - } -} diff --git a/spring-cloud-gcp-native-support/src/test/java/com/google/cloud/spring/nativex/firestore/TestDocument.java b/spring-cloud-gcp-native-support/src/test/java/com/google/cloud/spring/nativex/firestore/TestDocument.java deleted file mode 100644 index 500965ab82..0000000000 --- a/spring-cloud-gcp-native-support/src/test/java/com/google/cloud/spring/nativex/firestore/TestDocument.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2017-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.cloud.spring.nativex.firestore; - -import com.google.cloud.firestore.annotation.DocumentId; -import com.google.cloud.spring.data.firestore.Document; -import java.util.List; -import lombok.Data; -import lombok.Value; - -@Document(collectionName = "usersCollection") -@Data -public class TestDocument { - @DocumentId private String id; - private String name; - private List
addresses; - - @Value - public static class Address { - String streetAddress; - String country; - } -} diff --git a/spring-cloud-gcp-native-support/src/test/java/com/google/cloud/spring/nativex/firestore/TestDocumentRepository.java b/spring-cloud-gcp-native-support/src/test/java/com/google/cloud/spring/nativex/firestore/TestDocumentRepository.java deleted file mode 100644 index ce917e8148..0000000000 --- a/spring-cloud-gcp-native-support/src/test/java/com/google/cloud/spring/nativex/firestore/TestDocumentRepository.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2017-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.cloud.spring.nativex.firestore; - -import com.google.cloud.spring.data.firestore.FirestoreReactiveRepository; -import org.springframework.stereotype.Repository; -import reactor.core.publisher.Flux; - -@Repository -public interface TestDocumentRepository extends FirestoreReactiveRepository { - Flux findAllByName(String name); -} diff --git a/spring-cloud-gcp-pubsub-stream-binder/pom.xml b/spring-cloud-gcp-pubsub-stream-binder/pom.xml index d4caf6b917..52e649de90 100644 --- a/spring-cloud-gcp-pubsub-stream-binder/pom.xml +++ b/spring-cloud-gcp-pubsub-stream-binder/pom.xml @@ -5,7 +5,7 @@ spring-cloud-gcp com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 @@ -29,7 +29,7 @@ org.springframework.cloud - spring-cloud-stream-binder-test + spring-cloud-stream-test-support test diff --git a/spring-cloud-gcp-pubsub-stream-binder/src/test/java/com/google/cloud/spring/stream/binder/pubsub/PubSubMessageChannelBinderEmulatorIntegrationTests.java b/spring-cloud-gcp-pubsub-stream-binder/src/test/java/com/google/cloud/spring/stream/binder/pubsub/PubSubMessageChannelBinderEmulatorIntegrationTests.java index 4502c6fd41..a1ec157874 100644 --- a/spring-cloud-gcp-pubsub-stream-binder/src/test/java/com/google/cloud/spring/stream/binder/pubsub/PubSubMessageChannelBinderEmulatorIntegrationTests.java +++ b/spring-cloud-gcp-pubsub-stream-binder/src/test/java/com/google/cloud/spring/stream/binder/pubsub/PubSubMessageChannelBinderEmulatorIntegrationTests.java @@ -47,7 +47,7 @@ class PubSubMessageChannelBinderEmulatorIntegrationTests ExtendedConsumerProperties, ExtendedProducerProperties> { - private String hostPort; + private final String hostPort; private TestInfo testInfo; diff --git a/spring-cloud-gcp-pubsub-stream-binder/src/test/java/com/google/cloud/spring/stream/binder/pubsub/PubSubMessageChannelBinderTests.java b/spring-cloud-gcp-pubsub-stream-binder/src/test/java/com/google/cloud/spring/stream/binder/pubsub/PubSubMessageChannelBinderTests.java index 0506f78f74..16ce391154 100644 --- a/spring-cloud-gcp-pubsub-stream-binder/src/test/java/com/google/cloud/spring/stream/binder/pubsub/PubSubMessageChannelBinderTests.java +++ b/spring-cloud-gcp-pubsub-stream-binder/src/test/java/com/google/cloud/spring/stream/binder/pubsub/PubSubMessageChannelBinderTests.java @@ -38,10 +38,13 @@ import com.google.cloud.spring.stream.binder.pubsub.provisioning.PubSubChannelProvisioner; import java.util.List; import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Supplier; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.junit.Assert; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -50,23 +53,18 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.cloud.stream.annotation.EnableBinding; -import org.springframework.cloud.stream.annotation.Input; -import org.springframework.cloud.stream.annotation.StreamListener; import org.springframework.cloud.stream.binder.Binding; import org.springframework.cloud.stream.binder.ExtendedConsumerProperties; import org.springframework.cloud.stream.binder.ExtendedProducerProperties; -import org.springframework.cloud.stream.binder.PollableMessageSource; import org.springframework.cloud.stream.binding.BindingService; import org.springframework.cloud.stream.config.ConsumerEndpointCustomizer; import org.springframework.cloud.stream.config.ProducerMessageHandlerCustomizer; -import org.springframework.cloud.stream.messaging.Processor; -import org.springframework.cloud.stream.messaging.Sink; import org.springframework.cloud.stream.provisioning.ConsumerDestination; import org.springframework.cloud.stream.provisioning.ProducerDestination; import org.springframework.context.annotation.Bean; import org.springframework.integration.core.MessageProducer; import org.springframework.messaging.MessageChannel; +import reactor.core.publisher.Flux; /** * Tests for channel binder. @@ -77,7 +75,7 @@ class PubSubMessageChannelBinderTests { private static final Log LOGGER = LogFactory.getLog(PubSubMessageChannelBinderTests.class); - PubSubMessageChannelBinder binder; + private PubSubMessageChannelBinder binder; @Mock PubSubChannelProvisioner channelProvisioner; @@ -97,18 +95,23 @@ class PubSubMessageChannelBinderTests { @Mock HealthTrackerRegistry healthTrackerRegistry; - ApplicationContextRunner baseContext = - new ApplicationContextRunner() - .withBean(PubSubTemplate.class, () -> pubSubTemplate) - .withBean(PubSubAdmin.class, () -> pubSubAdmin) - .withConfiguration( - AutoConfigurations.of( - PubSubBinderConfiguration.class, PubSubExtendedBindingProperties.class)); + private ApplicationContextRunner baseContext; + + @BeforeEach + void init() { + baseContext = + new ApplicationContextRunner() + .withBean(PubSubTemplate.class, () -> pubSubTemplate) + .withBean(PubSubAdmin.class, () -> pubSubAdmin) + .withConfiguration( + AutoConfigurations.of( + PubSubBinderConfiguration.class, + PubSubExtendedBindingProperties.class)); + this.binder = new PubSubMessageChannelBinder(new String[0], this.channelProvisioner, this.pubSubTemplate, this.properties); + } @Test void testAfterUnbindConsumer() { - - this.binder = new PubSubMessageChannelBinder(new String[0], this.channelProvisioner, this.pubSubTemplate, this.properties); this.binder.afterUnbindConsumer(this.consumerDestination, "group1", this.consumerProperties); verify(this.channelProvisioner).afterUnbindConsumer(this.consumerDestination); @@ -116,8 +119,6 @@ void testAfterUnbindConsumer() { @Test void producerSyncPropertyFalseByDefault() { - - this.binder = new PubSubMessageChannelBinder(new String[0], this.channelProvisioner, this.pubSubTemplate, this.properties); when(producerDestination.getName()).thenReturn("test-topic"); baseContext.run( ctx -> { @@ -137,8 +138,6 @@ void producerSyncPropertyFalseByDefault() { @Test void producerSyncPropertyPropagatesToMessageHandler() { - - this.binder = new PubSubMessageChannelBinder(new String[0], this.channelProvisioner, this.pubSubTemplate, this.properties); when(producerDestination.getName()).thenReturn("test-topic"); baseContext .withPropertyValues("spring.cloud.stream.gcp.pubsub.default.producer.sync=true") @@ -162,7 +161,6 @@ void producerSyncPropertyPropagatesToMessageHandler() { @Test void producerHeaderPropertyPropagatesToMessageHandler() { - this.binder = new PubSubMessageChannelBinder(new String[0], this.channelProvisioner, this.pubSubTemplate, this.properties); when(producerDestination.getName()).thenReturn("test-topic"); baseContext .withPropertyValues("spring.cloud.stream.gcp.pubsub.default.producer.allowedHeaders=foo4,foo5") @@ -181,16 +179,19 @@ void producerHeaderPropertyPropagatesToMessageHandler() { errorChannel); PubSubHeaderMapper mapper = (PubSubHeaderMapper) FieldUtils.readField(messageHandler, "headerMapper", true); String[] headersToCheck = (String[]) FieldUtils.readField(mapper, "outboundHeaderPatterns", true); - Assert.assertArrayEquals(headersToCheck, new String[]{"foo4", "foo5"}); + Assertions.assertArrayEquals(new String[]{"foo4", "foo5"}, headersToCheck); }); } @Test void consumerMaxFetchPropertyPropagatesToMessageSource() { - this.binder = new PubSubMessageChannelBinder(new String[0], this.channelProvisioner, this.pubSubTemplate, this.properties); when(consumerDestination.getName()).thenReturn("test-subscription"); baseContext - .withPropertyValues("spring.cloud.stream.gcp.pubsub.default.consumer.maxFetchSize=20") + .withPropertyValues( + "spring.cloud.stream.gcp.pubsub.default.consumer.maxFetchSize=20", + "spring.cloud.stream.gcp.pubsub.default.consumer.subscription-name=mock", + "spring.cloud.stream.gcp.pubsub.default.consumer.auto-create-resources=false" + ) .run( ctx -> { PubSubMessageChannelBinder binder = ctx.getBean(PubSubMessageChannelBinder.class); @@ -198,6 +199,14 @@ void consumerMaxFetchPropertyPropagatesToMessageSource() { ctx.getBean( "pubSubExtendedBindingProperties", PubSubExtendedBindingProperties.class); + assertThat(props.getExtendedConsumerProperties("test") + .getSubscriptionName()) + .isEqualTo("mock"); + + assertThat(props.getExtendedConsumerProperties("test") + .isAutoCreateResources()) + .isFalse(); + PubSubMessageSource source = binder.createPubSubMessageSource( consumerDestination, @@ -209,8 +218,6 @@ void consumerMaxFetchPropertyPropagatesToMessageSource() { @Test void testCreateConsumerWithRegistry() { - - this.binder = new PubSubMessageChannelBinder(new String[0], this.channelProvisioner, this.pubSubTemplate, this.properties); when(consumerDestination.getName()).thenReturn("test-subscription"); baseContext.run( ctx -> { @@ -235,11 +242,13 @@ void testCreateConsumerWithRegistry() { @Test void testProducerAndConsumerCustomizers() { - - this.binder = new PubSubMessageChannelBinder(new String[0], this.channelProvisioner, this.pubSubTemplate, this.properties); baseContext .withUserConfiguration(PubSubBinderTestConfig.class) - .withPropertyValues("spring.cloud.stream.bindings.input.group=testGroup") + .withPropertyValues( + "spring.cloud.function.definition=producer;consumer", + "spring.cloud.stream.bindings.producer-out-0.destination=my-topic", + "spring.cloud.stream.bindings.consumer-in-0.destination=my-topic" + ) .run( context -> { DirectFieldAccessor channelBindingServiceAccessor = @@ -248,26 +257,26 @@ void testProducerAndConsumerCustomizers() { Map>> consumerBindings = (Map>>) channelBindingServiceAccessor.getPropertyValue("consumerBindings"); + assertThat(consumerBindings).isNotEmpty(); assertThat( - new DirectFieldAccessor(consumerBindings.get("input").get(0)) + new DirectFieldAccessor(consumerBindings.get("consumer-in-0").get(0)) .getPropertyValue("lifecycle.beanName")) - .isEqualTo("setByCustomizer:input"); + .isEqualTo("setByCustomizer:my-topic"); @SuppressWarnings("unchecked") Map> producerBindings = (Map>) channelBindingServiceAccessor.getPropertyValue("producerBindings"); + assertThat(producerBindings).isNotEmpty(); assertThat( - new DirectFieldAccessor(producerBindings.get("output")) + new DirectFieldAccessor(producerBindings.get("producer-out-0")) .getPropertyValue("val$producerMessageHandler.beanName")) - .isEqualTo("setByCustomizer:output"); + .isEqualTo("setByCustomizer:my-topic"); }); } @Test void testConsumerEndpointCreation() { - - this.binder = new PubSubMessageChannelBinder(new String[0], this.channelProvisioner, this.pubSubTemplate, this.properties); when(consumerDestination.getName()).thenReturn("test-subscription"); baseContext .withPropertyValues( @@ -300,8 +309,6 @@ void testConsumerEndpointCreation() { @Test void testConsumerEndpointCreationWithNoHeadersProvided() { - - this.binder = new PubSubMessageChannelBinder(new String[0], this.channelProvisioner, this.pubSubTemplate, this.properties); when(consumerDestination.getName()).thenReturn("test-subscription"); baseContext .run( @@ -323,14 +330,13 @@ void testConsumerEndpointCreationWithNoHeadersProvided() { (PubSubInboundChannelAdapter) messageProducer; PubSubHeaderMapper mapper = (PubSubHeaderMapper) FieldUtils.readField(inboundChannelAdapter, "headerMapper", true); String [] headersToCheck = (String[]) FieldUtils.readField(mapper, "inboundHeaderPatterns", true); - Assert.assertArrayEquals(headersToCheck, (String[]) FieldUtils.readField(mapper, "inboundHeaderPatterns", true)); + Assertions.assertArrayEquals(headersToCheck, + (String[]) FieldUtils.readField(mapper, "inboundHeaderPatterns", true)); }); } @Test void testConsumerEndpointCreationWithHeadersProvided() { - - this.binder = new PubSubMessageChannelBinder(new String[0], this.channelProvisioner, this.pubSubTemplate, this.properties); when(consumerDestination.getName()).thenReturn("test-subscription"); baseContext .withPropertyValues("spring.cloud.stream.gcp.pubsub.default.consumer.allowedHeaders=foo2,foo3") @@ -353,16 +359,10 @@ void testConsumerEndpointCreationWithHeadersProvided() { (PubSubInboundChannelAdapter) messageProducer; PubSubHeaderMapper mapper = (PubSubHeaderMapper) FieldUtils.readField(inboundChannelAdapter, "headerMapper", true); String [] headersToCheck = (String[]) FieldUtils.readField(mapper, "inboundHeaderPatterns", true); - Assert.assertArrayEquals(headersToCheck, new String[]{"foo2", "foo3"}); + Assertions.assertArrayEquals(new String[]{"foo2", "foo3"}, headersToCheck); }); } - public interface PollableSource { - @Input - PollableMessageSource source(); - } - - @EnableBinding({Processor.class, PollableSource.class}) @EnableAutoConfiguration public static class PubSubBinderTestConfig { @@ -377,9 +377,14 @@ public ProducerMessageHandlerCustomizer handlerCustomizer( handler.setBeanName("setByCustomizer:" + destinationName); } - @StreamListener(Sink.INPUT) - public void process(String payload) throws InterruptedException { - LOGGER.info("received: " + payload); + @Bean + public Supplier> producer() { + return () -> Flux.just("empty"); + } + + @Bean + public Consumer consumer() { + return str -> LOGGER.info("received " + str); } @Bean diff --git a/spring-cloud-gcp-pubsub-stream-binder/src/test/java/com/google/cloud/spring/stream/binder/pubsub/properties/PubSubExtendedBindingsPropertiesTests.java b/spring-cloud-gcp-pubsub-stream-binder/src/test/java/com/google/cloud/spring/stream/binder/pubsub/properties/PubSubExtendedBindingsPropertiesTests.java index 895e6c761d..2d15569ea0 100644 --- a/spring-cloud-gcp-pubsub-stream-binder/src/test/java/com/google/cloud/spring/stream/binder/pubsub/properties/PubSubExtendedBindingsPropertiesTests.java +++ b/spring-cloud-gcp-pubsub-stream-binder/src/test/java/com/google/cloud/spring/stream/binder/pubsub/properties/PubSubExtendedBindingsPropertiesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,116 +17,108 @@ package com.google.cloud.spring.stream.binder.pubsub.properties; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import com.google.cloud.pubsub.v1.Subscriber; -import com.google.cloud.pubsub.v1.stub.SubscriberStub; -import com.google.cloud.spring.pubsub.PubSubAdmin; -import com.google.cloud.spring.pubsub.core.PubSubTemplate; -import com.google.cloud.spring.pubsub.core.publisher.PubSubPublisherTemplate; -import com.google.cloud.spring.pubsub.core.subscriber.PubSubSubscriberTemplate; import com.google.cloud.spring.pubsub.integration.AckMode; -import com.google.cloud.spring.pubsub.support.PublisherFactory; -import com.google.cloud.spring.pubsub.support.SubscriberFactory; -import com.google.cloud.spring.stream.binder.pubsub.PubSubMessageChannelBinder; -import com.google.cloud.spring.stream.binder.pubsub.properties.PubSubExtendedBindingsPropertiesTests.PubSubBindingsTestConfiguration; -import com.google.pubsub.v1.Subscription; -import com.google.pubsub.v1.Topic; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.stream.annotation.EnableBinding; -import org.springframework.cloud.stream.annotation.Input; -import org.springframework.cloud.stream.annotation.StreamListener; -import org.springframework.cloud.stream.binder.BinderFactory; -import org.springframework.cloud.stream.config.BindingServiceConfiguration; -import org.springframework.cloud.stream.messaging.Sink; +import org.springframework.cloud.stream.binder.Binder; +import org.springframework.cloud.stream.binder.BinderConfiguration; +import org.springframework.cloud.stream.binder.BinderType; +import org.springframework.cloud.stream.binder.BinderTypeRegistry; +import org.springframework.cloud.stream.binder.DefaultBinderFactory; +import org.springframework.cloud.stream.binder.DefaultBinderTypeRegistry; +import org.springframework.cloud.stream.binder.ExtendedPropertiesBinder; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.StandardEnvironment; import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.SubscribableChannel; -import org.springframework.test.context.junit.jupiter.SpringExtension; -/** Tests for extended binding properties. */ -@ExtendWith(SpringExtension.class) -@SpringBootTest( - webEnvironment = SpringBootTest.WebEnvironment.NONE, - classes = {PubSubBindingsTestConfiguration.class, BindingServiceConfiguration.class}, - properties = { - "spring.cloud.stream.gcp.pubsub.bindings.input.consumer.ack-mode=AUTO_ACK", - "spring.cloud.stream.gcp.pubsub.bindings.input.consumer.auto-create-resources=true", - "spring.cloud.stream.gcp.pubsub.default.consumer.auto-create-resources=false" - }) +/** + * Tests for extended binding properties. + */ class PubSubExtendedBindingsPropertiesTests { - @Autowired private ConfigurableApplicationContext context; + private static Binder binder; + + @BeforeAll + static void init() { + DefaultBinderFactory binderFactory = createMockExtendedBinderFactory(); + binder = binderFactory.getBinder(null, + MessageChannel.class); + } @Test - void testExtendedPropertiesOverrideDefaults() { - BinderFactory binderFactory = this.context.getBeanFactory().getBean(BinderFactory.class); - PubSubMessageChannelBinder binder = - (PubSubMessageChannelBinder) binderFactory.getBinder("pubsub", MessageChannel.class); + void testExtendedDefaultProducerProperties() { + PubSubProducerProperties producerProperties = (PubSubProducerProperties) ((ExtendedPropertiesBinder) binder) + .getExtendedProducerProperties("default-output"); + assertThat(producerProperties.isAutoCreateResources()).isTrue(); + assertThat(producerProperties.getAllowedHeaders()).isNull(); + assertThat(producerProperties.isSync()).isFalse(); + } - assertThat(binder.getExtendedConsumerProperties("custom-in").isAutoCreateResources()).isFalse(); - assertThat(binder.getExtendedConsumerProperties("input").isAutoCreateResources()).isTrue(); + @Test + void testExtendedDefaultConsumerProperties() { + PubSubConsumerProperties consumerProperties = (PubSubConsumerProperties) ((ExtendedPropertiesBinder) binder) + .getExtendedConsumerProperties("default-input"); + assertThat(consumerProperties.isAutoCreateResources()).isTrue(); + assertThat(consumerProperties.getAllowedHeaders()).isNull(); + assertThat(consumerProperties.getAckMode()).isEqualTo(AckMode.AUTO); + assertThat(consumerProperties.getMaxFetchSize()).isEqualTo(1); + assertThat(consumerProperties.getSubscriptionName()).isNull(); + assertThat(consumerProperties.getDeadLetterPolicy()).isNull(); + } - assertThat(binder.getExtendedConsumerProperties("custom-in").getAckMode()) - .isEqualTo(AckMode.AUTO); - assertThat(binder.getExtendedConsumerProperties("input").getAckMode()) - .isEqualTo(AckMode.AUTO_ACK); + private static DefaultBinderFactory createMockExtendedBinderFactory() { + BinderTypeRegistry binderTypeRegistry = createMockExtendedBinderTypeRegistry(); + return new DefaultBinderFactory( + Collections.singletonMap("mock", + new BinderConfiguration("mock", new HashMap<>(), true, true)), + binderTypeRegistry, null); } - /** Spring Boot config for tests. */ - @Configuration - @EnableBinding(PubSubBindingsTestConfiguration.CustomTestSink.class) - static class PubSubBindingsTestConfiguration { + private static DefaultBinderTypeRegistry createMockExtendedBinderTypeRegistry() { + return new DefaultBinderTypeRegistry( + Collections.singletonMap("mock", new BinderType("mock", + new Class[]{ MockExtendedBinderConfiguration.class }))); + } - @Bean - public PubSubAdmin pubSubAdmin() { - PubSubAdmin pubSubAdminMock = Mockito.mock(PubSubAdmin.class); - when(pubSubAdminMock.createSubscription(anyString(), anyString())) - .thenReturn(Subscription.getDefaultInstance()); - when(pubSubAdminMock.getSubscription(anyString())) - .thenReturn(Subscription.getDefaultInstance()); - when(pubSubAdminMock.getTopic(anyString())).thenReturn(Topic.getDefaultInstance()); - return pubSubAdminMock; - } + @Configuration + public static class MockExtendedBinderConfiguration { + @SuppressWarnings("rawtypes") @Bean - public PubSubTemplate pubSubTemplate() { - PublisherFactory publisherFactory = Mockito.mock(PublisherFactory.class); - - SubscriberFactory subscriberFactory = Mockito.mock(SubscriberFactory.class); - when(subscriberFactory.getProjectId()).thenReturn("test-project"); - when(subscriberFactory.createSubscriberStub(any())) - .thenReturn(Mockito.mock(SubscriberStub.class)); - when(subscriberFactory.createSubscriber(anyString(), any())) - .thenReturn(Mockito.mock(Subscriber.class)); - - return new PubSubTemplate( - new PubSubPublisherTemplate(publisherFactory), - new PubSubSubscriberTemplate(subscriberFactory)); - } - - @StreamListener("input") - public void process(String payload) { - System.out.println(payload); - } - - @StreamListener("custom-in") - public void processCustom(String payload) { - System.out.println(payload); - } - - /** interface for testing. */ - interface CustomTestSink extends Sink { - @Input("custom-in") - SubscribableChannel customIn(); + public Binder extendedPropertiesBinder() { + Binder mock = mock(Binder.class, + Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS) + .extraInterfaces(ExtendedPropertiesBinder.class)); + ConfigurableEnvironment environment = new StandardEnvironment(); + Map propertiesToAdd = new HashMap<>(); + environment.getPropertySources() + .addLast(new MapPropertySource("extPropertiesConfig", propertiesToAdd)); + ConfigurableApplicationContext applicationContext = new GenericApplicationContext(); + applicationContext.setEnvironment(environment); + + PubSubExtendedBindingProperties pubSubExtendedBindingProperties = new PubSubExtendedBindingProperties(); + pubSubExtendedBindingProperties.setApplicationContext(applicationContext); + final PubSubConsumerProperties defaultConsumerProperties = pubSubExtendedBindingProperties + .getExtendedConsumerProperties("default-input"); + final PubSubProducerProperties defaultProducerProperties = pubSubExtendedBindingProperties + .getExtendedProducerProperties("default-output"); + when(((ExtendedPropertiesBinder) mock).getExtendedConsumerProperties("default-input")) + .thenReturn(defaultConsumerProperties); + when(((ExtendedPropertiesBinder) mock).getExtendedProducerProperties("default-output")) + .thenReturn(defaultProducerProperties); + return mock; } } } diff --git a/spring-cloud-gcp-pubsub/pom.xml b/spring-cloud-gcp-pubsub/pom.xml index 4dcfc2bce7..135cd49c5b 100644 --- a/spring-cloud-gcp-pubsub/pom.xml +++ b/spring-cloud-gcp-pubsub/pom.xml @@ -5,7 +5,7 @@ com.google.cloud spring-cloud-gcp - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT spring-cloud-gcp-pubsub diff --git a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/PubSubAdmin.java b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/PubSubAdmin.java index cf2cc5e258..cdb0e696a5 100644 --- a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/PubSubAdmin.java +++ b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/PubSubAdmin.java @@ -291,7 +291,8 @@ public Subscription getSubscription(String subscriptionName) { try { return this.subscriptionAdminClient.getSubscription( - PubSubSubscriptionUtils.toProjectSubscriptionName(subscriptionName, this.projectId)); + PubSubSubscriptionUtils + .toProjectSubscriptionName(subscriptionName, this.projectId).toString()); } catch (ApiException aex) { if (aex.getStatusCode().getCode() == StatusCode.Code.NOT_FOUND) { return null; @@ -312,7 +313,8 @@ public void deleteSubscription(String subscriptionName) { Assert.hasText(subscriptionName, "No subscription name was specified"); this.subscriptionAdminClient.deleteSubscription( - PubSubSubscriptionUtils.toProjectSubscriptionName(subscriptionName, this.projectId)); + PubSubSubscriptionUtils + .toProjectSubscriptionName(subscriptionName, this.projectId).toString()); } /** diff --git a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/PubSubConfiguration.java b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/PubSubConfiguration.java index 83326319c7..944d586718 100644 --- a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/PubSubConfiguration.java +++ b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/PubSubConfiguration.java @@ -132,19 +132,6 @@ public void initialize(String defaultProjectId) { this.fullyQualifiedSubscriptionProperties = Collections.unmodifiableMap(fullyQualifiedProps); } - /** - * Returns properties for the specified subscription name and project ID. - * - * @param name short subscription name - * @param projectId subscription project name - * @return user-provided subscription properties - * @deprecated use {@link #getSubscriptionProperties(ProjectSubscriptionName)} instead. - */ - @Deprecated - public Subscriber getSubscriber(String name, String projectId) { - return getSubscriptionProperties(PubSubSubscriptionUtils.toProjectSubscriptionName(name, projectId)); - } - /** * Returns properties for the specified fully-qualified {@link ProjectSubscriptionName}. * @@ -161,22 +148,6 @@ public Subscriber getSubscriptionProperties(ProjectSubscriptionName projectSubsc return globalSubscriber; } - /** - * Computes flow control settings to use. The subscription-specific property takes precedence if - * both global and subscription-specific properties are set. If subscription-specific settings are - * not set then global settings are picked. - * - * @param subscriptionName subscription name - * @param projectId project id - * @return flow control settings - * @deprecated use {@link #computeSubscriberFlowControlSettings(ProjectSubscriptionName)} - */ - @Deprecated - public FlowControl computeSubscriberFlowControlSettings( - String subscriptionName, String projectId) { - return computeSubscriberFlowControlSettings(ProjectSubscriptionName.of(projectId, subscriptionName)); - } - /** * Computes flow control settings to use. The subscription-specific property takes precedence if * both global and subscription-specific properties are set. If subscription-specific settings are @@ -320,21 +291,6 @@ public String computePullEndpoint(String subscriptionName, String projectId) { return pullEndpoint != null ? pullEndpoint : this.globalSubscriber.getPullEndpoint(); } - /** - * Computes the retry settings. The subscription-specific property takes precedence if both global - * and subscription-specific properties are set. If subscription-specific settings are not set - * then the global settings are picked. - * - * @param subscriptionName subscription name - * @param projectId project id - * @return retry settings - * @deprecated Use {{@link #computeSubscriberRetrySettings(ProjectSubscriptionName)}} - */ - @Deprecated - public Retry computeSubscriberRetrySettings(String subscriptionName, String projectId) { - return computeSubscriberRetrySettings(ProjectSubscriptionName.of(projectId, subscriptionName)); - } - /** * Computes the retry settings. The subscription-specific property takes precedence if both global * and subscription-specific properties are set. If subscription-specific settings are not set diff --git a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/PubSubTemplate.java b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/PubSubTemplate.java index 92ce884c7c..8fff15c844 100644 --- a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/PubSubTemplate.java +++ b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/PubSubTemplate.java @@ -30,9 +30,9 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import org.springframework.util.Assert; -import org.springframework.util.concurrent.ListenableFuture; /** * Default implementation of {@link PubSubOperations}. @@ -105,18 +105,18 @@ public void setMessageConverter(PubSubMessageConverter messageConverter) { * PubsubMessage} and then publish it. */ @Override - public ListenableFuture publish( + public CompletableFuture publish( String topic, T payload, Map headers) { return this.pubSubPublisherTemplate.publish(topic, payload, headers); } @Override - public ListenableFuture publish(String topic, T payload) { + public CompletableFuture publish(String topic, T payload) { return this.pubSubPublisherTemplate.publish(topic, payload, null); } @Override - public ListenableFuture publish(final String topic, PubsubMessage pubsubMessage) { + public CompletableFuture publish(final String topic, PubsubMessage pubsubMessage) { return this.pubSubPublisherTemplate.publish(topic, pubsubMessage); } @@ -142,7 +142,7 @@ public List pull( } @Override - public ListenableFuture> pullAsync( + public CompletableFuture> pullAsync( String subscription, Integer maxMessages, Boolean returnImmediately) { return this.pubSubSubscriberTemplate.pullAsync(subscription, maxMessages, returnImmediately); } @@ -155,7 +155,7 @@ public List> pullAndConvert( } @Override - public ListenableFuture>> pullAndConvertAsync( + public CompletableFuture>> pullAndConvertAsync( String subscription, Integer maxMessages, Boolean returnImmediately, Class payloadType) { return this.pubSubSubscriberTemplate.pullAndConvertAsync( subscription, maxMessages, returnImmediately, payloadType); @@ -168,7 +168,7 @@ public List pullAndAck( } @Override - public ListenableFuture> pullAndAckAsync( + public CompletableFuture> pullAndAckAsync( String subscription, Integer maxMessages, Boolean returnImmediately) { return this.pubSubSubscriberTemplate.pullAndAckAsync( subscription, maxMessages, returnImmediately); @@ -180,24 +180,24 @@ public PubsubMessage pullNext(String subscription) { } @Override - public ListenableFuture pullNextAsync(String subscription) { + public CompletableFuture pullNextAsync(String subscription) { return this.pubSubSubscriberTemplate.pullNextAsync(subscription); } @Override - public ListenableFuture ack( + public CompletableFuture ack( Collection acknowledgeablePubsubMessages) { return this.pubSubSubscriberTemplate.ack(acknowledgeablePubsubMessages); } @Override - public ListenableFuture nack( + public CompletableFuture nack( Collection acknowledgeablePubsubMessages) { return this.pubSubSubscriberTemplate.nack(acknowledgeablePubsubMessages); } @Override - public ListenableFuture modifyAckDeadline( + public CompletableFuture modifyAckDeadline( Collection acknowledgeablePubsubMessages, int ackDeadlineSeconds) { return this.pubSubSubscriberTemplate.modifyAckDeadline( diff --git a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/publisher/PubSubPublisherOperations.java b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/publisher/PubSubPublisherOperations.java index ba48697f52..01bef19a26 100644 --- a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/publisher/PubSubPublisherOperations.java +++ b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/publisher/PubSubPublisherOperations.java @@ -18,7 +18,7 @@ import com.google.pubsub.v1.PubsubMessage; import java.util.Map; -import org.springframework.util.concurrent.ListenableFuture; +import java.util.concurrent.CompletableFuture; /** * An abstraction for Google Cloud Pub/Sub publisher operations. @@ -37,7 +37,7 @@ public interface PubSubPublisherOperations { * @param the type of the payload to publish * @return the listenable future of the call */ - ListenableFuture publish(String topic, T payload, Map headers); + CompletableFuture publish(String topic, T payload, Map headers); /** * Send a message to Pub/Sub. @@ -48,7 +48,7 @@ public interface PubSubPublisherOperations { * @param the type of the payload to publish * @return the listenable future of the call */ - ListenableFuture publish(String topic, T payload); + CompletableFuture publish(String topic, T payload); /** * Send a message to Pub/Sub. @@ -58,5 +58,5 @@ public interface PubSubPublisherOperations { * @param pubsubMessage a Google Cloud Pub/Sub API message * @return the listenable future of the call */ - ListenableFuture publish(String topic, PubsubMessage pubsubMessage); + CompletableFuture publish(String topic, PubsubMessage pubsubMessage); } diff --git a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/publisher/PubSubPublisherTemplate.java b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/publisher/PubSubPublisherTemplate.java index e9b9a91487..17532fc05c 100644 --- a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/publisher/PubSubPublisherTemplate.java +++ b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/publisher/PubSubPublisherTemplate.java @@ -27,11 +27,10 @@ import com.google.cloud.spring.pubsub.support.converter.SimplePubSubMessageConverter; import com.google.pubsub.v1.PubsubMessage; import java.util.Map; +import java.util.concurrent.CompletableFuture; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; -import org.springframework.util.concurrent.ListenableFuture; -import org.springframework.util.concurrent.SettableListenableFuture; /** * Default implementation of {@link PubSubPublisherOperations}. @@ -76,28 +75,28 @@ public void setMessageConverter(PubSubMessageConverter pubSubMessageConverter) { * PubsubMessage} and then publish it. */ @Override - public ListenableFuture publish( + public CompletableFuture publish( String topic, T payload, Map headers) { return publish(topic, this.pubSubMessageConverter.toPubSubMessage(payload, headers)); } @Override - public ListenableFuture publish(String topic, T payload) { + public CompletableFuture publish(String topic, T payload) { return publish(topic, payload, null); } @Override - public ListenableFuture publish(final String topic, PubsubMessage pubsubMessage) { + public CompletableFuture publish(final String topic, PubsubMessage pubsubMessage) { Assert.hasText(topic, "The topic can't be null or empty."); Assert.notNull(pubsubMessage, "The pubsubMessage can't be null."); ApiFuture publishFuture = this.publisherFactory.createPublisher(topic).publish(pubsubMessage); - final SettableListenableFuture settableFuture = new SettableListenableFuture<>(); + final CompletableFuture completableFuture = new CompletableFuture<>(); ApiFutures.addCallback( publishFuture, - new ApiFutureCallback() { + new ApiFutureCallback<>() { @Override public void onFailure(Throwable throwable) { @@ -105,7 +104,7 @@ public void onFailure(Throwable throwable) { LOGGER.warn(errorMessage, throwable); PubSubDeliveryException pubSubDeliveryException = new PubSubDeliveryException(pubsubMessage, errorMessage, throwable); - settableFuture.setException(pubSubDeliveryException); + completableFuture.completeExceptionally(pubSubDeliveryException); } @Override @@ -113,12 +112,12 @@ public void onSuccess(String result) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Publishing to " + topic + " was successful. Message ID: " + result); } - settableFuture.set(result); + completableFuture.complete(result); } }, directExecutor()); - return settableFuture; + return completableFuture; } public PublisherFactory getPublisherFactory() { diff --git a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/publisher/PublisherCustomizer.java b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/publisher/PublisherCustomizer.java index 6cd2bc813c..fa493c9c1a 100644 --- a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/publisher/PublisherCustomizer.java +++ b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/publisher/PublisherCustomizer.java @@ -23,5 +23,5 @@ * Can be implemented as a lambda accepting a {@link Publisher.Builder} and a `String` topic. */ public interface PublisherCustomizer { - public void apply(Publisher.Builder publisherBuilder, String topic); + void apply(Publisher.Builder publisherBuilder, String topic); } diff --git a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/subscriber/PubSubSubscriberOperations.java b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/subscriber/PubSubSubscriberOperations.java index 4bc9bd1cc9..b0200b2ff0 100644 --- a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/subscriber/PubSubSubscriberOperations.java +++ b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/subscriber/PubSubSubscriberOperations.java @@ -24,8 +24,8 @@ import com.google.pubsub.v1.PubsubMessage; import java.util.Collection; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; -import org.springframework.util.concurrent.ListenableFuture; /** * An abstraction for Google Cloud Pub/Sub subscription / pulling operations. @@ -97,11 +97,11 @@ List pullAndAck( * @param returnImmediately returns immediately even if subscription doesn't contain enough * messages to satisfy {@code maxMessages}. Setting this parameter to {@code true} is not * recommended as it may result in long delays in message delivery. - * @return the ListenableFuture for the asynchronous execution, returning the list of received + * @return the CompletableFuture for the asynchronous execution, returning the list of received * acknowledgeable messages * @since 1.2.3 */ - ListenableFuture> pullAndAckAsync( + CompletableFuture> pullAndAckAsync( String subscription, Integer maxMessages, Boolean returnImmediately); /** @@ -131,11 +131,11 @@ List pull( * @param returnImmediately returns immediately even if subscription doesn't contain enough * messages to satisfy {@code maxMessages}. Setting this parameter to {@code true} is not * recommended as it may result in long delays in message delivery. - * @return the ListenableFuture for the asynchronous execution, returning the list of received + * @return the CompletableFuture for the asynchronous execution, returning the list of received * acknowledgeable messages * @since 1.2.3 */ - ListenableFuture> pullAsync( + CompletableFuture> pullAsync( String subscription, Integer maxMessages, Boolean returnImmediately); /** @@ -172,11 +172,11 @@ List> pullAndConvert( * recommended as it may result in long delays in message delivery. * @param payloadType the type to which the payload of the Pub/Sub messages should be converted * @param the type of the payload - * @return the ListenableFuture for the asynchronous execution, returning the list of received + * @return the CompletableFuture for the asynchronous execution, returning the list of received * acknowledgeable messages * @since 1.2.3 */ - ListenableFuture>> pullAndConvertAsync( + CompletableFuture>> pullAndConvertAsync( String subscription, Integer maxMessages, Boolean returnImmediately, Class payloadType); /** @@ -195,28 +195,28 @@ ListenableFuture>> pullAndConv * @param subscription short subscription name, e.g., "subscriptionName", or the * fully-qualified subscription name in the {@code * projects/[project_name]/subscriptions/[subscription_name]} format - * @return the ListenableFuture for the asynchronous execution, returning a received message, or + * @return the CompletableFuture for the asynchronous execution, returning a received message, or * {@code null} if none exists in the subscription * @since 1.2.3 */ - ListenableFuture pullNextAsync(String subscription); + CompletableFuture pullNextAsync(String subscription); /** * Acknowledge a batch of messages. The messages must have the same project id. * * @param acknowledgeablePubsubMessages messages to be acknowledged - * @return {@code ListenableFuture} the ListenableFuture for the asynchronous execution + * @return {@code CompletableFuture} the CompletableFuture for the asynchronous execution */ - ListenableFuture ack( + CompletableFuture ack( Collection acknowledgeablePubsubMessages); /** * Negatively acknowledge a batch of messages. The messages must have the same project id. * * @param acknowledgeablePubsubMessages messages to be negatively acknowledged - * @return {@code ListenableFuture} the ListenableFuture for the asynchronous execution + * @return {@code CompletableFuture} the CompletableFuture for the asynchronous execution */ - ListenableFuture nack( + CompletableFuture nack( Collection acknowledgeablePubsubMessages); /** @@ -225,10 +225,10 @@ ListenableFuture nack( * @param acknowledgeablePubsubMessages messages to be modified * @param ackDeadlineSeconds the new ack deadline in seconds. A deadline of 0 effectively nacks * the messages. - * @return {@code ListenableFuture} the ListenableFuture for the asynchronous execution + * @return {@code CompletableFuture} the CompletableFuture for the asynchronous execution * @since 1.1 */ - ListenableFuture modifyAckDeadline( + CompletableFuture modifyAckDeadline( Collection acknowledgeablePubsubMessages, int ackDeadlineSeconds); } diff --git a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/subscriber/PubSubSubscriberTemplate.java b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/subscriber/PubSubSubscriberTemplate.java index 89e8cfcab2..29b3e40566 100644 --- a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/subscriber/PubSubSubscriberTemplate.java +++ b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/core/subscriber/PubSubSubscriberTemplate.java @@ -42,6 +42,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; @@ -52,8 +53,6 @@ import java.util.stream.Collectors; import org.springframework.beans.factory.DisposableBean; import org.springframework.util.Assert; -import org.springframework.util.concurrent.ListenableFuture; -import org.springframework.util.concurrent.SettableListenableFuture; /** * Default implementation of {@link PubSubSubscriberOperations}. @@ -83,7 +82,7 @@ public class PubSubSubscriberTemplate implements PubSubSubscriberOperations, Dis private Executor asyncPullExecutor = Runnable::run; - private ConcurrentHashMap subscriptionNameToStubMap = + private final ConcurrentHashMap subscriptionNameToStubMap = new ConcurrentHashMap<>(); /** @@ -200,23 +199,23 @@ private List pull(PullRequest pullRequest) { * Pulls messages asynchronously, on demand, using the pull request in argument. * * @param pullRequest pull request containing the subscription name - * @return the ListenableFuture for the asynchronous execution, returning the list of {@link + * @return the CompletableFuture for the asynchronous execution, returning the list of {@link * AcknowledgeablePubsubMessage} containing the ack ID, subscription and acknowledger */ - private ListenableFuture> pullAsync(PullRequest pullRequest) { + private CompletableFuture> pullAsync(PullRequest pullRequest) { Assert.notNull(pullRequest, "The pull request can't be null."); ApiFuture pullFuture = getSubscriberStub(pullRequest.getSubscription()).pullCallable().futureCall(pullRequest); - final SettableListenableFuture> settableFuture = - new SettableListenableFuture<>(); + final CompletableFuture> completableFuture = + new CompletableFuture<>(); ApiFutures.addCallback( pullFuture, - new ApiFutureCallback() { + new ApiFutureCallback<>() { @Override public void onFailure(Throwable throwable) { - settableFuture.setException(throwable); + completableFuture.completeExceptionally(throwable); } @Override @@ -225,12 +224,12 @@ public void onSuccess(PullResponse pullResponse) { toAcknowledgeablePubsubMessageList( pullResponse.getReceivedMessagesList(), pullRequest.getSubscription()); - settableFuture.set(result); + completableFuture.complete(result); } }, asyncPullExecutor); - return settableFuture; + return completableFuture; } private List toAcknowledgeablePubsubMessageList( @@ -238,12 +237,12 @@ private List toAcknowledgeablePubsubMessageList( return messages.stream() .map( message -> - new PulledAcknowledgeablePubsubMessage( + (AcknowledgeablePubsubMessage) new PulledAcknowledgeablePubsubMessage( PubSubSubscriptionUtils.toProjectSubscriptionName( subscriptionId, this.subscriberFactory.getProjectId()), message.getMessage(), message.getAckId())) - .collect(Collectors.toList()); + .toList(); } @Override @@ -254,7 +253,7 @@ public List pull( } @Override - public ListenableFuture> pullAsync( + public CompletableFuture> pullAsync( String subscription, Integer maxMessages, Boolean returnImmediately) { return pullAsync( this.subscriberFactory.createPullRequest(subscription, maxMessages, returnImmediately)); @@ -270,32 +269,36 @@ public List> pullAndConvert( } @Override - public ListenableFuture>> pullAndConvertAsync( + public CompletableFuture>> pullAndConvertAsync( String subscription, Integer maxMessages, Boolean returnImmediately, Class payloadType) { - final SettableListenableFuture>> settableFuture = - new SettableListenableFuture<>(); + final CompletableFuture>> completableFuture = + new CompletableFuture<>(); this.pullAsync(subscription, maxMessages, returnImmediately) - .addCallback( - ackableMessages -> - settableFuture.set( - this.toConvertedAcknowledgeablePubsubMessages(payloadType, ackableMessages)), - settableFuture::setException); + .whenComplete( + (ackableMessages, exception) -> { + if (exception != null) { + completableFuture.completeExceptionally(exception); + return; + } + completableFuture.complete( + this.toConvertedAcknowledgeablePubsubMessages(payloadType, ackableMessages)); + }); - return settableFuture; + return completableFuture; } - private - List> toConvertedAcknowledgeablePubsubMessages( - Class payloadType, List ackableMessages) { + private List> toConvertedAcknowledgeablePubsubMessages( + Class payloadType, List ackableMessages) { return ackableMessages.stream() .map( m -> - new ConvertedPulledAcknowledgeablePubsubMessage<>( - m, - this.pubSubMessageConverter.fromPubSubMessage( - m.getPubsubMessage(), payloadType))) - .collect(Collectors.toList()); + (ConvertedAcknowledgeablePubsubMessage) + new ConvertedPulledAcknowledgeablePubsubMessage<>( + m, + this.pubSubMessageConverter + .fromPubSubMessage(m.getPubsubMessage(), payloadType))) + .toList(); } @Override @@ -312,34 +315,37 @@ public List pullAndAck( return ackableMessages.stream() .map(AcknowledgeablePubsubMessage::getPubsubMessage) - .collect(Collectors.toList()); + .toList(); } @Override - public ListenableFuture> pullAndAckAsync( + public CompletableFuture> pullAndAckAsync( String subscription, Integer maxMessages, Boolean returnImmediately) { PullRequest pullRequest = this.subscriberFactory.createPullRequest(subscription, maxMessages, returnImmediately); - final SettableListenableFuture> settableFuture = - new SettableListenableFuture<>(); + final CompletableFuture> completableFuture = + new CompletableFuture<>(); this.pullAsync(pullRequest) - .addCallback( - ackableMessages -> { + .whenComplete( + (ackableMessages, exception) -> { + if (exception != null) { + completableFuture.completeExceptionally(exception); + return; + } if (!ackableMessages.isEmpty()) { ack(ackableMessages); } List messages = ackableMessages.stream() .map(AcknowledgeablePubsubMessage::getPubsubMessage) - .collect(Collectors.toList()); + .toList(); - settableFuture.set(messages); - }, - settableFuture::setException); + completableFuture.complete(messages); + }); - return settableFuture; + return completableFuture; } @Override @@ -350,18 +356,22 @@ public PubsubMessage pullNext(String subscription) { } @Override - public ListenableFuture pullNextAsync(String subscription) { - final SettableListenableFuture settableFuture = new SettableListenableFuture<>(); + public CompletableFuture pullNextAsync(String subscription) { + final CompletableFuture completableFuture = new CompletableFuture<>(); this.pullAndAckAsync(subscription, 1, true) - .addCallback( - messages -> { + .whenComplete( + (messages, exception) -> { + if (exception != null) { + completableFuture.completeExceptionally(exception); + return; + } + PubsubMessage message = messages.isEmpty() ? null : messages.get(0); - settableFuture.set(message); - }, - settableFuture::setException); + completableFuture.complete(message); + }); - return settableFuture; + return completableFuture; } public SubscriberFactory getSubscriberFactory() { @@ -374,10 +384,10 @@ public SubscriberFactory getSubscriberFactory() { * exception was detected first. * * @param acknowledgeablePubsubMessages messages, potentially from different subscriptions. - * @return {@link ListenableFuture} indicating overall success or failure. + * @return {@link CompletableFuture} indicating overall success or failure. */ @Override - public ListenableFuture ack( + public CompletableFuture ack( Collection acknowledgeablePubsubMessages) { Assert.notEmpty( acknowledgeablePubsubMessages, "The acknowledgeablePubsubMessages can't be empty."); @@ -391,10 +401,10 @@ public ListenableFuture ack( * detected first. * * @param acknowledgeablePubsubMessages messages, potentially from different subscriptions. - * @return {@link ListenableFuture} indicating overall success or failure. + * @return {@link CompletableFuture} indicating overall success or failure. */ @Override - public ListenableFuture nack( + public CompletableFuture nack( Collection acknowledgeablePubsubMessages) { return modifyAckDeadline(acknowledgeablePubsubMessages, 0); } @@ -405,10 +415,10 @@ public ListenableFuture nack( * whichever exception was detected first. * * @param acknowledgeablePubsubMessages messages, potentially from different subscriptions. - * @return {@link ListenableFuture} indicating overall success or failure. + * @return {@link CompletableFuture} indicating overall success or failure. */ @Override - public ListenableFuture modifyAckDeadline( + public CompletableFuture modifyAckDeadline( Collection acknowledgeablePubsubMessages, int ackDeadlineSeconds) { Assert.notEmpty( @@ -455,17 +465,17 @@ private ApiFuture modifyAckDeadline( /** * Perform Pub/Sub operations (ack/nack/modifyAckDeadline) in per-subscription batches. * - *

The returned {@link ListenableFuture} will complete when either all batches completes + *

The returned {@link CompletableFuture} will complete when either all batches completes * successfully or when at least one fails. * *

In case of multiple batch failures, which exception will be in the final {@link - * ListenableFuture} is non-deterministic. + * CompletableFuture} is non-deterministic. * * @param acknowledgeablePubsubMessages messages, could be from different subscriptions. * @param asyncOperation specific Pub/Sub operation to perform. - * @return {@link ListenableFuture} indicating overall success or failure. + * @return {@link CompletableFuture} indicating overall success or failure. */ - private ListenableFuture doBatchedAsyncOperation( + private CompletableFuture doBatchedAsyncOperation( Collection acknowledgeablePubsubMessages, BiFunction, ApiFuture> asyncOperation) { @@ -485,7 +495,7 @@ private ListenableFuture doBatchedAsyncOperation( == 1, "The project id of all messages must match."); - SettableListenableFuture settableListenableFuture = new SettableListenableFuture<>(); + CompletableFuture completableFuture = new CompletableFuture<>(); int numExpectedFutures = groupedMessages.size(); AtomicInteger numCompletedFutures = new AtomicInteger(); @@ -495,7 +505,7 @@ private ListenableFuture doBatchedAsyncOperation( ApiFutures.addCallback( ackApiFuture, - new ApiFutureCallback() { + new ApiFutureCallback<>() { @Override public void onFailure(Throwable throwable) { processResult(throwable); @@ -508,16 +518,16 @@ public void onSuccess(Empty empty) { private void processResult(Throwable throwable) { if (throwable != null) { - settableListenableFuture.setException(throwable); + completableFuture.completeExceptionally(throwable); } else if (numCompletedFutures.incrementAndGet() == numExpectedFutures) { - settableListenableFuture.set(null); + completableFuture.complete(null); } } }, this.ackExecutor); }); - return settableListenableFuture; + return completableFuture; } private SubscriberStub getSubscriberStub(String subscription) { @@ -569,17 +579,17 @@ public String getAckId() { } @Override - public ListenableFuture ack() { + public CompletableFuture ack() { return PubSubSubscriberTemplate.this.ack(Collections.singleton(this)); } @Override - public ListenableFuture nack() { + public CompletableFuture nack() { return modifyAckDeadline(0); } @Override - public ListenableFuture modifyAckDeadline(int ackDeadlineSeconds) { + public CompletableFuture modifyAckDeadline(int ackDeadlineSeconds) { return PubSubSubscriberTemplate.this.modifyAckDeadline( Collections.singleton(this), ackDeadlineSeconds); } @@ -616,31 +626,31 @@ private static class PushedAcknowledgeablePubsubMessage } @Override - public ListenableFuture ack() { - SettableListenableFuture settableListenableFuture = new SettableListenableFuture<>(); + public CompletableFuture ack() { + CompletableFuture completableFuture = new CompletableFuture<>(); try { this.ackReplyConsumer.ack(); - settableListenableFuture.set(null); + completableFuture.complete(null); } catch (Exception e) { - settableListenableFuture.setException(e); + completableFuture.completeExceptionally(e); } - return settableListenableFuture; + return completableFuture; } @Override - public ListenableFuture nack() { - SettableListenableFuture settableListenableFuture = new SettableListenableFuture<>(); + public CompletableFuture nack() { + CompletableFuture completableFuture = new CompletableFuture<>(); try { this.ackReplyConsumer.nack(); - settableListenableFuture.set(null); + completableFuture.complete(null); } catch (Exception e) { - settableListenableFuture.setException(e); + completableFuture.completeExceptionally(e); } - return settableListenableFuture; + return completableFuture; } @Override diff --git a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/integration/inbound/PubSubInboundChannelAdapter.java b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/integration/inbound/PubSubInboundChannelAdapter.java index f8532254ee..14ad6ad288 100644 --- a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/integration/inbound/PubSubInboundChannelAdapter.java +++ b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/integration/inbound/PubSubInboundChannelAdapter.java @@ -29,6 +29,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.integration.endpoint.MessageProducerSupport; import org.springframework.integration.mapping.HeaderMapper; +import org.springframework.messaging.MessageDeliveryException; import org.springframework.util.Assert; /** @@ -156,21 +157,30 @@ private void consumeMessage(ConvertedBasicAcknowledgeablePubsubMessage messag } catch (RuntimeException re) { if (this.ackMode == AckMode.AUTO) { message.nack(); - LOGGER.warn( - "Sending Spring message [" - + message.getPubsubMessage().getMessageId() - + "] failed; message nacked automatically.", - re); + logWarning(message, re, "message nacked automatically."); } else { - LOGGER.warn( - "Sending Spring message [" - + message.getPubsubMessage().getMessageId() - + "] failed; message neither acked nor nacked.", - re); + logWarning(message, re, "message neither acked nor nacked."); } } } + private void logWarning( + ConvertedBasicAcknowledgeablePubsubMessage message, + RuntimeException re, + String actionMessage) { + LOGGER.warn(String.format("Sending Spring message [%s] failed; %s", + message.getPubsubMessage().getMessageId(), actionMessage)); + // Starting from Spring 3.0, nested exception message is NOT included in stacktrace. + // However, customers may still rely on messages in nested exception to troubleshoot, + // so we explicitly log failure messages. + // See https://github.com/spring-projects/spring-framework/issues/25162 for more info. + if (re instanceof MessageDeliveryException messageDeliveryException) { + LOGGER.warn(messageDeliveryException.getFailedMessage()); + } else { + LOGGER.warn(re.getMessage()); + } + } + private void addToHealthRegistry() { if (healthCheckEnabled()) { healthTrackerRegistry.registerTracker(subscriptionName); diff --git a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/integration/outbound/PubSubMessageHandler.java b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/integration/outbound/PubSubMessageHandler.java index c5cd50af87..dcab0f43c6 100644 --- a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/integration/outbound/PubSubMessageHandler.java +++ b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/integration/outbound/PubSubMessageHandler.java @@ -21,9 +21,11 @@ import com.google.cloud.spring.pubsub.support.GcpPubSubHeaders; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.function.BiConsumer; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.common.LiteralExpression; @@ -35,8 +37,6 @@ import org.springframework.messaging.Message; import org.springframework.messaging.MessageHandlingException; import org.springframework.util.Assert; -import org.springframework.util.concurrent.ListenableFuture; -import org.springframework.util.concurrent.ListenableFutureCallback; /** * Outbound channel adapter to publish messages to Google Cloud Pub/Sub. @@ -58,7 +58,7 @@ public class PubSubMessageHandler extends AbstractMessageHandler { private Expression publishTimeoutExpression = new ValueExpression<>(DEFAULT_PUBLISH_TIMEOUT); - private ListenableFutureCallback publishCallback; + private BiConsumer publishCallback; private SuccessCallback successCallback; @@ -130,21 +130,10 @@ public void setPublishTimeout(long timeoutMillis) { setPublishTimeoutExpression(new ValueExpression<>(timeoutMillis)); } - protected ListenableFutureCallback getPublishCallback() { + protected BiConsumer getPublishCallback() { return this.publishCallback; } - /** - * Set the callback to be activated when the publish call resolves. - * - * @param publishCallback callback for the publish future - * @deprecated Use {@link #setSuccessCallback} and {@link #setFailureCallback} instead. - */ - @Deprecated - public void setPublishCallback(ListenableFutureCallback publishCallback) { - this.publishCallback = publishCallback; - } - /** * Set callback (can be a lambda) for processing the published message ID and the original {@code * Message} after the message was successfully published. @@ -217,15 +206,15 @@ protected void handleMessageInternal(Message message) { Map headers = new HashMap<>(); this.headerMapper.fromHeaders(message.getHeaders(), headers); - ListenableFuture pubsubFuture = + CompletableFuture pubsubFuture = this.pubSubPublisherOperations.publish(topic, payload, headers); if (this.publishCallback != null) { - pubsubFuture.addCallback(this.publishCallback); + pubsubFuture.whenComplete(this.publishCallback); } if (this.successCallback != null || this.failureCallback != null) { - pubsubFuture.addCallback(new PubSubPublishCallback(message)); + pubsubFuture.whenComplete(new PubSubPublishCallback(message)); } if (this.sync) { @@ -256,7 +245,7 @@ private String calculateTopic(Message message) { } private void blockOnPublishFuture( - ListenableFuture pubsubFuture, Message message, Long timeout) { + CompletableFuture pubsubFuture, Message message, Long timeout) { try { if (timeout == null || timeout < 0) { pubsubFuture.get(); @@ -298,25 +287,22 @@ public interface FailureCallback { * Publish callback that invokes the parent {@code PubSubMessageHandler}'s success or failure * callback, if available. */ - private class PubSubPublishCallback implements ListenableFutureCallback { - private Message message; + private class PubSubPublishCallback implements BiConsumer { + private final Message message; PubSubPublishCallback(Message message) { this.message = message; } @Override - public void onFailure(Throwable throwable) { - if (PubSubMessageHandler.this.failureCallback != null) { - PubSubMessageHandler.this.failureCallback.onFailure(throwable, message); - } - } - - @Override - public void onSuccess(String messageId) { + public void accept(String messageId, Throwable throwable) { if (PubSubMessageHandler.this.successCallback != null) { PubSubMessageHandler.this.successCallback.onSuccess(messageId, message); } + + if (PubSubMessageHandler.this.failureCallback != null) { + PubSubMessageHandler.this.failureCallback.onFailure(throwable, message); + } } } } diff --git a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/reactive/PubSubReactiveFactory.java b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/reactive/PubSubReactiveFactory.java index 02a8788afc..d384e28a87 100644 --- a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/reactive/PubSubReactiveFactory.java +++ b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/reactive/PubSubReactiveFactory.java @@ -132,7 +132,7 @@ private void pollingPull( private Flux pullAll(String subscriptionName) { CompletableFuture> pullResponseFuture = - this.subscriberOperations.pullAsync(subscriptionName, maxMessages, true).completable(); + this.subscriberOperations.pullAsync(subscriptionName, maxMessages, true); return Mono.fromFuture(pullResponseFuture).flatMapMany(Flux::fromIterable); } @@ -142,8 +142,13 @@ private void backpressurePull( int intDemand = numRequested > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) numRequested; this.subscriberOperations .pullAsync(subscriptionName, intDemand, false) - .addCallback( - messages -> { + .whenComplete( + (messages, exception) -> { + if (exception != null) { + exceptionHandler(subscriptionName, numRequested, sink, exception); + return; + } + if (!sink.isCancelled()) { messages.forEach(sink::next); } @@ -153,19 +158,24 @@ private void backpressurePull( backpressurePull(subscriptionName, numToPull, sink); } } - }, - exception -> { - if (exception instanceof DeadlineExceededException) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace( - "Blocking pull timed out due to empty subscription " - + subscriptionName - + "; retrying."); - } - backpressurePull(subscriptionName, numRequested, sink); - } else { - sink.error(exception); - } }); } + + private void exceptionHandler( + String subscriptionName, + long numRequested, + FluxSink sink, + Throwable exception) { + if (exception instanceof DeadlineExceededException) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace( + "Blocking pull timed out due to empty subscription " + + subscriptionName + + "; retrying."); + } + backpressurePull(subscriptionName, numRequested, sink); + } else { + sink.error(exception); + } + } } diff --git a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/support/AcknowledgeablePubsubMessage.java b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/support/AcknowledgeablePubsubMessage.java index df17632380..4e72ce416b 100644 --- a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/support/AcknowledgeablePubsubMessage.java +++ b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/support/AcknowledgeablePubsubMessage.java @@ -16,7 +16,7 @@ package com.google.cloud.spring.pubsub.support; -import org.springframework.util.concurrent.ListenableFuture; +import java.util.concurrent.CompletableFuture; /** * An extension of {@link BasicAcknowledgeablePubsubMessage} that exposes ack ID and subscription @@ -39,8 +39,8 @@ public interface AcknowledgeablePubsubMessage extends BasicAcknowledgeablePubsub * * @param ackDeadlineSeconds the new ack deadline in seconds. A deadline of 0 effectively nacks * the message. - * @return {@code ListenableFuture} + * @return {@code CompletableFuture} * @since 1.1 */ - ListenableFuture modifyAckDeadline(int ackDeadlineSeconds); + CompletableFuture modifyAckDeadline(int ackDeadlineSeconds); } diff --git a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/support/BasicAcknowledgeablePubsubMessage.java b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/support/BasicAcknowledgeablePubsubMessage.java index f27432ac80..393afabaaf 100644 --- a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/support/BasicAcknowledgeablePubsubMessage.java +++ b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/support/BasicAcknowledgeablePubsubMessage.java @@ -18,7 +18,7 @@ import com.google.pubsub.v1.ProjectSubscriptionName; import com.google.pubsub.v1.PubsubMessage; -import org.springframework.util.concurrent.ListenableFuture; +import java.util.concurrent.CompletableFuture; /** * A {@link PubsubMessage} wrapper that allows it to be acknowledged. @@ -44,14 +44,14 @@ public interface BasicAcknowledgeablePubsubMessage { /** * Acknowledge (ack) the message asynchronously. * - * @return {@code ListenableFuture}} + * @return {@code CompletableFuture}} */ - ListenableFuture ack(); + CompletableFuture ack(); /** * Negatatively achnowledge (nack) the message asynchronously. * - * @return {@code ListenableFuture}} + * @return {@code CompletableFuture}} */ - ListenableFuture nack(); + CompletableFuture nack(); } diff --git a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/support/DefaultSubscriberFactory.java b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/support/DefaultSubscriberFactory.java index 325da7a64c..55373404c3 100644 --- a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/support/DefaultSubscriberFactory.java +++ b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/support/DefaultSubscriberFactory.java @@ -93,27 +93,6 @@ public class DefaultSubscriberFactory implements SubscriberFactory { private Code[] retryableCodes; - /** - * Default {@link DefaultSubscriberFactory} constructor. - * - * @param projectIdProvider provides the default GCP project ID for selecting the subscriptions - * @deprecated Use the new {@link DefaultSubscriberFactory - * (GcpProjectIdProvider,PubSubConfiguration)} instead - */ - @Deprecated - public DefaultSubscriberFactory(GcpProjectIdProvider projectIdProvider) { - this(projectIdProvider, getBlankConfiguration(projectIdProvider)); - } - - private static PubSubConfiguration getBlankConfiguration(GcpProjectIdProvider projectIdProvider) { - if (projectIdProvider == null) { - return null; - } - PubSubConfiguration config = new PubSubConfiguration(); - config.initialize(projectIdProvider.getProjectId()); - return config; - } - /** * Default {@link DefaultSubscriberFactory} constructor. * @@ -349,15 +328,6 @@ public PullRequest createPullRequest( return pullRequestBuilder.build(); } - @Override - public SubscriberStub createSubscriberStub() { - try { - return GrpcSubscriberStub.create(buildGlobalSubscriberStubSettings()); - } catch (IOException ex) { - throw new PubSubException("Error creating the SubscriberStub", ex); - } - } - @Override public SubscriberStub createSubscriberStub(String subscriptionName) { try { diff --git a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/support/PubSubSubscriptionUtils.java b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/support/PubSubSubscriptionUtils.java index daa7eb3560..3a5db2f2d9 100644 --- a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/support/PubSubSubscriptionUtils.java +++ b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/support/PubSubSubscriptionUtils.java @@ -33,7 +33,7 @@ private PubSubSubscriptionUtils() {} * Create a {@link ProjectSubscriptionName} based on a subscription name within a project or the * fully-qualified subscription name. If the specified subscription is in the {@code * projects/[project_name]/subscriptions/[subscription_name]} format, then the {@code projectId} - * is ignored} + * is ignored. * * @param subscription the subscription name in the project or the fully-qualified project name * @param projectId the project ID to use if the subscription is not a fully-qualified name diff --git a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/support/SubscriberFactory.java b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/support/SubscriberFactory.java index 72c8f17566..209db4658f 100644 --- a/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/support/SubscriberFactory.java +++ b/spring-cloud-gcp-pubsub/src/main/java/com/google/cloud/spring/pubsub/support/SubscriberFactory.java @@ -60,16 +60,6 @@ public interface SubscriberFactory { PullRequest createPullRequest( String subscriptionName, Integer maxMessages, Boolean returnImmediately); - /** - * Create a {@link SubscriberStub} that is needed to execute {@link PullRequest}s. This method - * will only set global settings. - * - * @return the {@link SubscriberStub} used for executing {@link PullRequest}s. - * @deprecated Use the new {@code createSubscriberStub(subscriptionName)} instead. - */ - @Deprecated - SubscriberStub createSubscriberStub(); - /** * Create a {@link SubscriberStub} that is needed to execute {@link PullRequest}s. * diff --git a/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/PubSubAdminTests.java b/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/PubSubAdminTests.java index 20cde0df3d..ca36ccd022 100644 --- a/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/PubSubAdminTests.java +++ b/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/PubSubAdminTests.java @@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -28,8 +29,8 @@ import com.google.cloud.NoCredentials; import com.google.cloud.pubsub.v1.SubscriptionAdminClient; import com.google.cloud.pubsub.v1.TopicAdminClient; +import com.google.cloud.spring.pubsub.support.PubSubSubscriptionUtils; import com.google.pubsub.v1.ProjectName; -import com.google.pubsub.v1.ProjectSubscriptionName; import com.google.pubsub.v1.PushConfig; import com.google.pubsub.v1.Subscription; import com.google.pubsub.v1.TopicName; @@ -236,7 +237,8 @@ void testGetSubscription() { () -> "test-project", this.mockTopicAdminClient, this.mockSubscriptionAdminClient) .getSubscription("fooSubscription"); verify(this.mockSubscriptionAdminClient) - .getSubscription(ProjectSubscriptionName.of("test-project", "fooSubscription")); + .getSubscription( + PubSubSubscriptionUtils.toProjectSubscriptionName("fooSubscription", "test-project").toString()); } @Test @@ -245,12 +247,12 @@ void testGetSubscription_fullName() { () -> "test-project", this.mockTopicAdminClient, this.mockSubscriptionAdminClient) .getSubscription("projects/differentProject/subscriptions/fooSubscription"); verify(this.mockSubscriptionAdminClient) - .getSubscription(ProjectSubscriptionName.of("differentProject", "fooSubscription")); + .getSubscription("projects/differentProject/subscriptions/fooSubscription"); } @Test void testGetSubscription_notFound() { - when(this.mockSubscriptionAdminClient.getSubscription(any(ProjectSubscriptionName.class))) + when(this.mockSubscriptionAdminClient.getSubscription(anyString())) .thenThrow(new ApiException(null, GrpcStatusCode.of(io.grpc.Status.Code.NOT_FOUND), false)); assertThat( new PubSubAdmin( @@ -260,12 +262,13 @@ void testGetSubscription_notFound() { .getSubscription("fooSubscription")) .isNull(); verify(this.mockSubscriptionAdminClient) - .getSubscription(ProjectSubscriptionName.of("test-project", "fooSubscription")); + .getSubscription( + PubSubSubscriptionUtils.toProjectSubscriptionName("fooSubscription", "test-project").toString()); } @Test void testGetSubscription_serviceDown() { - when(this.mockSubscriptionAdminClient.getSubscription(any(ProjectSubscriptionName.class))) + when(this.mockSubscriptionAdminClient.getSubscription(anyString())) .thenThrow( new ApiException(null, GrpcStatusCode.of(io.grpc.Status.Code.UNAVAILABLE), false)); PubSubAdmin psa = @@ -275,7 +278,8 @@ void testGetSubscription_serviceDown() { assertThatExceptionOfType(ApiException.class) .isThrownBy(() -> psa.getSubscription("fooSubscription")); verify(this.mockSubscriptionAdminClient) - .getSubscription(ProjectSubscriptionName.of("test-project", "fooSubscription")); + .getSubscription( + PubSubSubscriptionUtils.toProjectSubscriptionName("fooSubscription", "test-project").toString()); } @Test @@ -284,7 +288,8 @@ void testDeleteSubscription() { () -> "test-project", this.mockTopicAdminClient, this.mockSubscriptionAdminClient) .deleteSubscription("fooSubscription"); verify(this.mockSubscriptionAdminClient) - .deleteSubscription(ProjectSubscriptionName.of("test-project", "fooSubscription")); + .deleteSubscription( + PubSubSubscriptionUtils.toProjectSubscriptionName("fooSubscription", "test-project").toString()); } @Test @@ -293,7 +298,7 @@ void testDeleteSubscription_fullName() { () -> "test-project", this.mockTopicAdminClient, this.mockSubscriptionAdminClient) .deleteSubscription("projects/differentProject/subscriptions/fooSubscription"); verify(this.mockSubscriptionAdminClient) - .deleteSubscription(ProjectSubscriptionName.of("differentProject", "fooSubscription")); + .deleteSubscription("projects/differentProject/subscriptions/fooSubscription"); } @Test diff --git a/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/core/PubSubConfigurationTests.java b/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/core/PubSubConfigurationTests.java index e6d7360e9e..15e0249bfc 100644 --- a/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/core/PubSubConfigurationTests.java +++ b/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/core/PubSubConfigurationTests.java @@ -20,6 +20,7 @@ import com.google.api.gax.batching.FlowController; import com.google.api.gax.rpc.StatusCode.Code; +import com.google.cloud.spring.pubsub.support.PubSubSubscriptionUtils; import com.google.pubsub.v1.ProjectSubscriptionName; import java.util.Collections; import org.junit.jupiter.api.BeforeEach; @@ -384,7 +385,8 @@ void testComputeSubscriberRetrySettings_returnCustom() { pubSubConfiguration.initialize("projectId"); PubSubConfiguration.Retry result = - pubSubConfiguration.computeSubscriberRetrySettings("subscription-name", "projectId"); + pubSubConfiguration.computeSubscriberRetrySettings( + ProjectSubscriptionName.of("projectId", "subscription-name")); assertThat(result.getTotalTimeoutSeconds()).isEqualTo(10L); assertThat(result.getInitialRetryDelaySeconds()).isEqualTo(15L); @@ -414,7 +416,8 @@ void testComputeSubscriberRetrySettings_returnGlobal() { pubSubConfiguration.initialize("projectId"); PubSubConfiguration.Retry result = - pubSubConfiguration.computeSubscriberRetrySettings("subscription-name", "projectId"); + pubSubConfiguration.computeSubscriberRetrySettings( + ProjectSubscriptionName.of("projectId", "subscription-name")); assertThat(result.getTotalTimeoutSeconds()).isEqualTo(10L); assertThat(result.getInitialRetryDelaySeconds()).isEqualTo(15L); @@ -454,7 +457,8 @@ void testSubscriberMapProperties_defaultOrGlobal_notAddedToMap() { assertThat(pubSubConfiguration.getFullyQualifiedSubscriberProperties()).isEmpty(); assertThat( pubSubConfiguration - .getSubscriber("subscription-name", "projectId") + .getSubscriptionProperties(PubSubSubscriptionUtils + .toProjectSubscriptionName("subscription-name", "projectId")) .getExecutorThreads()) .isNull(); assertThat(pubSubConfiguration.getFullyQualifiedSubscriberProperties()).isEmpty(); @@ -471,7 +475,8 @@ void testSubscriberMapProperties_subscriptionName_returnCustom() { assertThat(pubSubConfiguration.getFullyQualifiedSubscriberProperties()).hasSize(1); assertThat( pubSubConfiguration - .getSubscriber("subscription-name", "projectId") + .getSubscriptionProperties(PubSubSubscriptionUtils + .toProjectSubscriptionName("subscription-name", "projectId")) .getExecutorThreads()) .isEqualTo(8); // asserts that map did not change from a getter. Might not be needed now that map is immutable. @@ -495,7 +500,8 @@ void testSubscriberMapProperties_fullNamePresentInMap_returnCustom() { assertThat(pubSubConfiguration.getFullyQualifiedSubscriberProperties()).hasSize(1); assertThat( pubSubConfiguration - .getSubscriber(QUALIFIED_SUBSCRIPTION_NAME, "projectId") + .getSubscriptionProperties(PubSubSubscriptionUtils + .toProjectSubscriptionName(QUALIFIED_SUBSCRIPTION_NAME, "projectId")) .getExecutorThreads()) .isEqualTo(8); assertThat( @@ -517,8 +523,8 @@ void testSubscriberMapProperties_fullNamePresentInMap_projectIdIgnored_returnCus assertThat(pubSubConfiguration.getFullyQualifiedSubscriberProperties()).hasSize(1); assertThat( pubSubConfiguration - .getSubscriber( - "projects/otherProjectId/subscriptions/subscription-name", "projectId") + .getSubscriptionProperties(PubSubSubscriptionUtils + .toProjectSubscriptionName("projects/otherProjectId/subscriptions/subscription-name", "projectId")) .getExecutorThreads()) .isEqualTo(8); assertThat( diff --git a/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/core/PubSubTemplateTests.java b/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/core/PubSubTemplateTests.java index d95f5055c6..e2f2759b3c 100644 --- a/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/core/PubSubTemplateTests.java +++ b/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/core/PubSubTemplateTests.java @@ -45,13 +45,13 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.util.concurrent.ListenableFuture; /** Tests for the Pub/Sub template. */ @ExtendWith(MockitoExtension.class) @@ -72,10 +72,8 @@ class PubSubTemplateTests { private SettableApiFuture settableApiFuture; private PubSubTemplate createTemplate() { - PubSubTemplate pubSubTemplate = - new PubSubTemplate(this.mockPublisherFactory, this.mockSubscriberFactory); - return pubSubTemplate; + return new PubSubTemplate(this.mockPublisherFactory, this.mockSubscriberFactory); } private PubSubPublisherTemplate createPublisherTemplate() { @@ -99,7 +97,7 @@ void testPublish() throws ExecutionException, InterruptedException { when(this.mockPublisherFactory.createPublisher("testTopic")).thenReturn(this.mockPublisher); when(this.mockPublisher.publish(isA(PubsubMessage.class))).thenReturn(this.settableApiFuture); this.settableApiFuture.set("result"); - ListenableFuture future = this.pubSubTemplate.publish("testTopic", this.pubsubMessage); + CompletableFuture future = this.pubSubTemplate.publish("testTopic", this.pubsubMessage); assertThat(future.get()).isEqualTo("result"); } @@ -181,24 +179,24 @@ void testSend_onFailure() { when(this.mockPublisherFactory.createPublisher("testTopic")).thenReturn(this.mockPublisher); when(this.mockPublisher.publish(isA(PubsubMessage.class))).thenReturn(this.settableApiFuture); - ListenableFuture future = this.pubSubTemplate.publish("testTopic", this.pubsubMessage); + CompletableFuture future = this.pubSubTemplate.publish("testTopic", this.pubsubMessage); this.settableApiFuture.setException(new Exception("future failed.")); - assertThatThrownBy(() -> future.get()) + assertThatThrownBy(future::get) .isInstanceOf(ExecutionException.class) - .hasMessageContaining("future failed."); + .hasStackTraceContaining("future failed."); } @Test void testPublish_onFailureWithPayload() { when(this.mockPublisherFactory.createPublisher("testTopic")).thenReturn(this.mockPublisher); when(this.mockPublisher.publish(isA(PubsubMessage.class))).thenReturn(this.settableApiFuture); - ListenableFuture future = this.pubSubTemplate.publish("testTopic", this.pubsubMessage); + CompletableFuture future = this.pubSubTemplate.publish("testTopic", this.pubsubMessage); this.settableApiFuture.setException(new Exception("Publish failed")); - assertThatThrownBy(() -> future.get()) + assertThatThrownBy(future::get) .isInstanceOf(ExecutionException.class) .hasCauseInstanceOf(PubSubDeliveryException.class) - .hasMessageContaining("Publish failed"); + .hasStackTraceContaining("Publish failed"); } @Test diff --git a/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/core/subscriber/PubSubSubscriberTemplateTests.java b/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/core/subscriber/PubSubSubscriberTemplateTests.java index bdccd9227f..1f741fffa7 100644 --- a/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/core/subscriber/PubSubSubscriberTemplateTests.java +++ b/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/core/subscriber/PubSubSubscriberTemplateTests.java @@ -51,11 +51,13 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.function.BiConsumer; import java.util.function.Consumer; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -67,8 +69,6 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; -import org.springframework.util.concurrent.ListenableFuture; -import org.springframework.util.concurrent.ListenableFutureCallback; /** Unit tests for {@link PubSubSubscriberTemplate}. */ @ExtendWith(MockitoExtension.class) @@ -77,7 +77,7 @@ class PubSubSubscriberTemplateTests { private PubSubSubscriberTemplate pubSubSubscriberTemplate; - private PubsubMessage pubsubMessage = PubsubMessage.newBuilder().build(); + private final PubsubMessage pubsubMessage = PubsubMessage.newBuilder().build(); @Mock private MessageReceiver messageReceiver; @@ -206,20 +206,20 @@ void testSubscribe_AndManualAck() verify(this.subscriber).startAsync(); verify(this.consumer).accept(this.message.capture()); - TestListenableFutureCallback testListenableFutureCallback = new TestListenableFutureCallback(); + TestCompletableFutureCallback completableFutureCallback = new TestCompletableFutureCallback(); - ListenableFuture listenableFuture = this.message.getValue().ack(); + CompletableFuture completableFuture = this.message.getValue().ack(); - assertThat(listenableFuture).isNotNull(); + assertThat(completableFuture).isNotNull(); - listenableFuture.addCallback(testListenableFutureCallback); - listenableFuture.get(10L, TimeUnit.SECONDS); + completableFuture.whenComplete(completableFutureCallback); + completableFuture.get(10L, TimeUnit.SECONDS); - assertThat(listenableFuture.isDone()).isTrue(); + assertThat(completableFuture.isDone()).isTrue(); verify(this.ackReplyConsumer).ack(); - assertThat(testListenableFutureCallback.getThrowable()).isNull(); + assertThat(completableFutureCallback.getThrowable()).isNull(); } @Test @@ -230,20 +230,20 @@ void testSubscribe_AndManualNack() verify(this.subscriber).startAsync(); verify(this.consumer).accept(this.message.capture()); - TestListenableFutureCallback testListenableFutureCallback = new TestListenableFutureCallback(); + TestCompletableFutureCallback completableFutureCallback = new TestCompletableFutureCallback(); - ListenableFuture listenableFuture = this.message.getValue().nack(); + CompletableFuture completableFuture = this.message.getValue().nack(); - assertThat(listenableFuture).isNotNull(); + assertThat(completableFuture).isNotNull(); - listenableFuture.addCallback(testListenableFutureCallback); - listenableFuture.get(10L, TimeUnit.SECONDS); + completableFuture.whenComplete(completableFutureCallback); + completableFuture.get(10L, TimeUnit.SECONDS); - assertThat(listenableFuture.isDone()).isTrue(); + assertThat(completableFuture.isDone()).isTrue(); verify(this.ackReplyConsumer).nack(); - assertThat(testListenableFutureCallback.getThrowable()).isNull(); + assertThat(completableFutureCallback.getThrowable()).isNull(); } @Test @@ -262,20 +262,20 @@ void testSubscribeAndConvert_AndManualAck() assertThat(this.convertedMessage.getValue().getProjectSubscriptionName().getSubscription()) .isEqualTo("sub1"); - TestListenableFutureCallback testListenableFutureCallback = new TestListenableFutureCallback(); + TestCompletableFutureCallback completableFutureCallback = new TestCompletableFutureCallback(); - ListenableFuture listenableFuture = this.convertedMessage.getValue().ack(); + CompletableFuture completableFuture = this.convertedMessage.getValue().ack(); - assertThat(listenableFuture).isNotNull(); + assertThat(completableFuture).isNotNull(); - listenableFuture.addCallback(testListenableFutureCallback); - listenableFuture.get(10L, TimeUnit.SECONDS); + completableFuture.whenComplete(completableFutureCallback); + completableFuture.get(10L, TimeUnit.SECONDS); - assertThat(listenableFuture.isDone()).isTrue(); + assertThat(completableFuture.isDone()).isTrue(); verify(this.ackReplyConsumer).ack(); - assertThat(testListenableFutureCallback.getThrowable()).isNull(); + assertThat(completableFutureCallback.getThrowable()).isNull(); } @Test @@ -294,20 +294,20 @@ void testSubscribeAndConvert_AndManualNack() assertThat(this.convertedMessage.getValue().getProjectSubscriptionName().getSubscription()) .isEqualTo("sub1"); - TestListenableFutureCallback testListenableFutureCallback = new TestListenableFutureCallback(); + TestCompletableFutureCallback completableFutureCallback = new TestCompletableFutureCallback(); - ListenableFuture listenableFuture = this.convertedMessage.getValue().nack(); + CompletableFuture completableFuture = this.convertedMessage.getValue().nack(); - assertThat(listenableFuture).isNotNull(); + assertThat(completableFuture).isNotNull(); - listenableFuture.addCallback(testListenableFutureCallback); - listenableFuture.get(10L, TimeUnit.SECONDS); + completableFuture.whenComplete(completableFutureCallback); + completableFuture.get(10L, TimeUnit.SECONDS); - assertThat(listenableFuture.isDone()).isTrue(); + assertThat(completableFuture.isDone()).isTrue(); verify(this.ackReplyConsumer).nack(); - assertThat(testListenableFutureCallback.getThrowable()).isNull(); + assertThat(completableFutureCallback.getThrowable()).isNull(); } @Test @@ -338,18 +338,18 @@ void testPull_AndManualAck() AcknowledgeablePubsubMessage acknowledgeablePubsubMessage = result.get(0); assertThat(acknowledgeablePubsubMessage.getAckId()).isNotNull(); - TestListenableFutureCallback testListenableFutureCallback = new TestListenableFutureCallback(); + TestCompletableFutureCallback completableFutureCallback = new TestCompletableFutureCallback(); - ListenableFuture listenableFuture = this.pubSubSubscriberTemplate.ack(result); + CompletableFuture completableFuture = this.pubSubSubscriberTemplate.ack(result); - assertThat(listenableFuture).isNotNull(); + assertThat(completableFuture).isNotNull(); - listenableFuture.addCallback(testListenableFutureCallback); - listenableFuture.get(10L, TimeUnit.SECONDS); + completableFuture.whenComplete(completableFutureCallback); + completableFuture.get(10L, TimeUnit.SECONDS); - assertThat(listenableFuture.isDone()).isTrue(); + assertThat(completableFuture.isDone()).isTrue(); - assertThat(testListenableFutureCallback.getThrowable()).isNull(); + assertThat(completableFutureCallback.getThrowable()).isNull(); } @Test @@ -365,18 +365,18 @@ void testPull_AndManualNack() AcknowledgeablePubsubMessage acknowledgeablePubsubMessage = result.get(0); assertThat(acknowledgeablePubsubMessage.getAckId()).isNotNull(); - TestListenableFutureCallback testListenableFutureCallback = new TestListenableFutureCallback(); + TestCompletableFutureCallback completableFutureCallback = new TestCompletableFutureCallback(); - ListenableFuture listenableFuture = this.pubSubSubscriberTemplate.nack(result); + CompletableFuture completableFuture = this.pubSubSubscriberTemplate.nack(result); - assertThat(listenableFuture).isNotNull(); + assertThat(completableFuture).isNotNull(); - listenableFuture.addCallback(testListenableFutureCallback); - listenableFuture.get(10L, TimeUnit.SECONDS); + completableFuture.whenComplete(completableFutureCallback); + completableFuture.get(10L, TimeUnit.SECONDS); - assertThat(listenableFuture.isDone()).isTrue(); + assertThat(completableFuture.isDone()).isTrue(); - assertThat(testListenableFutureCallback.getThrowable()).isNull(); + assertThat(completableFutureCallback.getThrowable()).isNull(); } @Test @@ -394,16 +394,16 @@ void testPull_AndManualMultiSubscriptionAck() assertThat(combinedMessages).hasSize(2); - TestListenableFutureCallback testListenableFutureCallback = new TestListenableFutureCallback(); + TestCompletableFutureCallback completableFutureCallback = new TestCompletableFutureCallback(); - ListenableFuture listenableFuture = this.pubSubSubscriberTemplate.ack(combinedMessages); - assertThat(listenableFuture).isNotNull(); + CompletableFuture completableFuture = this.pubSubSubscriberTemplate.ack(combinedMessages); + assertThat(completableFuture).isNotNull(); - listenableFuture.addCallback(testListenableFutureCallback); - listenableFuture.get(10L, TimeUnit.SECONDS); + completableFuture.whenComplete(completableFutureCallback); + completableFuture.get(10L, TimeUnit.SECONDS); - assertThat(listenableFuture.isDone()).isTrue(); - assertThat(testListenableFutureCallback.getThrowable()).isNull(); + assertThat(completableFuture.isDone()).isTrue(); + assertThat(completableFutureCallback.getThrowable()).isNull(); verify(this.ackCallable, times(2)).futureCall(any(AcknowledgeRequest.class)); verify(this.ackApiFuture, times(2)).addListener(any(), same(mockExecutor)); } @@ -412,7 +412,7 @@ void testPull_AndManualMultiSubscriptionAck() void testPullAsync_AndManualAck() throws InterruptedException, ExecutionException, TimeoutException { - ListenableFuture> asyncResult = + CompletableFuture> asyncResult = this.pubSubSubscriberTemplate.pullAsync("sub", 1, true); List result = asyncResult.get(10L, TimeUnit.SECONDS); @@ -427,19 +427,19 @@ void testPullAsync_AndManualAck() AcknowledgeablePubsubMessage acknowledgeablePubsubMessage = result.get(0); assertThat(acknowledgeablePubsubMessage.getAckId()).isNotNull(); - TestListenableFutureCallback ackTestListenableFutureCallback = - new TestListenableFutureCallback(); + TestCompletableFutureCallback completableFutureCallback = + new TestCompletableFutureCallback(); - ListenableFuture ackListenableFuture = this.pubSubSubscriberTemplate.ack(result); + CompletableFuture completableFuture = this.pubSubSubscriberTemplate.ack(result); - assertThat(ackListenableFuture).isNotNull(); + assertThat(completableFuture).isNotNull(); - ackListenableFuture.addCallback(ackTestListenableFutureCallback); - ackListenableFuture.get(10L, TimeUnit.SECONDS); + completableFuture.whenComplete(completableFutureCallback); + completableFuture.get(10L, TimeUnit.SECONDS); - assertThat(ackListenableFuture.isDone()).isTrue(); + assertThat(completableFuture.isDone()).isTrue(); - assertThat(ackTestListenableFutureCallback.getThrowable()).isNull(); + assertThat(completableFutureCallback.getThrowable()).isNull(); } @Test @@ -461,7 +461,7 @@ void testPullAndAck_NoMessages() { List result = this.pubSubSubscriberTemplate.pullAndAck("sub2", 1, true); - assertThat(result.size()).isZero(); + assertThat(result).isEmpty(); verify(this.pubSubSubscriberTemplate, never()).ack(any()); } @@ -469,7 +469,7 @@ void testPullAndAck_NoMessages() { @Test void testPullAndAckAsync() throws InterruptedException, ExecutionException, TimeoutException { - ListenableFuture> asyncResult = + CompletableFuture> asyncResult = this.pubSubSubscriberTemplate.pullAndAckAsync("sub2", 1, true); List result = asyncResult.get(10L, TimeUnit.SECONDS); @@ -488,13 +488,13 @@ void testPullAndAckAsync_NoMessages() throws InterruptedException, ExecutionException, TimeoutException { when(this.pullApiFuture.get()).thenReturn(PullResponse.newBuilder().build()); - ListenableFuture> asyncResult = + CompletableFuture> asyncResult = this.pubSubSubscriberTemplate.pullAndAckAsync("sub2", 1, true); List result = asyncResult.get(10L, TimeUnit.SECONDS); assertThat(asyncResult.isDone()).isTrue(); - assertThat(result.size()).isZero(); + assertThat(result).isEmpty(); verify(this.pubSubSubscriberTemplate, never()).ack(any()); } @@ -515,7 +515,7 @@ void testPullAndConvert() { @Test void testPullAndConvertAsync() throws InterruptedException, ExecutionException, TimeoutException { - ListenableFuture>> asyncResult = + CompletableFuture>> asyncResult = this.pubSubSubscriberTemplate.pullAndConvertAsync("sub2", 1, true, BigInteger.class); List> result = @@ -557,7 +557,7 @@ void testPullNext_NoMessages() { @Test void testPullNextAsync() throws InterruptedException, ExecutionException, TimeoutException { - ListenableFuture asyncResult = + CompletableFuture asyncResult = this.pubSubSubscriberTemplate.pullNextAsync("sub2"); PubsubMessage message = asyncResult.get(10L, TimeUnit.SECONDS); @@ -574,7 +574,7 @@ void testPullNextAsync_NoMessages() throws InterruptedException, ExecutionException, TimeoutException { when(this.pullApiFuture.get()).thenReturn(PullResponse.newBuilder().build()); - ListenableFuture asyncResult = + CompletableFuture asyncResult = this.pubSubSubscriberTemplate.pullNextAsync("sub2"); PubsubMessage message = asyncResult.get(10L, TimeUnit.SECONDS); @@ -586,20 +586,17 @@ void testPullNextAsync_NoMessages() verify(this.pubSubSubscriberTemplate, never()).ack(any()); } - private class TestListenableFutureCallback implements ListenableFutureCallback { + private static class TestCompletableFutureCallback implements BiConsumer { private Throwable throwable; - @Override - public void onFailure(Throwable throwable) { - this.throwable = throwable; + public Throwable getThrowable() { + return this.throwable; } @Override - public void onSuccess(Void unusedVoid) {} + public void accept(Void unused, Throwable throwable) { - public Throwable getThrowable() { - return this.throwable; } } } diff --git a/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/integration/inbound/PubSubInboundChannelAdapterTests.java b/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/integration/inbound/PubSubInboundChannelAdapterTests.java index 4e6febb390..05289026e6 100644 --- a/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/integration/inbound/PubSubInboundChannelAdapterTests.java +++ b/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/integration/inbound/PubSubInboundChannelAdapterTests.java @@ -57,17 +57,17 @@ @ExtendWith(OutputCaptureExtension.class) class PubSubInboundChannelAdapterTests { - private TestUtils.TestApplicationContext context = TestUtils.createTestApplicationContext(); + private final TestUtils.TestApplicationContext context = TestUtils.createTestApplicationContext(); - PubSubInboundChannelAdapter adapter; + private PubSubInboundChannelAdapter adapter; - static final String EXCEPTION_MESSAGE = "Simulated downstream message processing failure"; + private static final String EXCEPTION_MESSAGE = "Simulated downstream message processing failure"; - @Mock PubSubSubscriberOperations mockPubSubSubscriberOperations; + @Mock private PubSubSubscriberOperations mockPubSubSubscriberOperations; - @Mock MessageChannel mockMessageChannel; + @Mock private MessageChannel mockMessageChannel; - @Mock ConvertedBasicAcknowledgeablePubsubMessage mockAcknowledgeableMessage; + @Mock private ConvertedBasicAcknowledgeablePubsubMessage mockAcknowledgeableMessage; @BeforeEach @SuppressWarnings("unchecked") @@ -80,7 +80,7 @@ void setUp() { } - void setupSubscribeAndConvert() { + private void setupSubscribeAndConvert() { when(this.mockMessageChannel.send(any())).thenReturn(true); when(mockAcknowledgeableMessage.getPubsubMessage()) @@ -160,7 +160,6 @@ void testAckModeAuto_nacksWhenDownstreamProcessingFailsWhenContextShutdown(Captu this.adapter.stop(); throw new RuntimeException(EXCEPTION_MESSAGE); }); - this.adapter.start(); verify(mockAcknowledgeableMessage).nack(); @@ -168,7 +167,6 @@ void testAckModeAuto_nacksWhenDownstreamProcessingFailsWhenContextShutdown(Captu // original message handling exception assertThat(capturedOutput).contains("failed; message nacked automatically").contains(EXCEPTION_MESSAGE); - } @Test @@ -283,7 +281,7 @@ private void verifyOriginalMessage() { MessageHeaders headers = argument.getValue().getHeaders(); assertThat(headers).containsKey(GcpPubSubHeaders.ORIGINAL_MESSAGE); assertThat(headers.get(GcpPubSubHeaders.ORIGINAL_MESSAGE)).isNotNull(); - assertThat(headers.get(GcpPubSubHeaders.ORIGINAL_MESSAGE)) - .isEqualTo(mockAcknowledgeableMessage); + assertThat(headers) + .containsEntry(GcpPubSubHeaders.ORIGINAL_MESSAGE, mockAcknowledgeableMessage); } } diff --git a/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/integration/inbound/PubSubMessageSourceTests.java b/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/integration/inbound/PubSubMessageSourceTests.java index cb3d99831c..6b53039ac1 100644 --- a/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/integration/inbound/PubSubMessageSourceTests.java +++ b/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/integration/inbound/PubSubMessageSourceTests.java @@ -254,7 +254,7 @@ void doReceive_autoModeNacksAutomatically() { }); }) .isInstanceOf(MessageHandlingException.class) - .hasMessageContaining("Nope."); + .hasStackTraceContaining("Nope."); verify(this.msg1).nack(); } @@ -276,7 +276,7 @@ void doReceive_autoAckModeDoesNotNackAutomatically() { }); }) .isInstanceOf(MessageHandlingException.class) - .hasMessageContaining("Nope."); + .hasStackTraceContaining("Nope."); verify(this.msg1, times(0)).nack(); } diff --git a/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/integration/outbound/PubSubMessageHandlerTests.java b/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/integration/outbound/PubSubMessageHandlerTests.java index a88cd2c006..5bad9f52d9 100644 --- a/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/integration/outbound/PubSubMessageHandlerTests.java +++ b/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/integration/outbound/PubSubMessageHandlerTests.java @@ -31,6 +31,7 @@ import com.google.cloud.spring.pubsub.support.GcpPubSubHeaders; import java.time.Duration; import java.util.Collections; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; import org.awaitility.Awaitility; import org.junit.jupiter.api.BeforeEach; @@ -45,8 +46,6 @@ import org.springframework.integration.expression.ValueExpression; import org.springframework.messaging.Message; import org.springframework.messaging.support.GenericMessage; -import org.springframework.util.concurrent.ListenableFutureCallback; -import org.springframework.util.concurrent.SettableListenableFuture; /** Tests for the Pub/Sub message handler. */ @ExtendWith(MockitoExtension.class) @@ -62,11 +61,11 @@ class PubSubMessageHandlerTests { @BeforeEach void setUp() { this.message = - new GenericMessage( + new GenericMessage<>( "testPayload".getBytes(), new MapBuilder().put("key1", "value1").put("key2", "value2").build()); - SettableListenableFuture future = new SettableListenableFuture<>(); - future.set("benfica"); + CompletableFuture future = new CompletableFuture<>(); + future.complete("benfica"); when(this.pubSubTemplate.publish(eq("testTopic"), eq("testPayload".getBytes()), anyMap())) .thenReturn(future); this.adapter = new PubSubMessageHandler(this.pubSubTemplate, "testTopic"); @@ -81,7 +80,7 @@ void testPublish() { @Test void testPublishDynamicTopic() { Message dynamicMessage = - new GenericMessage( + new GenericMessage<>( "testPayload".getBytes(), new MapBuilder() .put("key1", "value1") @@ -97,7 +96,7 @@ void testSendToExpressionTopic() { this.adapter.setTopicExpressionString("headers['sendToTopic']"); this.adapter.onInit(); Message expressionMessage = - new GenericMessage( + new GenericMessage<>( "testPayload".getBytes(), new MapBuilder() .put("key1", "value1") @@ -119,27 +118,6 @@ void testPublishSync() { verify(timeout).getValue(isNull(), eq(this.message), eq(Long.class)); } - @Test - void testPublishCallback() { - ListenableFutureCallback callbackSpy = - spy( - new ListenableFutureCallback() { - @Override - public void onFailure(Throwable ex) {} - - @Override - public void onSuccess(String result) {} - }); - - this.adapter.setPublishCallback(callbackSpy); - - this.adapter.handleMessage(this.message); - - assertThat(this.adapter.getPublishCallback()).isSameAs(callbackSpy); - - verify(callbackSpy).onSuccess("benfica"); - } - @Test void testSetPublishTimeoutExpressionStringWithNull() { @@ -221,7 +199,7 @@ void testSetHeaderMapperWithNull() { @Test void testPublishWithOrderingKey() { this.message = - new GenericMessage( + new GenericMessage<>( "testPayload".getBytes(), new MapBuilder().put(GcpPubSubHeaders.ORDERING_KEY, "key1").build()); @@ -234,15 +212,15 @@ void testPublishWithOrderingKey() { } @Test - void publishWithSuccessCallback() throws Exception { + void publishWithSuccessCallback() { - SettableListenableFuture future = new SettableListenableFuture<>(); - future.set("published12345"); + CompletableFuture future = new CompletableFuture<>(); + future.complete("published12345"); when(this.pubSubTemplate.publish(eq("testTopic"), eq("testPayload"), anyMap())) .thenReturn(future); Message testMessage = - new GenericMessage("testPayload", Collections.singletonMap("message_id", "123")); + new GenericMessage<>("testPayload", Collections.singletonMap("message_id", "123")); AtomicReference messageIdRef = new AtomicReference<>(); AtomicReference ackIdRef = new AtomicReference<>(); @@ -261,15 +239,15 @@ void publishWithSuccessCallback() throws Exception { } @Test - void publishWithFailureCallback() throws Exception { + void publishWithFailureCallback() { - SettableListenableFuture future = new SettableListenableFuture<>(); - future.setException(new RuntimeException("boom!")); + CompletableFuture future = new CompletableFuture<>(); + future.completeExceptionally(new RuntimeException("boom!")); when(this.pubSubTemplate.publish(eq("testTopic"), eq("testPayload"), anyMap())) .thenReturn(future); Message testMessage = - new GenericMessage("testPayload", Collections.singletonMap("message_id", "123")); + new GenericMessage<>("testPayload", Collections.singletonMap("message_id", "123")); AtomicReference failureCauseRef = new AtomicReference<>(); AtomicReference messageIdRef = new AtomicReference<>(); diff --git a/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/reactive/PubSubReactiveFactoryTests.java b/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/reactive/PubSubReactiveFactoryTests.java index 174f683f18..5c55d0655b 100644 --- a/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/reactive/PubSubReactiveFactoryTests.java +++ b/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/reactive/PubSubReactiveFactoryTests.java @@ -17,7 +17,7 @@ package com.google.cloud.spring.pubsub.reactive; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -36,6 +36,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.concurrent.CompletableFuture; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -43,7 +44,6 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.scheduling.annotation.AsyncResult; import reactor.test.StepVerifier; import reactor.test.scheduler.VirtualTimeScheduler; @@ -73,7 +73,7 @@ void testIllegalArgumentExceptionWithMaxMessagesLessThanOne() { } @Test - void testSequentialRequests() throws InterruptedException { + void testSequentialRequests() { setUpMessages("msg1", "msg2", "msg3", "msg4"); StepVerifier.withVirtualTime(() -> factory.poll("sub1", 10).map(this::messageToString), 1) @@ -92,8 +92,7 @@ void testSequentialRequests() throws InterruptedException { } @Test - void testSequentialRequestWithInsufficientDemandGetsSplitIntoTwoRequests() - throws InterruptedException { + void testSequentialRequestWithInsufficientDemandGetsSplitIntoTwoRequests() { setUpMessages("msg1", "stop", "msg2", "msg3", "msg4"); StepVerifier.withVirtualTime(() -> factory.poll("sub1", 10).map(this::messageToString), 4) @@ -110,7 +109,7 @@ void testSequentialRequestWithInsufficientDemandGetsSplitIntoTwoRequests() } @Test - void testDeadlineExceededCausesRetry() throws InterruptedException { + void testDeadlineExceededCausesRetry() { setUpMessages("timeout", "msg1", "msg2"); StepVerifier.withVirtualTime(() -> factory.poll("sub1", 10).map(this::messageToString), 2) @@ -126,8 +125,7 @@ void testDeadlineExceededCausesRetry() throws InterruptedException { } @Test - void testExceptionThrownByPubSubClientResultingInErrorStream() - throws InterruptedException { + void testExceptionThrownByPubSubClientResultingInErrorStream() { setUpMessages("msg1", "msg2", "throw"); StepVerifier.withVirtualTime(() -> factory.poll("sub1", 10).map(this::messageToString), 2) @@ -143,7 +141,7 @@ void testExceptionThrownByPubSubClientResultingInErrorStream() } @Test - void testUnlimitedDemand() throws InterruptedException { + void testUnlimitedDemand() { setUpMessages("msg1", "msg2", "stop", "msg3", "msg4", "stop", "msg5", "stop"); StepVerifier.withVirtualTime(() -> factory.poll("sub1", 10).map(this::messageToString)) @@ -233,19 +231,19 @@ private void setUpMessages(String... messages) { String nextPayload = msgList.remove(0); switch (nextPayload) { case "stop": - return AsyncResult.forValue(result); + return CompletableFuture.completedFuture(result); case "timeout": if (!result.isEmpty()) { fail("Bad setup -- 'throw' should be the first event in batch"); } - return AsyncResult.forExecutionException( + return CompletableFuture.failedFuture( new DeadlineExceededException( "this is a noop", null, GrpcStatusCode.of(Status.Code.DEADLINE_EXCEEDED), true)); case "throw": - return AsyncResult.forExecutionException( + return CompletableFuture.failedFuture( new RuntimeException("expected exception during pull of messages")); default: // continue processing @@ -259,7 +257,7 @@ private void setUpMessages(String... messages) { when(msg.getPubsubMessage()).thenReturn(pubsubMessage); result.add(msg); } - return AsyncResult.forValue(result); + return CompletableFuture.completedFuture(result); }); } } diff --git a/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/support/DefaultSubscriberFactoryTests.java b/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/support/DefaultSubscriberFactoryTests.java index 9900b177f1..b260ca7345 100644 --- a/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/support/DefaultSubscriberFactoryTests.java +++ b/spring-cloud-gcp-pubsub/src/test/java/com/google/cloud/spring/pubsub/support/DefaultSubscriberFactoryTests.java @@ -84,7 +84,7 @@ void setUp() { @Test void testNewSubscriber() { - DefaultSubscriberFactory factory = new DefaultSubscriberFactory(() -> "angeldust"); + DefaultSubscriberFactory factory = new DefaultSubscriberFactory(() -> "angeldust", pubSubConfig); factory.setCredentialsProvider(this.credentialsProvider); Subscriber subscriber = factory.createSubscriber("midnight cowboy", (message, consumer) -> {}); @@ -117,7 +117,7 @@ void testNewSubscriber_constructorWithPubSubConfiguration_nullPubSubConfiguratio @Test void testNewDefaultSubscriberFactory_nullProjectProvider() { - assertThatThrownBy(() -> new DefaultSubscriberFactory(null)) + assertThatThrownBy(() -> new DefaultSubscriberFactory(null, pubSubConfig)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("The project ID provider can't be null."); } @@ -125,7 +125,7 @@ void testNewDefaultSubscriberFactory_nullProjectProvider() { @Test void testNewDefaultSubscriberFactory_nullProject() { - assertThatThrownBy(() -> new DefaultSubscriberFactory(() -> null)) + assertThatThrownBy(() -> new DefaultSubscriberFactory(() -> null, pubSubConfig)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("The project ID can't be null or empty."); @@ -133,7 +133,7 @@ void testNewDefaultSubscriberFactory_nullProject() { @Test void testCreatePullRequest_greaterThanZeroMaxMessages() { - DefaultSubscriberFactory factory = new DefaultSubscriberFactory(() -> "project"); + DefaultSubscriberFactory factory = new DefaultSubscriberFactory(() -> "project", pubSubConfig); factory.setCredentialsProvider(this.credentialsProvider); assertThatThrownBy(() -> factory.createPullRequest("test", -1, true)) @@ -143,7 +143,7 @@ void testCreatePullRequest_greaterThanZeroMaxMessages() { @Test void testCreatePullRequest_nonNullMaxMessages() { - DefaultSubscriberFactory factory = new DefaultSubscriberFactory(() -> "project"); + DefaultSubscriberFactory factory = new DefaultSubscriberFactory(() -> "project", pubSubConfig); factory.setCredentialsProvider(this.credentialsProvider); PullRequest request = factory.createPullRequest("test", null, true); @@ -717,23 +717,6 @@ void testBuildGlobalSubscriberStubSettings_retryableCodes_pickConfiguration() assertThat(settings.pullSettings().getRetryableCodes()).containsExactly(Code.INTERNAL); } - @Test - void createSubscriberStubSucceeds_noSubscriptionNameAndNewConfiguration() { - - when(this.mockTransportChannel.getEmptyCallContext()).thenReturn(this.mockApiCallContext); - when(this.mockApiCallContext.withCredentials(any())).thenReturn(this.mockApiCallContext); - when(this.mockApiCallContext.withTransportChannel(any())).thenReturn(this.mockApiCallContext); - - GcpProjectIdProvider projectIdProvider = () -> "project"; - DefaultSubscriberFactory factory = - new DefaultSubscriberFactory(projectIdProvider, this.pubSubConfig); - factory.setChannelProvider(FixedTransportChannelProvider.create(this.mockTransportChannel)); - factory.setCredentialsProvider(() -> NoCredentials.getInstance()); - - SubscriberStub stub = factory.createSubscriberStub(); - assertThat(stub.isShutdown()).isFalse(); - } - @Test void createSubscriberStubSucceeds() { @@ -745,7 +728,7 @@ void createSubscriberStubSucceeds() { DefaultSubscriberFactory factory = new DefaultSubscriberFactory(projectIdProvider, this.pubSubConfig); factory.setChannelProvider(FixedTransportChannelProvider.create(this.mockTransportChannel)); - factory.setCredentialsProvider(() -> NoCredentials.getInstance()); + factory.setCredentialsProvider(NoCredentials::getInstance); SubscriberStub stub = factory.createSubscriberStub("unusedSubscription"); assertThat(stub.isShutdown()).isFalse(); @@ -775,7 +758,7 @@ void testNewSubscriber_shouldNotAddToHealthCheck() { when(healthTrackerRegistry.isTracked(subscriptionName)).thenReturn(true); - DefaultSubscriberFactory factory = new DefaultSubscriberFactory(() -> "angeldust"); + DefaultSubscriberFactory factory = new DefaultSubscriberFactory(() -> "angeldust", pubSubConfig); factory.setCredentialsProvider(this.credentialsProvider); factory.setHealthTrackerRegistry(healthTrackerRegistry); @@ -794,7 +777,7 @@ void testNewSubscriber_shouldAddToHealthCheck() { when(healthTrackerRegistry.isTracked(subscriptionName)).thenReturn(false); - DefaultSubscriberFactory factory = new DefaultSubscriberFactory(() -> "angeldust"); + DefaultSubscriberFactory factory = new DefaultSubscriberFactory(() -> "angeldust", pubSubConfig); factory.setCredentialsProvider(this.credentialsProvider); factory.setHealthTrackerRegistry(healthTrackerRegistry); diff --git a/spring-cloud-gcp-samples/README.adoc b/spring-cloud-gcp-samples/README.adoc index 1a2a7dd604..e3be79f4d8 100644 --- a/spring-cloud-gcp-samples/README.adoc +++ b/spring-cloud-gcp-samples/README.adoc @@ -43,7 +43,7 @@ Populates database with values and reads them. Uses the https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/Resource.html[Spring Resource] abstraction to read and write files to https://cloud.google.com/storage/[Google Cloud Storage]. - link:spring-cloud-gcp-trace-sample[Spring Framework on Google Cloud Trace Sample]: -Uses https://cloud.spring.io/spring-cloud-sleuth/[Spring Cloud Sleuth] to automatically generate trace IDs for requests to your application and store data about those requests in https://cloud.google.com/trace/[Google Stackdriver Trace]. +Uses https://micrometer.io/[Micrometer] to automatically generate trace IDs for requests to your application and store data about those requests in https://cloud.google.com/trace/[Google Stackdriver Trace]. - link:spring-cloud-gcp-vision-api-sample[Spring Framework on Google Cloud Vision API Sample]: Provides an example of how to use Spring Framework on Google Cloud to improve the general usability of Google Cloud diff --git a/spring-cloud-gcp-samples/pom.xml b/spring-cloud-gcp-samples/pom.xml index cbc7f343c0..64856564bc 100644 --- a/spring-cloud-gcp-samples/pom.xml +++ b/spring-cloud-gcp-samples/pom.xml @@ -6,15 +6,14 @@ org.springframework.boot spring-boot-starter-parent - 2.7.7 + 3.0.0 Spring Framework on Google Cloud Code Samples spring-cloud-gcp-samples com.google.cloud - 3.4.3-SNAPSHOT - + 4.0.0-SNAPSHOT pom 4.0.0 @@ -22,7 +21,6 @@ ${basedir}/.. - 0.12.2 0.7.0 1.17.6 @@ -66,13 +64,10 @@ spring-cloud-gcp-pubsub-bus-config-sample spring-cloud-gcp-pubsub-stream-dead-letter-sample spring-cloud-gcp-pubsub-stream-functional-sample - spring-cloud-gcp-pubsub-stream-polling-sample - spring-cloud-gcp-pubsub-stream-sample spring-cloud-gcp-pubsub-reactive-sample spring-cloud-gcp-integration-pubsub-json-sample spring-cloud-gcp-security-iap-sample spring-cloud-gcp-sql-postgres-sample - spring-cloud-gcp-sql-mysql-r2dbc-sample spring-cloud-gcp-sql-postgres-r2dbc-sample spring-cloud-gcp-vision-ocr-demo spring-cloud-gcp-firestore-sample diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-bigquery-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-bigquery-sample/pom.xml index f8ffd70556..31aba9b7e7 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-bigquery-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-bigquery-sample/pom.xml @@ -6,7 +6,7 @@ spring-cloud-gcp-samples com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-bigquery-sample/src/main/java/com/example/BigQuerySampleConfiguration.java b/spring-cloud-gcp-samples/spring-cloud-gcp-bigquery-sample/src/main/java/com/example/BigQuerySampleConfiguration.java index accc0e24fe..e2a5e94269 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-bigquery-sample/src/main/java/com/example/BigQuerySampleConfiguration.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-bigquery-sample/src/main/java/com/example/BigQuerySampleConfiguration.java @@ -21,15 +21,16 @@ import com.google.cloud.spring.bigquery.core.BigQueryTemplate; import com.google.cloud.spring.bigquery.integration.BigQuerySpringMessageHeaders; import com.google.cloud.spring.bigquery.integration.outbound.BigQueryFileMessageHandler; +import java.util.concurrent.CompletableFuture; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; import org.springframework.integration.annotation.MessagingGateway; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.gateway.GatewayProxyFactoryBean; import org.springframework.messaging.MessageHandler; import org.springframework.messaging.handler.annotation.Header; -import org.springframework.util.concurrent.ListenableFuture; /** Sample configuration for using BigQuery with Spring Integration. */ @Configuration @@ -54,12 +55,13 @@ public MessageHandler messageSender(BigQueryTemplate bigQueryTemplate) { return messageHandler; } + @Primary @Bean public GatewayProxyFactoryBean gatewayProxyFactoryBean() { GatewayProxyFactoryBean factoryBean = new GatewayProxyFactoryBean(BigQueryFileGateway.class); factoryBean.setDefaultRequestChannel(bigQueryWriteDataChannel()); factoryBean.setDefaultReplyChannel(bigQueryJobReplyChannel()); - // Ensures that BigQueryFileGateway does not return double-wrapped ListenableFutures + // Ensures that BigQueryFileGateway does not return double-wrapped CompletableFutures factoryBean.setAsyncExecutor(null); return factoryBean; } @@ -67,7 +69,7 @@ public GatewayProxyFactoryBean gatewayProxyFactoryBean() { /** Spring Integration gateway which allows sending data to load to BigQuery through a channel. */ @MessagingGateway public interface BigQueryFileGateway { - ListenableFuture writeToBigQueryTable( + CompletableFuture writeToBigQueryTable( byte[] csvData, @Header(BigQuerySpringMessageHeaders.TABLE_NAME) String tableName); } } diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-bigquery-sample/src/main/java/com/example/WebController.java b/spring-cloud-gcp-samples/spring-cloud-gcp-bigquery-sample/src/main/java/com/example/WebController.java index 9c675c8326..be583f5cca 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-bigquery-sample/src/main/java/com/example/WebController.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-bigquery-sample/src/main/java/com/example/WebController.java @@ -26,11 +26,10 @@ import com.google.cloud.spring.bigquery.core.WriteApiResponse; import java.io.ByteArrayInputStream; import java.io.IOException; -import org.springframework.beans.factory.annotation.Autowired; +import java.util.concurrent.CompletableFuture; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; -import org.springframework.util.concurrent.ListenableFuture; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -41,22 +40,30 @@ @Controller public class WebController { - @Autowired BigQueryFileGateway bigQueryFileGateway; + private final BigQueryFileGateway bigQueryFileGateway; - @Autowired BigQueryTemplate bigQueryTemplate; + private final BigQueryTemplate bigQueryTemplate; + + private static final String DATASET_NAME = "datasetName"; @Value("${spring.cloud.gcp.bigquery.datasetName}") private String datasetName; + public WebController(BigQueryFileGateway bigQueryFileGateway, + BigQueryTemplate bigQueryTemplate) { + this.bigQueryFileGateway = bigQueryFileGateway; + this.bigQueryTemplate = bigQueryTemplate; + } + @GetMapping("/") public ModelAndView renderIndex(ModelMap map) { - map.put("datasetName", this.datasetName); + map.put(DATASET_NAME, this.datasetName); return new ModelAndView("index.html", map); } @GetMapping("/write-api-json-upload") public ModelAndView renderUploadJson(ModelMap map) { - map.put("datasetName", this.datasetName); + map.put(DATASET_NAME, this.datasetName); return new ModelAndView("upload-json.html", map); } @@ -74,7 +81,7 @@ public ModelAndView handleJsonFileUpload( @RequestParam("tableName") String tableName, @RequestParam(name = "createTable", required = false) String createDefaultTable) throws IOException { - ListenableFuture writeApiRes = null; + CompletableFuture writeApiRes; if (createDefaultTable != null && createDefaultTable.equals("createTable")) { // create the default table writeApiRes = @@ -107,7 +114,7 @@ public ModelAndView handleJsonTextUpload( @RequestParam("jsonRows") String jsonRows, @RequestParam("tableName") String tableName, @RequestParam(name = "createTable", required = false) String createDefaultTable) { - ListenableFuture writeApiRes = null; + CompletableFuture writeApiRes; if (createDefaultTable != null && createDefaultTable.equals("createTable")) { // create the default table @@ -123,13 +130,13 @@ public ModelAndView handleJsonTextUpload( } private ModelAndView getWriteApiResponse( - ListenableFuture writeApiFuture, String tableName) { + CompletableFuture writeApiFuture, String tableName) { String message = null; try { WriteApiResponse apiResponse = writeApiFuture.get(); if (apiResponse.isSuccessful()) { message = "Successfully loaded data to " + tableName; - } else if (apiResponse.getErrors() != null && apiResponse.getErrors().size() > 0) { + } else if (apiResponse.getErrors() != null && !apiResponse.getErrors().isEmpty()) { message = String.format( "Error occurred while loading the file, printing first error %s. Use WriteApiResponse.getErrors() to get the complete list of errors", @@ -141,7 +148,7 @@ private ModelAndView getWriteApiResponse( message = "Error: " + e.getMessage(); } return new ModelAndView("upload-json.html") - .addObject("datasetName", this.datasetName) + .addObject(DATASET_NAME, this.datasetName) .addObject("message", message); } @@ -150,7 +157,7 @@ private ModelAndView getWriteApiResponse( * * @param file the CSV file to upload to BigQuery * @param tableName name of the table to load data into - * @return ModelAndView of the response the send back to users + * @return ModelAndView of the response to send back to users * @throws IOException if the file is unable to be loaded. */ @PostMapping("/uploadFile") @@ -158,7 +165,7 @@ public ModelAndView handleFileUpload( @RequestParam("file") MultipartFile file, @RequestParam("tableName") String tableName) throws IOException { - ListenableFuture loadJob = + CompletableFuture loadJob = this.bigQueryTemplate.writeDataToTable( tableName, file.getInputStream(), FormatOptions.csv()); @@ -176,13 +183,13 @@ public ModelAndView handleFileUpload( public ModelAndView handleCsvTextUpload( @RequestParam("csvText") String csvData, @RequestParam("tableName") String tableName) { - ListenableFuture loadJob = + CompletableFuture loadJob = this.bigQueryFileGateway.writeToBigQueryTable(csvData.getBytes(), tableName); return getResponse(loadJob, tableName); } - private ModelAndView getResponse(ListenableFuture loadJob, String tableName) { + private ModelAndView getResponse(CompletableFuture loadJob, String tableName) { String message; try { Job job = loadJob.get(); @@ -193,7 +200,7 @@ private ModelAndView getResponse(ListenableFuture loadJob, String tableName } return new ModelAndView("index") - .addObject("datasetName", this.datasetName) + .addObject(DATASET_NAME, this.datasetName) .addObject("message", message); } } diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-bigquery-sample/src/main/resources/application.properties b/spring-cloud-gcp-samples/spring-cloud-gcp-bigquery-sample/src/main/resources/application.properties index c8b7e1992f..a96211b09d 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-bigquery-sample/src/main/resources/application.properties +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-bigquery-sample/src/main/resources/application.properties @@ -1,2 +1,2 @@ # Specify your BigQuery dataset name. -spring.cloud.gcp.bigquery.datasetName=[YOUR_DATASET_NAME] +spring.cloud.gcp.bigquery.datasetName=test_dataset diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-bigquery-sample/src/test/java/com/example/BigQuerySampleApplicationIntegrationTests.java b/spring-cloud-gcp-samples/spring-cloud-gcp-bigquery-sample/src/test/java/com/example/BigQuerySampleApplicationIntegrationTests.java index 592f6954a9..8d8d57e02b 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-bigquery-sample/src/test/java/com/example/BigQuerySampleApplicationIntegrationTests.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-bigquery-sample/src/test/java/com/example/BigQuerySampleApplicationIntegrationTests.java @@ -23,7 +23,6 @@ import com.google.cloud.bigquery.QueryJobConfiguration; import com.google.cloud.bigquery.TableId; import com.google.cloud.bigquery.TableResult; -import java.io.IOException; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; @@ -91,9 +90,10 @@ private String getRandSuffix() { @Test void testJsonTextUpload() throws InterruptedException { String jsonTxt = - "{\"CompanyName\":\"TALES\",\"Description\":\"mark\",\"SerialNumber\":97,\"Leave\":0,\"EmpName\":\"Mark\"}\n" - + "{\"CompanyName\":\"1Q84\",\"Description\":\"ark\",\"SerialNumber\":978,\"Leave\":0,\"EmpName\":\"HARUKI\"}\n" - + "{\"CompanyName\":\"MY\",\"Description\":\"M\",\"SerialNumber\":9780,\"Leave\":0,\"EmpName\":\"Mark\"}"; + """ + {"CompanyName":"TALES","Description":"mark","SerialNumber":97,"Leave":0,"EmpName":"Mark"} + {"CompanyName":"1Q84","Description":"ark","SerialNumber":978,"Leave":0,"EmpName":"HARUKI"} + {"CompanyName":"MY","Description":"M","SerialNumber":9780,"Leave":0,"EmpName":"Mark"}"""; LinkedMultiValueMap map = new LinkedMultiValueMap<>(); map.add("jsonRows", jsonTxt); @@ -113,12 +113,12 @@ void testJsonTextUpload() throws InterruptedException { TableResult queryResult = this.bigQuery.query(queryJobConfiguration); assertThat(queryResult.getTotalRows()).isEqualTo(3); FieldValueList row = queryResult.getValues().iterator().next(); // match the first record - assertThat(row.get("SerialNumber").getLongValue() == 9780); + assertThat(row.get("SerialNumber").getLongValue()).isEqualTo(9780L); assertThat(row.get("EmpName").getStringValue()).isEqualTo("Mark"); } @Test - void testJsonFileUpload() throws InterruptedException, IOException { + void testJsonFileUpload() throws InterruptedException { LinkedMultiValueMap map = new LinkedMultiValueMap<>(); map.add("file", jsonFile); map.add("tableName", TABLE_NAME); @@ -140,12 +140,12 @@ void testJsonFileUpload() throws InterruptedException, IOException { TableResult queryResult = this.bigQuery.query(queryJobConfiguration); assertThat(queryResult.getTotalRows()).isEqualTo(3); FieldValueList row = queryResult.getValues().iterator().next(); // match the first record - assertThat(row.get("SerialNumber").getLongValue() == 9780); + assertThat(row.get("SerialNumber").getLongValue()).isEqualTo(9780L); assertThat(row.get("EmpName").getStringValue()).isEqualTo("Mark"); } @Test - void testFileUpload() throws InterruptedException, IOException { + void testFileUpload() throws InterruptedException { LinkedMultiValueMap map = new LinkedMultiValueMap<>(); map.add("file", csvFile); map.add("tableName", TABLE_NAME); diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-config-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-config-sample/pom.xml index 375274331a..f265db2ef9 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-config-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-config-sample/pom.xml @@ -7,11 +7,10 @@ spring-cloud-gcp-samples com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 - com.google.cloud spring-cloud-gcp-config-sample Spring Framework on Google Cloud Code Sample - Runtime Configuration diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-basic-sample/README.adoc b/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-basic-sample/README.adoc index 80c19061f9..4eb0258fcb 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-basic-sample/README.adoc +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-basic-sample/README.adoc @@ -21,35 +21,68 @@ Then, uncomment the `spring.cloud.gcp.datastore.credentials.location` property i + `$ mvn spring-boot:run` -. Use shell to create, find and remove books. -Available commands: +. Use the following commands to create 4 books + ---- -save-book <author> <year> -find-all-books -find-by-author <author> -find-by-year-greater-than <year> -find-by-author-year <author> <year> -remove-all-books +curl --location --request POST 'localhost:8080/saveBook' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "title": "The Moon Is a Harsh Mistress", + "author": "Robert A. Heinlein", + "year": 1966 +}' + +curl --location --request POST 'localhost:8080/saveBook' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "title": "Stranger in a Strange Land", + "author": "Robert A. Heinlein", + "year": 1961 +}' + +curl --location --request POST 'localhost:8080/saveBook' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "title": "The Crack in Space", + "author": "Philip K. Dick", + "year": 1966 +}' + +curl --location --request POST 'localhost:8080/saveBook' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "title": "Ubik", + "author": "Philip K. Dick", + "year": 1969 +}' ---- + +. Use the following command to query all books + -Example: -+ ---- -shell:>save-book "The Moon Is a Harsh Mistress" "Robert A. Heinlein" 1966 -shell:>save-book "Stranger in a Strange Land" "Robert A. Heinlein" 1961 -shell:>save-book "The Crack in Space" "Philip K. Dick" 1966 -shell:>save-book "Ubik" "Philip K. Dick" 1969 +curl --location --request GET 'localhost:8080/findAllBooks' +---- -shell:>find-by-year-greater-than 1967 -[Book{id=5734055144325120, title='Ubik', author='Philip K. Dick', year=1969}] +. Use the following command to find books by author ++ +---- +curl --location --request GET 'localhost:8080/findByAuthor?author=Robert A. Heinlein' +---- -shell:>find-by-author-year "Robert A. Heinlein" 1966 -[Book{id=5652161459388416, title='The Moon Is a Harsh Mistress', author='Robert A. Heinlein', year=1966}] +. Use the following command to query books newer than a given publication year ++ +---- +curl --location --request GET 'localhost:8080/findByYearGreaterThan?year=1960' ---- -. To clean up the database run the following command in the application shell: +. Use the following command to query books with a given author and publication year + -`shell:>remove-all-books` +---- +curl --location --request GET 'localhost:8080/findByAuthorYear?author=Robert A. Heinlein&year=1966' +---- -. Use `Ctrl+C` `Ctrl+C` to exit +. Use the following command to delete all books ++ +---- +curl --location --request DELETE 'localhost:8080/removeAllBooks' +---- diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-basic-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-basic-sample/pom.xml index 55c4d54a37..4c83f11566 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-basic-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-basic-sample/pom.xml @@ -6,11 +6,10 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-samples</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> - <groupId>com.google.cloud</groupId> <artifactId>spring-cloud-gcp-data-datastore-basic-sample</artifactId> <name>Spring Framework on Google Cloud Code Sample - Datastore Bookshelf</name> @@ -36,11 +35,6 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-rest</artifactId> </dependency> - <dependency> - <groupId>org.springframework.shell</groupId> - <artifactId>spring-shell-starter</artifactId> - <version>2.1.5</version> - </dependency> <!-- Test-related dependencies. --> <dependency> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-basic-sample/src/main/java/com/example/BookController.java b/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-basic-sample/src/main/java/com/example/BookController.java new file mode 100644 index 0000000000..9a22e97628 --- /dev/null +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-basic-sample/src/main/java/com/example/BookController.java @@ -0,0 +1,75 @@ +/* + * Copyright 2017-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class BookController { + private final BookRepository bookRepository; + + public BookController(BookRepository bookRepository) { + this.bookRepository = bookRepository; + } + + @PostMapping("/saveBook") + public String saveBook(@RequestBody Book book) { + Book savedBook = this.bookRepository.save(book); + return savedBook.toString(); + } + + @GetMapping("/findAllBooks") + public String findAllBooks() { + Iterable<Book> books = this.bookRepository.findAll(); + List<Book> bookList = new ArrayList<>(); + books.forEach(bookList::add); + return books.toString(); + } + + @GetMapping("/findByAuthor") + public String findByAuthor(@RequestParam("author") String author) { + List<Book> books = this.bookRepository.findByAuthor(author); + return books.toString(); + } + + @GetMapping("/findByYearGreaterThan") + public String findByYearGreaterThan(@RequestParam("year") Optional<Integer> year) { + List<Book> books = this.bookRepository.findByYearGreaterThan(year.orElse(0)); + return books.toString(); + } + + @GetMapping("/findByAuthorYear") + public String findByAuthorYear( + @RequestParam("author") String author, + @RequestParam("year") Optional<Integer> year) { + List<Book> books = this.bookRepository.findByAuthorAndYear(author, year.orElse(0)); + return books.toString(); + } + + @DeleteMapping("/removeAllBooks") + public void removeAllBooks() { + this.bookRepository.deleteAll(); + } +} diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-basic-sample/src/main/java/com/example/CustomPromptProvider.java b/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-basic-sample/src/main/java/com/example/CustomPromptProvider.java deleted file mode 100644 index c225ab44e6..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-basic-sample/src/main/java/com/example/CustomPromptProvider.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2017-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example; - -import org.jline.utils.AttributedString; -import org.jline.utils.AttributedStyle; -import org.springframework.shell.jline.PromptProvider; -import org.springframework.stereotype.Component; - -/** A prompt provider for the sample application web app. */ -@Component -public class CustomPromptProvider implements PromptProvider { - @Override - public AttributedString getPrompt() { - return new AttributedString( - "enter a command or type 'help' for info :> ", - AttributedStyle.DEFAULT.foreground(AttributedStyle.YELLOW)); - } -} diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-basic-sample/src/main/java/com/example/DatastoreBookshelfExample.java b/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-basic-sample/src/main/java/com/example/DatastoreBookshelfExample.java index 97edd25f26..6a7090492a 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-basic-sample/src/main/java/com/example/DatastoreBookshelfExample.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-basic-sample/src/main/java/com/example/DatastoreBookshelfExample.java @@ -16,58 +16,14 @@ package com.example; -import java.util.ArrayList; -import java.util.List; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.shell.standard.ShellComponent; -import org.springframework.shell.standard.ShellMethod; /** This class contains the main method and performs common operations with books. */ -@ShellComponent @SpringBootApplication public class DatastoreBookshelfExample { - @Autowired BookRepository bookRepository; public static void main(String[] args) { SpringApplication.run(DatastoreBookshelfExample.class, args); } - - @ShellMethod("Saves a book to Cloud Datastore: save-book <title> <author> <year>") - public String saveBook(String title, String author, int year) { - Book savedBook = this.bookRepository.save(new Book(title, author, year)); - return savedBook.toString(); - } - - @ShellMethod("Loads all books") - public String findAllBooks() { - Iterable<Book> books = this.bookRepository.findAll(); - List<Book> bookList = new ArrayList<>(); - books.forEach(bookList::add); - return books.toString(); - } - - @ShellMethod("Loads books by author: find-by-author <author>") - public String findByAuthor(String author) { - List<Book> books = this.bookRepository.findByAuthor(author); - return books.toString(); - } - - @ShellMethod("Loads books released after a specified year: find-by-year-greater-than <year>") - public String findByYearGreaterThan(int year) { - List<Book> books = this.bookRepository.findByYearGreaterThan(year); - return books.toString(); - } - - @ShellMethod("Loads books by author and year: find-by-author-year <author> <year>") - public String findByAuthorYear(String author, int year) { - List<Book> books = this.bookRepository.findByAuthorAndYear(author, year); - return books.toString(); - } - - @ShellMethod("Removes all books") - public void removeAllBooks() { - this.bookRepository.deleteAll(); - } } diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-basic-sample/src/test/java/com/example/DatastoreBookshelfExampleIntegrationTests.java b/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-basic-sample/src/test/java/com/example/DatastoreBookshelfExampleIntegrationTests.java index 3acfbab174..e9a7dcf4ae 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-basic-sample/src/test/java/com/example/DatastoreBookshelfExampleIntegrationTests.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-basic-sample/src/test/java/com/example/DatastoreBookshelfExampleIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 the original author or authors. + * Copyright 2017-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import java.util.concurrent.TimeUnit; import org.awaitility.Awaitility; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfSystemProperty; import org.junit.jupiter.api.extension.ExtendWith; @@ -31,7 +32,6 @@ import org.springframework.http.HttpEntity; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; -import org.springframework.shell.Shell; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -41,19 +41,34 @@ @ExtendWith(SpringExtension.class) @SpringBootTest( classes = DatastoreBookshelfExample.class, - properties = "spring.shell.interactive.enabled=false", webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @EnabledIfSystemProperty(named = "it.datastore", matches = "true") class DatastoreBookshelfExampleIntegrationTests { - @Autowired private Shell shell; - @Autowired private DatastoreTemplate datastoreTemplate; - @Autowired private BookRepository bookRepository; - @Autowired private TestRestTemplate restTemplate; + @BeforeEach + void saveBooks() { + sendRequest( + "/saveBook", + "{\"id\":12345678, \"title\":\"The Moon Is a Harsh Mistress\", \"author\":\"Robert A. Heinlein\", \"year\":1966}", + HttpMethod.POST); + sendRequest( + "/saveBook", + "{\"title\":\"Stranger in a Strange Land\", \"author\":\"Robert A. Heinlein\", \"year\":1961}", + HttpMethod.POST); + sendRequest( + "/saveBook", + "{\"title\":\"The Crack in Space\", \"author\":\"Philip K. Dick\", \"year\":1966}", + HttpMethod.POST); + sendRequest( + "/saveBook", + "{\"title\":\"Ubik\", \"author\":\"Philip K. Dick\", \"year\":1969}", + HttpMethod.POST); + } + @AfterEach void cleanUp() { this.datastoreTemplate.deleteAll(Book.class); @@ -61,35 +76,71 @@ void cleanUp() { @Test void testSerializedPage() { - Book book = new Book("Book1", "Author1", 2019); - book.id = 12345678L; - this.bookRepository.save(book); - Awaitility.await().atMost(15, TimeUnit.SECONDS).until(() -> this.bookRepository.count() == 1); String responseBody = sendRequest("/allbooksserialized", null, HttpMethod.GET); - assertThat(responseBody).contains("content\":[{\"id\":12345678}],\"pageable\":"); - assertThat(responseBody).containsPattern("\"urlSafeCursor\":\".+\""); + assertThat(responseBody) + .contains("content\":[{\"id\":12345678}],\"pageable\":") + .containsPattern("\"urlSafeCursor\":\".+\""); } @Test - void testSaveBook() { - String book1 = (String) this.shell.evaluate(() -> "save-book book1 author1 1984"); - String book2 = (String) this.shell.evaluate(() -> "save-book book2 author2 2000"); - - String allBooks = (String) this.shell.evaluate(() -> "find-all-books"); - assertThat(allBooks).containsSequence(book1); - assertThat(allBooks).containsSequence(book2); - - assertThat(this.shell.evaluate(() -> "find-by-author author1")).isEqualTo("[" + book1 + "]"); + void findAllBooksTest() { + Awaitility.await().atMost(15, TimeUnit.SECONDS).untilAsserted(() -> { + String responseBody = sendRequest("/findAllBooks", null, HttpMethod.GET); + assertThat(responseBody) + .contains("title='The Moon Is a Harsh Mistress', author='Robert A. Heinlein', year=1966") + .contains("title='Stranger in a Strange Land', author='Robert A. Heinlein', year=1961") + .contains("title='The Crack in Space', author='Philip K. Dick', year=1966") + .contains("title='Ubik', author='Philip K. Dick', year=1969"); + }); + } - assertThat(this.shell.evaluate(() -> "find-by-author-year author2 2000")) - .isEqualTo("[" + book2 + "]"); + @Test + void findByAuthorTest() { + Awaitility.await().atMost(15, TimeUnit.SECONDS).untilAsserted(() -> { + String responseBody = sendRequest("/findByAuthor?author=Robert A. Heinlein", null, HttpMethod.GET); + assertThat(responseBody) + .contains("title='The Moon Is a Harsh Mistress', author='Robert A. Heinlein', year=1966") + .contains("title='Stranger in a Strange Land', author='Robert A. Heinlein', year=1961") + .doesNotContain("title='The Crack in Space', author='Philip K. Dick', year=1966") + .doesNotContain("title='Ubik', author='Philip K. Dick', year=1969"); + }); + } - assertThat(this.shell.evaluate(() -> "find-by-year-greater-than 1985")) - .isEqualTo("[" + book2 + "]"); + @Test + void findByYearGreaterThanTest() { + Awaitility.await().atMost(15, TimeUnit.SECONDS).untilAsserted(() -> { + String responseBody = sendRequest("/findByYearGreaterThan?year=1967", null, HttpMethod.GET); + assertThat(responseBody) + .doesNotContain("title='The Moon Is a Harsh Mistress', author='Robert A. Heinlein', year=1966") + .doesNotContain("title='Stranger in a Strange Land', author='Robert A. Heinlein', year=1961") + .doesNotContain("title='The Crack in Space', author='Philip K. Dick', year=1966") + .contains("title='Ubik', author='Philip K. Dick', year=1969"); + }); + } - this.shell.evaluate(() -> "remove-all-books"); + @Test + void findByAuthorYearTest() { + Awaitility.await().atMost(15, TimeUnit.SECONDS).untilAsserted(() -> { + String responseBody = sendRequest("/findByAuthorYear?author=Philip K. Dick&year=1966", null, HttpMethod.GET); + assertThat(responseBody) + .doesNotContain("title='The Moon Is a Harsh Mistress', author='Robert A. Heinlein', year=1966") + .doesNotContain("title='Stranger in a Strange Land', author='Robert A. Heinlein', year=1961") + .contains("title='The Crack in Space', author='Philip K. Dick', year=1966") + .doesNotContain("title='Ubik', author='Philip K. Dick', year=1969"); + }); + } - assertThat(this.shell.evaluate(() -> "find-all-books")).isEqualTo("[]"); + @Test + void removeAllBooksTest() { + sendRequest("/removeAllBooks", null, HttpMethod.DELETE); + Awaitility.await().atMost(15, TimeUnit.SECONDS).untilAsserted(() -> { + String responseBody = sendRequest("/findAllBooks", null, HttpMethod.GET); + assertThat(responseBody) + .doesNotContain("title='The Moon Is a Harsh Mistress', author='Robert A. Heinlein', year=1966") + .doesNotContain("title='Stranger in a Strange Land', author='Robert A. Heinlein', year=1961") + .doesNotContain("title='The Crack in Space', author='Philip K. Dick', year=1966") + .doesNotContain("title='Ubik', author='Philip K. Dick', year=1969"); + }); } private String sendRequest(String url, String json, HttpMethod method) { diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-sample/pom.xml index c9f492d8ee..ffea384f27 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-sample/pom.xml @@ -6,11 +6,10 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-samples</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> - <groupId>com.google.cloud</groupId> <artifactId>spring-cloud-gcp-data-datastore-sample</artifactId> <name>Spring Framework on Google Cloud Code Sample - Datastore</name> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-sample/src/main/java/com/example/DatastoreRepositoryExample.java b/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-sample/src/main/java/com/example/DatastoreRepositoryExample.java index 1de4466593..3c70b2177e 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-sample/src/main/java/com/example/DatastoreRepositoryExample.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-sample/src/main/java/com/example/DatastoreRepositoryExample.java @@ -23,9 +23,7 @@ import java.util.HashSet; import java.util.List; import java.util.TreeSet; -import java.util.stream.Collectors; import java.util.stream.Stream; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -40,9 +38,16 @@ @SpringBootApplication public class DatastoreRepositoryExample { - @Autowired private SingerRepository singerRepository; + private final SingerRepository singerRepository; - @Autowired private TransactionalRepositoryService transactionalRepositoryService; + private final TransactionalRepositoryService transactionalRepositoryService; + + public DatastoreRepositoryExample( + SingerRepository singerRepository, + TransactionalRepositoryService transactionalRepositoryService) { + this.singerRepository = singerRepository; + this.transactionalRepositoryService = transactionalRepositoryService; + } public static void main(String[] args) { SpringApplication.run(DatastoreRepositoryExample.class, args); @@ -54,7 +59,7 @@ public CommandLineRunner commandLineRunner() { System.out.println("Remove all records from 'singers' kind"); this.singerRepository.deleteAll(); - this.singerRepository.save(new Singer("singer1", "John", "Doe", new HashSet<Album>())); + this.singerRepository.save(new Singer("singer1", "John", "Doe", new HashSet<>())); Singer janeDoe = new Singer( @@ -70,7 +75,7 @@ public CommandLineRunner commandLineRunner() { "singer3", "Richard", "Roe", - new HashSet<>(Arrays.asList(new Album("c", LocalDate.of(2000, Month.AUGUST, 31))))); + new HashSet<>(List.of(new Album("c", LocalDate.of(2000, Month.AUGUST, 31))))); richardRoe.setMessage("Hello, dear fans!".getBytes(StandardCharsets.UTF_8)); this.singerRepository.saveAll(Arrays.asList(janeDoe, richardRoe)); @@ -122,7 +127,7 @@ private void retrieveAndPrintSingers() { System.out.println("Fluent Query by example"); List<String> singerNames = this.singerRepository.findBy( - example, q -> q.stream().map(Singer::firstAndLastName).collect(Collectors.toList())); + example, q -> q.stream().map(Singer::firstAndLastName).toList()); singerNames.forEach(System.out::println); // Pageable parameter @@ -169,14 +174,14 @@ private void createRelationshipsInTransaction(Singer singerA, Singer singerB) { List<String> singers = this.singerRepository.findSingersByFirstBand(band3).stream() .map(Singer::getFirstName) - .collect(Collectors.toList()); + .toList(); System.out.println(singers); System.out.println("Find by reference"); List<String> singers2 = this.singerRepository.findByFirstBand(band3).stream() .map(Singer::getFirstName) - .collect(Collectors.toList()); + .toList(); System.out.println(singers2); } } diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-sample/src/test/java/com/example/DatastoreSampleApplicationIntegrationTests.java b/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-sample/src/test/java/com/example/DatastoreSampleApplicationIntegrationTests.java index 67f2604e61..8e0ad4a93c 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-sample/src/test/java/com/example/DatastoreSampleApplicationIntegrationTests.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-sample/src/test/java/com/example/DatastoreSampleApplicationIntegrationTests.java @@ -39,7 +39,6 @@ import org.junit.jupiter.api.condition.EnabledIfSystemProperty; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.CommandLineRunner; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpEntity; @@ -65,23 +64,19 @@ webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class DatastoreSampleApplicationIntegrationTests { - private static PrintStream systemOut; - private static ByteArrayOutputStream baos; final ObjectMapper mapper = new ObjectMapper(); @Autowired private TestRestTemplate restTemplate; - @Autowired private CommandLineRunner commandLineRunner; - @Autowired private SingerRepository singerRepository; @Autowired DatastoreTemplate datastoreTemplate; @BeforeAll static void checkToRun() { - systemOut = System.out; + PrintStream systemOut = System.out; baos = new ByteArrayOutputStream(); TeeOutputStream out = new TeeOutputStream(systemOut, baos); System.setOut(new PrintStream(out)); @@ -179,25 +174,24 @@ void basicTest() throws Exception { assertThat(baos.toString()) .contains( - "Query by example\n" - + "Singer{singerId='singer1', firstName='John', lastName='Doe', " - + "albums=[], firstBand=null, bands=, personalInstruments=}\n" - + "Singer{singerId='singer2', firstName='Jane', lastName='Doe', " - + "albums=[Album{albumName='a', date=2012-01-20}"); + """ + Query by example + Singer{singerId='singer1', firstName='John', lastName='Doe', albums=[], firstBand=null, bands=, personalInstruments=} + Singer{singerId='singer2', firstName='Jane', lastName='Doe', albums=[Album{albumName='a', date=2012-01-20}"""); assertThat(baos.toString()) .contains( - "Fluent Query by example\n" - + "Singer{singerId='singer1', firstName='John', lastName='Doe}\n" - + "Singer{singerId='singer2', firstName='Jane', lastName='Doe}"); + """ + Fluent Query by example + Singer{singerId='singer1', firstName='John', lastName='Doe} + Singer{singerId='singer2', firstName='Jane', lastName='Doe}"""); assertThat(baos.toString()) .contains( - "Using Pageable parameter\n" - + "Singer{singerId='singer1', firstName='John', lastName='Doe', " - + "albums=[], firstBand=null, bands=, personalInstruments=}\n" - + "Singer{singerId='singer2', firstName='Jane', lastName='Doe', " - + "albums=[Album{albumName='a', date=2012-01-20}"); + """ + Using Pageable parameter + Singer{singerId='singer1', firstName='John', lastName='Doe', albums=[], firstBand=null, bands=, personalInstruments=} + Singer{singerId='singer2', firstName='Jane', lastName='Doe', albums=[Album{albumName='a', date=2012-01-20}"""); assertThat(baos.toString()).contains("Find by reference with query\n" + "[Richard]"); diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-data-firestore-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-data-firestore-sample/pom.xml index 252d5868e6..9f9f507693 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-data-firestore-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-data-firestore-sample/pom.xml @@ -6,7 +6,7 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-samples</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-data-firestore-sample/src/test/java/com/example/FirestoreSampleApplicationNativeIntegrationTests.java b/spring-cloud-gcp-samples/spring-cloud-gcp-data-firestore-sample/src/test/java/com/example/FirestoreSampleApplicationNativeIntegrationTests.java deleted file mode 100644 index 8612e17f29..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-data-firestore-sample/src/test/java/com/example/FirestoreSampleApplicationNativeIntegrationTests.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2017-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example; - -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.is; -import static org.junit.Assume.assumeThat; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.cloud.NoCredentials; -import com.google.cloud.firestore.CollectionReference; -import com.google.cloud.firestore.DocumentReference; -import com.google.cloud.firestore.Firestore; -import com.google.cloud.firestore.FirestoreOptions; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutionException; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.web.client.RestTemplate; -import org.testcontainers.containers.FirestoreEmulatorContainer; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.wait.strategy.Wait; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import org.testcontainers.utility.DockerImageName; - -/** - * Tests created native docker image when maven "native" profile is active. A firestore is run in - * docker using TestContainers, and then our app's docker image will connect to that firestore to - * persist/fetch data. Test scenario is almost exactly the same as {@link - * FirestoreSampleApplicationIntegrationTests}. - */ -@Testcontainers -@Tag("native") -public class FirestoreSampleApplicationNativeIntegrationTests { - private static final User ALPHA_USER = - new User("Alpha", 49, singletonList(new Pet("rat", "Snowflake"))); - private static final List<PhoneNumber> ALPHA_PHONE_NUMBERS = - Arrays.asList(new PhoneNumber("555666777"), new PhoneNumber("777666555")); - private static final User BETA_USER = new User("Beta", 23, emptyList()); - private static final User DELTA_USER = - new User("Delta", 49, Arrays.asList(new Pet("fish", "Dory"), new Pet("spider", "Man"))); - private static final String MY_GCP_PROJECT_ID = "my-gcp-project-id"; - - @Container - private static final FirestoreEmulatorContainer emulator = - new FirestoreEmulatorContainer( - DockerImageName.parse("gcr.io/google.com/cloudsdktool/cloud-sdk:316.0.0-emulators")); - - private static Firestore firestore; - - @BeforeAll - static void beforeAll() { - assumeThat( - "Firestore Native tests are " + "disabled. Please use '-Dit.native=true' to enable them. ", - System.getProperty("it.native"), - is("true")); - - FirestoreOptions options = - FirestoreOptions.getDefaultInstance().toBuilder() - .setHost(emulator.getEmulatorEndpoint()) - .setCredentials(NoCredentials.getInstance()) - .setProjectId(MY_GCP_PROJECT_ID) - .build(); - firestore = options.getService(); - } - - @Test - void testApplicationNativeDockerImage() throws ExecutionException, InterruptedException { - // expose firestore port from local machine to be used by our app's docker image - Integer firestorePort = emulator.getMappedPort(8080); - org.testcontainers.Testcontainers.exposeHostPorts(firestorePort); - - try (MyAppNativeDockerContainer myApp = new MyAppNativeDockerContainer(emulator)) { - myApp.start(); - RestTemplate restTemplate = new RestTemplateBuilder().rootUri(myApp.getBaseUrl()).build(); - TestUserClient testUserClient = new TestUserClient(restTemplate); - - testUserClient.removePhonesForUser("Alpha"); - List<User> allUsers = testUserClient.listUsers(); - assertThat(allUsers).isEmpty(); - - testUserClient.saveUser(ALPHA_USER, ALPHA_PHONE_NUMBERS); - verifyUserPersisted(ALPHA_USER); - - testUserClient.saveUser(BETA_USER, emptyList()); - verifyUserPersisted(BETA_USER); - - testUserClient.saveUser(DELTA_USER, emptyList()); - verifyUserPersisted(DELTA_USER); - - allUsers = testUserClient.listUsers(); - assertThat(allUsers).map(User::getName).containsExactlyInAnyOrder("Alpha", "Beta", "Delta"); - - List<User> users49 = testUserClient.findUsersByAge(49); - assertThat(users49).containsExactlyInAnyOrder(ALPHA_USER, DELTA_USER); - List<PhoneNumber> phoneNumbers = testUserClient.listPhoneNumbers("Alpha"); - assertThat(phoneNumbers) - .map(PhoneNumber::getNumber) - .containsExactlyInAnyOrder("555666777", "777666555"); - - testUserClient.removeUserByName("Alpha"); - phoneNumbers = testUserClient.listPhoneNumbers("Alpha"); - assertThat(phoneNumbers) - .map(PhoneNumber::getNumber) - .containsExactlyInAnyOrder("555666777", "777666555"); - - testUserClient.removePhonesForUser("Alpha"); - phoneNumbers = testUserClient.listPhoneNumbers("Alpha"); - assertThat(phoneNumbers).isEmpty(); - } - } - - private void verifyUserPersisted(User user) throws InterruptedException, ExecutionException { - CollectionReference users = firestore.collection("users"); - DocumentReference docRef = users.document(user.getName()); - Map<String, Object> storedDoc = docRef.get().get().getData(); - - assertThat(storedDoc).isNotNull().containsEntry("age", (long) user.getAge()); - List<Map<String, Object>> petsMapList = - new ObjectMapper() - .convertValue(user.getPets(), new TypeReference<List<Map<String, Object>>>() {}); - assertThat(storedDoc.get("pets")) - .isNotNull() - .asList() - .hasSize(user.getPets().size()) - .containsAll(petsMapList); - } - - private static class MyAppNativeDockerContainer - extends GenericContainer<MyAppNativeDockerContainer> { - public static final int APP_PORT = 8080; - - MyAppNativeDockerContainer(FirestoreEmulatorContainer emulator) { - super(DockerImageName.parse("spring-cloud-gcp-data-firestore-sample:test")); - dependsOn(emulator); - withEnv("SPRING_CLOUD_GCP_FIRESTORE_EMULATOR_ENABLED", "true"); - withEnv( - "SPRING_CLOUD_GCP_FIRESTORE_HOST_PORT", - "host.testcontainers.internal:" + emulator.getMappedPort(8080)); - withEnv("SPRING_CLOUD_GCP_FIRESTORE_PROJECT_ID", MY_GCP_PROJECT_ID); - withExposedPorts(8080); - waitingFor(Wait.forHttp("/")); - withLogConsumer( - outputFrame -> - System.out.println("MYAPP-DOCKER-OUTPUT " + outputFrame.getUtf8String().trim())); - } - - public String getBaseUrl() { - return "http://" + getHost() + ":" + getMappedPort(APP_PORT); - } - } -} diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-data-jpa-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-data-jpa-sample/pom.xml index 54971908d8..4d77e598a2 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-data-jpa-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-data-jpa-sample/pom.xml @@ -6,7 +6,7 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-samples</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> @@ -38,14 +38,6 @@ <artifactId>spring-cloud-gcp-starter-sql-mysql</artifactId> </dependency> - <!-- Cloud SQL (PostgreSQL) Starter --> - <!-- - <dependency> - <groupId>com.google.cloud</groupId> - <artifactId>spring-cloud-gcp-starter-sql-postgresql</artifactId> - </dependency> - --> - <!-- Test-related dependencies. --> <dependency> <groupId>org.awaitility</groupId> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-data-jpa-sample/src/main/java/com/example/House.java b/spring-cloud-gcp-samples/spring-cloud-gcp-data-jpa-sample/src/main/java/com/example/House.java index 527a929e51..e509286869 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-data-jpa-sample/src/main/java/com/example/House.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-data-jpa-sample/src/main/java/com/example/House.java @@ -16,9 +16,9 @@ package com.example; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; /** A house entity for the sample application. */ @Entity diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-data-multi-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-data-multi-sample/pom.xml index 26b9c017ad..7921852624 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-data-multi-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-data-multi-sample/pom.xml @@ -6,11 +6,10 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-samples</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> - <groupId>com.google.cloud</groupId> <name>Spring Framework on Google Cloud Code Sample - Multi-Spring-Data-Module</name> <artifactId>spring-cloud-gcp-data-multi-sample</artifactId> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-data-multi-sample/src/main/java/com/example/Trader.java b/spring-cloud-gcp-samples/spring-cloud-gcp-data-multi-sample/src/main/java/com/example/Trader.java index 8080efeec3..0f8232ab67 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-data-multi-sample/src/main/java/com/example/Trader.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-data-multi-sample/src/main/java/com/example/Trader.java @@ -21,7 +21,7 @@ import com.google.cloud.spring.data.spanner.core.mapping.Table; /** A sample entity. */ -@Table(name = "traders") +@Table(name = "traders_repository") public class Trader { @PrimaryKey @Column(name = "trader_id") diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-data-spanner-repository-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-data-spanner-repository-sample/pom.xml index 11fb13ca67..6024f3d2f8 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-data-spanner-repository-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-data-spanner-repository-sample/pom.xml @@ -6,11 +6,9 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-samples</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> - - <groupId>com.google.cloud</groupId> <artifactId>spring-cloud-gcp-data-spanner-repository-sample</artifactId> <name>Spring Framework on Google Cloud Code Sample - Spanner Repository</name> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-data-spanner-repository-sample/src/main/java/com/example/SpannerRepositoryExample.java b/spring-cloud-gcp-samples/spring-cloud-gcp-data-spanner-repository-sample/src/main/java/com/example/SpannerRepositoryExample.java index 963d0c6896..7edcbed4da 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-data-spanner-repository-sample/src/main/java/com/example/SpannerRepositoryExample.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-data-spanner-repository-sample/src/main/java/com/example/SpannerRepositoryExample.java @@ -23,7 +23,6 @@ import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -35,39 +34,59 @@ public class SpannerRepositoryExample { private static final Log LOGGER = LogFactory.getLog(SpannerRepositoryExample.class); - @Autowired private TraderRepository traderRepository; + private final TraderRepository traderRepository; - @Autowired private TradeRepository tradeRepository; + private final TradeRepository tradeRepository; - @Autowired private SpannerSchemaUtils spannerSchemaUtils; + private final SpannerSchemaUtils spannerSchemaUtils; - @Autowired private SpannerDatabaseAdminTemplate spannerDatabaseAdminTemplate; + private final SpannerDatabaseAdminTemplate spannerDatabaseAdminTemplate; + + private static final String DEMO_TRADER_1 = "demo_trader1"; + + private static final String DEMO_TRADER_2 = "demo_trader2"; + + private static final String DEMO_TRADER_3 = "demo_trader3"; + + private static final String STOCK_1 = "STOCK1"; + + private static final String STOCK_2 = "STOCK2"; + + public SpannerRepositoryExample(TraderRepository traderRepository, + TradeRepository tradeRepository, + SpannerSchemaUtils spannerSchemaUtils, + SpannerDatabaseAdminTemplate spannerDatabaseAdminTemplate) { + this.traderRepository = traderRepository; + this.tradeRepository = tradeRepository; + this.spannerSchemaUtils = spannerSchemaUtils; + this.spannerDatabaseAdminTemplate = spannerDatabaseAdminTemplate; + } public void runExample() { createTablesIfNotExists(); this.traderRepository.deleteAll(); this.tradeRepository.deleteAll(); - this.traderRepository.save(new Trader("demo_trader1", "John", "Doe")); - this.traderRepository.save(new Trader("demo_trader2", "Mary", "Jane")); - this.traderRepository.save(new Trader("demo_trader3", "Scott", "Smith")); + this.traderRepository.save(new Trader(DEMO_TRADER_1, "John", "Doe")); + this.traderRepository.save(new Trader(DEMO_TRADER_2, "Mary", "Jane")); + this.traderRepository.save(new Trader(DEMO_TRADER_3, "Scott", "Smith")); this.tradeRepository.save( - new Trade("1", "BUY", 100.0, 50.0, "STOCK1", "demo_trader1", Arrays.asList(99.0, 101.00))); + new Trade("1", "BUY", 100.0, 50.0, STOCK_1, DEMO_TRADER_1, Arrays.asList(99.0, 101.00))); this.tradeRepository.save( - new Trade("2", "BUY", 105.0, 60.0, "STOCK2", "demo_trader1", Arrays.asList(99.0, 101.00))); + new Trade("2", "BUY", 105.0, 60.0, STOCK_2, DEMO_TRADER_1, Arrays.asList(99.0, 101.00))); this.tradeRepository.save( - new Trade("3", "BUY", 100.0, 50.0, "STOCK1", "demo_trader1", Arrays.asList(99.0, 101.00))); + new Trade("3", "BUY", 100.0, 50.0, STOCK_1, DEMO_TRADER_1, Arrays.asList(99.0, 101.00))); this.tradeRepository.save( - new Trade("1", "BUY", 100.0, 70.0, "STOCK2", "demo_trader2", Arrays.asList(99.0, 101.00))); + new Trade("1", "BUY", 100.0, 70.0, STOCK_2, DEMO_TRADER_2, Arrays.asList(99.0, 101.00))); this.tradeRepository.save( - new Trade("2", "BUY", 103.0, 50.0, "STOCK1", "demo_trader2", Arrays.asList(99.0, 101.00))); + new Trade("2", "BUY", 103.0, 50.0, STOCK_1, DEMO_TRADER_2, Arrays.asList(99.0, 101.00))); this.tradeRepository.save( - new Trade("3", "SELL", 100.0, 52.0, "STOCK2", "demo_trader2", Arrays.asList(99.0, 101.00))); + new Trade("3", "SELL", 100.0, 52.0, STOCK_2, DEMO_TRADER_2, Arrays.asList(99.0, 101.00))); this.tradeRepository.save( - new Trade("1", "SELL", 98.0, 50.0, "STOCK1", "demo_trader3", Arrays.asList(99.0, 101.00))); + new Trade("1", "SELL", 98.0, 50.0, STOCK_1, DEMO_TRADER_3, Arrays.asList(99.0, 101.00))); this.tradeRepository.save( - new Trade("2", "SELL", 110.0, 50.0, "STOCK2", "demo_trader3", Arrays.asList(99.0, 101.00))); + new Trade("2", "SELL", 110.0, 50.0, STOCK_2, DEMO_TRADER_3, Arrays.asList(99.0, 101.00))); LOGGER.info( "The table for trades has been cleared and " @@ -84,7 +103,7 @@ public void runExample() { LOGGER.info("Make a pageable query:"); List<Trade> tradesPageOne = this.tradeRepository.findByActionAndSymbol(PageRequest.of(0, 1), "BUY", - "STOCK1" + STOCK_1 ); for (Trade t : tradesPageOne) { LOGGER.info(t); @@ -105,10 +124,11 @@ public void runExample() { LOGGER.info(this.tradeRepository.getAnyOneTrade()); LOGGER.info("A query method can also select properties in entities:"); - this.tradeRepository.getTradeIds("BUY").stream().forEach(x -> LOGGER.info(x)); + this.tradeRepository.getTradeIds("BUY").forEach(LOGGER::info); LOGGER.info("Lazy-loading collection of trades for 'demo_trader1':"); - LOGGER.info(this.traderRepository.findById("demo_trader1").get().getTrades()); + this.traderRepository.findById(DEMO_TRADER_1) + .ifPresent(trader -> LOGGER.info(trader.getTrades())); LOGGER.info("Try http://localhost:8080/trades in the browser to see all trades."); @@ -133,17 +153,14 @@ public void runExample() { this.traderRepository.save(trader3); this.traderRepository.save(trader4); - LOGGER.info( - "Find trader by Id and print out JSON field 'workAddress' as string: " - + this.traderRepository.findById("demo_trader_json1").get().getWorkAddress()); + this.traderRepository.findById("demo_trader_json1") + .ifPresent(trader -> LOGGER.info(String.format("Find trader by Id and print out JSON field 'workAddress' as string: %s", trader.getWorkAddress()))); - LOGGER.info( - "Find trader by Id and print out JSON field 'unusedDetails' as string: " - + this.traderRepository.findById("demo_trader_json3").get().getHomeAddress()); + this.traderRepository.findById("demo_trader_json3") + .ifPresent(trader -> LOGGER.info(String.format("Find trader by Id and print out JSON field 'unusedDetails' as string: %s", trader.getHomeAddress()))); - LOGGER.info( - "Find trader by Id and print out ARRAY<JSON> field 'addressList' as string: " - + this.traderRepository.findById("demo_trader_json4").get().getAddressList().toString()); + this.traderRepository.findById("demo_trader_json4") + .ifPresent(trader -> LOGGER.info(String.format("Find trader by Id and print out ARRAY<JSON> field 'addressList' as string: %s", trader.getAddressList()))); long count = this.traderRepository.getCountActive("true"); LOGGER.info("A query method can query on the properties of JSON values"); @@ -158,12 +175,12 @@ public void runExample() { void createTablesIfNotExists() { if (!this.spannerDatabaseAdminTemplate.tableExists("trades_repository")) { this.spannerDatabaseAdminTemplate.executeDdlStrings( - Arrays.asList(this.spannerSchemaUtils.getCreateTableDdlString(Trade.class)), true); + List.of(this.spannerSchemaUtils.getCreateTableDdlString(Trade.class)), true); } if (!this.spannerDatabaseAdminTemplate.tableExists("traders_repository")) { this.spannerDatabaseAdminTemplate.executeDdlStrings( - Arrays.asList(this.spannerSchemaUtils.getCreateTableDdlString(Trader.class)), true); + List.of(this.spannerSchemaUtils.getCreateTableDdlString(Trader.class)), true); } } diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-data-spanner-repository-sample/src/main/java/com/example/Trader.java b/spring-cloud-gcp-samples/spring-cloud-gcp-data-spanner-repository-sample/src/main/java/com/example/Trader.java index 76218ca998..beeadb8877 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-data-spanner-repository-sample/src/main/java/com/example/Trader.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-data-spanner-repository-sample/src/main/java/com/example/Trader.java @@ -40,12 +40,12 @@ public class Trader { private String lastName; @Column(name = "CREATED_ON") - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MMM-dd HH:mm:ss z") - private java.sql.Timestamp createdOn; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + private Timestamp createdOn; @Column(name = "MODIFIED_ON") - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MMM-dd HH:mm:ss z") - private List<java.sql.Timestamp> modifiedOn; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + private List<Timestamp> modifiedOn; @Interleaved(lazy = true) private List<Trade> trades; diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-data-spanner-repository-sample/src/test/java/com/example/SpannerRepositoryIntegrationTests.java b/spring-cloud-gcp-samples/spring-cloud-gcp-data-spanner-repository-sample/src/test/java/com/example/SpannerRepositoryIntegrationTests.java index e3c6cc91c9..c21051f550 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-data-spanner-repository-sample/src/test/java/com/example/SpannerRepositoryIntegrationTests.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-data-spanner-repository-sample/src/test/java/com/example/SpannerRepositoryIntegrationTests.java @@ -18,7 +18,6 @@ import static org.assertj.core.api.Assertions.assertThat; -import com.google.cloud.spring.data.spanner.core.admin.SpannerDatabaseAdminTemplate; import com.google.cloud.spring.data.spanner.core.admin.SpannerSchemaUtils; import java.sql.Timestamp; import java.time.ZoneOffset; @@ -37,7 +36,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.core.ParameterizedTypeReference; import org.springframework.data.domain.PageRequest; import org.springframework.hateoas.PagedModel; @@ -55,7 +54,8 @@ @TestPropertySource("classpath:application-test.properties") @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class SpannerRepositoryIntegrationTests { - @LocalServerPort private int port; + @LocalServerPort + private int port; @Autowired private TraderRepository traderRepository; @@ -63,8 +63,6 @@ class SpannerRepositoryIntegrationTests { @Autowired private SpannerSchemaUtils spannerSchemaUtils; - @Autowired private SpannerDatabaseAdminTemplate spannerDatabaseAdminTemplate; - @Autowired private SpannerRepositoryExample spannerRepositoryExample; @BeforeEach @@ -82,10 +80,10 @@ void testRestEndpoint() { TestRestTemplate testRestTemplate = new TestRestTemplate(); ResponseEntity<PagedModel<Trade>> tradesResponse = testRestTemplate.exchange( - String.format("http://localhost:%s/trades/", this.port), + String.format("http://localhost:%s/trades", this.port), HttpMethod.GET, null, - new ParameterizedTypeReference<PagedModel<Trade>>() {}); + new ParameterizedTypeReference<>() {}); assertThat(tradesResponse.getBody().getMetadata().getTotalElements()).isEqualTo(8); } @@ -102,10 +100,10 @@ void testRestEndpointPut() { String.format("http://localhost:%s/traders/t123", this.port), HttpMethod.PUT, new HttpEntity<>( - "{\"firstName\": \"John\", \"lastName\": \"Smith\", \"createdOn\": \"2000-Jan-02" - + " 03:04:05 UTC\", \"modifiedOn\": [\"2000-Jan-02 03:04:05 UTC\"]}", + "{\"firstName\": \"John\", \"lastName\": \"Smith\", \"createdOn\": \"2000-01-02" + + "T03:04:05.000Z\", \"modifiedOn\": [\"2000-01-02T03:04:05.000Z\"]}", headers), - new ParameterizedTypeReference<Trader>() {}); + new ParameterizedTypeReference<>() {}); ZonedDateTime expectedUtcDate = ZonedDateTime.of(2000, 1, 2, 3, 4, 5, 0, ZoneOffset.UTC); Timestamp expectedTimestamp = new Timestamp(expectedUtcDate.toEpochSecond() * 1000L); diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-data-spanner-template-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-data-spanner-template-sample/pom.xml index 215e120df1..9b5d069de8 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-data-spanner-template-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-data-spanner-template-sample/pom.xml @@ -6,11 +6,9 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-samples</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> - - <groupId>com.google.cloud</groupId> <artifactId>spring-cloud-gcp-data-spanner-template-sample</artifactId> <name>Spring Framework on Google Cloud Code Sample - Spanner Template</name> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-firestore-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-firestore-sample/pom.xml index 36ad883ccb..356c986e53 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-firestore-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-firestore-sample/pom.xml @@ -7,7 +7,7 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-samples</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-json-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-json-sample/pom.xml index c12c7f2c8d..5229bcbdec 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-json-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-json-sample/pom.xml @@ -7,7 +7,7 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-samples</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-json-sample/src/main/java/com/example/ReceiverConfiguration.java b/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-json-sample/src/main/java/com/example/ReceiverConfiguration.java index 75fba6ad63..81f632a86a 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-json-sample/src/main/java/com/example/ReceiverConfiguration.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-json-sample/src/main/java/com/example/ReceiverConfiguration.java @@ -22,6 +22,7 @@ import com.google.cloud.spring.pubsub.support.BasicAcknowledgeablePubsubMessage; import com.google.cloud.spring.pubsub.support.GcpPubSubHeaders; import java.util.ArrayList; +import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Qualifier; @@ -69,7 +70,7 @@ public void messageReceiver( @Bean @Qualifier("ProcessedPersonsList") - public ArrayList<Person> processedPersonsList() { + public List<Person> processedPersonsList() { return this.processedPersonsList; } } diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-json-sample/src/main/java/com/example/SenderConfiguration.java b/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-json-sample/src/main/java/com/example/SenderConfiguration.java index f051fce87a..e6629e9951 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-json-sample/src/main/java/com/example/SenderConfiguration.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-json-sample/src/main/java/com/example/SenderConfiguration.java @@ -26,7 +26,6 @@ import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.channel.DirectChannel; import org.springframework.messaging.MessageHandler; -import org.springframework.util.concurrent.ListenableFutureCallback; /** Configuration for sending custom JSON payloads to a Pub/Sub topic. */ @Configuration @@ -45,19 +44,8 @@ public DirectChannel pubSubOutputChannel() { @ServiceActivator(inputChannel = "pubSubOutputChannel") public MessageHandler messageSender(PubSubTemplate pubSubTemplate) { PubSubMessageHandler adapter = new PubSubMessageHandler(pubSubTemplate, TOPIC_NAME); - adapter.setPublishCallback( - new ListenableFutureCallback<String>() { - @Override - public void onFailure(Throwable ex) { - LOGGER.info("There was an error sending the message."); - } - - @Override - public void onSuccess(String result) { - LOGGER.info("Message was sent successfully."); - } - }); - + adapter.setSuccessCallback((ackId, message) -> LOGGER.info("Message was sent successfully.")); + adapter.setFailureCallback((cause, message) -> LOGGER.info("There was an error sending the message.")); return adapter; } diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-json-sample/src/main/java/com/example/WebController.java b/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-json-sample/src/main/java/com/example/WebController.java index baa7486323..9eb9dd549f 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-json-sample/src/main/java/com/example/WebController.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-json-sample/src/main/java/com/example/WebController.java @@ -17,7 +17,6 @@ package com.example; import com.example.SenderConfiguration.PubSubPersonGateway; -import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -35,7 +34,7 @@ public class WebController { @Autowired @Qualifier("ProcessedPersonsList") - private ArrayList<Person> processedPersonsList; + private List<Person> processedPersonsList; public WebController(PubSubPersonGateway pubSubPersonGateway) { this.pubSubPersonGateway = pubSubPersonGateway; diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-sample/pom.xml index 972ae2777f..750e680b2d 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-sample/pom.xml @@ -5,12 +5,10 @@ <parent> <artifactId>spring-cloud-gcp-samples</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <artifactId>spring-cloud-gcp-integration-pubsub-sample</artifactId> - <groupId>com.google.cloud</groupId> - <modelVersion>4.0.0</modelVersion> <packaging>pom</packaging> <name>Spring Framework on Google Cloud Code Sample - Spring Integration Pub/Sub Channel Adapters</name> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-sample/spring-cloud-gcp-integration-pubsub-sample-polling-receiver/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-sample/spring-cloud-gcp-integration-pubsub-sample-polling-receiver/pom.xml index 1a8c62083d..88208c840d 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-sample/spring-cloud-gcp-integration-pubsub-sample-polling-receiver/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-sample/spring-cloud-gcp-integration-pubsub-sample-polling-receiver/pom.xml @@ -7,7 +7,7 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-integration-pubsub-sample</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-sample/spring-cloud-gcp-integration-pubsub-sample-receiver/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-sample/spring-cloud-gcp-integration-pubsub-sample-receiver/pom.xml index 6353b77dac..5af3231c16 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-sample/spring-cloud-gcp-integration-pubsub-sample-receiver/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-sample/spring-cloud-gcp-integration-pubsub-sample-receiver/pom.xml @@ -6,7 +6,7 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-integration-pubsub-sample</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-sample/spring-cloud-gcp-integration-pubsub-sample-sender/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-sample/spring-cloud-gcp-integration-pubsub-sample-sender/pom.xml index d5a5d5795d..a0ad664374 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-sample/spring-cloud-gcp-integration-pubsub-sample-sender/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-sample/spring-cloud-gcp-integration-pubsub-sample-sender/pom.xml @@ -6,7 +6,7 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-integration-pubsub-sample</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-sample/spring-cloud-gcp-integration-pubsub-sample-test/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-sample/spring-cloud-gcp-integration-pubsub-sample-test/pom.xml index 20ebe76f8d..15fa083a2a 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-sample/spring-cloud-gcp-integration-pubsub-sample-test/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-sample/spring-cloud-gcp-integration-pubsub-sample-test/pom.xml @@ -6,7 +6,7 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-integration-pubsub-sample</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-integration-storage-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-integration-storage-sample/pom.xml index e4c10060c2..f2734c1d88 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-integration-storage-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-integration-storage-sample/pom.xml @@ -6,7 +6,7 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-samples</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-kms-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-kms-sample/pom.xml index 6cbe4265fa..51514fb0f5 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-kms-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-kms-sample/pom.xml @@ -7,7 +7,7 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-samples</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/pom.xml index ee4ad4f35f..1c06b95044 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/pom.xml @@ -7,15 +7,12 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-samples</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> - <groupId>com.google.cloud</groupId> <artifactId>spring-cloud-gcp-kotlin-samples</artifactId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> - + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> <name>Spring Framework on Google Cloud Code Samples - Kotlin</name> - <modelVersion>4.0.0</modelVersion> <packaging>pom</packaging> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/pom.xml index 39003f255b..316b41c8d2 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/pom.xml @@ -6,7 +6,7 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-kotlin-samples</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> @@ -14,7 +14,7 @@ <name>Spring Framework on Google Cloud Code Sample - Kotlin App Sample</name> <properties> - <kotlin.version>1.6.21</kotlin.version> + <kotlin.version>1.7.21</kotlin.version> </properties> <!-- The Spring Framework on Google Cloud BOM will manage spring-cloud-gcp version numbers for you. --> @@ -79,7 +79,7 @@ </dependency> <dependency> - <artifactId>kotlin-test</artifactId> + <artifactId>kotlin-test-junit</artifactId> <groupId>org.jetbrains.kotlin</groupId> <version>${kotlin.version}</version> <scope>test</scope> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/main/kotlin/com/example/app/Controller.kt b/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/main/kotlin/com/example/app/Controller.kt index 1adac92edc..f9c59a76b8 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/main/kotlin/com/example/app/Controller.kt +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/main/kotlin/com/example/app/Controller.kt @@ -34,7 +34,7 @@ import org.springframework.web.servlet.view.RedirectView @RestController class Controller(val pubSubTemplate: PubSubTemplate, val personRepository: PersonRepository) { - val REGISTRATION_TOPIC = "registrations" + private val registrationTopic = "registrations" @PostMapping("/registerPerson") fun registerPerson( @@ -42,7 +42,7 @@ class Controller(val pubSubTemplate: PubSubTemplate, val personRepository: Perso @RequestParam("lastName") lastName: String, @RequestParam("email") email: String): RedirectView { - pubSubTemplate.publish(REGISTRATION_TOPIC, Person(firstName, lastName, email)) + pubSubTemplate.publish(registrationTopic, Person(firstName, lastName, email)) return RedirectView("/") } diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/main/kotlin/com/example/app/PubSubConfiguration.kt b/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/main/kotlin/com/example/app/PubSubConfiguration.kt index 6af335d113..28dbffa806 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/main/kotlin/com/example/app/PubSubConfiguration.kt +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/main/kotlin/com/example/app/PubSubConfiguration.kt @@ -56,7 +56,7 @@ class PubSubConfiguration { /** * This bean enables serialization/deserialization of Java objects to JSON allowing you - * utilize JSON message payloads in Cloud Pub/Sub. + * to utilize JSON message payloads in Cloud Pub/Sub. * @param objectMapper the object mapper to use * @return a Jackson message converter */ diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/main/kotlin/com/example/app/RegistrationApplication.kt b/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/main/kotlin/com/example/app/RegistrationApplication.kt index d2abb15b87..843b22bb92 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/main/kotlin/com/example/app/RegistrationApplication.kt +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/main/kotlin/com/example/app/RegistrationApplication.kt @@ -28,7 +28,7 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories * * @since 1.1 */ -@SpringBootApplication() +@SpringBootApplication @EnableJpaRepositories(basePackageClasses = [PersonRepository::class]) @EntityScan(basePackageClasses = [Person::class]) class Application diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/main/kotlin/com/example/data/Person.kt b/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/main/kotlin/com/example/data/Person.kt index fd9bc38e5c..3fabed0a32 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/main/kotlin/com/example/data/Person.kt +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/main/kotlin/com/example/data/Person.kt @@ -16,9 +16,9 @@ package com.example.data -import javax.persistence.Entity -import javax.persistence.GeneratedValue -import javax.persistence.Id +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.Id /** * Data object representing a registered person. diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/main/resources/application.properties b/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/main/resources/application.properties index 41478f72cd..5aa23342b4 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/main/resources/application.properties +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/main/resources/application.properties @@ -2,7 +2,7 @@ spring.cloud.gcp.sql.database-name=registrants spring.cloud.gcp.sql.instance-connection-name=[YOUR_SQL_INSTANCE_NAME] # So app starts despite "table already exists" errors. -spring.datasource.continue-on-error=true +spring.sql.init.continue-on-error=true # Enforces database initialization spring.sql.init.mode=always diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/test/kotlin/com/example/RegistrationKotlinSampleApplicationIntegrationTests.kt b/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/test/kotlin/com/example/RegistrationKotlinSampleApplicationIntegrationTests.kt index f4d6771425..fbf025e0fc 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/test/kotlin/com/example/RegistrationKotlinSampleApplicationIntegrationTests.kt +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample/src/test/kotlin/com/example/RegistrationKotlinSampleApplicationIntegrationTests.kt @@ -68,8 +68,8 @@ class RegistrationKotlinSampleApplicationIntegrationTests { .perform(get(REGISTRANTS_URL)) .andReturn() - val resultParams = mvcResult.modelAndView.modelMap - val personsList = resultParams.get("personsList") as List<Person> + val resultParams = mvcResult.modelAndView?.modelMap + val personsList = resultParams?.get("personsList") as List<Person> assertThat(personsList).hasSize(1) val person = personsList[0] diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-logging-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-logging-sample/pom.xml index d8eb89b133..a0ec7568a9 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-logging-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-logging-sample/pom.xml @@ -3,7 +3,6 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> - <groupId>com.google.cloud</groupId> <artifactId>spring-cloud-gcp-logging-sample</artifactId> <name>Spring Framework on Google Cloud Code Sample - Logging</name> @@ -11,7 +10,7 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-samples</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <!-- The Spring Framework on Google Cloud BOM will manage spring-cloud-gcp version numbers for you. --> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-logging-sample/src/test/java/com.example/LoggingSampleApplicationIntegrationTests.java b/spring-cloud-gcp-samples/spring-cloud-gcp-logging-sample/src/test/java/com.example/LoggingSampleApplicationIntegrationTests.java index 2ee6dd7dd0..59a7d3b08e 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-logging-sample/src/test/java/com.example/LoggingSampleApplicationIntegrationTests.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-logging-sample/src/test/java/com.example/LoggingSampleApplicationIntegrationTests.java @@ -39,7 +39,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -61,7 +61,8 @@ class LoggingSampleApplicationIntegrationTests { @Autowired private TestRestTemplate testRestTemplate; - @LocalServerPort private int port; + @LocalServerPort + private int port; private Logging logClient; diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-metrics-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-metrics-sample/pom.xml index df7475b1e8..3283934b1b 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-metrics-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-metrics-sample/pom.xml @@ -3,7 +3,6 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> - <groupId>com.google.cloud</groupId> <artifactId>spring-cloud-gcp-metrics-sample</artifactId> <name>Spring Framework on Google Cloud Code Sample - Metrics</name> @@ -11,7 +10,7 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-samples</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <!-- The Spring Framework on Google Cloud BOM will manage spring-cloud-gcp version numbers for you. --> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-metrics-sample/src/test/java/com/example/MetricsSampleApplicationIntegrationTests.java b/spring-cloud-gcp-samples/spring-cloud-gcp-metrics-sample/src/test/java/com/example/MetricsSampleApplicationIntegrationTests.java index cfd94e685a..46a45a127c 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-metrics-sample/src/test/java/com/example/MetricsSampleApplicationIntegrationTests.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-metrics-sample/src/test/java/com/example/MetricsSampleApplicationIntegrationTests.java @@ -30,10 +30,10 @@ import org.junit.jupiter.api.condition.EnabledIfSystemProperty; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.actuate.metrics.AutoConfigureMetrics; +import org.springframework.boot.test.autoconfigure.actuate.observability.AutoConfigureObservability; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.ResponseEntity; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -43,14 +43,15 @@ @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = MetricsApplication.class) -@AutoConfigureMetrics // needed to enable metrics export in Spring Boot tests +@AutoConfigureObservability // needed to enable metrics export in Spring Boot tests class MetricsSampleApplicationIntegrationTests { @Autowired private GcpProjectIdProvider projectIdProvider; @Autowired private TestRestTemplate testRestTemplate; - @LocalServerPort private int port; + @LocalServerPort + private int port; private MetricServiceClient metricClient; diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/pom.xml index b43f3d52e6..a9dcb8dee4 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/pom.xml @@ -5,17 +5,16 @@ <parent> <artifactId>spring-cloud-gcp-samples</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>spring-cloud-gcp-pubsub-bus-config-sample</artifactId> - <groupId>com.google.cloud</groupId> <packaging>pom</packaging> <name>Spring Framework on Google Cloud Code Sample - Pub/Sub Bus Configuration Management</name> <properties> - <spring-cloud-config.version>3.1.5</spring-cloud-config.version> + <spring-cloud-config.version>4.0.0</spring-cloud-config.version> </properties> <!-- The Spring Framework on Google Cloud BOM will manage spring-cloud-gcp version numbers for you. --> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-client/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-client/pom.xml index f57841edbe..f803f02155 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-client/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-client/pom.xml @@ -6,7 +6,7 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-pubsub-bus-config-sample</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-client/src/main/resources/application.properties b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-client/src/main/resources/application.properties index 1e51ebab23..e82237b0a1 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-client/src/main/resources/application.properties +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-client/src/main/resources/application.properties @@ -3,3 +3,4 @@ #spring.cloud.gcp.project-id=[YOUR_GCP_PROJECT_ID] #spring.cloud.gcp.credentials.location=file:[LOCAL_PATH_TO_CREDENTIALS] +spring.config.import=configserver: diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-server-github/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-server-github/pom.xml index 91cec94c52..c83091f9b1 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-server-github/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-server-github/pom.xml @@ -6,7 +6,7 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-pubsub-bus-config-sample</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-server-local/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-server-local/pom.xml index 01f6ae7c83..bb8455b75a 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-server-local/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-server-local/pom.xml @@ -6,7 +6,7 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-pubsub-bus-config-sample</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-server-local/src/main/resources/application.properties b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-server-local/src/main/resources/application.properties index 2f26726c79..cb21277840 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-server-local/src/main/resources/application.properties +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-server-local/src/main/resources/application.properties @@ -1,7 +1,7 @@ server.port=8888 spring.profiles.active=native -spring.cloud.config.server.native.searchLocations=file:/tmp/config/ +spring.cloud.config.server.native.searchLocations=file:/tmp/config # Customize the Pub/Sub Topic to be used as the message bus. #spring.cloud.bus.destination=custom-spring-cloud-bus-topic diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-test/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-test/pom.xml index 2b4bcec672..777b454a6c 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-test/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-bus-config-sample/spring-cloud-gcp-pubsub-bus-config-sample-test/pom.xml @@ -6,7 +6,7 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-pubsub-bus-config-sample</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-reactive-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-reactive-sample/pom.xml index bcf46f4e4b..151241dea7 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-reactive-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-reactive-sample/pom.xml @@ -6,7 +6,7 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-samples</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-reactive-sample/src/test/java/com/example/ReactiveReceiverApplicationIntegrationTest.java b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-reactive-sample/src/test/java/com/example/ReactiveReceiverApplicationIntegrationTest.java index 5e2a48886d..16b2adafa3 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-reactive-sample/src/test/java/com/example/ReactiveReceiverApplicationIntegrationTest.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-reactive-sample/src/test/java/com/example/ReactiveReceiverApplicationIntegrationTest.java @@ -24,7 +24,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.MediaType; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.reactive.server.FluxExchangeResult; @@ -43,7 +43,8 @@ classes = ReactiveReceiverApplication.class) class ReactiveReceiverApplicationIntegrationTest { - @LocalServerPort private int port; + @LocalServerPort + private int port; @Autowired private WebTestClient webTestClient; diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-sample/pom.xml index de45690941..2d6dba28b3 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-sample/pom.xml @@ -7,7 +7,7 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-samples</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-sample/src/main/java/com/example/WebController.java b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-sample/src/main/java/com/example/WebController.java index c74a35de74..f9b9bc653e 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-sample/src/main/java/com/example/WebController.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-sample/src/main/java/com/example/WebController.java @@ -24,9 +24,9 @@ import java.util.Collection; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.CompletableFuture; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.util.concurrent.ListenableFuture; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -37,7 +37,7 @@ @RestController public class WebController { - private static final Log LOGGER = LogFactory.getLog(PubSubApplication.class); + private static final Log LOGGER = LogFactory.getLog(WebController.class); private final PubSubTemplate pubSubTemplate; @@ -85,22 +85,7 @@ public RedirectView pull(@RequestParam("subscription1") String subscriptionName) Collection<AcknowledgeablePubsubMessage> messages = this.pubSubTemplate.pull(subscriptionName, 10, true); - if (messages.isEmpty()) { - return buildStatusView("No messages available for retrieval."); - } - - RedirectView returnView; - try { - ListenableFuture<Void> ackFuture = this.pubSubTemplate.ack(messages); - ackFuture.get(); - returnView = - buildStatusView(String.format("Pulled and acked %s message(s)", messages.size())); - } catch (Exception ex) { - LOGGER.warn("Acking failed.", ex); - returnView = buildStatusView("Acking failed"); - } - - return returnView; + return handleMessage(messages); } @GetMapping("/multipull") @@ -112,23 +97,7 @@ public RedirectView multipull( mixedSubscriptionMessages.addAll(this.pubSubTemplate.pull(subscriptionName1, 1000, true)); mixedSubscriptionMessages.addAll(this.pubSubTemplate.pull(subscriptionName2, 1000, true)); - if (mixedSubscriptionMessages.isEmpty()) { - return buildStatusView("No messages available for retrieval."); - } - - RedirectView returnView; - try { - ListenableFuture<Void> ackFuture = this.pubSubTemplate.ack(mixedSubscriptionMessages); - ackFuture.get(); - returnView = - buildStatusView( - String.format("Pulled and acked %s message(s)", mixedSubscriptionMessages.size())); - } catch (Exception ex) { - LOGGER.warn("Acking failed.", ex); - returnView = buildStatusView("Acking failed"); - } - - return returnView; + return handleMessage(mixedSubscriptionMessages); } @GetMapping("/subscribe") @@ -168,4 +137,24 @@ private RedirectView buildStatusView(String statusMessage) { view.addStaticAttribute("statusMessage", statusMessage); return view; } + + private RedirectView handleMessage(Collection<AcknowledgeablePubsubMessage> messages) { + if (messages.isEmpty()) { + return buildStatusView("No messages available for retrieval."); + } + + RedirectView returnView; + try { + CompletableFuture<Void> ackFuture = this.pubSubTemplate.ack(messages); + ackFuture.get(); + returnView = + buildStatusView( + String.format("Pulled and acked %s message(s)", messages.size())); + } catch (Exception ex) { + LOGGER.warn("Acking failed.", ex); + returnView = buildStatusView("Acking failed"); + } + + return returnView; + } } diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-sample/src/test/java/com/example/PubSubSampleApplicationIntegrationTests.java b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-sample/src/test/java/com/example/PubSubSampleApplicationIntegrationTests.java index 3b46c217f0..79a0294582 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-sample/src/test/java/com/example/PubSubSampleApplicationIntegrationTests.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-sample/src/test/java/com/example/PubSubSampleApplicationIntegrationTests.java @@ -31,7 +31,9 @@ import com.google.pubsub.v1.PullResponse; import com.google.pubsub.v1.PushConfig; import com.google.pubsub.v1.Subscription; +import com.google.pubsub.v1.SubscriptionName; import com.google.pubsub.v1.Topic; +import com.google.pubsub.v1.TopicName; import java.io.IOException; import java.util.List; import java.util.UUID; @@ -50,7 +52,7 @@ import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.ResponseEntity; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.web.util.UriComponentsBuilder; @@ -93,7 +95,8 @@ class PubSubSampleApplicationIntegrationTests { private static String projectName; - @LocalServerPort private int port; + @LocalServerPort + private int port; @Autowired private TestRestTemplate testRestTemplate; @@ -106,23 +109,23 @@ static void prepare() throws IOException { topicAdminClient = TopicAdminClient.create(); subscriptionAdminClient = SubscriptionAdminClient.create(); - topicAdminClient.createTopic(ProjectTopicName.of(projectName, SAMPLE_TEST_TOPIC)); - topicAdminClient.createTopic(ProjectTopicName.of(projectName, SAMPLE_TEST_TOPIC2)); + topicAdminClient.createTopic(TopicName.of(projectName, SAMPLE_TEST_TOPIC)); + topicAdminClient.createTopic(TopicName.of(projectName, SAMPLE_TEST_TOPIC2)); subscriptionAdminClient.createSubscription( - ProjectSubscriptionName.of(projectName, SAMPLE_TEST_SUBSCRIPTION1), - ProjectTopicName.of(projectName, SAMPLE_TEST_TOPIC), + SubscriptionName.of(projectName, SAMPLE_TEST_SUBSCRIPTION1), + TopicName.of(projectName, SAMPLE_TEST_TOPIC), PushConfig.getDefaultInstance(), 10); subscriptionAdminClient.createSubscription( - ProjectSubscriptionName.of(projectName, SAMPLE_TEST_SUBSCRIPTION2), - ProjectTopicName.of(projectName, SAMPLE_TEST_TOPIC2), + SubscriptionName.of(projectName, SAMPLE_TEST_SUBSCRIPTION2), + TopicName.of(projectName, SAMPLE_TEST_TOPIC2), PushConfig.getDefaultInstance(), 10); subscriptionAdminClient.createSubscription( - ProjectSubscriptionName.of(projectName, SAMPLE_TEST_SUBSCRIPTION3), - ProjectTopicName.of(projectName, SAMPLE_TEST_TOPIC2), + SubscriptionName.of(projectName, SAMPLE_TEST_SUBSCRIPTION3), + TopicName.of(projectName, SAMPLE_TEST_TOPIC2), PushConfig.getDefaultInstance(), 10); } @@ -147,7 +150,7 @@ static void cleanupPubsubClients() { } @BeforeEach - void initializeAppUrl() throws IOException { + void initializeAppUrl() { this.appUrl = "http://localhost:" + this.port; } @@ -233,7 +236,7 @@ private void createTopic(String topicName) { UriComponentsBuilder.fromHttpUrl(this.appUrl + "/createTopic") .queryParam("topicName", topicName) .toUriString(); - ResponseEntity<String> response = this.testRestTemplate.postForEntity(url, null, String.class); + this.testRestTemplate.postForEntity(url, null, String.class); String projectTopicName = ProjectTopicName.format(projectName, topicName); await() @@ -258,6 +261,7 @@ private void deleteTopic(String topicName) { .untilAsserted( () -> { List<String> projectTopics = getTopicNamesFromProject(); + assertThat(projectTopics).isNotNull(); assertThat(projectTopics).doesNotContain(projectTopicName); }); } @@ -293,6 +297,7 @@ private void deleteSubscription(String subscriptionName) { .untilAsserted( () -> { List<String> subscriptions = getSubscriptionNamesFromProject(); + assertThat(subscriptions).isNotNull(); assertThat(subscriptions).doesNotContain(projectSubscriptionName); }); } diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-dead-letter-sample/README.adoc b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-dead-letter-sample/README.adoc index dcce1044ee..7728fa759b 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-dead-letter-sample/README.adoc +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-dead-letter-sample/README.adoc @@ -2,7 +2,7 @@ This code sample demonstrates how to use the Spring Cloud Stream binder for Google Cloud Pub/Sub with Dead Letter Topics. -The sample app prompts a user for a message and user name. +The sample app prompts a user for a message and username. That data is added to a `UserMessage` object, together with the time of message creation, and is sent through Google Cloud Pub/Sub to a sink that `nack()`s the message. Pub/Sub will attempt to redeliver the message up to maximum number of retries before routing it to the configured dead letter topic.\ This sample app also listens to this dead letter topic, and logs any messages received. @@ -30,7 +30,7 @@ The topics will be created in your account if they do not already exist. (spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-dead-letter-sample) to get the app running. 7. Browse to `localhost:8080` or use the `Web Preview` button in Cloud Shell to preview the app on port 8080, -and type in a message, a user name, and press the `Post it!` button. +and type in a message, a username, and press the `Post it!` button. 8. Verify in your app's logs that a similar message was posted: + diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-dead-letter-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-dead-letter-sample/pom.xml index 67789757c2..62c6e5edcc 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-dead-letter-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-dead-letter-sample/pom.xml @@ -6,7 +6,7 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-samples</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-dead-letter-sample/src/main/java/com/example/SourceExample.java b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-dead-letter-sample/src/main/java/com/example/SourceExample.java index a7b043bca0..48d2e9f830 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-dead-letter-sample/src/main/java/com/example/SourceExample.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-dead-letter-sample/src/main/java/com/example/SourceExample.java @@ -20,7 +20,6 @@ import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.bind.annotation.PostMapping; @@ -35,7 +34,11 @@ public class SourceExample { private final Logger log = LoggerFactory.getLogger(this.getClass()); - @Autowired private Sinks.Many<UserMessage> postOffice; + private final Sinks.Many<UserMessage> postOffice; + + public SourceExample(Sinks.Many<UserMessage> postOffice) { + this.postOffice = postOffice; + } @PostMapping("/newMessage") public UserMessage sendMessage( diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-dead-letter-sample/src/main/resources/application.properties b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-dead-letter-sample/src/main/resources/application.properties index c613071ac4..40f6c91201 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-dead-letter-sample/src/main/resources/application.properties +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-dead-letter-sample/src/main/resources/application.properties @@ -4,7 +4,7 @@ spring.cloud.function.definition=generateUserMessages;logUserMessage;deadLetterM # The application's Source sends messages sent through the generateUserMessages method to this topic. spring.cloud.stream.bindings.generateUserMessages-out-0.destination=my-main-topic -# The applications' Sink receives messages throug the logUserMessage Consumer. The anonymous subscriber is configured to +# The applications' Sink receives messages through the logUserMessage Consumer. The anonymous subscriber is configured to # send any unprocessable messages to the dead letter topic after a set number of retry attempts. spring.cloud.stream.bindings.logUserMessage-in-0.destination=my-main-topic spring.cloud.stream.gcp.pubsub.bindings.logUserMessage-in-0.consumer.deadLetterPolicy.deadLetterTopic=my-dead-letter-topic @@ -16,8 +16,6 @@ spring.cloud.stream.gcp.pubsub.bindings.logUserMessage-in-0.consumer.ackMode=MAN # The application also subscribes to the dead letter topic to show that it's just another normal topic. spring.cloud.stream.bindings.deadLetterMessages-in-0.destination=my-dead-letter-topic -spring.cloud.stream.gcp.pubsub.default.consumer.auto-create-resources=true - #spring.cloud.gcp.project-id=[YOUR_GCP_PROJECT_ID] #spring.cloud.gcp.credentials.location=file:[LOCAL_PATH_TO_CREDENTIALS] diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-functional-sample/README.adoc b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-functional-sample/README.adoc index ee3eabdb84..4f0bd853bf 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-functional-sample/README.adoc +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-functional-sample/README.adoc @@ -1,6 +1,6 @@ = Spring Framework on Google Cloud Stream Binder for Pub/Sub Functional Code Sample -This code sample demonstrates how to use the Spring Cloud Stream binder for Google Cloud Pub/Sub. For the legacy annotation-based style, see link:../spring-cloud-gcp-pubsub-stream-sample[this sample]. +This code sample demonstrates how to use the Spring Cloud Stream binder for Google Cloud Pub/Sub. The sample consists of two applications, a source (`spring-cloud-gcp-pubsub-stream-functional-sample-source`) and a sink (`spring-cloud-gcp-pubsub-stream-functional-sample-sink`). The source collects a user name and a message through a web form, adds the data along with the time of message creation to a `UserMessage` object, and sends it through Google Cloud Pub/Sub. The sink application simply logs the message. diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-functional-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-functional-sample/pom.xml index 84109366fc..497d7daaa2 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-functional-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-functional-sample/pom.xml @@ -5,7 +5,7 @@ <parent> <artifactId>spring-cloud-gcp-samples</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-functional-sample/spring-cloud-gcp-pubsub-stream-functional-sample-sink/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-functional-sample/spring-cloud-gcp-pubsub-stream-functional-sample-sink/pom.xml index 1886fa4e45..0731d71ea5 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-functional-sample/spring-cloud-gcp-pubsub-stream-functional-sample-sink/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-functional-sample/spring-cloud-gcp-pubsub-stream-functional-sample-sink/pom.xml @@ -6,7 +6,7 @@ <!-- Your own application should inherit from spring-boot-starter-parent --> <artifactId>spring-cloud-gcp-pubsub-stream-functional-sample</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-functional-sample/spring-cloud-gcp-pubsub-stream-functional-sample-source/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-functional-sample/spring-cloud-gcp-pubsub-stream-functional-sample-source/pom.xml index 3a634bc200..82a631bb8c 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-functional-sample/spring-cloud-gcp-pubsub-stream-functional-sample-source/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-functional-sample/spring-cloud-gcp-pubsub-stream-functional-sample-source/pom.xml @@ -5,7 +5,7 @@ <parent> <artifactId>spring-cloud-gcp-pubsub-stream-functional-sample</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-functional-sample/spring-cloud-gcp-pubsub-stream-functional-sample-test/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-functional-sample/spring-cloud-gcp-pubsub-stream-functional-sample-test/pom.xml index fffa84b912..5d60946a7c 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-functional-sample/spring-cloud-gcp-pubsub-stream-functional-sample-test/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-functional-sample/spring-cloud-gcp-pubsub-stream-functional-sample-test/pom.xml @@ -5,7 +5,7 @@ <parent> <artifactId>spring-cloud-gcp-pubsub-stream-functional-sample</artifactId> <groupId>com.google.cloud</groupId> - <version>3.4.3-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> + <version>4.0.0-SNAPSHOT</version><!-- {x-version-update:spring-cloud-gcp:current} --> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/README.adoc b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/README.adoc deleted file mode 100644 index 558256e0bc..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/README.adoc +++ /dev/null @@ -1,50 +0,0 @@ -= Spring Framework on Google Cloud Stream for Pub/Sub Polling Code Sample - -This code sample demonstrates how to use the Spring Cloud Stream binder for Google Cloud Pub/Sub. -The sample app prompts a user for a message and their user name. -That data is added to a `UserMessage` object, together with the time of message creation, and is sent through Google Cloud Pub/Sub to a sink which simply logs the message. - -If the topic and subscription for the sink and source don't exist, the binder will automatically create them in Google Cloud Pub/Sub based on the values in link:src/main/resources/application.properties[application.properties]. - -The `SourceExample` in this sample is the same as in link:../spring-cloud-gcp-pubsub-stream-sample/[Streaming PubSub Sample]; there is nothing polling-specific in it. -`SinkExample`, however, describes a poll-style interaction with a Pub/Sub subscription. - -The following code, combined with `@EnableScheduling` annotation in `PubSubBinderApplication`, triggers retrieval of a message every second. -Note that `destIn` bean is provided by Spring Cloud Stream, using the _spring-cloud-gcp-pubsub-stream-binder_ dependency for the bean implementation. - -[source,java] ----- -@Scheduled(fixedRate = 1000) -public void poller() { - - destIn.poll(this.messageHandler, ParameterizedTypeReference.forType(UserMessage.class)); -} ----- - - -== Running the code sample - -image:http://gstatic.com/cloudssh/images/open-btn.svg[link=https://ssh.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https%3A%2F%2Fgithub.com%2FGoogleCloudPlatform%2Fspring-cloud-gcp&cloudshell_open_in_editor=spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/README.adoc] - -1. Configure your credentials and project ID by following link:../../docs/src/main/asciidoc/core.adoc#project-id[these instructions]. -+ -Alternatively, if you have the https://cloud.google.com/sdk/[Google Cloud SDK] installed and initialized, and are logged in with https://developers.google.com/identity/protocols/application-default-credentials[application default credentials], Spring will auto-discover those parameters for you. - -2. Set your project ID using the `spring.cloud.gcp.project-id` property in link:src/main/resources/application.properties[application.properties] or use the `gcloud config set project [PROJECT_ID]` Cloud SDK command. - -3. Set the topic and subscription name in the link:src/main/resources/application.properties[application.properties] file. -+ -If you're relying on the binder automatically creating the topic and subscription for you, they need to have the same name so that the messages are properly routed. -+ -Alternatively, you can manually create the topic and subscription from the https://console.cloud.google.com/cloudpubsub[Google Cloud Console]. - -4. Run `$ mvn clean install` from the root directory of the project. - -5. Run `$ mvn clean spring-boot:run` in the root of the code sample to get the app running. - -6. Browse to `localhost:8080` or use the `Web Preview` feature in Cloud Shell to preview the app on port 8080, -type in a message and a user name and press the `Post it!` button. - -7. Verify in your app's logs that a similar message was posted: -+ -`New message received from [USERNAME] via polling: [MESSAGE] at [TIME_MESSAGE_SENT]` diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/main/java/com/example/PollableSink.java b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/main/java/com/example/PollableSink.java deleted file mode 100644 index c04a2c9e71..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/main/java/com/example/PollableSink.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2017-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example; - -import org.springframework.cloud.stream.annotation.Input; -import org.springframework.cloud.stream.binder.PollableMessageSource; - -/** - * Binds "input" stream to a pollable channel. - * - * @since 1.2 - */ -public interface PollableSink { - - @Input("input") - PollableMessageSource input(); -} diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/main/java/com/example/PubSubBinderApplication.java b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/main/java/com/example/PubSubBinderApplication.java deleted file mode 100644 index 87283a9c54..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/main/java/com/example/PubSubBinderApplication.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2017-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.scheduling.annotation.EnableScheduling; - -/** - * Sample application for a binder. - * - * @since 1.2 - */ -@SpringBootApplication -@EnableScheduling -public class PubSubBinderApplication { - - public static void main(String[] args) { - SpringApplication.run(PubSubBinderApplication.class, args); - } -} diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/main/java/com/example/SinkExample.java b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/main/java/com/example/SinkExample.java deleted file mode 100644 index 8090e0839e..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/main/java/com/example/SinkExample.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2017-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example; - -import com.google.cloud.spring.pubsub.support.AcknowledgeablePubsubMessage; -import com.google.cloud.spring.pubsub.support.GcpPubSubHeaders; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.stream.annotation.EnableBinding; -import org.springframework.cloud.stream.binder.PollableMessageSource; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHandler; -import org.springframework.scheduling.annotation.Scheduled; - -/** - * Example of a sink for the sample app. - * - * @since 1.2 - */ -@EnableBinding(PollableSink.class) -public class SinkExample { - - private static final Log LOGGER = LogFactory.getLog(SinkExample.class); - - @Autowired PollableMessageSource destIn; - - PolledMessageHandler messageHandler = new PolledMessageHandler(); - - @Scheduled(fixedRate = 1000) - public void poller() { - - destIn.poll(this.messageHandler, ParameterizedTypeReference.forType(UserMessage.class)); - } - - static class PolledMessageHandler implements MessageHandler { - @Override - public void handleMessage(Message<?> message) { - AcknowledgeablePubsubMessage ackableMessage = - (AcknowledgeablePubsubMessage) - message.getHeaders().get(GcpPubSubHeaders.ORIGINAL_MESSAGE); - ackableMessage.ack(); - - UserMessage userMessage = (UserMessage) message.getPayload(); - LOGGER.info( - "New message received from " - + userMessage.getUsername() - + " via polling: " - + userMessage.getBody() - + " at " - + userMessage.getCreatedAt()); - } - } -} diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/main/java/com/example/SourceExample.java b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/main/java/com/example/SourceExample.java deleted file mode 100644 index d3f2f55078..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/main/java/com/example/SourceExample.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2017-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example; - -import java.time.LocalDateTime; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.stream.annotation.EnableBinding; -import org.springframework.cloud.stream.messaging.Source; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -/** - * A sample source application that constructs a {@link UserMessage} object based on a HTTP request, - * and sends it to a PubSub topic. - * - * @since 1.2 - */ -@EnableBinding(Source.class) -@RestController -public class SourceExample { - - @Autowired private Source source; - - @PostMapping("/newMessage") - public UserMessage sendMessage( - @RequestParam("messageBody") String messageBody, @RequestParam("username") String username) { - - UserMessage userMessage = new UserMessage(messageBody, username, LocalDateTime.now()); - this.source.output().send(new GenericMessage<>(userMessage)); - return userMessage; - } -} diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/main/java/com/example/UserMessage.java b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/main/java/com/example/UserMessage.java deleted file mode 100644 index b77136af76..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/main/java/com/example/UserMessage.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2017-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example; - -import java.time.LocalDateTime; - -/** - * A user message for the sample app. - * - * @since 1.2 - */ -public class UserMessage { - - private String body; - - private String username; - - private LocalDateTime createdAt; - - public UserMessage() {} - - public UserMessage(String body, String username, LocalDateTime createdAt) { - this.body = body; - this.username = username; - this.createdAt = createdAt; - } - - public String getBody() { - return this.body; - } - - public String getUsername() { - return this.username; - } - - public LocalDateTime getCreatedAt() { - return this.createdAt; - } -} diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/main/resources/application.properties b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/main/resources/application.properties deleted file mode 100644 index 364dec203e..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/main/resources/application.properties +++ /dev/null @@ -1,8 +0,0 @@ -spring.cloud.stream.bindings.input.destination=[PUBSUB_TOPIC_NAME] -spring.cloud.stream.bindings.output.destination=[PUBSUB_TOPIC_NAME] - -# If group is specified, the Pub/Sub subscription name will be [PUBSUB_TOPIC_NAME].[PUBSUB_GROUP_NAME] -#spring.cloud.stream.bindings.input.group=[PUBSUB_GROUP_NAME] - -#spring.cloud.gcp.project-id=[YOUR_GCP_PROJECT_ID] -#spring.cloud.gcp.credentials.location=file:[LOCAL_PATH_TO_CREDENTIALS] diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/main/resources/static/index.html b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/main/resources/static/index.html deleted file mode 100644 index 9dd6b615e4..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/main/resources/static/index.html +++ /dev/null @@ -1,15 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="UTF-8"> - <title>Spring Cloud GCP Pub/Sub Stream Binder Code Sample - - - New message:
-

- Message body:
- Username:
- -
- - diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/test/java/com/example/SampleAppIntegrationTest.java b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/test/java/com/example/SampleAppIntegrationTest.java deleted file mode 100644 index f4081ff720..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/test/java/com/example/SampleAppIntegrationTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2017-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; -import org.awaitility.Awaitility; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfSystemProperty; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.system.CapturedOutput; -import org.springframework.boot.test.system.OutputCaptureExtension; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; - -/** - * These tests verifies that the pubsub-polling-binder-sample works. - * - * @since 1.2 - */ -// Please use "-Dit.pubsub=true" to enable the tests -@EnabledIfSystemProperty(named = "it.pubsub", matches = "true") -@ExtendWith(OutputCaptureExtension.class) -@ExtendWith(SpringExtension.class) -@SpringBootTest( - webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - properties = { - "spring.cloud.stream.bindings.input.destination=sub1", - "spring.cloud.stream.bindings.output.destination=sub1" - }) -@DirtiesContext -class SampleAppIntegrationTest { - - @Autowired private TestRestTemplate restTemplate; - - @Test - void testSample(CapturedOutput capturedOutput) throws Exception { - MultiValueMap map = new LinkedMultiValueMap<>(); - String message = "test message " + UUID.randomUUID(); - - map.add("messageBody", message); - map.add("username", "testUserName"); - - this.restTemplate.postForObject("/newMessage", map, String.class); - - Callable logCheck = - () -> - capturedOutput - .toString() - .contains("New message received from testUserName via polling: " + message); - Awaitility.await().atMost(60, TimeUnit.SECONDS).until(logCheck); - - assertThat(logCheck.call()).isTrue(); - } -} diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/test/resources/logback-test.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/test/resources/logback-test.xml deleted file mode 100644 index 5535de3c96..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-polling-sample/src/test/resources/logback-test.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/README.adoc b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/README.adoc deleted file mode 100644 index 4fdb7125da..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/README.adoc +++ /dev/null @@ -1,40 +0,0 @@ -= Spring Framework on Google Cloud Stream for Pub/Sub Code Sample - -This code sample demonstrates how to use the Spring Cloud Stream binder for Google Cloud Pub/Sub in the legacy (annotation-based), Spring Cloud Stream style. For the modern (functional) style, check out link:../spring-cloud-gcp-pubsub-stream-functional-sample[this sample]. - -The sample app prompts a user for a message and their user name. -That data is added to a `UserMessage` object, together with the time of message creation, and is sent through Google Cloud Pub/Sub to a sink which simply logs the message. - -Additionally, you may specify the setting `throwError` through the app. -If set to true, this will trigger an exception in the message handler and demonstrate how messages will be forwarded to the error channel. - -If the topic for the sink and source does not exist, the binder will automatically create them in Google Cloud Pub/Sub based on the values in link:src/main/resources/application.properties[application.properties]. - -If the group is not specified, an anonymous subscription with the name `anonymous..` will be generated. - -== Running the code sample - -image:http://gstatic.com/cloudssh/images/open-btn.svg[link=https://ssh.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https%3A%2F%2Fgithub.com%2FGoogleCloudPlatform%2Fspring-cloud-gcp&cloudshell_open_in_editor=spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/README.adoc] - -1. Configure your credentials and project ID by following link:../../docs/src/main/asciidoc/core.adoc#project-id[these instructions]. -+ -Alternatively, if you have the https://cloud.google.com/sdk/[Google Cloud SDK] installed and initialized, and are logged in with https://developers.google.com/identity/protocols/application-default-credentials[application default credentials], Spring will auto-discover those parameters for you. - -2. Set your project ID using the `spring.cloud.gcp.project-id` property in link:src/main/resources/application.properties[application.properties] or use the `gcloud config set project [PROJECT_ID]` Cloud SDK command. - -3. In the link:src/main/resources/application.properties[application.properties] file, a topic and group name is already preconfigured for you. -The topic will be created in your account if it does not already exist. -+ -Setting the topic name and group allows you to configure error handling for your Pub/Sub binder. -An error handler is configured in link:src/main/java/com/example/SinkExample.java[SinkExample.java]. - -4. Run `$ mvn clean install` from the root directory of the project. - -5. Run `$ mvn clean spring-boot:run` in the root of the code sample to get the app running. - -6. Browse to `localhost:8080` or use the `Web Preview` feature in Cloud Shell to preview the app on port 8080, -type in a message and a user name and press the `Post it!` button. - -7. Verify in your app's logs that a similar message was posted: -+ -`New message received from [USERNAME] at [TIME_MESSAGE_SENT]: [MESSAGE]` diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/main/java/com/example/PubSubBinderApplication.java b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/main/java/com/example/PubSubBinderApplication.java deleted file mode 100644 index 3d03fc9853..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/main/java/com/example/PubSubBinderApplication.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2017-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -/** Sample application for a binder. */ -@SpringBootApplication -public class PubSubBinderApplication { - - public static void main(String[] args) { - SpringApplication.run(PubSubBinderApplication.class, args); - } -} diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/main/java/com/example/SinkExample.java b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/main/java/com/example/SinkExample.java deleted file mode 100644 index 57164ae262..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/main/java/com/example/SinkExample.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2017-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.stream.annotation.EnableBinding; -import org.springframework.cloud.stream.annotation.StreamListener; -import org.springframework.cloud.stream.messaging.Sink; -import org.springframework.integration.annotation.ServiceActivator; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessagingException; - -/** Example of a sink for the sample app. */ -@EnableBinding(Sink.class) -public class SinkExample { - - private static final Log LOGGER = LogFactory.getLog(SinkExample.class); - - @StreamListener(Sink.INPUT) - public void handleMessage(UserMessage userMessage) { - if (userMessage.isThrowError()) { - throw new RuntimeException("An error was triggered in the message handler!"); - } - - LOGGER.info( - "New message received from " - + userMessage.getUsername() - + ": " - + userMessage.getBody() - + " at " - + userMessage.getCreatedAt()); - } - - // Note that the error `inputChannel` is formatted as [Pub/Sub subscription name].errors - // or the equivalent of [Pub/Sub topic name].[group name].errors. If you change the topic name in - // `application.properties`, you will also have to change the `inputChannel` below. - @ServiceActivator(inputChannel = "my-topic.my-group.errors") - public void error(Message message) { - LOGGER.error("The message that was sent is now processed by the error handler."); - LOGGER.error("Failed message: " + message.getPayload().getFailedMessage()); - } -} diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/main/java/com/example/SourceExample.java b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/main/java/com/example/SourceExample.java deleted file mode 100644 index d20d8b4326..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/main/java/com/example/SourceExample.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2017-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example; - -import java.time.LocalDateTime; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.stream.annotation.EnableBinding; -import org.springframework.cloud.stream.messaging.Source; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -/** An example source for the sample app. */ -@EnableBinding(Source.class) -@RestController -public class SourceExample { - - @Autowired private Source source; - - @PostMapping("/newMessage") - public UserMessage sendMessage( - @RequestParam("messageBody") String messageBody, - @RequestParam("username") String username, - @RequestParam("throwError") boolean shouldThrowError) { - UserMessage userMessage = - new UserMessage(messageBody, username, LocalDateTime.now(), shouldThrowError); - this.source.output().send(new GenericMessage<>(userMessage)); - return userMessage; - } -} diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/main/java/com/example/UserMessage.java b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/main/java/com/example/UserMessage.java deleted file mode 100644 index 29d1eb657b..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/main/java/com/example/UserMessage.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2017-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example; - -import java.time.LocalDateTime; - -/** A user message for the sample app. */ -public class UserMessage { - - private String body; - - private String username; - - private LocalDateTime createdAt; - - private boolean throwError; - - public UserMessage() {} - - public UserMessage(String body, String username, LocalDateTime createdAt, boolean throwError) { - this.body = body; - this.username = username; - this.createdAt = createdAt; - this.throwError = throwError; - } - - public String getBody() { - return this.body; - } - - public void setBody(String body) { - this.body = body; - } - - public String getUsername() { - return this.username; - } - - public void setUsername(String username) { - this.username = username; - } - - public LocalDateTime getCreatedAt() { - return this.createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public boolean isThrowError() { - return throwError; - } - - public void setThrowError(boolean throwError) { - this.throwError = throwError; - } -} diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/main/resources/application.properties b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/main/resources/application.properties deleted file mode 100644 index e8bb5d9d7c..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/main/resources/application.properties +++ /dev/null @@ -1,13 +0,0 @@ -spring.cloud.stream.bindings.input.destination=my-topic -spring.cloud.stream.bindings.output.destination=my-topic - -# If group is specified, the Pub/Sub subscription name will be [PUBSUB_TOPIC_NAME].[PUBSUB_GROUP_NAME] -spring.cloud.stream.bindings.input.group=my-group - -# Alternatively, you can use a custom, pre-existing subscription. -#spring.cloud.stream.bindings.input.customSubscription=my-custom-subscription -#spring.cloud.stream.bindings.input.customSubscription=projects/my-project/subscriptions/my-other-custom-subscription - -#spring.cloud.gcp.project-id=[YOUR_GCP_PROJECT_ID] -#spring.cloud.gcp.credentials.location=file:[LOCAL_PATH_TO_CREDENTIALS] - diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/main/resources/static/index.html b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/main/resources/static/index.html deleted file mode 100644 index b76e4a6cbd..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/main/resources/static/index.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - Spring Cloud GCP Pub/Sub Stream Binder Code Sample - - - New message:
-
- Message body:
- Username:
- - Throw Error? (triggers the error handler) - -
- - -
- - diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/test/java/com/example/PubSubBinderSampleAppIntegrationTest.java b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/test/java/com/example/PubSubBinderSampleAppIntegrationTest.java deleted file mode 100644 index f273e455b6..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/test/java/com/example/PubSubBinderSampleAppIntegrationTest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2017-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; - -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfSystemProperty; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.system.CapturedOutput; -import org.springframework.boot.test.system.OutputCaptureExtension; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; - -/** - * These tests verifies that the pubsub-binder-sample works. - * - * @since 1.1 - */ -@EnabledIfSystemProperty(named = "it.pubsub", matches = "true") -@ExtendWith(SpringExtension.class) -@ExtendWith(OutputCaptureExtension.class) -@SpringBootTest( - webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - properties = { - "spring.cloud.stream.bindings.input.destination=my-topic", - "spring.cloud.stream.bindings.output.destination=my-topic", - "spring.cloud.stream.bindings.input.group=my-group" - }) -@DirtiesContext -class PubSubBinderSampleAppIntegrationTest { - - @Autowired private TestRestTemplate restTemplate; - - @Test - void testSample_successfulMessage(CapturedOutput capturedOutput) throws Exception { - MultiValueMap map = new LinkedMultiValueMap<>(); - String message = "test message " + UUID.randomUUID(); - - map.add("messageBody", message); - map.add("username", "testUserName"); - map.add("throwError", false); - - this.restTemplate.postForObject("/newMessage", map, String.class); - - await() - .atMost(20, TimeUnit.SECONDS) - .untilAsserted( - () -> - assertThat(capturedOutput.toString()) - .contains("New message received from testUserName: " + message + " at ")); - } - - @Test - void testSample_error(CapturedOutput capturedOutput) throws Exception { - MultiValueMap map = new LinkedMultiValueMap<>(); - String message = "test message " + UUID.randomUUID(); - - map.add("messageBody", message); - map.add("username", "testUserName"); - map.add("throwError", true); - - this.restTemplate.postForObject("/newMessage", map, String.class); - - await() - .atMost(20, TimeUnit.SECONDS) - .untilAsserted( - () -> - assertThat(capturedOutput.toString()) - .contains("The message that was sent is now processed by the error handler.")); - } -} diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/test/resources/logback-test.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/test/resources/logback-test.xml deleted file mode 100644 index 5535de3c96..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-pubsub-stream-sample/src/test/resources/logback-test.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-secretmanager-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-secretmanager-sample/pom.xml index b3cafbd81c..b39a05241d 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-secretmanager-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-secretmanager-sample/pom.xml @@ -7,7 +7,7 @@ spring-cloud-gcp-samples com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-secretmanager-sample/src/main/resources/bootstrap.properties b/spring-cloud-gcp-samples/spring-cloud-gcp-secretmanager-sample/src/main/resources/bootstrap.properties deleted file mode 100644 index 502f0585f0..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-secretmanager-sample/src/main/resources/bootstrap.properties +++ /dev/null @@ -1,7 +0,0 @@ -# Configuration-level settings for Secret Manager must be specified in a bootstrap.properties file because -# Secret Manager secrets are resolved before application.properties is resolved by Spring. - -# spring.cloud.gcp.secretmanager.enabled= (this is true by default when the starter is added.) -# spring.cloud.gcp.secretmanager.project-id= -# spring.cloud.gcp.secretmanager.credentials.location= -spring.cloud.gcp.secretmanager.legacy=false diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-security-firebase-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-security-firebase-sample/pom.xml index 57d59886c2..9cf4515079 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-security-firebase-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-security-firebase-sample/pom.xml @@ -7,7 +7,7 @@ spring-cloud-gcp-samples com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-security-firebase-sample/src/main/java/com/example/SecurityConfiguration.java b/spring-cloud-gcp-samples/spring-cloud-gcp-security-firebase-sample/src/main/java/com/example/SecurityConfiguration.java index db2beaca48..6b177e9afe 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-security-firebase-sample/src/main/java/com/example/SecurityConfiguration.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-security-firebase-sample/src/main/java/com/example/SecurityConfiguration.java @@ -16,10 +16,11 @@ package com.example; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint; /** @@ -27,23 +28,25 @@ */ @Configuration @EnableWebSecurity -public class SecurityConfiguration extends WebSecurityConfigurerAdapter { +public class SecurityConfiguration { - @Override - protected void configure(HttpSecurity http) throws Exception { - http.authorizeRequests() - .antMatchers("/") + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.authorizeHttpRequests() + .requestMatchers("/") .permitAll() - .antMatchers("/css/**") + .requestMatchers("/css/**") .permitAll() - .antMatchers("/templates/**") + .requestMatchers("/templates/**") .permitAll() - .antMatchers("/answer") + .requestMatchers("/answer") .authenticated() .and() .oauth2ResourceServer() .jwt() .and() .authenticationEntryPoint(new Http403ForbiddenEntryPoint()); + + return http.build(); } } diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-security-iap-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-security-iap-sample/pom.xml index 2f952dec62..3e29cfedc4 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-security-iap-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-security-iap-sample/pom.xml @@ -3,7 +3,6 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - com.google.cloud spring-cloud-gcp-security-iap-sample Spring Framework on Google Cloud Code Sample - IAP Spring Cloud IAP Security Sample @@ -12,7 +11,7 @@ spring-cloud-gcp-samples com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-security-iap-sample/src/main/java/com/example/ExampleController.java b/spring-cloud-gcp-samples/spring-cloud-gcp-security-iap-sample/src/main/java/com/example/ExampleController.java index 6c1a89c802..494ea93ec3 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-security-iap-sample/src/main/java/com/example/ExampleController.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-security-iap-sample/src/main/java/com/example/ExampleController.java @@ -16,11 +16,11 @@ package com.example; +import jakarta.servlet.http.HttpServletRequest; import java.util.Collections; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; -import javax.servlet.http.HttpServletRequest; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.jwt.Jwt; diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-security-iap-sample/src/main/java/com/example/SecurityConfigurer.java b/spring-cloud-gcp-samples/spring-cloud-gcp-security-iap-sample/src/main/java/com/example/SecurityConfigurer.java index 69d04803ab..4455398452 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-security-iap-sample/src/main/java/com/example/SecurityConfigurer.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-security-iap-sample/src/main/java/com/example/SecurityConfigurer.java @@ -16,14 +16,15 @@ package com.example; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint; /** - * Sample custom {@link WebSecurityConfigurerAdapter} that applies OAuth Resource Server + * Sample custom {@link SecurityFilterChain} that applies OAuth Resource Server * pre-authentication, and rejects unauthenticated requests to a single page, {@code /topsecret}. * All other pages are unsecured. * @@ -35,16 +36,19 @@ */ @Configuration @EnableWebSecurity -public class SecurityConfigurer extends WebSecurityConfigurerAdapter { - @Override - protected void configure(HttpSecurity http) throws Exception { - http.authorizeRequests() - .antMatchers("/topsecret") +public class SecurityConfigurer { + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.authorizeHttpRequests() + .requestMatchers("/topsecret") .authenticated() .and() .oauth2ResourceServer() .jwt() .and() .authenticationEntryPoint(new Http403ForbiddenEntryPoint()); + + return http.build(); } } diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/README.adoc b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/README.adoc deleted file mode 100644 index b5eff6d4ba..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/README.adoc +++ /dev/null @@ -1,50 +0,0 @@ -= Spring Framework on Google Cloud MySQL R2DBC Sample - -This code sample demonstrates how to connect to a MySQL Google Cloud SQL instance using the link:../../spring-cloud-gcp-starters/spring-cloud-gcp-starter-sql-mysql-r2dbc[Spring Framework on Google Cloud MySQL R2DBC Starter]. - -You will create an instance, a database within the instance, populate the database and then query it. - -== Setup - -image:http://gstatic.com/cloudssh/images/open-btn.svg[link=https://ssh.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https%3A%2F%2Fgithub.com%2FGoogleCloudPlatform%2Fspring-cloud-gcp&cloudshell_open_in_editor=spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/README.adoc] - -1. Follow https://cloud.google.com/sql/docs/mysql/quickstart[these instructions] to set up a Google Cloud Project with billing enabled. - -2. Create a Google Cloud MySQL instance from the https://console.cloud.google.com/sql/instances[Google Cloud Console Cloud SQL page]. - -3. Still within the Google Cloud Console SQL page, create a new database in the instance from the "Databases" section. -If you decided to set a root password for your instance footnoteref:[note,Google Cloud SQL allows for creating MySQL instances without a password,while still remaining safe,as the connection is using Cloud SDK authentication and encrypted communication: see docs on https://cloud.google.com/sql/docs/mysql/sql-proxy[Cloud SQL proxy] and https://cloud.google.com/sql/docs/mysql/connect-external-app#java[the java socketFactory],which is used in spring-cloud-gcp to create the connection!], then don't forget to take note of your root password, as you will need it in the next step! - -4. Open the link:src/main/resources/application.properties[application.properties] file and replace the following: -- *[database-name]* with the name of the database you created in step 3. -- *[instance-connection-name]* with the instance name (in the format [GCP_PROJECT_ID]:[REGION]:[INSTANCE_NAME]) of the MySQL instance you created in step 1. -+ -If you would like to use a different user instead of the default (`root`), set the `spring.r2dbc.username` property appropriately. -+ -If you set a root password, add the `spring.r2dbc.password` property with the root password as the value. - -5. https://cloud.google.com/sdk/gcloud/reference/auth/application-default/login[If you are authenticated in the Cloud SDK], your credentials will be automatically found by the Spring Boot Starter for Google Cloud SQL. -+ -Alternatively, https://console.cloud.google.com/iam-admin/serviceaccounts[create a service account from the Google Cloud Console] and download its private key. -Then, uncomment the `spring.cloud.gcp.credentials.location` property in the link:src/main/resources/application.properties[application.properties] file and fill its value with the path to your service account private key on your local file system, prepended with `file:`. - -6. Run `$ mvn clean install` from the root directory of the project. - -== Running the application - -You can run the `SqlApplication` Spring Boot app by running the following command in the root directory of this sample app (spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-sample): - -`$ mvn spring-boot:run` - -The database will be populated based on the link:src/main/resources/schema.sql[schema.sql] and link:src/main/resources/data.sql[data.sql] files. - -When the application is up, navigate to http://localhost:8080/getTuples in your browser, or use the `Web Preview` -button in Cloud Shell to preview the app on port 8080. This will print the contents of the `users` table. - -== Deploy to App Engine Standard Environment - -If you have Cloud SDK installed, https://cloud.google.com/appengine/docs/standard/java11/testing-and-deploying-your-app[Maven App Engine Plug-in] can be used to deploy the application to App Engine Standard environment: - ----- -$ mvn clean package appengine:deploy -Dapp.deploy.projectId=$PROJECT_ID -Dapp.deploy.version=$VERSION ----- \ No newline at end of file diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/appengine/app.yaml b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/appengine/app.yaml deleted file mode 100644 index c6b97a33fd..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/appengine/app.yaml +++ /dev/null @@ -1,5 +0,0 @@ -runtime: java11 -instance_class: F1 - -env_variables: - JAVA_TOOL_OPTIONS: "-XX:MaxRAM=256m -XX:ActiveProcessorCount=2 -Xmx32m" \ No newline at end of file diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/java/com/example/SqlApplication.java b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/java/com/example/SqlApplication.java deleted file mode 100644 index c31ff1b21c..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/java/com/example/SqlApplication.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2021-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example; - -import io.r2dbc.spi.ConnectionFactory; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.core.io.ClassPathResource; -import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer; -import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator; - -@SpringBootApplication -public class SqlApplication { - - public static void main(String[] args) { - SpringApplication.run(SqlApplication.class, args); - } - - /** - * Populates Cloud SQL database table with data from data.sql and deletes the table after the - * application finishes running. - */ - @Bean - ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) { - ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer(); - initializer.setConnectionFactory(connectionFactory); - initializer.setDatabasePopulator( - new ResourceDatabasePopulator( - new ClassPathResource("schema.sql"), new ClassPathResource("data.sql"))); - initializer.setDatabaseCleaner( - new ResourceDatabasePopulator(new ClassPathResource("cleanup.sql"))); - return initializer; - } -} diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/java/com/example/User.java b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/java/com/example/User.java deleted file mode 100644 index a6c1e69c94..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/java/com/example/User.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2022-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example; - -import org.springframework.data.relational.core.mapping.Table; - -@Table("users") -public class User { - - private String email; - private String firstName; - private String lastName; - - public User(String email, String firstName, String lastName) { - this.email = email; - this.firstName = firstName; - this.lastName = lastName; - } - - public String getEmail() { - return email; - } - - public String getFirstName() { - return firstName; - } - - public String getLastName() { - return lastName; - } - - @Override - public String toString() { - return "[" + email + ", " + firstName + ", " + lastName + "]"; - } -} diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/java/com/example/WebController.java b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/java/com/example/WebController.java deleted file mode 100644 index bc591cb1e1..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/java/com/example/WebController.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2021-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example; - -import org.springframework.data.r2dbc.core.R2dbcEntityTemplate; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import reactor.core.publisher.Flux; - -/** - * Web app controller class for sample application. Contains a function that runs a query and - * displays the results. - */ -@RestController -public class WebController { - - private final R2dbcEntityTemplate template; - - public WebController(R2dbcEntityTemplate template) { - this.template = template; - } - - @GetMapping("/getTuples") - public Flux getTuples() { - return template.select(User.class).all().map(user -> user.toString()); - } -} diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/resources/application.properties b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/resources/application.properties deleted file mode 100644 index 228247d649..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/resources/application.properties +++ /dev/null @@ -1,7 +0,0 @@ -spring.cloud.gcp.sql.database-name=[database-name] -# Must be formatted in the form: [gcp-project-id]:[region]:[instance-name] -spring.cloud.gcp.sql.instance-connection-name=[instance-connection-name] -# Leave empty for root, uncomment and fill out if you specified a user -# spring.r2dbc.username= -# Uncomment if root password is specified -# spring.r2dbc.password= diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/resources/cleanup.sql b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/resources/cleanup.sql deleted file mode 100644 index 365a210755..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/resources/cleanup.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE IF EXISTS users; \ No newline at end of file diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/resources/data.sql b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/resources/data.sql deleted file mode 100644 index ff9001a36f..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/resources/data.sql +++ /dev/null @@ -1,4 +0,0 @@ -INSERT INTO users VALUES - ('luisao@example.com', 'Anderson', 'Silva'), - ('jonas@example.com', 'Jonas', 'Goncalves'), - ('fejsa@example.com', 'Ljubomir', 'Fejsa'); diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/resources/schema.sql b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/resources/schema.sql deleted file mode 100644 index 1ac8ef2b4b..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/main/resources/schema.sql +++ /dev/null @@ -1,5 +0,0 @@ -CREATE TABLE users ( - email VARCHAR(255), - first_name VARCHAR(255), - last_name VARCHAR(255), - PRIMARY KEY (email)); diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/test/java/com/example/SqlR2dbcMySqlSampleApplicationIntegrationTests.java b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/test/java/com/example/SqlR2dbcMySqlSampleApplicationIntegrationTests.java deleted file mode 100644 index 0a291e6928..0000000000 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-r2dbc-sample/src/test/java/com/example/SqlR2dbcMySqlSampleApplicationIntegrationTests.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2021-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfSystemProperty; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; - -/** Simple integration test to verify the SQL sample application with MySQL. */ -@SpringBootTest( - webEnvironment = WebEnvironment.RANDOM_PORT, - classes = {SqlApplication.class}) -@EnabledIfSystemProperty(named = "it.cloudsql", matches = "true") -public class SqlR2dbcMySqlSampleApplicationIntegrationTests { - - @Autowired private TestRestTemplate testRestTemplate; - - @Test - void testSqlRowsAccess() { - ResponseEntity result = - this.testRestTemplate.exchange( - "/getTuples", HttpMethod.GET, null, new ParameterizedTypeReference() {}); - - assertThat(result.getBody()) - .isEqualTo( - "[fejsa@example.com, Ljubomir, Fejsa][jonas@example.com, Jonas," - + " Goncalves][luisao@example.com, Anderson, Silva]"); - } -} diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-sample/pom.xml index 460d87761b..065cba1c41 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-sample/pom.xml @@ -6,7 +6,7 @@ spring-cloud-gcp-samples com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-r2dbc-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-r2dbc-sample/pom.xml index 7a4c1ed428..4c2637c2c7 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-r2dbc-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-r2dbc-sample/pom.xml @@ -6,7 +6,7 @@ spring-cloud-gcp-samples com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-r2dbc-sample/src/main/java/com/example/User.java b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-r2dbc-sample/src/main/java/com/example/User.java index a6c1e69c94..9be9f018ad 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-r2dbc-sample/src/main/java/com/example/User.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-r2dbc-sample/src/main/java/com/example/User.java @@ -21,9 +21,9 @@ @Table("users") public class User { - private String email; - private String firstName; - private String lastName; + private final String email; + private final String firstName; + private final String lastName; public User(String email, String firstName, String lastName) { this.email = email; diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-r2dbc-sample/src/main/java/com/example/WebController.java b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-r2dbc-sample/src/main/java/com/example/WebController.java index bc591cb1e1..bf74832852 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-r2dbc-sample/src/main/java/com/example/WebController.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-r2dbc-sample/src/main/java/com/example/WebController.java @@ -36,6 +36,6 @@ public WebController(R2dbcEntityTemplate template) { @GetMapping("/getTuples") public Flux getTuples() { - return template.select(User.class).all().map(user -> user.toString()); + return template.select(User.class).all().map(User::toString); } } diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-r2dbc-sample/src/test/java/com/example/SqlR2dbcPostgresSampleApplicationIntegrationTests.java b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-r2dbc-sample/src/test/java/com/example/SqlR2dbcPostgresSampleApplicationIntegrationTests.java index c2ef177ae1..f66527ec49 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-r2dbc-sample/src/test/java/com/example/SqlR2dbcPostgresSampleApplicationIntegrationTests.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-r2dbc-sample/src/test/java/com/example/SqlR2dbcPostgresSampleApplicationIntegrationTests.java @@ -38,7 +38,7 @@ "spring.r2dbc.password=test" }) @EnabledIfSystemProperty(named = "it.cloudsql", matches = "true") -public class SqlR2dbcPostgresSampleApplicationIntegrationTests { +class SqlR2dbcPostgresSampleApplicationIntegrationTests { @Autowired private TestRestTemplate testRestTemplate; @@ -46,7 +46,7 @@ public class SqlR2dbcPostgresSampleApplicationIntegrationTests { void testSqlRowsAccess() { ResponseEntity result = this.testRestTemplate.exchange( - "/getTuples", HttpMethod.GET, null, new ParameterizedTypeReference() {}); + "/getTuples", HttpMethod.GET, null, new ParameterizedTypeReference<>() {}); assertThat(result.getBody()) .isEqualTo( diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-sample/pom.xml index fba21b9581..0e47c613d8 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-sample/pom.xml @@ -6,7 +6,7 @@ spring-cloud-gcp-samples com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-storage-resource-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-storage-resource-sample/pom.xml index 8e81f1610b..5738d428f3 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-storage-resource-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-storage-resource-sample/pom.xml @@ -6,7 +6,7 @@ spring-cloud-gcp-samples com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/README.adoc b/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/README.adoc index 82c66080ef..23b4ace0ac 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/README.adoc +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/README.adoc @@ -4,7 +4,7 @@ This sample application demonstrates using the link:../../spring-cloud-gcp-start You can read about the Spring Boot Starter for Stackdriver Trace in more detail at the link:../../docs/src/main/asciidoc/trace.adoc[Spring Framework on Google Cloud Reference Document Stackdriver Trace section]. -Similar content is also available in the https://codelabs.developers.google.com/codelabs/cloud-spring-cloud-gcp-trace/index.html[Distributed tracing with Spring Cloud Sleuth] codelab. +Similar content is also available in the https://codelabs.developers.google.com/codelabs/cloud-spring-cloud-gcp-trace/index.html[Distributed tracing with Micrometer] codelab. == Setup & Configuration diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/pom.xml index ff3408d448..73586aa760 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/pom.xml @@ -3,7 +3,6 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - com.google.cloud spring-cloud-gcp-trace-sample Spring Framework on Google Cloud Code Sample - Trace @@ -11,7 +10,7 @@ spring-cloud-gcp-samples com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT @@ -82,7 +81,6 @@ grpc-google-cloud-trace-v1 test - diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/src/main/java/com/example/Application.java b/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/src/main/java/com/example/Application.java index 8be6b2ee78..4de0374b44 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/src/main/java/com/example/Application.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/src/main/java/com/example/Application.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 the original author or authors. + * Copyright 2017-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,30 +23,32 @@ import com.google.cloud.spring.pubsub.integration.outbound.PubSubMessageHandler; import com.google.cloud.spring.pubsub.support.BasicAcknowledgeablePubsubMessage; import com.google.cloud.spring.pubsub.support.GcpPubSubHeaders; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; +import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.channel.DirectChannel; +import org.springframework.lang.NonNull; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; import org.springframework.messaging.handler.annotation.Header; -import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** Sample spring boot application. */ -@SpringBootApplication -@Component +@AutoConfiguration +@SpringBootApplication(exclude = ZipkinAutoConfiguration.class) public class Application implements WebMvcConfigurer { private static final Logger LOGGER = LoggerFactory.getLogger(Application.class); @@ -56,18 +58,20 @@ public class Application implements WebMvcConfigurer { @Value("${sampleTopic}") private String sampleTopic; - @Autowired PubSubTemplate pubSubTemplate; + private final SpanCustomizer spanCustomizer; public static void main(String[] args) { SpringApplication.run(Application.class, args); } - @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); + public Application(SpanCustomizer spanCustomizer) { + this.spanCustomizer = spanCustomizer; } - @Autowired private SpanCustomizer spanCustomizer; + @Bean + public RestTemplate restTemplate(RestTemplateBuilder builder) { + return builder.build(); + } @Override public void addInterceptors(InterceptorRegistry registry) { @@ -75,8 +79,9 @@ public void addInterceptors(InterceptorRegistry registry) { new HandlerInterceptor() { @Override public boolean preHandle( - HttpServletRequest request, HttpServletResponse response, Object handler) - throws Exception { + @NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull Object handler) { spanCustomizer.tag("session-id", request.getSession().getId()); spanCustomizer.tag("environment", "QA"); @@ -119,6 +124,6 @@ public PubSubInboundChannelAdapter messageChannelAdapter( public void messageReceiver( String payload, @Header(GcpPubSubHeaders.ORIGINAL_MESSAGE) BasicAcknowledgeablePubsubMessage message) { - LOGGER.info("Message arrived! Payload: " + payload); + LOGGER.info("Message arrived! Payload: {}", payload); } } diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/src/main/java/com/example/ExampleController.java b/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/src/main/java/com/example/ExampleController.java index d87cc9d74d..4b40bfab1f 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/src/main/java/com/example/ExampleController.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/src/main/java/com/example/ExampleController.java @@ -18,16 +18,14 @@ import com.google.cloud.spring.pubsub.core.PubSubTemplate; import com.google.pubsub.v1.PubsubMessage; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -/** Sample REST Controller to demonstrate Spring Cloud Sleuth and Stackdriver Trace. */ @RestController public class ExampleController { @@ -38,10 +36,11 @@ public class ExampleController { private final WorkService workService; - @Autowired PubSubTemplate pubSubTemplate; + private final PubSubTemplate pubSubTemplate; - public ExampleController(WorkService workService) { + public ExampleController(WorkService workService, PubSubTemplate pubSubTemplate) { this.workService = workService; + this.pubSubTemplate = pubSubTemplate; } @GetMapping("/") @@ -64,7 +63,7 @@ public String meet() throws InterruptedException { } @RequestMapping("/pull") - public String pull() throws InterruptedException { + public String pull() { String result = "nothing"; PubsubMessage message = pubSubTemplate.pullNext(sampleSubscription); diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/src/main/java/com/example/WorkService.java b/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/src/main/java/com/example/WorkService.java index 09932b907e..3e69daf357 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/src/main/java/com/example/WorkService.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/src/main/java/com/example/WorkService.java @@ -18,11 +18,10 @@ import com.google.cloud.spring.pubsub.core.PubSubTemplate; import com.google.cloud.spring.pubsub.support.GcpPubSubHeaders; +import io.micrometer.observation.annotation.Observed; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.cloud.sleuth.annotation.NewSpan; import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; @@ -40,15 +39,20 @@ public class WorkService { private final RestTemplate restTemplate; - @Autowired private PubSubTemplate pubSubTemplate; + private final PubSubTemplate pubSubTemplate; - @Autowired private MessageChannel pubsubOutputChannel; + private final MessageChannel pubsubOutputChannel; - public WorkService(RestTemplate restTemplate) { + public WorkService( + RestTemplate restTemplate, + PubSubTemplate pubSubTemplate, + MessageChannel pubsubOutputChannel) { this.restTemplate = restTemplate; + this.pubSubTemplate = pubSubTemplate; + this.pubsubOutputChannel = pubsubOutputChannel; } - @NewSpan + @Observed(contextualName = "visit-meet-endpoint") public void visitMeetEndpoint(String meetUrl) { LOGGER.info("starting busy work"); for (int i = 0; i < 3; i++) { @@ -57,14 +61,14 @@ public void visitMeetEndpoint(String meetUrl) { LOGGER.info("finished busy work"); } - @NewSpan + @Observed(contextualName = "send-message-spring-integration") public void sendMessageSpringIntegration(String text) throws MessagingException { final Message message = MessageBuilder.withPayload(text).setHeader(GcpPubSubHeaders.TOPIC, sampleTopic).build(); pubsubOutputChannel.send(message); } - @NewSpan + @Observed(contextualName = "send-message-pub-sub-template") public void sendMessagePubSubTemplate(String text) throws MessagingException { pubSubTemplate.publish(sampleTopic, text); } diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/src/main/resources/application.properties b/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/src/main/resources/application.properties index c7675ced4c..6ff7321eb9 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/src/main/resources/application.properties +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/src/main/resources/application.properties @@ -1,18 +1,8 @@ # To send 100% of traces to Stackdriver Trace -spring.sleuth.sampler.probability=1.0 - -# To ignore some frequently used URL patterns that are not useful in trace -spring.sleuth.web.skipPattern=(^cleanup.*|.+favicon.*) - -# avoid "async" traces -spring.sleuth.scheduled.enabled=false +management.tracing.sampling.probability=1.0 # Enable Pub/Sub tracing using this property spring.cloud.gcp.trace.pubsub.enabled=true -# You should disable Spring Integration instrumentation by Sleuth as it's unnecessary when Pub/Sub tracing is enabled -spring.sleuth.integration.enabled=false - - sampleTopic = traceTopic sampleSubscription = traceSubscription \ No newline at end of file diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/src/test/java/com/example/TraceSampleApplicationIntegrationTests.java b/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/src/test/java/com/example/TraceSampleApplicationIntegrationTests.java index 1b7c9fd8b1..e4a855e06e 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/src/test/java/com/example/TraceSampleApplicationIntegrationTests.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-trace-sample/src/test/java/com/example/TraceSampleApplicationIntegrationTests.java @@ -45,7 +45,6 @@ import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -55,10 +54,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.actuate.observability.AutoConfigureObservability; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -73,6 +74,7 @@ @SpringBootTest( webEnvironment = WebEnvironment.RANDOM_PORT, classes = {Application.class}) +@AutoConfigureObservability class TraceSampleApplicationIntegrationTests { @DynamicPropertySource @@ -83,7 +85,8 @@ static void registerProperties(DynamicPropertyRegistry registry) { private final Logger log = LoggerFactory.getLogger(this.getClass()); - @LocalServerPort private int port; + @LocalServerPort + private int port; private static GcpProjectIdProvider projectIdProvider; @@ -91,9 +94,9 @@ static void registerProperties(DynamicPropertyRegistry registry) { private String url; - private static String SAMPLE_TOPIC = "traceTopic-" + UUID.randomUUID(); + private static final String SAMPLE_TOPIC = "traceTopic-" + UUID.randomUUID(); - private static String SAMPLE_SUBSCRIPTION = "traceSubscription-" + UUID.randomUUID(); + private static final String SAMPLE_SUBSCRIPTION = "traceSubscription-" + UUID.randomUUID(); private TestRestTemplate testRestTemplate; @@ -134,11 +137,11 @@ void setupTraceClient() throws IOException { // Create a new RestTemplate here because the auto-wired instance has built-in instrumentation // which interferes with us setting the 'x-cloud-trace-context' header. - this.testRestTemplate = new TestRestTemplate(); + this.testRestTemplate = new TestRestTemplate(new RestTemplateBuilder()); this.logClient = LoggingOptions.newBuilder() - .setProjectId(this.projectIdProvider.getProjectId()) + .setProjectId(projectIdProvider.getProjectId()) .setCredentials(this.credentialsProvider.getCredentials()) .build() .getService(); @@ -166,7 +169,7 @@ void testTracesAreLoggedCorrectly() { GetTraceRequest getTraceRequest = GetTraceRequest.newBuilder() - .setProjectId(this.projectIdProvider.getProjectId()) + .setProjectId(projectIdProvider.getProjectId()) .setTraceId(uuidString) .build(); @@ -174,9 +177,9 @@ void testTracesAreLoggedCorrectly() { String.format( "trace=projects/%s/traces/%s AND logName=projects/%s/logs/spring.log AND" + " timestamp>=\"%s\"", - this.projectIdProvider.getProjectId(), + projectIdProvider.getProjectId(), uuidString, - this.projectIdProvider.getProjectId(), + projectIdProvider.getProjectId(), startDateTime.toStringRfc3339()); await() @@ -193,30 +196,30 @@ void testTracesAreLoggedCorrectly() { + " with " + trace.getSpansCount() + " spans (" - + trace.getSpansList().stream() - .map(TraceSpan::getName) - .collect(Collectors.toList()) + + trace.getSpansList().stream().map(TraceSpan::getName).toList() + ")."); assertThat(trace.getTraceId()).isEqualTo(uuidString); // The 16 expected spans are: - // get /, visit-meet-endpoint, get, get /meet, get, get /meet, get, get /meet, + // http get /, visit-meet-endpoint, http get, http get /meet, http get, + // http get /meet, http get, http get /meet, // send-message-spring-integration, publish, send-message-pub-sub-template, publish, // next-message, on-message, next-message, on-message + assertThat(trace.getSpansCount()).isGreaterThanOrEqualTo(16); log.debug("Trace spans match."); // verify custom tags - assertThat(trace.getSpans(0).getLabelsMap().get("environment")).isEqualTo("QA"); + assertThat(trace.getSpans(0).getLabelsMap()).containsEntry("environment", "QA"); assertThat(trace.getSpans(0).getLabelsMap().get("session-id")).isNotNull(); log.debug("Trace labels match."); assertThat(trace.getSpansList().stream().map(TraceSpan::getName).distinct()) .containsExactly( - "get /", + "http get /", "visit-meet-endpoint", - "get", - "get /meet", + "http get", + "http get /meet", "send-message-spring-integration", "publish", "send-message-pub-sub-template", @@ -236,7 +239,7 @@ void testTracesAreLoggedCorrectly() { String wantTraceRegex = "projects/" - + this.projectIdProvider.getProjectId() + + projectIdProvider.getProjectId() + "/traces/([a-z0-9]){32}"; log.debug("Want trace " + wantTraceRegex + " and got " + le.getTrace()); assertThat(le.getTrace()).matches(wantTraceRegex); @@ -256,7 +259,7 @@ void testTracesAreLoggedCorrectly() { ((JsonPayload) logEntry.getPayload()) .getDataAsMap() .get("message")) - .collect(Collectors.toList()); + .toList(); log.info( "\n========================= [START OF LOG CONTENTS] =========================\n" diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-vision-api-sample/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-vision-api-sample/pom.xml index 12bde49355..c1b6c68b96 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-vision-api-sample/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-vision-api-sample/pom.xml @@ -6,7 +6,7 @@ spring-cloud-gcp-samples com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-vision-ocr-demo/README.adoc b/spring-cloud-gcp-samples/spring-cloud-gcp-vision-ocr-demo/README.adoc index 047116dcda..ba53952222 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-vision-ocr-demo/README.adoc +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-vision-ocr-demo/README.adoc @@ -32,7 +32,7 @@ public ModelAndView submitDocument(@RequestParam("documentUrl") String documentU GoogleStorageLocation outputLocation = GoogleStorageLocation.forFolder( outputBlobId.getBucket(), "ocr_results/" + documentLocation.getBlobName()); - ListenableFuture result = + CompletableFuture result = documentOcrTemplate.runOcrForDocument(documentLocation, outputLocation); ocrStatusReporter.registerFuture(documentLocation.uriString(), result); diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-vision-ocr-demo/pom.xml b/spring-cloud-gcp-samples/spring-cloud-gcp-vision-ocr-demo/pom.xml index 24ebf8f950..e5bf8c4f76 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-vision-ocr-demo/pom.xml +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-vision-ocr-demo/pom.xml @@ -6,7 +6,7 @@ spring-cloud-gcp-samples com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-vision-ocr-demo/src/main/java/com/example/OcrStatusReporter.java b/spring-cloud-gcp-samples/spring-cloud-gcp-vision-ocr-demo/src/main/java/com/example/OcrStatusReporter.java index c4d969abe5..5b42e9f4a7 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-vision-ocr-demo/src/main/java/com/example/OcrStatusReporter.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-vision-ocr-demo/src/main/java/com/example/OcrStatusReporter.java @@ -19,8 +19,8 @@ import com.google.cloud.spring.vision.DocumentOcrResultSet; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import org.springframework.util.concurrent.ListenableFuture; public class OcrStatusReporter { @@ -31,7 +31,7 @@ public OcrStatusReporter() { } public void registerFuture( - String documentPath, ListenableFuture resultFuture) { + String documentPath, CompletableFuture resultFuture) { pendingOcrOperations.put(documentPath, new OcrOperationStatus(documentPath, resultFuture)); } @@ -42,10 +42,10 @@ public Map getDocumentOcrStatuses() { public static final class OcrOperationStatus { final String gcsLocation; - final ListenableFuture ocrResultFuture; + final CompletableFuture ocrResultFuture; public OcrOperationStatus( - String gcsLocation, ListenableFuture ocrResultFuture) { + String gcsLocation, CompletableFuture ocrResultFuture) { this.gcsLocation = gcsLocation; this.ocrResultFuture = ocrResultFuture; } diff --git a/spring-cloud-gcp-samples/spring-cloud-gcp-vision-ocr-demo/src/main/java/com/example/WebController.java b/spring-cloud-gcp-samples/spring-cloud-gcp-vision-ocr-demo/src/main/java/com/example/WebController.java index aa9032589d..11a3111fc5 100644 --- a/spring-cloud-gcp-samples/spring-cloud-gcp-vision-ocr-demo/src/main/java/com/example/WebController.java +++ b/spring-cloud-gcp-samples/spring-cloud-gcp-vision-ocr-demo/src/main/java/com/example/WebController.java @@ -16,7 +16,6 @@ package com.example; -import com.google.api.client.util.ByteStreams; import com.google.cloud.WriteChannel; import com.google.cloud.spring.storage.GoogleStorageLocation; import com.google.cloud.spring.vision.DocumentOcrResultSet; @@ -26,16 +25,17 @@ import com.google.cloud.storage.Storage; import com.google.cloud.storage.Storage.BucketGetOption; import com.google.cloud.vision.v1.TextAnnotation; +import com.google.common.io.ByteStreams; import com.google.protobuf.InvalidProtocolBufferException; import java.io.IOException; import java.nio.channels.Channels; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.ui.ModelMap; -import org.springframework.util.concurrent.ListenableFuture; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -116,7 +116,7 @@ public ModelAndView submitDocument(@RequestParam("documentUrl") String documentU GoogleStorageLocation.forFolder( outputBlobId.getBucket(), "ocr_results/" + documentLocation.getBlobName()); - ListenableFuture result = + CompletableFuture result = documentOcrTemplate.runOcrForDocument(documentLocation, outputLocation); ocrStatusReporter.registerFuture(documentLocation.uriString(), result); @@ -145,13 +145,11 @@ private static String getFileType(Resource documentResource) { int extensionIdx = documentResource.getFilename().lastIndexOf("."); String fileType = documentResource.getFilename().substring(extensionIdx); - switch (fileType) { - case ".tif": - return "image/tiff"; - case ".pdf": - return "application/pdf"; - default: - throw new IllegalArgumentException("Does not support processing file type: " + fileType); - } + return switch (fileType) { + case ".tif" -> "image/tiff"; + case ".pdf" -> "application/pdf"; + default -> throw new IllegalArgumentException( + "Does not support processing file type: " + fileType); + }; } } diff --git a/spring-cloud-gcp-secretmanager/pom.xml b/spring-cloud-gcp-secretmanager/pom.xml index 471ce6245e..75a2b9cc42 100644 --- a/spring-cloud-gcp-secretmanager/pom.xml +++ b/spring-cloud-gcp-secretmanager/pom.xml @@ -5,7 +5,7 @@ spring-cloud-gcp com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 diff --git a/spring-cloud-gcp-security-firebase/pom.xml b/spring-cloud-gcp-security-firebase/pom.xml index 2ed947871d..cbaa9d4f82 100644 --- a/spring-cloud-gcp-security-firebase/pom.xml +++ b/spring-cloud-gcp-security-firebase/pom.xml @@ -6,10 +6,9 @@ spring-cloud-gcp com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT - spring-cloud-gcp-security-firebase Spring Framework on Google Cloud Module - Firebase Auth Integrate Firebase Authentication with Spring Security OAuth2 support @@ -35,7 +34,4 @@ test - - - diff --git a/spring-cloud-gcp-security-iap/pom.xml b/spring-cloud-gcp-security-iap/pom.xml index ca6ec290f1..0e6dd805fb 100644 --- a/spring-cloud-gcp-security-iap/pom.xml +++ b/spring-cloud-gcp-security-iap/pom.xml @@ -6,7 +6,7 @@ com.google.cloud spring-cloud-gcp - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT spring-cloud-gcp-security-iap diff --git a/spring-cloud-gcp-starters/pom.xml b/spring-cloud-gcp-starters/pom.xml index 5ed70d8ecf..656ae59df2 100644 --- a/spring-cloud-gcp-starters/pom.xml +++ b/spring-cloud-gcp-starters/pom.xml @@ -6,7 +6,7 @@ spring-cloud-gcp com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT pom Spring Framework on Google Cloud Starters @@ -34,7 +34,6 @@ spring-cloud-gcp-starter-metrics spring-cloud-gcp-starter-sql-mysql spring-cloud-gcp-starter-sql-postgresql - spring-cloud-gcp-starter-sql-mysql-r2dbc spring-cloud-gcp-starter-sql-postgres-r2dbc spring-cloud-gcp-starter-security-iap spring-cloud-gcp-starter-vision diff --git a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-bigquery/pom.xml b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-bigquery/pom.xml index 2fb5eabd18..5a2e213679 100644 --- a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-bigquery/pom.xml +++ b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-bigquery/pom.xml @@ -5,7 +5,7 @@ spring-cloud-gcp-starters com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 diff --git a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-bus-pubsub/pom.xml b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-bus-pubsub/pom.xml index 1c1958baf1..b0086ebe60 100644 --- a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-bus-pubsub/pom.xml +++ b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-bus-pubsub/pom.xml @@ -6,7 +6,7 @@ com.google.cloud spring-cloud-gcp-starters - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT spring-cloud-gcp-starter-bus-pubsub diff --git a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-cloudfoundry/pom.xml b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-cloudfoundry/pom.xml index fab1ca4782..7b88e2caeb 100644 --- a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-cloudfoundry/pom.xml +++ b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-cloudfoundry/pom.xml @@ -5,7 +5,7 @@ spring-cloud-gcp-starters com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 diff --git a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-config/pom.xml b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-config/pom.xml index 9c9d18dc6b..b39b1653b7 100644 --- a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-config/pom.xml +++ b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-config/pom.xml @@ -4,7 +4,7 @@ com.google.cloud spring-cloud-gcp-starters - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT spring-cloud-gcp-starter-config Spring Framework on Google Cloud Starter - Config diff --git a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-data-datastore/pom.xml b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-data-datastore/pom.xml index 08dc1e42ad..444da1e5bf 100644 --- a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-data-datastore/pom.xml +++ b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-data-datastore/pom.xml @@ -5,7 +5,7 @@ com.google.cloud spring-cloud-gcp-starters - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT spring-cloud-gcp-starter-data-datastore Spring Framework on Google Cloud Starter - Datastore diff --git a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-data-firestore/pom.xml b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-data-firestore/pom.xml index ec4bcbc7b2..cc598fae91 100644 --- a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-data-firestore/pom.xml +++ b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-data-firestore/pom.xml @@ -5,7 +5,7 @@ com.google.cloud spring-cloud-gcp-starters - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT spring-cloud-gcp-starter-data-firestore Spring Framework on Google Cloud Starter - Firestore diff --git a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-data-spanner/pom.xml b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-data-spanner/pom.xml index 17b0c71132..603637453a 100644 --- a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-data-spanner/pom.xml +++ b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-data-spanner/pom.xml @@ -5,7 +5,7 @@ com.google.cloud spring-cloud-gcp-starters - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT spring-cloud-gcp-starter-data-spanner Spring Framework on Google Cloud Starter - Spanner diff --git a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-firestore/pom.xml b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-firestore/pom.xml index 0b19b2873b..a9a8c82477 100644 --- a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-firestore/pom.xml +++ b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-firestore/pom.xml @@ -6,7 +6,7 @@ com.google.cloud spring-cloud-gcp-starters - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT spring-cloud-gcp-starter-firestore Spring Framework on Google Cloud Starter - Firestore diff --git a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-kms/pom.xml b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-kms/pom.xml index 593b46767d..537d8d6906 100644 --- a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-kms/pom.xml +++ b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-kms/pom.xml @@ -5,7 +5,7 @@ com.google.cloud spring-cloud-gcp-starters - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 diff --git a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-logging/pom.xml b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-logging/pom.xml index 3fe33215d6..10c69c86ca 100644 --- a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-logging/pom.xml +++ b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-logging/pom.xml @@ -5,7 +5,7 @@ spring-cloud-gcp-starters com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 diff --git a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-metrics/pom.xml b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-metrics/pom.xml index c04c10b37b..37f65ac214 100644 --- a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-metrics/pom.xml +++ b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-metrics/pom.xml @@ -5,7 +5,7 @@ com.google.cloud spring-cloud-gcp-starters - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 diff --git a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-pubsub/pom.xml b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-pubsub/pom.xml index 6792816c13..033b66ac52 100644 --- a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-pubsub/pom.xml +++ b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-pubsub/pom.xml @@ -4,7 +4,7 @@ com.google.cloud spring-cloud-gcp-starters - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT spring-cloud-gcp-starter-pubsub Spring Framework on Google Cloud Starter - Pub/Sub diff --git a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-secretmanager/pom.xml b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-secretmanager/pom.xml index 5ae6c016b4..d3e3b4bd7d 100644 --- a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-secretmanager/pom.xml +++ b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-secretmanager/pom.xml @@ -5,7 +5,7 @@ com.google.cloud spring-cloud-gcp-starters - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 @@ -25,11 +25,6 @@ spring-cloud-context - - org.springframework.cloud - spring-cloud-starter-bootstrap - - com.google.cloud spring-cloud-gcp-secretmanager diff --git a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-security-firebase/pom.xml b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-security-firebase/pom.xml index 1ef7d20579..8cfe2cbaff 100644 --- a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-security-firebase/pom.xml +++ b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-security-firebase/pom.xml @@ -5,7 +5,7 @@ spring-cloud-gcp-starters com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 diff --git a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-security-iap/pom.xml b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-security-iap/pom.xml index c3f0bb9dfe..74f4371c4c 100644 --- a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-security-iap/pom.xml +++ b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-security-iap/pom.xml @@ -6,7 +6,7 @@ spring-cloud-gcp-starters com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 diff --git a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-sql-mysql-r2dbc/.repo.metadata.json b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-sql-mysql-r2dbc/.repo.metadata.json deleted file mode 100644 index 6d971eaed8..0000000000 --- a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-sql-mysql-r2dbc/.repo.metadata.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "spring-cloud-gcp-starter-sql-mysql-r2dbc", - "name_pretty": "Spring Cloud GCP R2DBC Support for Cloud SQL - MySQL", - "client_documentation": "https://spring.io/projects/spring-cloud-gcp", - "api_description": "Provides support for MySQL databases in Google Cloud SQL using Spring R2DBC", - "release_level": "ga", - "language": "java", - "repo": "googlecloudplatform/spring-cloud-gcp", - "repo_short": "spring-cloud-gcp", - "distribution_name": "com.google.cloud:spring-cloud-gcp-starter-sql-mysql-r2dbc" -} diff --git a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-sql-mysql/pom.xml b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-sql-mysql/pom.xml index f98db20da1..3c045265b4 100644 --- a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-sql-mysql/pom.xml +++ b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-sql-mysql/pom.xml @@ -5,7 +5,7 @@ spring-cloud-gcp-starters com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 diff --git a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-sql-postgres-r2dbc/pom.xml b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-sql-postgres-r2dbc/pom.xml index 4e8aca6ba7..1486b48ddb 100644 --- a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-sql-postgres-r2dbc/pom.xml +++ b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-sql-postgres-r2dbc/pom.xml @@ -5,7 +5,7 @@ spring-cloud-gcp-starters com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 @@ -23,13 +23,13 @@ spring-boot-starter-data-r2dbc - + com.google.cloud.sql cloud-sql-connector-r2dbc-postgres - io.r2dbc + org.postgresql r2dbc-postgresql diff --git a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-sql-postgresql/pom.xml b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-sql-postgresql/pom.xml index b5fb047cca..8e78234430 100644 --- a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-sql-postgresql/pom.xml +++ b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-sql-postgresql/pom.xml @@ -5,7 +5,7 @@ spring-cloud-gcp-starters com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 diff --git a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-storage/pom.xml b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-storage/pom.xml index 650843b241..587ce7536a 100644 --- a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-storage/pom.xml +++ b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-storage/pom.xml @@ -5,7 +5,7 @@ com.google.cloud spring-cloud-gcp-starters - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT spring-cloud-gcp-starter-storage Spring Framework on Google Cloud Starter - Storage diff --git a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-trace/pom.xml b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-trace/pom.xml index fe0078fb08..4f026adb7d 100644 --- a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-trace/pom.xml +++ b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-trace/pom.xml @@ -5,7 +5,7 @@ spring-cloud-gcp-starters com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 @@ -19,12 +19,20 @@ spring-cloud-gcp-starter - org.springframework.cloud - spring-cloud-starter-sleuth + org.springframework + spring-aspects - org.springframework.cloud - spring-cloud-sleuth-brave + org.springframework.boot + spring-boot-starter-actuator + + + io.micrometer + micrometer-tracing-bridge-brave + + + io.zipkin.brave + brave-instrumentation-messaging io.zipkin.gcp diff --git a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-vision/pom.xml b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-vision/pom.xml index 06fa3e6d2e..89214faf4e 100644 --- a/spring-cloud-gcp-starters/spring-cloud-gcp-starter-vision/pom.xml +++ b/spring-cloud-gcp-starters/spring-cloud-gcp-starter-vision/pom.xml @@ -5,7 +5,7 @@ spring-cloud-gcp-starters com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 diff --git a/spring-cloud-gcp-starters/spring-cloud-gcp-starter/pom.xml b/spring-cloud-gcp-starters/spring-cloud-gcp-starter/pom.xml index 4b63054253..db5a2e07ec 100644 --- a/spring-cloud-gcp-starters/spring-cloud-gcp-starter/pom.xml +++ b/spring-cloud-gcp-starters/spring-cloud-gcp-starter/pom.xml @@ -4,7 +4,7 @@ com.google.cloud spring-cloud-gcp-starters - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT spring-cloud-gcp-starter Spring Framework on Google Cloud Starter - Support diff --git a/spring-cloud-gcp-storage/pom.xml b/spring-cloud-gcp-storage/pom.xml index 23d4dd82f7..62871ea1b8 100644 --- a/spring-cloud-gcp-storage/pom.xml +++ b/spring-cloud-gcp-storage/pom.xml @@ -6,7 +6,7 @@ spring-cloud-gcp com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT spring-cloud-gcp-storage Spring Framework on Google Cloud Module - Storage diff --git a/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/GoogleStorageResource.java b/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/GoogleStorageResource.java index 93f3086248..57d8cc4336 100644 --- a/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/GoogleStorageResource.java +++ b/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/GoogleStorageResource.java @@ -255,7 +255,10 @@ public long contentLength() throws IOException { @Override public long lastModified() throws IOException { - return throwExceptionForNullBlob(getBlob()).getUpdateTime(); + return throwExceptionForNullBlob(getBlob()) + .getUpdateTimeOffsetDateTime() + .toInstant() + .toEpochMilli(); } /** @@ -270,7 +273,7 @@ public long lastModified() throws IOException { */ @Override @NonNull - public GoogleStorageResource createRelative(String relativePath) { + public GoogleStorageResource createRelative(@NonNull String relativePath) { return new GoogleStorageResource( this.storage, getURI().resolve(relativePath).toString(), this.autoCreateFiles); } diff --git a/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/integration/GcsFileInfo.java b/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/integration/GcsFileInfo.java index 5e79813ae3..a26ac74a4d 100644 --- a/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/integration/GcsFileInfo.java +++ b/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/integration/GcsFileInfo.java @@ -24,7 +24,7 @@ /** An object that holds metadata information for a Cloud Storage file. */ public class GcsFileInfo extends AbstractFileInfo { - private BlobInfo gcsFile; + private final BlobInfo gcsFile; public GcsFileInfo(BlobInfo gcsFile) { Assert.notNull(gcsFile, "The GCS blob can't be null."); @@ -48,7 +48,7 @@ public long getSize() { @Override public long getModified() { - return this.gcsFile.getUpdateTime(); + return this.gcsFile.getUpdateTimeOffsetDateTime().toInstant().toEpochMilli(); } @Override diff --git a/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/integration/filters/GcsAcceptModifiedAfterFileListFilter.java b/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/integration/filters/GcsAcceptModifiedAfterFileListFilter.java index 0bf44d1bda..cf04f34432 100644 --- a/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/integration/filters/GcsAcceptModifiedAfterFileListFilter.java +++ b/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/integration/filters/GcsAcceptModifiedAfterFileListFilter.java @@ -29,8 +29,9 @@ * The {@link GcsAcceptModifiedAfterFileListFilter} is a filter which accepts all files that were * modified after a specified point in time. * - *

More specifically, it accepts (includes) all files whose {@link BlobInfo#getUpdateTime()} is - * after (greater than or equal to) the {@link #acceptAfterCutoffTimestamp}. + *

More specifically, it accepts (includes) all files whose + * {@link BlobInfo#getUpdateTimeOffsetDateTime()} is after (greater than or equal to) the + * {@link #acceptAfterCutoffTimestamp}. * *

{@link #acceptAfterCutoffTimestamp} defaults to Instant.now() (UTC) in millis, but an * alternative {@link Instant} can be provided via the constructor. @@ -78,7 +79,8 @@ public boolean accept(BlobInfo file) { } private boolean fileUpdateTimeOnOrAfterPointInTime(BlobInfo file) { - return file.getUpdateTime() >= acceptAfterCutoffTimestamp; + return file.getUpdateTimeOffsetDateTime().toInstant().toEpochMilli() + >= acceptAfterCutoffTimestamp; } @Override diff --git a/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/integration/filters/GcsDiscardRecentModifiedFileListFilter.java b/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/integration/filters/GcsDiscardRecentModifiedFileListFilter.java index d23f190c37..14ee46c105 100644 --- a/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/integration/filters/GcsDiscardRecentModifiedFileListFilter.java +++ b/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/integration/filters/GcsDiscardRecentModifiedFileListFilter.java @@ -28,8 +28,8 @@ * The {@link GcsDiscardRecentModifiedFileListFilter} is a filter which excludes all files that were * updated less than some specified amount of time ago. * - *

More specifically, it excludes all files whose {@link BlobInfo#getUpdateTime()} is within - * {@link #age} of the current time. + *

More specifically, it excludes all files whose {@link BlobInfo#getUpdateTimeOffsetDateTime()} + * is within {@link #age} of the current time. * *

When {@link #discardCallback} is provided, it called for all the rejected files. */ @@ -76,7 +76,8 @@ public boolean accept(BlobInfo file) { } private boolean fileIsAged(BlobInfo file) { - return file.getUpdateTime() + this.age.toMillis() <= System.currentTimeMillis(); + return file.getUpdateTimeOffsetDateTime().toInstant().toEpochMilli() + this.age.toMillis() + <= System.currentTimeMillis(); } @Override diff --git a/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/integration/filters/GcsPersistentAcceptOnceFileListFilter.java b/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/integration/filters/GcsPersistentAcceptOnceFileListFilter.java index 23196ee8cd..18f58158a8 100644 --- a/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/integration/filters/GcsPersistentAcceptOnceFileListFilter.java +++ b/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/integration/filters/GcsPersistentAcceptOnceFileListFilter.java @@ -30,7 +30,8 @@ public GcsPersistentAcceptOnceFileListFilter(ConcurrentMetadataStore store, Stri @Override protected long modified(BlobInfo blobInfo) { - return (blobInfo != null && blobInfo.getUpdateTime() != null) ? blobInfo.getUpdateTime() : -1; + return (blobInfo != null && blobInfo.getUpdateTimeOffsetDateTime() != null) + ? blobInfo.getUpdateTimeOffsetDateTime().toInstant().toEpochMilli() : -1; } @Override diff --git a/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/integration/inbound/GcsInboundFileSynchronizer.java b/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/integration/inbound/GcsInboundFileSynchronizer.java index 02285fafa4..2a33c98a19 100644 --- a/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/integration/inbound/GcsInboundFileSynchronizer.java +++ b/spring-cloud-gcp-storage/src/main/java/com/google/cloud/spring/storage/integration/inbound/GcsInboundFileSynchronizer.java @@ -44,7 +44,7 @@ protected String getFilename(BlobInfo file) { @Override protected long getModified(BlobInfo file) { - return file.getUpdateTime(); + return file.getUpdateTimeOffsetDateTime().toInstant().toEpochMilli(); } @Override diff --git a/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/GoogleStorageResourceTest.java b/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/GoogleStorageResourceTest.java index 610d07ddb8..7bb873f4fc 100644 --- a/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/GoogleStorageResourceTest.java +++ b/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/GoogleStorageResourceTest.java @@ -18,25 +18,32 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.google.cloud.storage.Blob; import com.google.cloud.storage.BlobId; +import com.google.cloud.storage.BlobInfo; import com.google.cloud.storage.Bucket; import com.google.cloud.storage.Storage; import java.io.IOException; +import java.time.OffsetDateTime; import org.junit.jupiter.api.Test; class GoogleStorageResourceTest { + private final Storage mockStorage = mock(Storage.class); + private final Bucket mockBucket = mock(Bucket.class); + private final Blob mockBlob = mock(Blob.class); + @Test void testConstructorValidation() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> new GoogleStorageResource(null, "gs://foo", false)) .withMessageContaining("Storage object can not be null"); - Storage mockStorage = mock(Storage.class); assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> new GoogleStorageResource(mockStorage, (String) null, false)); assertThatExceptionOfType(IllegalArgumentException.class) @@ -50,9 +57,6 @@ void testConstructorValidation() { @Test void getUrlBucket() throws IOException { - Storage mockStorage = mock(Storage.class); - Bucket mockBucket = mock(Bucket.class); - when(mockStorage.get("my-bucket")).thenReturn(mockBucket); when(mockBucket.getSelfLink()).thenReturn("https://www.googleapis.com/storage/v1/b/my-bucket"); @@ -62,9 +66,6 @@ void getUrlBucket() throws IOException { @Test void getUrlObject() throws IOException { - Storage mockStorage = mock(Storage.class); - Blob mockBlob = mock(Blob.class); - when(mockStorage.get(BlobId.of("my-bucket", "my-object"))).thenReturn(mockBlob); when(mockBlob.getSelfLink()) .thenReturn("https://www.googleapis.com/storage/v1/b/my-bucket/o/my-object"); @@ -72,4 +73,37 @@ void getUrlObject() throws IOException { GoogleStorageResource gsr = new GoogleStorageResource(mockStorage, "gs://my-bucket/my-object"); assertThat(gsr.getURL()).isNotNull(); } + + @Test + void getLastModifiedTest() throws IOException { + OffsetDateTime now = OffsetDateTime.now(); + when(mockStorage.get(any(BlobId.class))).thenReturn(mockBlob); + when(mockBlob.getUpdateTimeOffsetDateTime()).thenReturn(now); + GoogleStorageResource gsr = new GoogleStorageResource(mockStorage, "gs://my-bucket/my-object"); + assertThat(gsr.lastModified()).isEqualTo(now.toInstant().toEpochMilli()); + } + + @Test + void isReadableTest() { + GoogleStorageLocation location = mock(GoogleStorageLocation.class); + GoogleStorageResource gsr = new GoogleStorageResource(mockStorage, location, false); + when(location.isBucket()).thenReturn(true); + assertThat(gsr.isReadable()).isFalse(); + when(location.isBucket()).thenReturn(false); + assertThat(gsr.isReadable()).isTrue(); + } + + @Test + void createBlobTest() { + GoogleStorageResource gsr = new GoogleStorageResource(mockStorage, "gs://my-bucket/my-object"); + gsr.createBlob(); + verify(mockStorage) + .create(BlobInfo.newBuilder("my-bucket", "my-object").build()); + } + + @Test + void getGoogleStorageLocationTest() { + GoogleStorageResource gsr = new GoogleStorageResource(mockStorage, "gs://my-bucket/my-object"); + assertThat(gsr.getGoogleStorageLocation().uriString()).isEqualTo("gs://my-bucket/my-object"); + } } diff --git a/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/GoogleStorageTests.java b/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/GoogleStorageTests.java index c11dec591f..247d8c74b6 100644 --- a/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/GoogleStorageTests.java +++ b/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/GoogleStorageTests.java @@ -17,6 +17,10 @@ package com.google.cloud.spring.storage; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -37,7 +41,7 @@ import java.io.IOException; import java.io.OutputStream; import java.util.concurrent.TimeUnit; -import org.junit.Assert; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -89,40 +93,39 @@ void testValidObject() throws Exception { when(mockedBlob.getSize()).thenReturn(4096L); when(this.mockStorage.get(validBlobId)).thenReturn(mockedBlob); - Assert.assertTrue(this.remoteResource.exists()); - Assert.assertEquals(4096L, this.remoteResource.contentLength()); + assertTrue(this.remoteResource.exists()); + assertEquals(4096L, this.remoteResource.contentLength()); } @Test - void testValidObjectWithUnderscore() throws Exception { + void testValidObjectWithUnderscore() { BlobId validBlobWithUnderscore = BlobId.of("test_spring", "images/spring.png"); Blob mockedBlob = mock(Blob.class); when(mockStorage.get(validBlobWithUnderscore)).thenReturn(mockedBlob); - Assert.assertTrue(this.remoteResourceWithUnderscore.exists()); + assertTrue(this.remoteResourceWithUnderscore.exists()); } @Test void testValidBucket() throws IOException { - Assert.assertEquals("gs://test-spring/", this.bucketResource.getDescription()); - Assert.assertEquals("test-spring", this.bucketResource.getFilename()); - Assert.assertEquals("gs://test-spring/", this.bucketResource.getURI().toString()); + assertEquals("gs://test-spring/", this.bucketResource.getDescription()); + assertEquals("test-spring", this.bucketResource.getFilename()); + assertEquals("gs://test-spring/", this.bucketResource.getURI().toString()); String relative = this.bucketResource.createRelative("aaa/bbb").getURI().toString(); - Assert.assertEquals("gs://test-spring/aaa/bbb", relative); - Assert.assertEquals( - relative, this.bucketResource.createRelative("/aaa/bbb").getURI().toString()); + assertEquals("gs://test-spring/aaa/bbb", relative); + assertEquals(relative, this.bucketResource.createRelative("/aaa/bbb").getURI().toString()); - Assert.assertNull(((GoogleStorageResource) this.bucketResource).getBlobName()); - Assert.assertTrue(((GoogleStorageResource) this.bucketResource).isBucket()); + Assertions.assertNull(((GoogleStorageResource) this.bucketResource).getBlobName()); + assertTrue(((GoogleStorageResource) this.bucketResource).isBucket()); - Assert.assertTrue(this.bucketResource.exists()); - Assert.assertTrue(((GoogleStorageResource) this.bucketResource).bucketExists()); + assertTrue(this.bucketResource.exists()); + assertTrue(((GoogleStorageResource) this.bucketResource).bucketExists()); } @Test void testBucketNotEndingInSlash() { - Assert.assertTrue(new GoogleStorageResource(this.mockStorage, "gs://test-spring").isBucket()); + assertTrue(new GoogleStorageResource(this.mockStorage, "gs://test-spring").isBucket()); } @Test @@ -137,10 +140,10 @@ void testSpecifyBucketCorrect() { GoogleStorageResource googleStorageResource = new GoogleStorageResource(this.mockStorage, location, false); - Assert.assertTrue(googleStorageResource.isBucket()); - Assert.assertEquals("test-spring", googleStorageResource.getBucketName()); - Assert.assertEquals("test-spring", googleStorageResource.getBucket().getName()); - Assert.assertTrue(googleStorageResource.exists()); + assertTrue(googleStorageResource.isBucket()); + assertEquals("test-spring", googleStorageResource.getBucketName()); + assertEquals("test-spring", googleStorageResource.getBucket().getName()); + assertTrue(googleStorageResource.exists()); } @Test @@ -152,11 +155,11 @@ void testSpecifyPathCorrect() { GoogleStorageResource googleStorageResource = new GoogleStorageResource(this.mockStorage, "gs://test-spring/images/spring.png", false); - Assert.assertTrue(googleStorageResource.exists()); + assertTrue(googleStorageResource.exists()); } @Test - void testBucketOutputStream() throws IOException { + void testBucketOutputStream() { assertThatThrownBy(() -> ((WritableResource) this.bucketResource).getOutputStream()) .isInstanceOf(IllegalStateException.class) @@ -164,7 +167,7 @@ void testBucketOutputStream() throws IOException { } @Test - void testBucketNoBlobInputStream() throws IOException { + void testBucketNoBlobInputStream() { assertThatThrownBy(() -> this.bucketResource.getInputStream()) .isInstanceOf(IllegalStateException.class) @@ -172,7 +175,7 @@ void testBucketNoBlobInputStream() throws IOException { } @Test - void testBucketNoBlobContentLength() throws IOException { + void testBucketNoBlobContentLength() { assertThatThrownBy(() -> this.bucketResource.contentLength()) .isInstanceOf(IllegalStateException.class) @@ -180,7 +183,7 @@ void testBucketNoBlobContentLength() throws IOException { } @Test - void testBucketNoBlobFile() throws IOException { + void testBucketNoBlobFile() { assertThatThrownBy(() -> this.bucketResource.getFile()) .isInstanceOf(UnsupportedOperationException.class) @@ -188,7 +191,7 @@ void testBucketNoBlobFile() throws IOException { } @Test - void testBucketNoBlobLastModified() throws IOException { + void testBucketNoBlobLastModified() { assertThatThrownBy(() -> this.bucketResource.lastModified()) .isInstanceOf(IllegalStateException.class) @@ -196,11 +199,11 @@ void testBucketNoBlobLastModified() throws IOException { } @Test - void testBucketNoBlobResourceStatuses() throws IOException { - Assert.assertFalse(this.bucketResource.isOpen()); - Assert.assertFalse(this.bucketResource.isReadable()); - Assert.assertFalse(((WritableResource) this.bucketResource).isWritable()); - Assert.assertTrue(this.bucketResource.exists()); + void testBucketNoBlobResourceStatuses() { + assertFalse(this.bucketResource.isOpen()); + assertFalse(this.bucketResource.isReadable()); + assertFalse(((WritableResource) this.bucketResource).isWritable()); + assertTrue(this.bucketResource.exists()); } @Test @@ -208,9 +211,9 @@ void testWritable() throws Exception { WriteChannel writeChannel = mock(WriteChannel.class); when(this.mockStorage.writer(any(BlobInfo.class))).thenReturn(writeChannel); - Assert.assertTrue(this.remoteResource instanceof WritableResource); + assertTrue(this.remoteResource instanceof WritableResource); WritableResource writableResource = (WritableResource) this.remoteResource; - Assert.assertTrue(writableResource.isWritable()); + assertTrue(writableResource.isWritable()); writableResource.getOutputStream(); } @@ -225,18 +228,18 @@ void testWritableOutputStream() throws Exception { GoogleStorageResource resource = new GoogleStorageResource(this.mockStorage, location); OutputStream os = resource.getOutputStream(); - Assert.assertNotNull(os); + assertNotNull(os); } @Test - void testWritableOutputStreamNoAutoCreateOnNullBlob() throws Exception { + void testWritableOutputStreamNoAutoCreateOnNullBlob() { String location = "gs://test-spring/test"; when(this.mockStorage.get(BlobId.of("test-spring", "test"))).thenReturn(null); GoogleStorageResource resource = new GoogleStorageResource(this.mockStorage, location, false); - assertThatThrownBy(() -> resource.getOutputStream()) + assertThatThrownBy(resource::getOutputStream) .isInstanceOf(FileNotFoundException.class) .hasMessage("The blob was not found: gs://test-spring/test"); } @@ -255,7 +258,7 @@ void testWritableOutputStreamWithAutoCreateOnNullBlob() throws Exception { GoogleStorageResource resource = new GoogleStorageResource(this.mockStorage, location); GoogleStorageResource spyResource = spy(resource); OutputStream os = spyResource.getOutputStream(); - Assert.assertNotNull(os); + assertNotNull(os); } @Test @@ -274,7 +277,7 @@ void testWritableOutputStreamWithAutoCreateOnNonExistantBlob() throws Exception GoogleStorageResource resource = new GoogleStorageResource(this.mockStorage, location); GoogleStorageResource spyResource = spy(resource); OutputStream os = spyResource.getOutputStream(); - Assert.assertNotNull(os); + assertNotNull(os); } @Test @@ -291,27 +294,27 @@ void testCreateBlobWithContents() { } @Test - void testGetInputStreamOnNullBlob() throws Exception { + void testGetInputStreamOnNullBlob() { String location = "gs://test-spring/test"; Storage storage = mock(Storage.class); when(storage.get(BlobId.of("test-spring", "test"))).thenReturn(null); GoogleStorageResource resource = new GoogleStorageResource(storage, location, false); - assertThatThrownBy(() -> resource.getInputStream()) + assertThatThrownBy(resource::getInputStream) .isInstanceOf(FileNotFoundException.class) .hasMessage("The blob was not found: gs://test-spring/test"); } @Test - void testGetFilenameOnNonExistingBlob() throws Exception { + void testGetFilenameOnNonExistingBlob() { String location = "gs://test-spring/test"; Storage storage = mock(Storage.class); when(storage.get(BlobId.of("test-spring", "test"))).thenReturn(null); GoogleStorageResource resource = new GoogleStorageResource(storage, location, false); - Assert.assertEquals("test", resource.getFilename()); + assertEquals("test", resource.getFilename()); } @Test @@ -319,40 +322,38 @@ void testisAutoCreateFilesGetterSetter() { String location = "gs://test-spring/test"; Storage storage = mock(Storage.class); GoogleStorageResource resource = new GoogleStorageResource(storage, location); - Assert.assertTrue(resource.isAutoCreateFiles()); + assertTrue(resource.isAutoCreateFiles()); } @Test - void testCreateRelative() throws IOException { + void testCreateRelative() { String location = "gs://test-spring/test.png"; Storage storage = mock(Storage.class); GoogleStorageResource resource = new GoogleStorageResource(storage, location); - GoogleStorageResource relative = - (GoogleStorageResource) resource.createRelative("relative.png"); - Assert.assertEquals("gs://test-spring/relative.png", relative.getURI().toString()); + GoogleStorageResource relative = resource.createRelative("relative.png"); + assertEquals("gs://test-spring/relative.png", relative.getURI().toString()); } @Test - void testCreateRelativeSubdirs() throws IOException { + void testCreateRelativeSubdirs() { String location = "gs://test-spring/t1/test.png"; Storage storage = mock(Storage.class); GoogleStorageResource resource = new GoogleStorageResource(storage, location); - GoogleStorageResource relative = - (GoogleStorageResource) resource.createRelative("r1/relative.png"); - Assert.assertEquals("gs://test-spring/t1/r1/relative.png", relative.getURI().toString()); + GoogleStorageResource relative = resource.createRelative("r1/relative.png"); + assertEquals("gs://test-spring/t1/r1/relative.png", relative.getURI().toString()); } @Test - void nullSignedUrlForNullBlob() throws IOException { + void nullSignedUrlForNullBlob() { String location = "gs://test-spring/t1/test.png"; Storage storage = mock(Storage.class); GoogleStorageResource resource = new GoogleStorageResource(storage, location, false); when(storage.get(any(BlobId.class))).thenReturn(null); - Assert.assertNull(resource.createSignedUrl(TimeUnit.DAYS, 1)); + Assertions.assertNull(resource.createSignedUrl(TimeUnit.DAYS, 1)); } @Test - void signedUrlFunctionCalled() throws IOException { + void signedUrlFunctionCalled() { String location = "gs://test-spring/t1/test.png"; Storage storage = mock(Storage.class); Blob blob = mock(Blob.class); @@ -374,10 +375,10 @@ void testBucketDoesNotExist() { GoogleStorageResource bucket = new GoogleStorageResource(this.mockStorage, "gs://non-existing/"); - Assert.assertFalse(bucket.bucketExists()); - Assert.assertFalse(bucket.exists()); + assertFalse(bucket.bucketExists()); + assertFalse(bucket.exists()); - Assert.assertNotNull(bucket.createBucket()); + assertNotNull(bucket.createBucket()); } @Test @@ -389,8 +390,8 @@ void testBucketExistsButResourceDoesNot() { when(this.mockStorage.get("test-spring")).thenReturn(mockedBucket); when(mockedBucket.exists()).thenReturn(true); - Assert.assertTrue(resource.bucketExists()); - Assert.assertFalse(resource.exists()); + assertTrue(resource.bucketExists()); + assertFalse(resource.exists()); } /** Configuration for the tests. */ @@ -399,7 +400,7 @@ void testBucketExistsButResourceDoesNot() { static class StorageApplication { @Bean - public static Storage mockStorage() throws Exception { + public static Storage mockStorage() { return mock(Storage.class); } diff --git a/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/integration/GcsFileInfoTests.java b/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/integration/GcsFileInfoTests.java new file mode 100644 index 0000000000..524a1d9734 --- /dev/null +++ b/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/integration/GcsFileInfoTests.java @@ -0,0 +1,96 @@ +package com.google.cloud.spring.storage.integration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.cloud.storage.BlobInfo; +import java.time.OffsetDateTime; +import java.util.Date; +import java.util.Random; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; + +@TestInstance(Lifecycle.PER_CLASS) +class GcsFileInfoTests { + + private final BlobInfo gcsFile = mock(BlobInfo.class); + + private final GcsFileInfo gcsFileInfo = new GcsFileInfo(gcsFile); + + private final OffsetDateTime now = OffsetDateTime.now(); + + private final Random random = new Random(); + + private boolean randomBoolean; + + private long randomSize; + + @BeforeAll + void init() { + randomBoolean = random.nextBoolean(); + randomSize = random.nextLong(); + when(gcsFile.isDirectory()).thenReturn(randomBoolean); + when(gcsFile.getSize()).thenReturn(randomSize); + when(gcsFile.getUpdateTimeOffsetDateTime()).thenReturn(now); + when(gcsFile.getName()).thenReturn("fake-name"); + } + + @Test + void isDirectoryTest() { + assertThat(gcsFileInfo.isDirectory()).isEqualTo(randomBoolean); + } + + @Test + void isLinkTest() { + assertThat(gcsFileInfo.isLink()).isFalse(); + } + + @Test + void getSizeTest() { + assertThat(gcsFileInfo.getSize()).isEqualTo(randomSize); + } + + @Test + void getModifiedTest() { + assertThat(gcsFileInfo.getModified()).isEqualTo(now.toInstant().toEpochMilli()); + } + + @Test + void getFileNameTest() { + assertThat(gcsFileInfo.getFilename()).isEqualTo("fake-name"); + } + + @Test + void getPermissionsTest() { + assertThatThrownBy(gcsFileInfo::getPermissions) + .isInstanceOf(UnsupportedOperationException.class); + } + + @Test + void getFileInfoTest() { + assertThat(gcsFileInfo.getFileInfo()).isEqualTo(gcsFile); + } + + @Test + void toStringTest() { + assertThat(gcsFileInfo).hasToString( + "FileInfo [isDirectory=" + + randomBoolean + + ", isLink=" + + false + + ", Size=" + + randomSize + + ", ModifiedTime=" + + new Date(now.toInstant().toEpochMilli()) + + ", Filename=" + + "fake-name" + + ", RemoteDirectory=" + + null + + "]" + ); + } +} diff --git a/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/integration/filters/GcsAcceptModifiedAfterFileListFilterTest.java b/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/integration/filters/GcsAcceptModifiedAfterFileListFilterTest.java index bd2ec4130b..51d1478c6b 100644 --- a/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/integration/filters/GcsAcceptModifiedAfterFileListFilterTest.java +++ b/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/integration/filters/GcsAcceptModifiedAfterFileListFilterTest.java @@ -22,6 +22,7 @@ import com.google.cloud.storage.BlobInfo; import java.time.Instant; +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.Test; @@ -37,7 +38,7 @@ void addDiscardCallback() { filter.addDiscardCallback(blobInfo -> callbackTriggered.set(true)); BlobInfo blobInfo = mock(BlobInfo.class); - when(blobInfo.getUpdateTime()).thenReturn(1L); + when(blobInfo.getUpdateTimeOffsetDateTime()).thenReturn(OffsetDateTime.now().minusDays(1L)); filter.accept(blobInfo); @@ -47,23 +48,23 @@ void addDiscardCallback() { @Test void filterFiles() { - Instant now = Instant.now(); + OffsetDateTime now = OffsetDateTime.now(); BlobInfo oldBlob = mock(BlobInfo.class); - when(oldBlob.getUpdateTime()).thenReturn(now.toEpochMilli() - 1); + when(oldBlob.getUpdateTimeOffsetDateTime()).thenReturn(now.minusMinutes(1L)); BlobInfo currentBlob = mock(BlobInfo.class); - when(currentBlob.getUpdateTime()).thenReturn(now.toEpochMilli()); + when(currentBlob.getUpdateTimeOffsetDateTime()).thenReturn(now); BlobInfo newBlob = mock(BlobInfo.class); - when(newBlob.getUpdateTime()).thenReturn(now.toEpochMilli() + 1); + when(newBlob.getUpdateTimeOffsetDateTime()).thenReturn(now.plusMinutes(1L)); ArrayList expected = new ArrayList<>(); expected.add(currentBlob); expected.add(newBlob); assertThat( - new GcsAcceptModifiedAfterFileListFilter(now) + new GcsAcceptModifiedAfterFileListFilter(now.toInstant()) .filterFiles(new BlobInfo[] {oldBlob, currentBlob, newBlob})) .isEqualTo(expected); } diff --git a/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/integration/filters/GcsDiscardRecentModifiedFileListFilterTest.java b/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/integration/filters/GcsDiscardRecentModifiedFileListFilterTest.java index 31dd8f9f44..83bc98df98 100644 --- a/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/integration/filters/GcsDiscardRecentModifiedFileListFilterTest.java +++ b/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/integration/filters/GcsDiscardRecentModifiedFileListFilterTest.java @@ -22,10 +22,11 @@ import com.google.cloud.storage.BlobInfo; import java.time.Duration; +import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.Assert; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class GcsDiscardRecentModifiedFileListFilterTest { @@ -38,8 +39,8 @@ void testFileLessThanMinimumAgeIsFilteredOut() { filter.addDiscardCallback(blobInfo -> callbackTriggered.set(true)); BlobInfo blobInfo = mock(BlobInfo.class); - when(blobInfo.getUpdateTime()) - .thenReturn(ZonedDateTime.now(ZoneId.systemDefault()).toInstant().toEpochMilli()); + when(blobInfo.getUpdateTimeOffsetDateTime()) + .thenReturn(OffsetDateTime.now(ZoneId.systemDefault())); assertThat(filter.filterFiles(new BlobInfo[] {blobInfo})).isEmpty(); assertThat(filter.accept(blobInfo)).isFalse(); @@ -51,12 +52,12 @@ void testFileLessThanMinimumAgeIsFilteredOut() { void testFileOlderThanMinimumAgeIsReturned() { GcsDiscardRecentModifiedFileListFilter filter = new GcsDiscardRecentModifiedFileListFilter(Duration.ofSeconds(60)); - filter.addDiscardCallback(blobInfo -> Assert.fail("Not expected")); + filter.addDiscardCallback(blobInfo -> Assertions.fail("Not expected")); BlobInfo blobInfo = mock(BlobInfo.class); - when(blobInfo.getUpdateTime()) + when(blobInfo.getUpdateTimeOffsetDateTime()) .thenReturn( - ZonedDateTime.now(ZoneId.systemDefault()).minusMinutes(3).toInstant().toEpochMilli()); + OffsetDateTime.now(ZoneId.systemDefault()).minusMinutes(3)); assertThat(filter.filterFiles(new BlobInfo[] {blobInfo})).hasSize(1); assertThat(filter.accept(blobInfo)).isTrue(); diff --git a/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/integration/filters/GcsPersistentAcceptOnceFileListFilterTest.java b/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/integration/filters/GcsPersistentAcceptOnceFileListFilterTest.java index b6b41f3357..26f8fb7b6f 100644 --- a/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/integration/filters/GcsPersistentAcceptOnceFileListFilterTest.java +++ b/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/integration/filters/GcsPersistentAcceptOnceFileListFilterTest.java @@ -38,7 +38,7 @@ void modified_blobInfoIsNull_shouldReturnMinusOne() { @Test void modified_updateTimeIsNull_shouldReturnMinusOne() { BlobInfo blobInfo = mock(BlobInfo.class); - when(blobInfo.getUpdateTime()).thenReturn(null); + when(blobInfo.getUpdateTimeOffsetDateTime()).thenReturn(null); assertThat( new GcsPersistentAcceptOnceFileListFilter(mock(ConcurrentMetadataStore.class), "") diff --git a/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/integration/inbound/GcsInboundFileSynchronizerTests.java b/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/integration/inbound/GcsInboundFileSynchronizerTests.java index 01ccb67987..9d232c610d 100644 --- a/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/integration/inbound/GcsInboundFileSynchronizerTests.java +++ b/spring-cloud-gcp-storage/src/test/java/com/google/cloud/spring/storage/integration/inbound/GcsInboundFileSynchronizerTests.java @@ -20,6 +20,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.willAnswer; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.google.cloud.PageImpl; import com.google.cloud.storage.Blob; @@ -29,6 +30,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.OffsetDateTime; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.logging.Log; @@ -121,6 +123,8 @@ public Storage gcs() { Blob blob1 = mock(Blob.class); Blob blob2 = mock(Blob.class); + when(blob1.getUpdateTimeOffsetDateTime()).thenReturn(OffsetDateTime.now()); + when(blob2.getUpdateTimeOffsetDateTime()).thenReturn(OffsetDateTime.now()); willAnswer(invocation -> "legend of heroes").given(blob1).getName(); willAnswer(invocation -> "trails in the sky").given(blob2).getName(); diff --git a/spring-cloud-gcp-vision/pom.xml b/spring-cloud-gcp-vision/pom.xml index 1adb82d003..a42430cc7c 100644 --- a/spring-cloud-gcp-vision/pom.xml +++ b/spring-cloud-gcp-vision/pom.xml @@ -6,7 +6,7 @@ spring-cloud-gcp com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 diff --git a/spring-cloud-gcp-vision/src/main/java/com/google/cloud/spring/vision/DocumentOcrTemplate.java b/spring-cloud-gcp-vision/src/main/java/com/google/cloud/spring/vision/DocumentOcrTemplate.java index 9bad9c5dd0..ecda8acbba 100644 --- a/spring-cloud-gcp-vision/src/main/java/com/google/cloud/spring/vision/DocumentOcrTemplate.java +++ b/spring-cloud-gcp-vision/src/main/java/com/google/cloud/spring/vision/DocumentOcrTemplate.java @@ -38,12 +38,10 @@ import com.google.cloud.vision.v1.TextAnnotation; import java.util.Collections; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; -import java.util.stream.Collectors; import java.util.stream.StreamSupport; import org.springframework.util.Assert; -import org.springframework.util.concurrent.ListenableFuture; -import org.springframework.util.concurrent.SettableListenableFuture; /** * Template providing convenient operations for interfacing with Google Cloud Vision's Document OCR @@ -90,15 +88,15 @@ public DocumentOcrTemplate( * *

Note: OCR processing operations may take several minutes to complete, so it may not be * advisable to block on the completion of the operation. One may use the returned {@link - * ListenableFuture} to register callbacks or track the status of the operation. + * CompletableFuture} to register callbacks or track the status of the operation. * * @param document The {@link GoogleStorageLocation} of the document to run OCR processing * @param outputFilePathPrefix The {@link GoogleStorageLocation} of a file, folder, or a bucket * describing the path for which all output files shall be saved under - * @return A {@link ListenableFuture} allowing you to register callbacks or wait for the + * @return A {@link CompletableFuture} allowing you to register callbacks or wait for the * completion of the operation. */ - public ListenableFuture runOcrForDocument( + public CompletableFuture runOcrForDocument( GoogleStorageLocation document, GoogleStorageLocation outputFilePathPrefix) { Assert.isTrue( @@ -155,7 +153,7 @@ public DocumentOcrResultSet readOcrOutputFileSet(GoogleStorageLocation jsonOutpu List blobPages = StreamSupport.stream(blobsInFolder.getValues().spliterator(), false) .filter(blob -> blob.getContentType().equals("application/octet-stream")) - .collect(Collectors.toList()); + .toList(); return new DocumentOcrResultSet(blobPages); } @@ -182,17 +180,17 @@ public DocumentOcrResultSet readOcrOutputFile(GoogleStorageLocation jsonFile) { return new DocumentOcrResultSet(Collections.singletonList(jsonOutputBlob)); } - private ListenableFuture extractOcrResultFuture( + private CompletableFuture extractOcrResultFuture( OperationFuture grpcFuture) { - SettableListenableFuture result = new SettableListenableFuture<>(); + CompletableFuture result = new CompletableFuture<>(); ApiFutures.addCallback( grpcFuture, - new ApiFutureCallback() { + new ApiFutureCallback<>() { @Override public void onFailure(Throwable throwable) { - result.setException(throwable); + result.completeExceptionally(throwable); } @Override @@ -208,7 +206,7 @@ public void onSuccess(AsyncBatchAnnotateFilesResponse asyncBatchAnnotateFilesRes GoogleStorageLocation outputFolderLocation = new GoogleStorageLocation(outputLocationUri); - result.set(readOcrOutputFileSet(outputFolderLocation)); + result.complete(readOcrOutputFileSet(outputFolderLocation)); } }, this.executor); diff --git a/spring-cloud-gcp-vision/src/test/java/com/google/cloud/spring/vision/it/DocumentOcrTemplateIntegrationTests.java b/spring-cloud-gcp-vision/src/test/java/com/google/cloud/spring/vision/it/DocumentOcrTemplateIntegrationTests.java index 8d0963e802..e9ab827e2d 100644 --- a/spring-cloud-gcp-vision/src/test/java/com/google/cloud/spring/vision/it/DocumentOcrTemplateIntegrationTests.java +++ b/spring-cloud-gcp-vision/src/test/java/com/google/cloud/spring/vision/it/DocumentOcrTemplateIntegrationTests.java @@ -25,6 +25,7 @@ import com.google.protobuf.InvalidProtocolBufferException; import java.util.ArrayList; import java.util.Iterator; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -34,7 +35,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.util.concurrent.ListenableFuture; @EnabledIfSystemProperty(named = "it.vision", matches = "true") @ExtendWith(SpringExtension.class) @@ -53,7 +53,7 @@ void testDocumentOcrTemplate() GoogleStorageLocation outputLocationPrefix = GoogleStorageLocation.forFile("vision-integration-test-bucket", "it_output/test-"); - ListenableFuture result = + CompletableFuture result = this.documentOcrTemplate.runOcrForDocument(document, outputLocationPrefix); DocumentOcrResultSet ocrPages = result.get(5, TimeUnit.MINUTES); diff --git a/spring-cloud-previews/pom.xml b/spring-cloud-previews/pom.xml index a4837fc5c3..5960dfff96 100644 --- a/spring-cloud-previews/pom.xml +++ b/spring-cloud-previews/pom.xml @@ -5,7 +5,7 @@ spring-cloud-gcp com.google.cloud - 3.4.3-SNAPSHOT + 4.0.0-SNAPSHOT 4.0.0 diff --git a/versions.txt b/versions.txt index b0e52e960b..bffd967aaa 100644 --- a/versions.txt +++ b/versions.txt @@ -1,4 +1,4 @@ # Format: # module:released-version:current-version -spring-cloud-gcp:3.4.2:3.4.3-SNAPSHOT \ No newline at end of file +spring-cloud-gcp:4.0.0:4.0.1-SNAPSHOT