From ac5bec2c56ca7aa10e99b8035b26410e32efa7ce Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 19 Dec 2024 20:48:57 +0000 Subject: [PATCH] Sync docs from v3.8.13 to gh-pages --- 3.8.13/reference/html/Guardfile | 20 + 3.8.13/reference/html/appendix.html | 957 ++ 3.8.13/reference/html/bigquery.html | 523 + 3.8.13/reference/html/cloudfoundry.html | 322 + 3.8.13/reference/html/config.html | 463 + 3.8.13/reference/html/configuration.html | 202 + 3.8.13/reference/html/core.html | 515 + 3.8.13/reference/html/css/font-awesome.css | 2337 +++ 3.8.13/reference/html/css/site.css | 7 + 3.8.13/reference/html/css/site.css.map | 1 + 3.8.13/reference/html/datastore.html | 2210 +++ 3.8.13/reference/html/firestore.html | 1208 ++ 3.8.13/reference/html/first-page.html | 263 + 3.8.13/reference/html/fonts/FontAwesome.otf | Bin 0 -> 134808 bytes .../html/fonts/fontawesome-webfont.eot | Bin 0 -> 165742 bytes .../html/fonts/fontawesome-webfont.svg | 2671 ++++ .../html/fonts/fontawesome-webfont.ttf | Bin 0 -> 165548 bytes .../html/fonts/fontawesome-webfont.woff | Bin 0 -> 98024 bytes .../html/fonts/fontawesome-webfont.woff2 | Bin 0 -> 77160 bytes 3.8.13/reference/html/getting-started.html | 524 + .../html/images/GCP on Spring Initializr.png | Bin 0 -> 189333 bytes .../html/images/Stackdrive trace.png | Bin 0 -> 125360 bytes .../images/spring_cloud_bus_over_pubsub.png | Bin 0 -> 67191 bytes 3.8.13/reference/html/img/banner-logo.svg | 16 + .../html/img/doc-background-dark.svg | 8 + 3.8.13/reference/html/img/doc-background.svg | 8 + 3.8.13/reference/html/img/octicons-16.svg | 109 + 3.8.13/reference/html/index.html | 12334 ++++++++++++++++ 3.8.13/reference/html/js/setup.js | 3 + 3.8.13/reference/html/js/setup.js.map | 1 + 3.8.13/reference/html/js/site.js | 7 + 3.8.13/reference/html/js/site.js.map | 1 + 3.8.13/reference/html/kms.html | 328 + 3.8.13/reference/html/kotlin.html | 250 + 3.8.13/reference/html/logging.html | 568 + 3.8.13/reference/html/memorystore.html | 264 + 3.8.13/reference/html/metrics.html | 354 + .../reference/html/migration-guide-1.x.html | 336 + 3.8.13/reference/html/pubsub.html | 1506 ++ 3.8.13/reference/html/sagan-boot.html | 189 + 3.8.13/reference/html/sagan-index.html | 491 + 3.8.13/reference/html/secretmanager.html | 472 + 3.8.13/reference/html/security-firebase.html | 323 + 3.8.13/reference/html/security-iap.html | 362 + .../html/spanner-spring-data-r2dbc.html | 270 + 3.8.13/reference/html/spanner.html | 2488 ++++ .../html/spring-cloud-bus-pubsub.html | 277 + 3.8.13/reference/html/spring-cloud-gcp.html | 12334 ++++++++++++++++ .../html/spring-integration-pubsub.html | 692 + .../html/spring-integration-storage.html | 316 + 3.8.13/reference/html/spring-integration.html | 827 ++ 3.8.13/reference/html/spring-stream.html | 571 + 3.8.13/reference/html/sql.html | 683 + 3.8.13/reference/html/storage.html | 398 + 3.8.13/reference/html/trace.html | 553 + 3.8.13/reference/html/vision.html | 615 + 56 files changed, 50177 insertions(+) create mode 100644 3.8.13/reference/html/Guardfile create mode 100644 3.8.13/reference/html/appendix.html create mode 100644 3.8.13/reference/html/bigquery.html create mode 100644 3.8.13/reference/html/cloudfoundry.html create mode 100644 3.8.13/reference/html/config.html create mode 100644 3.8.13/reference/html/configuration.html create mode 100644 3.8.13/reference/html/core.html create mode 100755 3.8.13/reference/html/css/font-awesome.css create mode 100755 3.8.13/reference/html/css/site.css create mode 100755 3.8.13/reference/html/css/site.css.map create mode 100644 3.8.13/reference/html/datastore.html create mode 100644 3.8.13/reference/html/firestore.html create mode 100644 3.8.13/reference/html/first-page.html create mode 100755 3.8.13/reference/html/fonts/FontAwesome.otf create mode 100755 3.8.13/reference/html/fonts/fontawesome-webfont.eot create mode 100755 3.8.13/reference/html/fonts/fontawesome-webfont.svg create mode 100755 3.8.13/reference/html/fonts/fontawesome-webfont.ttf create mode 100755 3.8.13/reference/html/fonts/fontawesome-webfont.woff create mode 100755 3.8.13/reference/html/fonts/fontawesome-webfont.woff2 create mode 100644 3.8.13/reference/html/getting-started.html create mode 100644 3.8.13/reference/html/images/GCP on Spring Initializr.png create mode 100644 3.8.13/reference/html/images/Stackdrive trace.png create mode 100644 3.8.13/reference/html/images/spring_cloud_bus_over_pubsub.png create mode 100755 3.8.13/reference/html/img/banner-logo.svg create mode 100755 3.8.13/reference/html/img/doc-background-dark.svg create mode 100755 3.8.13/reference/html/img/doc-background.svg create mode 100755 3.8.13/reference/html/img/octicons-16.svg create mode 100644 3.8.13/reference/html/index.html create mode 100755 3.8.13/reference/html/js/setup.js create mode 100755 3.8.13/reference/html/js/setup.js.map create mode 100755 3.8.13/reference/html/js/site.js create mode 100755 3.8.13/reference/html/js/site.js.map create mode 100644 3.8.13/reference/html/kms.html create mode 100644 3.8.13/reference/html/kotlin.html create mode 100644 3.8.13/reference/html/logging.html create mode 100644 3.8.13/reference/html/memorystore.html create mode 100644 3.8.13/reference/html/metrics.html create mode 100644 3.8.13/reference/html/migration-guide-1.x.html create mode 100644 3.8.13/reference/html/pubsub.html create mode 100644 3.8.13/reference/html/sagan-boot.html create mode 100644 3.8.13/reference/html/sagan-index.html create mode 100644 3.8.13/reference/html/secretmanager.html create mode 100644 3.8.13/reference/html/security-firebase.html create mode 100644 3.8.13/reference/html/security-iap.html create mode 100644 3.8.13/reference/html/spanner-spring-data-r2dbc.html create mode 100644 3.8.13/reference/html/spanner.html create mode 100644 3.8.13/reference/html/spring-cloud-bus-pubsub.html create mode 100644 3.8.13/reference/html/spring-cloud-gcp.html create mode 100644 3.8.13/reference/html/spring-integration-pubsub.html create mode 100644 3.8.13/reference/html/spring-integration-storage.html create mode 100644 3.8.13/reference/html/spring-integration.html create mode 100644 3.8.13/reference/html/spring-stream.html create mode 100644 3.8.13/reference/html/sql.html create mode 100644 3.8.13/reference/html/storage.html create mode 100644 3.8.13/reference/html/trace.html create mode 100644 3.8.13/reference/html/vision.html diff --git a/3.8.13/reference/html/Guardfile b/3.8.13/reference/html/Guardfile new file mode 100644 index 0000000000..bdd4d72981 --- /dev/null +++ b/3.8.13/reference/html/Guardfile @@ -0,0 +1,20 @@ +require 'asciidoctor' +require 'erb' + +guard 'shell' do + watch(/.*\.adoc$/) {|m| + Asciidoctor.render_file('index.adoc', \ + :in_place => true, \ + :safe => Asciidoctor::SafeMode::UNSAFE, \ + :attributes=> { \ + 'source-highlighter' => 'prettify', \ + 'icons' => 'font', \ + 'linkcss'=> 'true', \ + 'copycss' => 'true', \ + 'doctype' => 'book'}) + } +end + +guard 'livereload' do + watch(%r{^.+\.(css|js|html)$}) +end diff --git a/3.8.13/reference/html/appendix.html b/3.8.13/reference/html/appendix.html new file mode 100644 index 0000000000..71871472e1 --- /dev/null +++ b/3.8.13/reference/html/appendix.html @@ -0,0 +1,957 @@ + + + + + + + +Common application properties + + + + + + + + + +
+
+
+ +
+
+

Appendix A: Common application properties

+
+
+

Various properties can be specified inside your application.properties file, inside your application.yml file, or as command line switches. +This appendix provides a list of common Spring Framework on Google Cloud properties and references to the underlying classes that consume them.

+
+
+ + + + + +
+ + +Property contributions can come from additional jar files on your classpath, so you should not consider this an exhaustive list. +Also, you can define your own properties. +
+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDefaultDescription

spring.cloud.gcp.bigquery.credentials.encoded-key

spring.cloud.gcp.bigquery.credentials.location

spring.cloud.gcp.bigquery.credentials.scopes

spring.cloud.gcp.bigquery.dataset-name

Name of the BigQuery dataset to use.

spring.cloud.gcp.bigquery.enabled

true

Auto-configure Google Cloud BigQuery components.

spring.cloud.gcp.bigquery.project-id

Overrides the Google Cloud project ID specified in the Core module to use for BigQuery.

spring.cloud.gcp.config.credentials.encoded-key

spring.cloud.gcp.config.credentials.location

spring.cloud.gcp.config.credentials.scopes

spring.cloud.gcp.config.enabled

true

Auto-configure Google Cloud Runtime components.

spring.cloud.gcp.config.name

Name of the application.

spring.cloud.gcp.config.profile

Comma-delimited string of profiles under which the app is running. Gets its default value from the {@code spring.profiles.active} property, falling back on the {@code spring.profiles.default} property.

spring.cloud.gcp.config.project-id

Overrides the Google Cloud project ID specified in the Core module.

spring.cloud.gcp.config.timeout-millis

60000

Timeout for Google Runtime Configuration API calls.

spring.cloud.gcp.core.enabled

true

Auto-configure Google Cloud Core components.

spring.cloud.gcp.credentials.encoded-key

spring.cloud.gcp.credentials.location

spring.cloud.gcp.credentials.scopes

spring.cloud.gcp.datastore.credentials.encoded-key

spring.cloud.gcp.datastore.credentials.location

spring.cloud.gcp.datastore.credentials.scopes

spring.cloud.gcp.datastore.emulator.consistency

0.9

Consistency to use creating the Datastore server instance. Default: {@code 0.9}

spring.cloud.gcp.datastore.emulator.enabled

false

If enabled the Datastore client will connect to an local datastore emulator.

spring.cloud.gcp.datastore.emulator.port

8081

Is the datastore emulator port. Default: {@code 8081}

spring.cloud.gcp.datastore.enabled

true

Auto-configure Google Cloud Datastore components.

spring.cloud.gcp.datastore.host

The host and port of a Datastore emulator as the following example: localhost:8081.

spring.cloud.gcp.datastore.namespace

spring.cloud.gcp.datastore.project-id

spring.cloud.gcp.firestore.credentials.encoded-key

spring.cloud.gcp.firestore.credentials.location

spring.cloud.gcp.firestore.credentials.scopes

spring.cloud.gcp.firestore.emulator.enabled

false

Enables autoconfiguration to use the Firestore emulator.

spring.cloud.gcp.firestore.enabled

true

Auto-configure Google Cloud Firestore components.

spring.cloud.gcp.firestore.host-port

firestore.googleapis.com:443

The host and port of the Firestore emulator service; can be overridden to specify an emulator.

spring.cloud.gcp.firestore.project-id

spring.cloud.gcp.logging.enabled

true

Auto-configure Google Cloud Stackdriver logging for Spring MVC.

spring.cloud.gcp.metrics.credentials.encoded-key

spring.cloud.gcp.metrics.credentials.location

spring.cloud.gcp.metrics.credentials.scopes

spring.cloud.gcp.metrics.enabled

true

Auto-configure Google Cloud Monitoring for Micrometer.

spring.cloud.gcp.metrics.project-id

Overrides the Google Cloud project ID specified in the Core module.

spring.cloud.gcp.project-id

Google Cloud project ID where services are running.

spring.cloud.gcp.pubsub.binder.enabled

true

Auto-configure Google Cloud Pub/Sub Stream Binder components.

spring.cloud.gcp.pubsub.credentials.encoded-key

spring.cloud.gcp.pubsub.credentials.location

spring.cloud.gcp.pubsub.credentials.scopes

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 pub/sub emulator.

spring.cloud.gcp.pubsub.enabled

true

Auto-configure Google Cloud Pub/Sub components.

spring.cloud.gcp.pubsub.keep-alive-interval-minutes

5

How often to ping the server to keep the channel alive.

spring.cloud.gcp.pubsub.project-id

Overrides the Google Cloud project ID specified in the Core module.

spring.cloud.gcp.pubsub.publisher.batching.delay-threshold-seconds

The delay threshold to use for batching. After this amount of time has elapsed (counting from the first element added), the elements will be wrapped up in a batch and sent.

spring.cloud.gcp.pubsub.publisher.batching.element-count-threshold

The element count threshold to use for batching.

spring.cloud.gcp.pubsub.publisher.batching.enabled

Enables batching if true.

spring.cloud.gcp.pubsub.publisher.batching.flow-control.limit-exceeded-behavior

The behavior when the specified limits are exceeded.

spring.cloud.gcp.pubsub.publisher.batching.flow-control.max-outstanding-element-count

Maximum number of outstanding elements to keep in memory before enforcing flow control.

spring.cloud.gcp.pubsub.publisher.batching.flow-control.max-outstanding-request-bytes

Maximum number of outstanding bytes to keep in memory before enforcing flow control.

spring.cloud.gcp.pubsub.publisher.batching.request-byte-threshold

The request byte threshold to use for batching.

spring.cloud.gcp.pubsub.publisher.executor-threads

4

Number of threads used by every publisher.

spring.cloud.gcp.pubsub.publisher.retry.initial-retry-delay-seconds

InitialRetryDelay controls the delay before the first retry. Subsequent retries will use this value adjusted according to the RetryDelayMultiplier.

spring.cloud.gcp.pubsub.publisher.retry.initial-rpc-timeout-seconds

InitialRpcTimeout controls the timeout for the initial RPC. Subsequent calls will use this value adjusted according to the RpcTimeoutMultiplier.

spring.cloud.gcp.pubsub.publisher.retry.jittered

Jitter determines if the delay time should be randomized.

spring.cloud.gcp.pubsub.publisher.retry.max-attempts

MaxAttempts defines the maximum number of attempts to perform. If this value is greater than 0, and the number of attempts reaches this limit, the logic will give up retrying even if the total retry time is still lower than TotalTimeout.

spring.cloud.gcp.pubsub.publisher.retry.max-retry-delay-seconds

MaxRetryDelay puts a limit on the value of the retry delay, so that the RetryDelayMultiplier can’t increase the retry delay higher than this amount.

spring.cloud.gcp.pubsub.publisher.retry.max-rpc-timeout-seconds

MaxRpcTimeout puts a limit on the value of the RPC timeout, so that the RpcTimeoutMultiplier can’t increase the RPC timeout higher than this amount.

spring.cloud.gcp.pubsub.publisher.retry.retry-delay-multiplier

RetryDelayMultiplier controls the change in retry delay. The retry delay of the previous call is multiplied by the RetryDelayMultiplier to calculate the retry delay for the next call.

spring.cloud.gcp.pubsub.publisher.retry.rpc-timeout-multiplier

RpcTimeoutMultiplier controls the change in RPC timeout. The timeout of the previous call is multiplied by the RpcTimeoutMultiplier to calculate the timeout for the next call.

spring.cloud.gcp.pubsub.publisher.retry.total-timeout-seconds

TotalTimeout has ultimate control over how long the logic should keep trying the remote call until it gives up completely. The higher the total timeout, the more retries can be attempted.

spring.cloud.gcp.pubsub.reactive.enabled

true

Auto-configure Google Cloud Pub/Sub Reactive components.

spring.cloud.gcp.pubsub.subscriber.executor-threads

4

Number of threads used by every subscriber.

spring.cloud.gcp.pubsub.subscriber.flow-control.limit-exceeded-behavior

The behavior when the specified limits are exceeded.

spring.cloud.gcp.pubsub.subscriber.flow-control.max-outstanding-element-count

Maximum number of outstanding elements to keep in memory before enforcing flow control.

spring.cloud.gcp.pubsub.subscriber.flow-control.max-outstanding-request-bytes

Maximum number of outstanding bytes to keep in memory before enforcing flow control.

spring.cloud.gcp.pubsub.subscriber.max-ack-extension-period

0

The optional max ack extension period in seconds for the subscriber factory.

spring.cloud.gcp.pubsub.subscriber.max-acknowledgement-threads

4

Number of threads used for batch acknowledgement.

spring.cloud.gcp.pubsub.subscriber.parallel-pull-count

The optional parallel pull count setting for the subscriber factory.

spring.cloud.gcp.pubsub.subscriber.pull-endpoint

The optional pull endpoint setting for the subscriber factory.

spring.cloud.gcp.pubsub.subscriber.retry.initial-retry-delay-seconds

InitialRetryDelay controls the delay before the first retry. Subsequent retries will use this value adjusted according to the RetryDelayMultiplier.

spring.cloud.gcp.pubsub.subscriber.retry.initial-rpc-timeout-seconds

InitialRpcTimeout controls the timeout for the initial RPC. Subsequent calls will use this value adjusted according to the RpcTimeoutMultiplier.

spring.cloud.gcp.pubsub.subscriber.retry.jittered

Jitter determines if the delay time should be randomized.

spring.cloud.gcp.pubsub.subscriber.retry.max-attempts

MaxAttempts defines the maximum number of attempts to perform. If this value is greater than 0, and the number of attempts reaches this limit, the logic will give up retrying even if the total retry time is still lower than TotalTimeout.

spring.cloud.gcp.pubsub.subscriber.retry.max-retry-delay-seconds

MaxRetryDelay puts a limit on the value of the retry delay, so that the RetryDelayMultiplier can’t increase the retry delay higher than this amount.

spring.cloud.gcp.pubsub.subscriber.retry.max-rpc-timeout-seconds

MaxRpcTimeout puts a limit on the value of the RPC timeout, so that the RpcTimeoutMultiplier can’t increase the RPC timeout higher than this amount.

spring.cloud.gcp.pubsub.subscriber.retry.retry-delay-multiplier

RetryDelayMultiplier controls the change in retry delay. The retry delay of the previous call is multiplied by the RetryDelayMultiplier to calculate the retry delay for the next call.

spring.cloud.gcp.pubsub.subscriber.retry.rpc-timeout-multiplier

RpcTimeoutMultiplier controls the change in RPC timeout. The timeout of the previous call is multiplied by the RpcTimeoutMultiplier to calculate the timeout for the next call.

spring.cloud.gcp.pubsub.subscriber.retry.total-timeout-seconds

TotalTimeout has ultimate control over how long the logic should keep trying the remote call until it gives up completely. The higher the total timeout, the more retries can be attempted.

spring.cloud.gcp.secretmanager.credentials.encoded-key

spring.cloud.gcp.secretmanager.credentials.location

spring.cloud.gcp.secretmanager.credentials.scopes

spring.cloud.gcp.secretmanager.enabled

true

Auto-configure Google Cloud Secret Manager support components.

spring.cloud.gcp.secretmanager.project-id

Overrides the Google Cloud project ID specified in the Core module.

spring.cloud.gcp.security.firebase.project-id

Overrides the Google Cloud project ID specified in the Core module.

spring.cloud.gcp.security.firebase.public-keys-endpoint

www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com

Link to Google’s public endpoint containing Firebase public keys.

spring.cloud.gcp.security.iap.algorithm

ES256

Encryption algorithm used to sign the JWK token.

spring.cloud.gcp.security.iap.audience

Non-dynamic audience string to validate.

spring.cloud.gcp.security.iap.enabled

true

Auto-configure Google Cloud IAP identity extraction components.

spring.cloud.gcp.security.iap.header

x-goog-iap-jwt-assertion

Header from which to extract the JWK key.

spring.cloud.gcp.security.iap.issuer

cloud.google.com/iap

JWK issuer to verify.

spring.cloud.gcp.security.iap.registry

www.gstatic.com/iap/verify/public_key-jwk

Link to JWK public key registry.

spring.cloud.gcp.spanner.create-interleaved-table-ddl-on-delete-cascade

true

spring.cloud.gcp.spanner.credentials.encoded-key

spring.cloud.gcp.spanner.credentials.location

spring.cloud.gcp.spanner.credentials.scopes

spring.cloud.gcp.spanner.database

spring.cloud.gcp.spanner.emulator-host

localhost:9010

spring.cloud.gcp.spanner.emulator.enabled

false

Enables auto-configuration to use the Spanner emulator.

spring.cloud.gcp.spanner.enabled

true

Auto-configure Google Cloud Spanner components.

spring.cloud.gcp.spanner.fail-if-pool-exhausted

false

spring.cloud.gcp.spanner.instance-id

spring.cloud.gcp.spanner.keep-alive-interval-minutes

-1

spring.cloud.gcp.spanner.max-idle-sessions

-1

spring.cloud.gcp.spanner.max-sessions

-1

spring.cloud.gcp.spanner.min-sessions

-1

spring.cloud.gcp.spanner.num-rpc-channels

-1

spring.cloud.gcp.spanner.prefetch-chunks

-1

spring.cloud.gcp.spanner.project-id

spring.cloud.gcp.spanner.write-sessions-fraction

-1

spring.cloud.gcp.sql.credentials

Overrides the Google Cloud OAuth2 credentials specified in the Core module.

spring.cloud.gcp.sql.database-name

Name of the database in the Cloud SQL instance.

spring.cloud.gcp.sql.enabled

true

Auto-configure Google Cloud SQL support components.

spring.cloud.gcp.sql.instance-connection-name

Cloud SQL instance connection name. [GCP_PROJECT_ID]:[INSTANCE_REGION]:[INSTANCE_NAME].

spring.cloud.gcp.sql.ip-types

A comma delimited list of preferred IP types for connecting to the Cloud SQL instance.

spring.cloud.gcp.storage.auto-create-files

spring.cloud.gcp.storage.credentials.encoded-key

spring.cloud.gcp.storage.credentials.location

spring.cloud.gcp.storage.credentials.scopes

spring.cloud.gcp.storage.enabled

true

Auto-configure Google Cloud Storage components.

spring.cloud.gcp.trace.authority

HTTP/2 authority the channel claims to be connecting to.

spring.cloud.gcp.trace.compression

Compression to use for the call.

spring.cloud.gcp.trace.credentials.encoded-key

spring.cloud.gcp.trace.credentials.location

spring.cloud.gcp.trace.credentials.scopes

spring.cloud.gcp.trace.deadline-ms

Call deadline.

spring.cloud.gcp.trace.enabled

true

Auto-configure Google Cloud Stackdriver tracing components.

spring.cloud.gcp.trace.max-inbound-size

Maximum size for an inbound message.

spring.cloud.gcp.trace.max-outbound-size

Maximum size for an outbound message.

spring.cloud.gcp.trace.message-timeout

1

Timeout in seconds before pending spans will be sent in batches to Google Cloud Stackdriver Trace.

spring.cloud.gcp.trace.num-executor-threads

4

Number of threads to be used by the Trace executor.

spring.cloud.gcp.trace.project-id

Overrides the Google Cloud project ID specified in the Core module.

spring.cloud.gcp.trace.wait-for-ready

Waits for the channel to be ready in case of a transient failure. Defaults to failing fast in that case.

spring.cloud.gcp.vision.credentials.encoded-key

spring.cloud.gcp.vision.credentials.location

spring.cloud.gcp.vision.credentials.scopes

spring.cloud.gcp.vision.enabled

true

Auto-configure Google Cloud Vision components.

spring.cloud.gcp.vision.executor-threads-count

1

Number of threads used to poll for the completion of Document OCR operations.

spring.cloud.gcp.vision.json-output-batch-size

20

Number of document pages to include in each JSON output file.

+
+
+
+ + +
+
+
+ + \ No newline at end of file diff --git a/3.8.13/reference/html/bigquery.html b/3.8.13/reference/html/bigquery.html new file mode 100644 index 0000000000..e53168b6bd --- /dev/null +++ b/3.8.13/reference/html/bigquery.html @@ -0,0 +1,523 @@ + + + + + + + +BigQuery + + + + + + + + + +
+
+
+ +
+
+

BigQuery

+
+
+

Google Cloud BigQuery is a fully managed, petabyte scale, low cost analytics data warehouse.

+
+
+

Spring Framework on Google Cloud provides:

+
+
+
    +
  • +

    A convenience starter which provides autoconfiguration for the BigQuery client objects with credentials needed to interface with BigQuery.

    +
  • +
  • +

    A Spring Integration message handler for loading data into BigQuery tables in your Spring integration pipelines.

    +
  • +
+
+
+

Maven coordinates, using Spring Framework on Google Cloud BOM:

+
+
+
+
<dependency>
+    <groupId>com.google.cloud</groupId>
+    <artifactId>spring-cloud-gcp-starter-bigquery</artifactId>
+</dependency>
+
+
+
+

Gradle coordinates:

+
+
+
+
dependencies {
+    implementation("com.google.cloud:spring-cloud-gcp-starter-bigquery")
+}
+
+
+
+

Configuration

+
+

The following application properties may be configured with Spring Framework on Google Cloud BigQuery libraries.

+
+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Name

Description

Required

Default value

spring.cloud.gcp.bigquery.datasetName

The BigQuery dataset that the BigQueryTemplate and BigQueryFileMessageHandler is scoped to.

Yes

spring.cloud.gcp.bigquery.enabled

Enables or disables Spring Framework on Google Cloud BigQuery autoconfiguration.

No

true

spring.cloud.gcp.bigquery.project-id

Google Cloud project ID of the project using BigQuery APIs, if different from the one in the Spring Framework on Google Cloud Core Module.

No

Project ID is typically inferred from gcloud configuration.

spring.cloud.gcp.bigquery.credentials.location

Credentials file location for authenticating with the Google Cloud BigQuery APIs, if different from the ones in the Spring Framework on Google Cloud Core Module

No

Inferred from Application Default Credentials, typically set by gcloud.

spring.cloud.gcp.bigquery.jsonWriterBatchSize

Batch size which will be used by BigQueryJsonDataWriter while using BigQuery Storage Write API. Note too large or too low values might impact performance.

No

1000

spring.cloud.gcp.bigquery.threadPoolSize

The size of thread pool of ThreadPoolTaskScheduler which is used by BigQueryTemplate

No

4

+
+

BigQuery Client Object

+
+

The GcpBigQueryAutoConfiguration class configures an instance of BigQuery for you by inferring your credentials and Project ID from the machine’s environment.

+
+
+

Example usage:

+
+
+
+
// BigQuery client object provided by our autoconfiguration.
+@Autowired
+BigQuery bigquery;
+
+public void runQuery() throws InterruptedException {
+  String query = "SELECT column FROM table;";
+  QueryJobConfiguration queryConfig =
+      QueryJobConfiguration.newBuilder(query).build();
+
+  // Run the query using the BigQuery object
+  for (FieldValueList row : bigquery.query(queryConfig).iterateAll()) {
+    for (FieldValue val : row) {
+      System.out.println(val);
+    }
+  }
+}
+
+
+
+
+

This object is used to interface with all BigQuery services. +For more information, see the BigQuery Client Library usage examples.

+
+
+
+

BigQueryTemplate

+
+

The BigQueryTemplate class is a wrapper over the BigQuery client object and makes it easier to load data into BigQuery tables. +A BigQueryTemplate is scoped to a single dataset. +The autoconfigured BigQueryTemplate instance will use the dataset provided through the property spring.cloud.gcp.bigquery.datasetName.

+
+
+

Below is a code snippet of how to load a CSV data InputStream to a BigQuery table.

+
+
+
+
// BigQuery client object provided by our autoconfiguration.
+@Autowired
+BigQueryTemplate bigQueryTemplate;
+
+public void loadData(InputStream dataInputStream, String tableName) {
+  ListenableFuture<Job> bigQueryJobFuture =
+      bigQueryTemplate.writeDataToTable(
+          tableName,
+          dataFile.getInputStream(),
+          FormatOptions.csv());
+
+  // After the future is complete, the data is successfully loaded.
+  Job job = bigQueryJobFuture.get();
+}
+
+
+
+
+

Below is a code snippet of how to load a newline-delimited JSON data InputStream to a BigQuery table. This implementation uses the BigQuery Storage Write API. +Here is a sample newline-delimited JSON file which can be used for testing this functionality.

+
+
+
+
// BigQuery client object provided by our autoconfiguration.
+@Autowired
+BigQueryTemplate bigQueryTemplate;
+
+  /**
+   * This method loads the InputStream of the newline-delimited JSON records to be written in the given table.
+   * @param tableName name of the table where the data is expected to be written
+   * @param jsonInputStream InputStream of the newline-delimited JSON records to be written in the given table
+   */
+  public void loadJsonStream(String tableName, InputStream jsonInputStream)
+      throws ExecutionException, InterruptedException {
+    ListenableFuture<WriteApiResponse> writeApFuture =
+        bigQueryTemplate.writeJsonStream(tableName, jsonInputStream);
+    WriteApiResponse apiRes = writeApFuture.get();//get the WriteApiResponse
+    if (!apiRes.isSuccessful()){
+      List<StorageError> errors = apiRes.getErrors();
+      // TODO(developer): process the List of StorageError
+    }
+    // else the write process has been successful
+  }
+
+
+
+
+

Below is a code snippet of how to create table and then load a newline-delimited JSON data InputStream to a BigQuery table. This implementation uses the BigQuery Storage Write API. +Here is a sample newline-delimited JSON file which can be used for testing this functionality.

+
+
+
+
// BigQuery client object provided by our autoconfiguration.
+@Autowired
+BigQueryTemplate bigQueryTemplate;
+
+  /**
+   * This method created a table with the given name and schema and then loads the InputStream of the newline-delimited JSON records in it.
+   * @param tableName name of the table where the data is expected to be written
+   * @param jsonInputStream InputStream of the newline-delimited JSON records to be written in the given table
+   * @param tableSchema Schema of the table which is required to be created
+   */
+  public void createTableAndloadJsonStream(String tableName, InputStream jsonInputStream, Schema tableSchema)
+      throws ExecutionException, InterruptedException {
+    ListenableFuture<WriteApiResponse> 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()){
+      List<StorageError> errors = apiRes.getErrors();
+      // TODO(developer): process the List of StorageError
+    }
+    // else the write process has been successful
+  }
+
+
+
+
+
+
+

Spring Integration

+
+

Spring Framework on Google Cloud BigQuery also provides a Spring Integration message handler BigQueryFileMessageHandler. +This is useful for incorporating BigQuery data loading operations in a Spring Integration pipeline.

+
+
+

Below is an example configuring a ServiceActivator bean using the BigQueryFileMessageHandler.

+
+
+
+
@Bean
+public DirectChannel bigQueryWriteDataChannel() {
+  return new DirectChannel();
+}
+
+@Bean
+public DirectChannel bigQueryJobReplyChannel() {
+  return new DirectChannel();
+}
+
+@Bean
+@ServiceActivator(inputChannel = "bigQueryWriteDataChannel")
+public MessageHandler messageSender(BigQueryTemplate bigQueryTemplate) {
+  BigQueryFileMessageHandler messageHandler = new BigQueryFileMessageHandler(bigQueryTemplate);
+  messageHandler.setFormatOptions(FormatOptions.csv());
+  messageHandler.setOutputChannel(bigQueryJobReplyChannel());
+  return messageHandler;
+}
+
+
+
+
+

BigQuery Message Handling

+
+

The BigQueryFileMessageHandler accepts the following message payload types for loading into BigQuery: java.io.File, byte[], org.springframework.core.io.Resource, and java.io.InputStream. +The message payload will be streamed and written to the BigQuery table you specify.

+
+
+

By default, the BigQueryFileMessageHandler is configured to read the headers of the messages it receives to determine how to load the data. +The headers are specified by the class BigQuerySpringMessageHeaders and summarized below.

+
+ ++++ + + + + + + + + + + + + + + +

Header

Description

BigQuerySpringMessageHeaders.TABLE_NAME

Specifies the BigQuery table within your dataset to write to.

BigQuerySpringMessageHeaders.FORMAT_OPTIONS

Describes the data format of your data to load (i.e. CSV, JSON, etc.).

+
+

Alternatively, you may omit these headers and explicitly set the table name or format options by calling setTableName(…​) and setFormatOptions(…​).

+
+
+
+

BigQuery Message Reply

+
+

After the BigQueryFileMessageHandler processes a message to load data to your BigQuery table, it will respond with a Job on the reply channel. +The 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<Job> 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.

+
+
+ + + + + +
+ + +If you decide to use Spring Integration Gateways and you wish to receive ListenableFuture<Job> 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

+
+

A BigQuery sample application is available.

+
+
+
+
+
+ + +
+
+
+ + \ No newline at end of file diff --git a/3.8.13/reference/html/cloudfoundry.html b/3.8.13/reference/html/cloudfoundry.html new file mode 100644 index 0000000000..7973455a4f --- /dev/null +++ b/3.8.13/reference/html/cloudfoundry.html @@ -0,0 +1,322 @@ + + + + + + + +Cloud Foundry + + + + + + + + + +
+
+
+ +
+
+

Cloud Foundry

+
+
+

Spring Framework on Google Cloud provides support for Cloud Foundry’s GCP Service Broker. +Our Pub/Sub, Cloud Spanner, Storage, Cloud Trace and Cloud SQL MySQL and PostgreSQL starters are Cloud Foundry aware and retrieve properties like project ID, credentials, etc., that are used in auto configuration from the Cloud Foundry environment.

+
+
+

In order to take advantage of the Cloud Foundry support make sure the following dependency is added:

+
+
+
+
<dependency>
+    <groupId>com.google.cloud</groupId>
+    <artifactId>spring-cloud-gcp-starter-cloudfoundry</artifactId>
+</dependency>
+
+
+
+

In cases like Pub/Sub’s topic and subscription, or Storage’s bucket name, where those parameters are not used in auto configuration, you can fetch them using the VCAP mapping provided by Spring Boot. +For example, to retrieve the provisioned Pub/Sub topic, you can use the vcap.services.mypubsub.credentials.topic_name property from the application environment.

+
+
+ + + + + +
+ + +If the same service is bound to the same application more than once, the auto configuration will not be able to choose among bindings and will not be activated for that service. +This includes both MySQL and PostgreSQL bindings to the same app. +
+
+
+ + + + + +
+ + +In order for the Cloud SQL integration to work in Cloud Foundry, auto-reconfiguration must be disabled. +You can do so using the cf set-env <APP> JBP_CONFIG_SPRING_AUTO_RECONFIGURATION '{enabled: false}' command. +Otherwise, Cloud Foundry will produce a DataSource with an invalid JDBC URL (i.e., jdbc:mysql://null/null). +
+
+
+

User-Provided Services

+
+

User-provided services enable developers to use services that are not available in the marketplace with their apps running on Cloud Foundry. +For example, you may want to use a user-provided service that points to a shared Google Service (like Cloud Spanner) used across your organization.

+
+
+

In order for Spring Framework on Google Cloud to detect your user-provided service as a Google Cloud Service, you must add an instance tag indicating the Google Cloud Service it uses. +The tag should simply be the Cloud Foundry name for the Google Service.

+
+
+

For example, if you create a user-provided service using Cloud Spanner, you might run:

+
+
+
+
$ cf create-user-provided-service user-spanner-service -t "google-spanner" ...
+
+
+
+

This allows Spring Framework on Google Cloud to retrieve the correct service properties from Cloud Foundry and use them in the auto configuration for your application.

+
+
+

A mapping of Google service names to Cloud Foundry names are provided below:

+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Google Cloud Service

Cloud Foundry Name (add this as a tag)

Google Cloud Pub/Sub

google-pubsub

Google Cloud Storage

google-storage

Google Cloud Spanner

google-spanner

Datastore

google-datastore

Firestore

google-firestore

BigQuery

google-bigquery

Cloud Trace

google-stackdriver-trace

Cloud Sql (MySQL)

google-cloudsql-mysql

Cloud Sql (PostgreSQL)

google-cloudsql-postgres

+
+
+
+
+ + +
+
+
+ + \ No newline at end of file diff --git a/3.8.13/reference/html/config.html b/3.8.13/reference/html/config.html new file mode 100644 index 0000000000..28d7596e31 --- /dev/null +++ b/3.8.13/reference/html/config.html @@ -0,0 +1,463 @@ + + + + + + + +Cloud Runtime Configuration API + + + + + + + + + +
+
+
+ +
+
+

Cloud Runtime Configuration API

+
+
+ + + + + +
+ + +The Google Cloud Runtime Configuration service is in Beta status, and is only available in snapshot and milestone versions of the project. It’s also not available in the Spring Framework on Google Cloud BOM, unlike other modules. +
+
+
+

Spring Framework on Google Cloud makes it possible to use the Google Runtime Configuration API as a Spring Cloud Config server to remotely store your application configuration data.

+
+
+

The Spring Framework on Google Cloud Config support is provided via its own Spring Boot starter. +It enables the use of the Google Runtime Configuration API as a source for Spring Boot configuration properties.

+
+
+

Maven coordinates:

+
+
+
+
<dependency>
+    <groupId>com.google.cloud</groupId>
+    <artifactId>spring-cloud-gcp-starter-config</artifactId>
+    <version>3.8.13</version>
+</dependency>
+
+
+
+

Gradle coordinates:

+
+
+
+
dependencies {
+    compile group: 'org.springframework.cloud',
+    name: 'spring-cloud-gcp-starter-config',
+    version: '3.8.13'
+}
+
+
+
+

Configuration

+
+

The following parameters are configurable in Spring Framework on Google Cloud Config:

+
+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Name

Description

Required

Default value

spring.cloud.gcp.config.enabled

Enables the Config client

No

false

spring.cloud.gcp.config.name

Name of your application

No

Value of the spring.application.name property. +If none, application

spring.cloud.gcp.config.profile

Active profile

No

Value of the spring.profiles.active property. +If more than a single profile, last one is chosen

spring.cloud.gcp.config.timeout-millis

Timeout in milliseconds for connecting to the Google Runtime Configuration API

No

60000

spring.cloud.gcp.config.project-id

Google Cloud project ID where the Google Runtime Configuration API is hosted

No

spring.cloud.gcp.config.credentials.location

OAuth2 credentials for authenticating with the Google Runtime Configuration API

No

spring.cloud.gcp.config.credentials.encoded-key

Base64-encoded OAuth2 credentials for authenticating with the Google Runtime Configuration API

No

spring.cloud.gcp.config.credentials.scopes

OAuth2 scope for Spring Framework on Google Cloud Config credentials

No

https://www.googleapis.com/auth/cloudruntimeconfig

+
+ + + + + +
+ + +These properties should be specified in a bootstrap.yml/bootstrap.properties file, rather than the usual applications.yml/application.properties. +
+
+
+ + + + + +
+ + +Core properties, as described in Spring Framework on Google Cloud Core Module, do not apply to Spring Framework on Google Cloud Config. +
+
+
+
+

Quick start

+
+
    +
  1. +

    Create a configuration in the Google Runtime Configuration API that is called ${spring.application.name}_${spring.profiles.active}. +In other words, if spring.application.name is myapp and spring.profiles.active is prod, the configuration should be called myapp_prod.

    +
    +

    In order to do that, you should have the Google Cloud SDK installed, own a Google Cloud Project and run the following command:

    +
    +
    +
    +
    gcloud init # if this is your first Google Cloud SDK run.
    +gcloud beta runtime-config configs create myapp_prod
    +gcloud beta runtime-config configs variables set myapp.queue-size 25 --config-name myapp_prod
    +
    +
    +
  2. +
  3. +

    Configure your bootstrap.properties file with your application’s configuration data:

    +
    +
    +
    spring.application.name=myapp
    +spring.profiles.active=prod
    +
    +
    +
  4. +
  5. +

    Add the @ConfigurationProperties annotation to a Spring-managed bean:

    +
    +
    +
    @Component
    +@ConfigurationProperties("myapp")
    +public class SampleConfig {
    +
    +  private int queueSize;
    +
    +  public int getQueueSize() {
    +    return this.queueSize;
    +  }
    +
    +  public void setQueueSize(int queueSize) {
    +    this.queueSize = queueSize;
    +  }
    +}
    +
    +
    +
  6. +
+
+
+

When your Spring application starts, the queueSize field value will be set to 25 for the above SampleConfig bean.

+
+
+
+

Refreshing the configuration at runtime

+
+

Spring Cloud provides support to have configuration parameters be reloadable with the POST request to /actuator/refresh endpoint.

+
+
+
    +
  1. +

    Add the Spring Boot Actuator dependency:

    +
    +

    Maven coordinates:

    +
    +
    +
    +
    <dependency>
    +    <groupId>org.springframework.boot</groupId>
    +    <artifactId>spring-boot-starter-actuator</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("org.springframework.boot:spring-boot-starter-actuator")
    +}
    +
    +
    +
  2. +
  3. +

    Add @RefreshScope to your Spring configuration class to have parameters be reloadable at runtime.

    +
  4. +
  5. +

    Add management.endpoints.web.exposure.include=refresh to your application.properties to allow unrestricted access to /actuator/refresh.

    +
  6. +
  7. +

    Update a property with gcloud:

    +
    +
    +
    $ gcloud beta runtime-config configs variables set \
    +  myapp.queue_size 200 \
    +  --config-name myapp_prod
    +
    +
    +
  8. +
  9. +

    Send a POST request to the refresh endpoint:

    +
    +
    +
    $ curl -XPOST https://myapp.host.com/actuator/refresh
    +
    +
    +
  10. +
+
+
+
+

Sample

+
+

A sample application and a codelab are available.

+
+
+
+
+
+ + +
+
+
+ + \ No newline at end of file diff --git a/3.8.13/reference/html/configuration.html b/3.8.13/reference/html/configuration.html new file mode 100644 index 0000000000..0d06b6d800 --- /dev/null +++ b/3.8.13/reference/html/configuration.html @@ -0,0 +1,202 @@ + + + + + + + +Configuration properties + + + + + + + + + +
+
+
+ +
+
+

Configuration properties

+
+
+

To see the list of all Google Cloud related configuration properties please check the Appendix page.

+
+
+
+
+ + +
+
+
+ + \ No newline at end of file diff --git a/3.8.13/reference/html/core.html b/3.8.13/reference/html/core.html new file mode 100644 index 0000000000..a56e98c0a9 --- /dev/null +++ b/3.8.13/reference/html/core.html @@ -0,0 +1,515 @@ + + + + + + + +Spring Framework on Google Cloud Core + + + + + + + + + +
+
+
+ +
+
+

Spring Framework on Google Cloud Core

+
+
+

Each Spring Framework on Google Cloud module uses GcpProjectIdProvider and CredentialsProvider to get the Google Cloud project ID and access credentials.

+
+
+

Spring Framework on Google Cloud provides a Spring Boot starter to auto-configure the core components.

+
+
+

Maven coordinates, using Spring Framework on Google Cloud BOM:

+
+
+
+
<dependency>
+    <groupId>com.google.cloud</groupId>
+    <artifactId>spring-cloud-gcp-starter</artifactId>
+</dependency>
+
+
+
+

Gradle coordinates:

+
+
+
+
dependencies {
+    implementation("com.google.cloud:spring-cloud-gcp-starter")
+}
+
+
+
+

Configuration

+
+

The following options may be configured with Spring Cloud core.

+
+ ++++++ + + + + + + + + + + + + + + +

Name

Description

Required

Default value

spring.cloud.gcp.core.enabled

Enables or disables Google Cloud core auto configuration

No

true

+
+
+

Project ID

+
+

GcpProjectIdProvider is a functional interface that returns a Google Cloud project ID string.

+
+
+
+
public interface GcpProjectIdProvider {
+	String getProjectId();
+}
+
+
+
+
+

The Spring Framework on Google Cloud starter auto-configures a GcpProjectIdProvider. +If a spring.cloud.gcp.project-id property is specified, the provided GcpProjectIdProvider returns that property value.

+
+
+
+
spring.cloud.gcp.project-id=my-gcp-project-id
+
+
+
+
+

Otherwise, the project ID is discovered based on an +ordered list of rules:

+
+
+
    +
  1. +

    The project ID specified by the GOOGLE_CLOUD_PROJECT environment variable

    +
  2. +
  3. +

    The Google App Engine project ID

    +
  4. +
  5. +

    The project ID specified in the JSON credentials file pointed by the GOOGLE_APPLICATION_CREDENTIALS environment variable

    +
  6. +
  7. +

    The Google Cloud SDK project ID

    +
  8. +
  9. +

    The Google Compute Engine project ID, from the Google Compute Engine Metadata Server

    +
  10. +
+
+
+
+

Credentials

+
+

CredentialsProvider is a functional interface that returns the credentials to authenticate and authorize calls to Google Cloud Client Libraries.

+
+
+
+
public interface CredentialsProvider {
+  Credentials getCredentials() throws IOException;
+}
+
+
+
+
+

The Spring Framework on Google Cloud starter auto-configures a CredentialsProvider. +It uses the spring.cloud.gcp.credentials.location property to locate the OAuth2 private key of a Google service account. +Keep in mind this property is a Spring Resource, so the credentials file can be obtained from a number of different locations such as the file system, classpath, URL, etc. +The next example specifies the credentials location property in the file system.

+
+
+
+
spring.cloud.gcp.credentials.location=file:/usr/local/key.json
+
+
+
+

Alternatively, you can set the credentials by directly specifying the spring.cloud.gcp.credentials.encoded-key property. +The value should be the base64-encoded account private key in JSON format.

+
+
+

If that credentials aren’t specified through properties, the starter tries to discover credentials from a number of places:

+
+
+
    +
  1. +

    Credentials file pointed to by the GOOGLE_APPLICATION_CREDENTIALS environment variable

    +
  2. +
  3. +

    Credentials provided by the Google Cloud SDK gcloud auth application-default login command

    +
  4. +
  5. +

    Google App Engine built-in credentials

    +
  6. +
  7. +

    Google Cloud Shell built-in credentials

    +
  8. +
  9. +

    Google Compute Engine built-in credentials

    +
  10. +
+
+
+

If your app is running on Google App Engine or Google Compute Engine, in most cases, you should omit the spring.cloud.gcp.credentials.location property and, instead, let the Spring Framework on Google Cloud Starter get the correct credentials for those environments. +On App Engine Standard, the App Identity service account credentials are used, on App Engine Flexible, the Flexible service account credential are used and on Google Compute Engine, the Compute Engine Default Service Account is used.

+
+
+

Scopes

+
+

By default, the credentials provided by the Spring Framework on Google Cloud Starter contain scopes for every service supported by Spring Framework on Google Cloud.

+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Service

Scope

Spanner

https://www.googleapis.com/auth/spanner.admin, https://www.googleapis.com/auth/spanner.data

Datastore

https://www.googleapis.com/auth/datastore

Pub/Sub

https://www.googleapis.com/auth/pubsub

Storage (Read Only)

https://www.googleapis.com/auth/devstorage.read_only

Storage (Read/Write)

https://www.googleapis.com/auth/devstorage.read_write

Runtime Config

https://www.googleapis.com/auth/cloudruntimeconfig

Trace (Append)

https://www.googleapis.com/auth/trace.append

Cloud Platform

https://www.googleapis.com/auth/cloud-platform

Vision

https://www.googleapis.com/auth/cloud-vision

+
+

The Spring Framework on Google Cloud starter allows you to configure a custom scope list for the provided credentials. +To do that, specify a comma-delimited list of Google OAuth2 scopes in the spring.cloud.gcp.credentials.scopes property.

+
+
+

spring.cloud.gcp.credentials.scopes is a comma-delimited list of Google OAuth2 scopes for Google Cloud services that the credentials returned by the provided CredentialsProvider support.

+
+
+
+
spring.cloud.gcp.credentials.scopes=https://www.googleapis.com/auth/pubsub,https://www.googleapis.com/auth/sqlservice.admin
+
+
+
+

You can also use DEFAULT_SCOPES placeholder as a scope to represent the starters default scopes, and append the additional scopes you need to add.

+
+
+
+
spring.cloud.gcp.credentials.scopes=DEFAULT_SCOPES,https://www.googleapis.com/auth/cloud-vision
+
+
+
+
+
+

Environment

+
+

GcpEnvironmentProvider is a functional interface, auto-configured by the Spring Framework on Google Cloud starter, that returns a GcpEnvironment enum. +The provider can help determine programmatically in which Google Cloud environment (App Engine Flexible, App Engine Standard, Kubernetes Engine or Compute Engine) the application is deployed.

+
+
+
+
public interface GcpEnvironmentProvider {
+	GcpEnvironment getCurrentEnvironment();
+}
+
+
+
+
+
+

Customizing bean scope

+
+

Spring Framework on Google Cloud starters autoconfigure all necessary beans in the default singleton scope. +If you need a particular bean or set of beans to be recreated dynamically (for example, to rotate credentials), there are two options:

+
+
+
    +
  1. +

    Annotate custom beans of the necessary types with @RefreshScope. +This makes the most sense if your application is already redefining those beans.

    +
  2. +
  3. +

    Override the scope for autoconfigured beans by listing them in the Spring Cloud property spring.cloud.refresh.extra-refreshable.

    +
    +

    For example, the beans involved in Cloud Pub/Sub subscription could be marked as refreshable as follows:

    +
    +
  4. +
+
+
+
+
spring.cloud.refresh.extra-refreshable=com.google.cloud.spring.pubsub.support.SubscriberFactory,\
+  com.google.cloud.spring.pubsub.core.subscriber.PubSubSubscriberTemplate
+
+
+
+ + + + + +
+ + +
+

SmartLifecycle beans, such as Spring Integration adapters, do not currently support @RefreshScope. +If your application refreshes any beans used by such SmartLifecycle objects, it may also have to restart the beans manually when RefreshScopeRefreshedEvent is detected, such as in the Cloud Pub/Sub example below:

+
+
+
+
@Autowired
+private PubSubInboundChannelAdapter pubSubAdapter;
+
+@EventListener(RefreshScopeRefreshedEvent.class)
+public void onRefreshScope(RefreshScopeRefreshedEvent event) {
+  this.pubSubAdapter.stop();
+  this.pubSubAdapter.start();
+}
+
+
+
+
+
+
+
+

Spring Initializr

+
+

This starter is available from Spring Initializr through the GCP Support entry.

+
+
+
+
+
+ + +
+
+
+ + \ No newline at end of file diff --git a/3.8.13/reference/html/css/font-awesome.css b/3.8.13/reference/html/css/font-awesome.css new file mode 100755 index 0000000000..ee906a8196 --- /dev/null +++ b/3.8.13/reference/html/css/font-awesome.css @@ -0,0 +1,2337 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */ +/* FONT PATH + * -------------------------- */ +@font-face { + font-family: 'FontAwesome'; + src: url('../fonts/fontawesome-webfont.eot?v=4.7.0'); + src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg'); + font-weight: normal; + font-style: normal; +} +.fa { + display: inline-block; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +/* makes the font 33% larger relative to the icon container */ +.fa-lg { + font-size: 1.33333333em; + line-height: 0.75em; + vertical-align: -15%; +} +.fa-2x { + font-size: 2em; +} +.fa-3x { + font-size: 3em; +} +.fa-4x { + font-size: 4em; +} +.fa-5x { + font-size: 5em; +} +.fa-fw { + width: 1.28571429em; + text-align: center; +} +.fa-ul { + padding-left: 0; + margin-left: 2.14285714em; + list-style-type: none; +} +.fa-ul > li { + position: relative; +} +.fa-li { + position: absolute; + left: -2.14285714em; + width: 2.14285714em; + top: 0.14285714em; + text-align: center; +} +.fa-li.fa-lg { + left: -1.85714286em; +} +.fa-border { + padding: .2em .25em .15em; + border: solid 0.08em #eeeeee; + border-radius: .1em; +} +.fa-pull-left { + float: left; +} +.fa-pull-right { + float: right; +} +.fa.fa-pull-left { + margin-right: .3em; +} +.fa.fa-pull-right { + margin-left: .3em; +} +/* Deprecated as of 4.4.0 */ +.pull-right { + float: right; +} +.pull-left { + float: left; +} +.fa.pull-left { + margin-right: .3em; +} +.fa.pull-right { + margin-left: .3em; +} +.fa-spin { + -webkit-animation: fa-spin 2s infinite linear; + animation: fa-spin 2s infinite linear; +} +.fa-pulse { + -webkit-animation: fa-spin 1s infinite steps(8); + animation: fa-spin 1s infinite steps(8); +} +@-webkit-keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +@keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +.fa-rotate-90 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); +} +.fa-rotate-180 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; + -webkit-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg); +} +.fa-rotate-270 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; + -webkit-transform: rotate(270deg); + -ms-transform: rotate(270deg); + transform: rotate(270deg); +} +.fa-flip-horizontal { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; + -webkit-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + transform: scale(-1, 1); +} +.fa-flip-vertical { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; + -webkit-transform: scale(1, -1); + -ms-transform: scale(1, -1); + transform: scale(1, -1); +} +:root .fa-rotate-90, +:root .fa-rotate-180, +:root .fa-rotate-270, +:root .fa-flip-horizontal, +:root .fa-flip-vertical { + filter: none; +} +.fa-stack { + position: relative; + display: inline-block; + width: 2em; + height: 2em; + line-height: 2em; + vertical-align: middle; +} +.fa-stack-1x, +.fa-stack-2x { + position: absolute; + left: 0; + width: 100%; + text-align: center; +} +.fa-stack-1x { + line-height: inherit; +} +.fa-stack-2x { + font-size: 2em; +} +.fa-inverse { + color: #ffffff; +} +/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen + readers do not read off random characters that represent icons */ +.fa-glass:before { + content: "\f000"; +} +.fa-music:before { + content: "\f001"; +} +.fa-search:before { + content: "\f002"; +} +.fa-envelope-o:before { + content: "\f003"; +} +.fa-heart:before { + content: "\f004"; +} +.fa-star:before { + content: "\f005"; +} +.fa-star-o:before { + content: "\f006"; +} +.fa-user:before { + content: "\f007"; +} +.fa-film:before { + content: "\f008"; +} +.fa-th-large:before { + content: "\f009"; +} +.fa-th:before { + content: "\f00a"; +} +.fa-th-list:before { + content: "\f00b"; +} +.fa-check:before { + content: "\f00c"; +} +.fa-remove:before, +.fa-close:before, +.fa-times:before { + content: "\f00d"; +} +.fa-search-plus:before { + content: "\f00e"; +} +.fa-search-minus:before { + content: "\f010"; +} +.fa-power-off:before { + content: "\f011"; +} +.fa-signal:before { + content: "\f012"; +} +.fa-gear:before, +.fa-cog:before { + content: "\f013"; +} +.fa-trash-o:before { + content: "\f014"; +} +.fa-home:before { + content: "\f015"; +} +.fa-file-o:before { + content: "\f016"; +} +.fa-clock-o:before { + content: "\f017"; +} +.fa-road:before { + content: "\f018"; +} +.fa-download:before { + content: "\f019"; +} +.fa-arrow-circle-o-down:before { + content: "\f01a"; +} +.fa-arrow-circle-o-up:before { + content: "\f01b"; +} +.fa-inbox:before { + content: "\f01c"; +} +.fa-play-circle-o:before { + content: "\f01d"; +} +.fa-rotate-right:before, +.fa-repeat:before { + content: "\f01e"; +} +.fa-refresh:before { + content: "\f021"; +} +.fa-list-alt:before { + content: "\f022"; +} +.fa-lock:before { + content: "\f023"; +} +.fa-flag:before { + content: "\f024"; +} +.fa-headphones:before { + content: "\f025"; +} +.fa-volume-off:before { + content: "\f026"; +} +.fa-volume-down:before { + content: "\f027"; +} +.fa-volume-up:before { + content: "\f028"; +} +.fa-qrcode:before { + content: "\f029"; +} +.fa-barcode:before { + content: "\f02a"; +} +.fa-tag:before { + content: "\f02b"; +} +.fa-tags:before { + content: "\f02c"; +} +.fa-book:before { + content: "\f02d"; +} +.fa-bookmark:before { + content: "\f02e"; +} +.fa-print:before { + content: "\f02f"; +} +.fa-camera:before { + content: "\f030"; +} +.fa-font:before { + content: "\f031"; +} +.fa-bold:before { + content: "\f032"; +} +.fa-italic:before { + content: "\f033"; +} +.fa-text-height:before { + content: "\f034"; +} +.fa-text-width:before { + content: "\f035"; +} +.fa-align-left:before { + content: "\f036"; +} +.fa-align-center:before { + content: "\f037"; +} +.fa-align-right:before { + content: "\f038"; +} +.fa-align-justify:before { + content: "\f039"; +} +.fa-list:before { + content: "\f03a"; +} +.fa-dedent:before, +.fa-outdent:before { + content: "\f03b"; +} +.fa-indent:before { + content: "\f03c"; +} +.fa-video-camera:before { + content: "\f03d"; +} +.fa-photo:before, +.fa-image:before, +.fa-picture-o:before { + content: "\f03e"; +} +.fa-pencil:before { + content: "\f040"; +} +.fa-map-marker:before { + content: "\f041"; +} +.fa-adjust:before { + content: "\f042"; +} +.fa-tint:before { + content: "\f043"; +} +.fa-edit:before, +.fa-pencil-square-o:before { + content: "\f044"; +} +.fa-share-square-o:before { + content: "\f045"; +} +.fa-check-square-o:before { + content: "\f046"; +} +.fa-arrows:before { + content: "\f047"; +} +.fa-step-backward:before { + content: "\f048"; +} +.fa-fast-backward:before { + content: "\f049"; +} +.fa-backward:before { + content: "\f04a"; +} +.fa-play:before { + content: "\f04b"; +} +.fa-pause:before { + content: "\f04c"; +} +.fa-stop:before { + content: "\f04d"; +} +.fa-forward:before { + content: "\f04e"; +} +.fa-fast-forward:before { + content: "\f050"; +} +.fa-step-forward:before { + content: "\f051"; +} +.fa-eject:before { + content: "\f052"; +} +.fa-chevron-left:before { + content: "\f053"; +} +.fa-chevron-right:before { + content: "\f054"; +} +.fa-plus-circle:before { + content: "\f055"; +} +.fa-minus-circle:before { + content: "\f056"; +} +.fa-times-circle:before { + content: "\f057"; +} +.fa-check-circle:before { + content: "\f058"; +} +.fa-question-circle:before { + content: "\f059"; +} +.fa-info-circle:before { + content: "\f05a"; +} +.fa-crosshairs:before { + content: "\f05b"; +} +.fa-times-circle-o:before { + content: "\f05c"; +} +.fa-check-circle-o:before { + content: "\f05d"; +} +.fa-ban:before { + content: "\f05e"; +} +.fa-arrow-left:before { + content: "\f060"; +} +.fa-arrow-right:before { + content: "\f061"; +} +.fa-arrow-up:before { + content: "\f062"; +} +.fa-arrow-down:before { + content: "\f063"; +} +.fa-mail-forward:before, +.fa-share:before { + content: "\f064"; +} +.fa-expand:before { + content: "\f065"; +} +.fa-compress:before { + content: "\f066"; +} +.fa-plus:before { + content: "\f067"; +} +.fa-minus:before { + content: "\f068"; +} +.fa-asterisk:before { + content: "\f069"; +} +.fa-exclamation-circle:before { + content: "\f06a"; +} +.fa-gift:before { + content: "\f06b"; +} +.fa-leaf:before { + content: "\f06c"; +} +.fa-fire:before { + content: "\f06d"; +} +.fa-eye:before { + content: "\f06e"; +} +.fa-eye-slash:before { + content: "\f070"; +} +.fa-warning:before, +.fa-exclamation-triangle:before { + content: "\f071"; +} +.fa-plane:before { + content: "\f072"; +} +.fa-calendar:before { + content: "\f073"; +} +.fa-random:before { + content: "\f074"; +} +.fa-comment:before { + content: "\f075"; +} +.fa-magnet:before { + content: "\f076"; +} +.fa-chevron-up:before { + content: "\f077"; +} +.fa-chevron-down:before { + content: "\f078"; +} +.fa-retweet:before { + content: "\f079"; +} +.fa-shopping-cart:before { + content: "\f07a"; +} +.fa-folder:before { + content: "\f07b"; +} +.fa-folder-open:before { + content: "\f07c"; +} +.fa-arrows-v:before { + content: "\f07d"; +} +.fa-arrows-h:before { + content: "\f07e"; +} +.fa-bar-chart-o:before, +.fa-bar-chart:before { + content: "\f080"; +} +.fa-twitter-square:before { + content: "\f081"; +} +.fa-facebook-square:before { + content: "\f082"; +} +.fa-camera-retro:before { + content: "\f083"; +} +.fa-key:before { + content: "\f084"; +} +.fa-gears:before, +.fa-cogs:before { + content: "\f085"; +} +.fa-comments:before { + content: "\f086"; +} +.fa-thumbs-o-up:before { + content: "\f087"; +} +.fa-thumbs-o-down:before { + content: "\f088"; +} +.fa-star-half:before { + content: "\f089"; +} +.fa-heart-o:before { + content: "\f08a"; +} +.fa-sign-out:before { + content: "\f08b"; +} +.fa-linkedin-square:before { + content: "\f08c"; +} +.fa-thumb-tack:before { + content: "\f08d"; +} +.fa-external-link:before { + content: "\f08e"; +} +.fa-sign-in:before { + content: "\f090"; +} +.fa-trophy:before { + content: "\f091"; +} +.fa-github-square:before { + content: "\f092"; +} +.fa-upload:before { + content: "\f093"; +} +.fa-lemon-o:before { + content: "\f094"; +} +.fa-phone:before { + content: "\f095"; +} +.fa-square-o:before { + content: "\f096"; +} +.fa-bookmark-o:before { + content: "\f097"; +} +.fa-phone-square:before { + content: "\f098"; +} +.fa-twitter:before { + content: "\f099"; +} +.fa-facebook-f:before, +.fa-facebook:before { + content: "\f09a"; +} +.fa-github:before { + content: "\f09b"; +} +.fa-unlock:before { + content: "\f09c"; +} +.fa-credit-card:before { + content: "\f09d"; +} +.fa-feed:before, +.fa-rss:before { + content: "\f09e"; +} +.fa-hdd-o:before { + content: "\f0a0"; +} +.fa-bullhorn:before { + content: "\f0a1"; +} +.fa-bell:before { + content: "\f0f3"; +} +.fa-certificate:before { + content: "\f0a3"; +} +.fa-hand-o-right:before { + content: "\f0a4"; +} +.fa-hand-o-left:before { + content: "\f0a5"; +} +.fa-hand-o-up:before { + content: "\f0a6"; +} +.fa-hand-o-down:before { + content: "\f0a7"; +} +.fa-arrow-circle-left:before { + content: "\f0a8"; +} +.fa-arrow-circle-right:before { + content: "\f0a9"; +} +.fa-arrow-circle-up:before { + content: "\f0aa"; +} +.fa-arrow-circle-down:before { + content: "\f0ab"; +} +.fa-globe:before { + content: "\f0ac"; +} +.fa-wrench:before { + content: "\f0ad"; +} +.fa-tasks:before { + content: "\f0ae"; +} +.fa-filter:before { + content: "\f0b0"; +} +.fa-briefcase:before { + content: "\f0b1"; +} +.fa-arrows-alt:before { + content: "\f0b2"; +} +.fa-group:before, +.fa-users:before { + content: "\f0c0"; +} +.fa-chain:before, +.fa-link:before { + content: "\f0c1"; +} +.fa-cloud:before { + content: "\f0c2"; +} +.fa-flask:before { + content: "\f0c3"; +} +.fa-cut:before, +.fa-scissors:before { + content: "\f0c4"; +} +.fa-copy:before, +.fa-files-o:before { + content: "\f0c5"; +} +.fa-paperclip:before { + content: "\f0c6"; +} +.fa-save:before, +.fa-floppy-o:before { + content: "\f0c7"; +} +.fa-square:before { + content: "\f0c8"; +} +.fa-navicon:before, +.fa-reorder:before, +.fa-bars:before { + content: "\f0c9"; +} +.fa-list-ul:before { + content: "\f0ca"; +} +.fa-list-ol:before { + content: "\f0cb"; +} +.fa-strikethrough:before { + content: "\f0cc"; +} +.fa-underline:before { + content: "\f0cd"; +} +.fa-table:before { + content: "\f0ce"; +} +.fa-magic:before { + content: "\f0d0"; +} +.fa-truck:before { + content: "\f0d1"; +} +.fa-pinterest:before { + content: "\f0d2"; +} +.fa-pinterest-square:before { + content: "\f0d3"; +} +.fa-google-plus-square:before { + content: "\f0d4"; +} +.fa-google-plus:before { + content: "\f0d5"; +} +.fa-money:before { + content: "\f0d6"; +} +.fa-caret-down:before { + content: "\f0d7"; +} +.fa-caret-up:before { + content: "\f0d8"; +} +.fa-caret-left:before { + content: "\f0d9"; +} +.fa-caret-right:before { + content: "\f0da"; +} +.fa-columns:before { + content: "\f0db"; +} +.fa-unsorted:before, +.fa-sort:before { + content: "\f0dc"; +} +.fa-sort-down:before, +.fa-sort-desc:before { + content: "\f0dd"; +} +.fa-sort-up:before, +.fa-sort-asc:before { + content: "\f0de"; +} +.fa-envelope:before { + content: "\f0e0"; +} +.fa-linkedin:before { + content: "\f0e1"; +} +.fa-rotate-left:before, +.fa-undo:before { + content: "\f0e2"; +} +.fa-legal:before, +.fa-gavel:before { + content: "\f0e3"; +} +.fa-dashboard:before, +.fa-tachometer:before { + content: "\f0e4"; +} +.fa-comment-o:before { + content: "\f0e5"; +} +.fa-comments-o:before { + content: "\f0e6"; +} +.fa-flash:before, +.fa-bolt:before { + content: "\f0e7"; +} +.fa-sitemap:before { + content: "\f0e8"; +} +.fa-umbrella:before { + content: "\f0e9"; +} +.fa-paste:before, +.fa-clipboard:before { + content: "\f0ea"; +} +.fa-lightbulb-o:before { + content: "\f0eb"; +} +.fa-exchange:before { + content: "\f0ec"; +} +.fa-cloud-download:before { + content: "\f0ed"; +} +.fa-cloud-upload:before { + content: "\f0ee"; +} +.fa-user-md:before { + content: "\f0f0"; +} +.fa-stethoscope:before { + content: "\f0f1"; +} +.fa-suitcase:before { + content: "\f0f2"; +} +.fa-bell-o:before { + content: "\f0a2"; +} +.fa-coffee:before { + content: "\f0f4"; +} +.fa-cutlery:before { + content: "\f0f5"; +} +.fa-file-text-o:before { + content: "\f0f6"; +} +.fa-building-o:before { + content: "\f0f7"; +} +.fa-hospital-o:before { + content: "\f0f8"; +} +.fa-ambulance:before { + content: "\f0f9"; +} +.fa-medkit:before { + content: "\f0fa"; +} +.fa-fighter-jet:before { + content: "\f0fb"; +} +.fa-beer:before { + content: "\f0fc"; +} +.fa-h-square:before { + content: "\f0fd"; +} +.fa-plus-square:before { + content: "\f0fe"; +} +.fa-angle-double-left:before { + content: "\f100"; +} +.fa-angle-double-right:before { + content: "\f101"; +} +.fa-angle-double-up:before { + content: "\f102"; +} +.fa-angle-double-down:before { + content: "\f103"; +} +.fa-angle-left:before { + content: "\f104"; +} +.fa-angle-right:before { + content: "\f105"; +} +.fa-angle-up:before { + content: "\f106"; +} +.fa-angle-down:before { + content: "\f107"; +} +.fa-desktop:before { + content: "\f108"; +} +.fa-laptop:before { + content: "\f109"; +} +.fa-tablet:before { + content: "\f10a"; +} +.fa-mobile-phone:before, +.fa-mobile:before { + content: "\f10b"; +} +.fa-circle-o:before { + content: "\f10c"; +} +.fa-quote-left:before { + content: "\f10d"; +} +.fa-quote-right:before { + content: "\f10e"; +} +.fa-spinner:before { + content: "\f110"; +} +.fa-circle:before { + content: "\f111"; +} +.fa-mail-reply:before, +.fa-reply:before { + content: "\f112"; +} +.fa-github-alt:before { + content: "\f113"; +} +.fa-folder-o:before { + content: "\f114"; +} +.fa-folder-open-o:before { + content: "\f115"; +} +.fa-smile-o:before { + content: "\f118"; +} +.fa-frown-o:before { + content: "\f119"; +} +.fa-meh-o:before { + content: "\f11a"; +} +.fa-gamepad:before { + content: "\f11b"; +} +.fa-keyboard-o:before { + content: "\f11c"; +} +.fa-flag-o:before { + content: "\f11d"; +} +.fa-flag-checkered:before { + content: "\f11e"; +} +.fa-terminal:before { + content: "\f120"; +} +.fa-code:before { + content: "\f121"; +} +.fa-mail-reply-all:before, +.fa-reply-all:before { + content: "\f122"; +} +.fa-star-half-empty:before, +.fa-star-half-full:before, +.fa-star-half-o:before { + content: "\f123"; +} +.fa-location-arrow:before { + content: "\f124"; +} +.fa-crop:before { + content: "\f125"; +} +.fa-code-fork:before { + content: "\f126"; +} +.fa-unlink:before, +.fa-chain-broken:before { + content: "\f127"; +} +.fa-question:before { + content: "\f128"; +} +.fa-info:before { + content: "\f129"; +} +.fa-exclamation:before { + content: "\f12a"; +} +.fa-superscript:before { + content: "\f12b"; +} +.fa-subscript:before { + content: "\f12c"; +} +.fa-eraser:before { + content: "\f12d"; +} +.fa-puzzle-piece:before { + content: "\f12e"; +} +.fa-microphone:before { + content: "\f130"; +} +.fa-microphone-slash:before { + content: "\f131"; +} +.fa-shield:before { + content: "\f132"; +} +.fa-calendar-o:before { + content: "\f133"; +} +.fa-fire-extinguisher:before { + content: "\f134"; +} +.fa-rocket:before { + content: "\f135"; +} +.fa-maxcdn:before { + content: "\f136"; +} +.fa-chevron-circle-left:before { + content: "\f137"; +} +.fa-chevron-circle-right:before { + content: "\f138"; +} +.fa-chevron-circle-up:before { + content: "\f139"; +} +.fa-chevron-circle-down:before { + content: "\f13a"; +} +.fa-html5:before { + content: "\f13b"; +} +.fa-css3:before { + content: "\f13c"; +} +.fa-anchor:before { + content: "\f13d"; +} +.fa-unlock-alt:before { + content: "\f13e"; +} +.fa-bullseye:before { + content: "\f140"; +} +.fa-ellipsis-h:before { + content: "\f141"; +} +.fa-ellipsis-v:before { + content: "\f142"; +} +.fa-rss-square:before { + content: "\f143"; +} +.fa-play-circle:before { + content: "\f144"; +} +.fa-ticket:before { + content: "\f145"; +} +.fa-minus-square:before { + content: "\f146"; +} +.fa-minus-square-o:before { + content: "\f147"; +} +.fa-level-up:before { + content: "\f148"; +} +.fa-level-down:before { + content: "\f149"; +} +.fa-check-square:before { + content: "\f14a"; +} +.fa-pencil-square:before { + content: "\f14b"; +} +.fa-external-link-square:before { + content: "\f14c"; +} +.fa-share-square:before { + content: "\f14d"; +} +.fa-compass:before { + content: "\f14e"; +} +.fa-toggle-down:before, +.fa-caret-square-o-down:before { + content: "\f150"; +} +.fa-toggle-up:before, +.fa-caret-square-o-up:before { + content: "\f151"; +} +.fa-toggle-right:before, +.fa-caret-square-o-right:before { + content: "\f152"; +} +.fa-euro:before, +.fa-eur:before { + content: "\f153"; +} +.fa-gbp:before { + content: "\f154"; +} +.fa-dollar:before, +.fa-usd:before { + content: "\f155"; +} +.fa-rupee:before, +.fa-inr:before { + content: "\f156"; +} +.fa-cny:before, +.fa-rmb:before, +.fa-yen:before, +.fa-jpy:before { + content: "\f157"; +} +.fa-ruble:before, +.fa-rouble:before, +.fa-rub:before { + content: "\f158"; +} +.fa-won:before, +.fa-krw:before { + content: "\f159"; +} +.fa-bitcoin:before, +.fa-btc:before { + content: "\f15a"; +} +.fa-file:before { + content: "\f15b"; +} +.fa-file-text:before { + content: "\f15c"; +} +.fa-sort-alpha-asc:before { + content: "\f15d"; +} +.fa-sort-alpha-desc:before { + content: "\f15e"; +} +.fa-sort-amount-asc:before { + content: "\f160"; +} +.fa-sort-amount-desc:before { + content: "\f161"; +} +.fa-sort-numeric-asc:before { + content: "\f162"; +} +.fa-sort-numeric-desc:before { + content: "\f163"; +} +.fa-thumbs-up:before { + content: "\f164"; +} +.fa-thumbs-down:before { + content: "\f165"; +} +.fa-youtube-square:before { + content: "\f166"; +} +.fa-youtube:before { + content: "\f167"; +} +.fa-xing:before { + content: "\f168"; +} +.fa-xing-square:before { + content: "\f169"; +} +.fa-youtube-play:before { + content: "\f16a"; +} +.fa-dropbox:before { + content: "\f16b"; +} +.fa-stack-overflow:before { + content: "\f16c"; +} +.fa-instagram:before { + content: "\f16d"; +} +.fa-flickr:before { + content: "\f16e"; +} +.fa-adn:before { + content: "\f170"; +} +.fa-bitbucket:before { + content: "\f171"; +} +.fa-bitbucket-square:before { + content: "\f172"; +} +.fa-tumblr:before { + content: "\f173"; +} +.fa-tumblr-square:before { + content: "\f174"; +} +.fa-long-arrow-down:before { + content: "\f175"; +} +.fa-long-arrow-up:before { + content: "\f176"; +} +.fa-long-arrow-left:before { + content: "\f177"; +} +.fa-long-arrow-right:before { + content: "\f178"; +} +.fa-apple:before { + content: "\f179"; +} +.fa-windows:before { + content: "\f17a"; +} +.fa-android:before { + content: "\f17b"; +} +.fa-linux:before { + content: "\f17c"; +} +.fa-dribbble:before { + content: "\f17d"; +} +.fa-skype:before { + content: "\f17e"; +} +.fa-foursquare:before { + content: "\f180"; +} +.fa-trello:before { + content: "\f181"; +} +.fa-female:before { + content: "\f182"; +} +.fa-male:before { + content: "\f183"; +} +.fa-gittip:before, +.fa-gratipay:before { + content: "\f184"; +} +.fa-sun-o:before { + content: "\f185"; +} +.fa-moon-o:before { + content: "\f186"; +} +.fa-archive:before { + content: "\f187"; +} +.fa-bug:before { + content: "\f188"; +} +.fa-vk:before { + content: "\f189"; +} +.fa-weibo:before { + content: "\f18a"; +} +.fa-renren:before { + content: "\f18b"; +} +.fa-pagelines:before { + content: "\f18c"; +} +.fa-stack-exchange:before { + content: "\f18d"; +} +.fa-arrow-circle-o-right:before { + content: "\f18e"; +} +.fa-arrow-circle-o-left:before { + content: "\f190"; +} +.fa-toggle-left:before, +.fa-caret-square-o-left:before { + content: "\f191"; +} +.fa-dot-circle-o:before { + content: "\f192"; +} +.fa-wheelchair:before { + content: "\f193"; +} +.fa-vimeo-square:before { + content: "\f194"; +} +.fa-turkish-lira:before, +.fa-try:before { + content: "\f195"; +} +.fa-plus-square-o:before { + content: "\f196"; +} +.fa-space-shuttle:before { + content: "\f197"; +} +.fa-slack:before { + content: "\f198"; +} +.fa-envelope-square:before { + content: "\f199"; +} +.fa-wordpress:before { + content: "\f19a"; +} +.fa-openid:before { + content: "\f19b"; +} +.fa-institution:before, +.fa-bank:before, +.fa-university:before { + content: "\f19c"; +} +.fa-mortar-board:before, +.fa-graduation-cap:before { + content: "\f19d"; +} +.fa-yahoo:before { + content: "\f19e"; +} +.fa-google:before { + content: "\f1a0"; +} +.fa-reddit:before { + content: "\f1a1"; +} +.fa-reddit-square:before { + content: "\f1a2"; +} +.fa-stumbleupon-circle:before { + content: "\f1a3"; +} +.fa-stumbleupon:before { + content: "\f1a4"; +} +.fa-delicious:before { + content: "\f1a5"; +} +.fa-digg:before { + content: "\f1a6"; +} +.fa-pied-piper-pp:before { + content: "\f1a7"; +} +.fa-pied-piper-alt:before { + content: "\f1a8"; +} +.fa-drupal:before { + content: "\f1a9"; +} +.fa-joomla:before { + content: "\f1aa"; +} +.fa-language:before { + content: "\f1ab"; +} +.fa-fax:before { + content: "\f1ac"; +} +.fa-building:before { + content: "\f1ad"; +} +.fa-child:before { + content: "\f1ae"; +} +.fa-paw:before { + content: "\f1b0"; +} +.fa-spoon:before { + content: "\f1b1"; +} +.fa-cube:before { + content: "\f1b2"; +} +.fa-cubes:before { + content: "\f1b3"; +} +.fa-behance:before { + content: "\f1b4"; +} +.fa-behance-square:before { + content: "\f1b5"; +} +.fa-steam:before { + content: "\f1b6"; +} +.fa-steam-square:before { + content: "\f1b7"; +} +.fa-recycle:before { + content: "\f1b8"; +} +.fa-automobile:before, +.fa-car:before { + content: "\f1b9"; +} +.fa-cab:before, +.fa-taxi:before { + content: "\f1ba"; +} +.fa-tree:before { + content: "\f1bb"; +} +.fa-spotify:before { + content: "\f1bc"; +} +.fa-deviantart:before { + content: "\f1bd"; +} +.fa-soundcloud:before { + content: "\f1be"; +} +.fa-database:before { + content: "\f1c0"; +} +.fa-file-pdf-o:before { + content: "\f1c1"; +} +.fa-file-word-o:before { + content: "\f1c2"; +} +.fa-file-excel-o:before { + content: "\f1c3"; +} +.fa-file-powerpoint-o:before { + content: "\f1c4"; +} +.fa-file-photo-o:before, +.fa-file-picture-o:before, +.fa-file-image-o:before { + content: "\f1c5"; +} +.fa-file-zip-o:before, +.fa-file-archive-o:before { + content: "\f1c6"; +} +.fa-file-sound-o:before, +.fa-file-audio-o:before { + content: "\f1c7"; +} +.fa-file-movie-o:before, +.fa-file-video-o:before { + content: "\f1c8"; +} +.fa-file-code-o:before { + content: "\f1c9"; +} +.fa-vine:before { + content: "\f1ca"; +} +.fa-codepen:before { + content: "\f1cb"; +} +.fa-jsfiddle:before { + content: "\f1cc"; +} +.fa-life-bouy:before, +.fa-life-buoy:before, +.fa-life-saver:before, +.fa-support:before, +.fa-life-ring:before { + content: "\f1cd"; +} +.fa-circle-o-notch:before { + content: "\f1ce"; +} +.fa-ra:before, +.fa-resistance:before, +.fa-rebel:before { + content: "\f1d0"; +} +.fa-ge:before, +.fa-empire:before { + content: "\f1d1"; +} +.fa-git-square:before { + content: "\f1d2"; +} +.fa-git:before { + content: "\f1d3"; +} +.fa-y-combinator-square:before, +.fa-yc-square:before, +.fa-hacker-news:before { + content: "\f1d4"; +} +.fa-tencent-weibo:before { + content: "\f1d5"; +} +.fa-qq:before { + content: "\f1d6"; +} +.fa-wechat:before, +.fa-weixin:before { + content: "\f1d7"; +} +.fa-send:before, +.fa-paper-plane:before { + content: "\f1d8"; +} +.fa-send-o:before, +.fa-paper-plane-o:before { + content: "\f1d9"; +} +.fa-history:before { + content: "\f1da"; +} +.fa-circle-thin:before { + content: "\f1db"; +} +.fa-header:before { + content: "\f1dc"; +} +.fa-paragraph:before { + content: "\f1dd"; +} +.fa-sliders:before { + content: "\f1de"; +} +.fa-share-alt:before { + content: "\f1e0"; +} +.fa-share-alt-square:before { + content: "\f1e1"; +} +.fa-bomb:before { + content: "\f1e2"; +} +.fa-soccer-ball-o:before, +.fa-futbol-o:before { + content: "\f1e3"; +} +.fa-tty:before { + content: "\f1e4"; +} +.fa-binoculars:before { + content: "\f1e5"; +} +.fa-plug:before { + content: "\f1e6"; +} +.fa-slideshare:before { + content: "\f1e7"; +} +.fa-twitch:before { + content: "\f1e8"; +} +.fa-yelp:before { + content: "\f1e9"; +} +.fa-newspaper-o:before { + content: "\f1ea"; +} +.fa-wifi:before { + content: "\f1eb"; +} +.fa-calculator:before { + content: "\f1ec"; +} +.fa-paypal:before { + content: "\f1ed"; +} +.fa-google-wallet:before { + content: "\f1ee"; +} +.fa-cc-visa:before { + content: "\f1f0"; +} +.fa-cc-mastercard:before { + content: "\f1f1"; +} +.fa-cc-discover:before { + content: "\f1f2"; +} +.fa-cc-amex:before { + content: "\f1f3"; +} +.fa-cc-paypal:before { + content: "\f1f4"; +} +.fa-cc-stripe:before { + content: "\f1f5"; +} +.fa-bell-slash:before { + content: "\f1f6"; +} +.fa-bell-slash-o:before { + content: "\f1f7"; +} +.fa-trash:before { + content: "\f1f8"; +} +.fa-copyright:before { + content: "\f1f9"; +} +.fa-at:before { + content: "\f1fa"; +} +.fa-eyedropper:before { + content: "\f1fb"; +} +.fa-paint-brush:before { + content: "\f1fc"; +} +.fa-birthday-cake:before { + content: "\f1fd"; +} +.fa-area-chart:before { + content: "\f1fe"; +} +.fa-pie-chart:before { + content: "\f200"; +} +.fa-line-chart:before { + content: "\f201"; +} +.fa-lastfm:before { + content: "\f202"; +} +.fa-lastfm-square:before { + content: "\f203"; +} +.fa-toggle-off:before { + content: "\f204"; +} +.fa-toggle-on:before { + content: "\f205"; +} +.fa-bicycle:before { + content: "\f206"; +} +.fa-bus:before { + content: "\f207"; +} +.fa-ioxhost:before { + content: "\f208"; +} +.fa-angellist:before { + content: "\f209"; +} +.fa-cc:before { + content: "\f20a"; +} +.fa-shekel:before, +.fa-sheqel:before, +.fa-ils:before { + content: "\f20b"; +} +.fa-meanpath:before { + content: "\f20c"; +} +.fa-buysellads:before { + content: "\f20d"; +} +.fa-connectdevelop:before { + content: "\f20e"; +} +.fa-dashcube:before { + content: "\f210"; +} +.fa-forumbee:before { + content: "\f211"; +} +.fa-leanpub:before { + content: "\f212"; +} +.fa-sellsy:before { + content: "\f213"; +} +.fa-shirtsinbulk:before { + content: "\f214"; +} +.fa-simplybuilt:before { + content: "\f215"; +} +.fa-skyatlas:before { + content: "\f216"; +} +.fa-cart-plus:before { + content: "\f217"; +} +.fa-cart-arrow-down:before { + content: "\f218"; +} +.fa-diamond:before { + content: "\f219"; +} +.fa-ship:before { + content: "\f21a"; +} +.fa-user-secret:before { + content: "\f21b"; +} +.fa-motorcycle:before { + content: "\f21c"; +} +.fa-street-view:before { + content: "\f21d"; +} +.fa-heartbeat:before { + content: "\f21e"; +} +.fa-venus:before { + content: "\f221"; +} +.fa-mars:before { + content: "\f222"; +} +.fa-mercury:before { + content: "\f223"; +} +.fa-intersex:before, +.fa-transgender:before { + content: "\f224"; +} +.fa-transgender-alt:before { + content: "\f225"; +} +.fa-venus-double:before { + content: "\f226"; +} +.fa-mars-double:before { + content: "\f227"; +} +.fa-venus-mars:before { + content: "\f228"; +} +.fa-mars-stroke:before { + content: "\f229"; +} +.fa-mars-stroke-v:before { + content: "\f22a"; +} +.fa-mars-stroke-h:before { + content: "\f22b"; +} +.fa-neuter:before { + content: "\f22c"; +} +.fa-genderless:before { + content: "\f22d"; +} +.fa-facebook-official:before { + content: "\f230"; +} +.fa-pinterest-p:before { + content: "\f231"; +} +.fa-whatsapp:before { + content: "\f232"; +} +.fa-server:before { + content: "\f233"; +} +.fa-user-plus:before { + content: "\f234"; +} +.fa-user-times:before { + content: "\f235"; +} +.fa-hotel:before, +.fa-bed:before { + content: "\f236"; +} +.fa-viacoin:before { + content: "\f237"; +} +.fa-train:before { + content: "\f238"; +} +.fa-subway:before { + content: "\f239"; +} +.fa-medium:before { + content: "\f23a"; +} +.fa-yc:before, +.fa-y-combinator:before { + content: "\f23b"; +} +.fa-optin-monster:before { + content: "\f23c"; +} +.fa-opencart:before { + content: "\f23d"; +} +.fa-expeditedssl:before { + content: "\f23e"; +} +.fa-battery-4:before, +.fa-battery:before, +.fa-battery-full:before { + content: "\f240"; +} +.fa-battery-3:before, +.fa-battery-three-quarters:before { + content: "\f241"; +} +.fa-battery-2:before, +.fa-battery-half:before { + content: "\f242"; +} +.fa-battery-1:before, +.fa-battery-quarter:before { + content: "\f243"; +} +.fa-battery-0:before, +.fa-battery-empty:before { + content: "\f244"; +} +.fa-mouse-pointer:before { + content: "\f245"; +} +.fa-i-cursor:before { + content: "\f246"; +} +.fa-object-group:before { + content: "\f247"; +} +.fa-object-ungroup:before { + content: "\f248"; +} +.fa-sticky-note:before { + content: "\f249"; +} +.fa-sticky-note-o:before { + content: "\f24a"; +} +.fa-cc-jcb:before { + content: "\f24b"; +} +.fa-cc-diners-club:before { + content: "\f24c"; +} +.fa-clone:before { + content: "\f24d"; +} +.fa-balance-scale:before { + content: "\f24e"; +} +.fa-hourglass-o:before { + content: "\f250"; +} +.fa-hourglass-1:before, +.fa-hourglass-start:before { + content: "\f251"; +} +.fa-hourglass-2:before, +.fa-hourglass-half:before { + content: "\f252"; +} +.fa-hourglass-3:before, +.fa-hourglass-end:before { + content: "\f253"; +} +.fa-hourglass:before { + content: "\f254"; +} +.fa-hand-grab-o:before, +.fa-hand-rock-o:before { + content: "\f255"; +} +.fa-hand-stop-o:before, +.fa-hand-paper-o:before { + content: "\f256"; +} +.fa-hand-scissors-o:before { + content: "\f257"; +} +.fa-hand-lizard-o:before { + content: "\f258"; +} +.fa-hand-spock-o:before { + content: "\f259"; +} +.fa-hand-pointer-o:before { + content: "\f25a"; +} +.fa-hand-peace-o:before { + content: "\f25b"; +} +.fa-trademark:before { + content: "\f25c"; +} +.fa-registered:before { + content: "\f25d"; +} +.fa-creative-commons:before { + content: "\f25e"; +} +.fa-gg:before { + content: "\f260"; +} +.fa-gg-circle:before { + content: "\f261"; +} +.fa-tripadvisor:before { + content: "\f262"; +} +.fa-odnoklassniki:before { + content: "\f263"; +} +.fa-odnoklassniki-square:before { + content: "\f264"; +} +.fa-get-pocket:before { + content: "\f265"; +} +.fa-wikipedia-w:before { + content: "\f266"; +} +.fa-safari:before { + content: "\f267"; +} +.fa-chrome:before { + content: "\f268"; +} +.fa-firefox:before { + content: "\f269"; +} +.fa-opera:before { + content: "\f26a"; +} +.fa-internet-explorer:before { + content: "\f26b"; +} +.fa-tv:before, +.fa-television:before { + content: "\f26c"; +} +.fa-contao:before { + content: "\f26d"; +} +.fa-500px:before { + content: "\f26e"; +} +.fa-amazon:before { + content: "\f270"; +} +.fa-calendar-plus-o:before { + content: "\f271"; +} +.fa-calendar-minus-o:before { + content: "\f272"; +} +.fa-calendar-times-o:before { + content: "\f273"; +} +.fa-calendar-check-o:before { + content: "\f274"; +} +.fa-industry:before { + content: "\f275"; +} +.fa-map-pin:before { + content: "\f276"; +} +.fa-map-signs:before { + content: "\f277"; +} +.fa-map-o:before { + content: "\f278"; +} +.fa-map:before { + content: "\f279"; +} +.fa-commenting:before { + content: "\f27a"; +} +.fa-commenting-o:before { + content: "\f27b"; +} +.fa-houzz:before { + content: "\f27c"; +} +.fa-vimeo:before { + content: "\f27d"; +} +.fa-black-tie:before { + content: "\f27e"; +} +.fa-fonticons:before { + content: "\f280"; +} +.fa-reddit-alien:before { + content: "\f281"; +} +.fa-edge:before { + content: "\f282"; +} +.fa-credit-card-alt:before { + content: "\f283"; +} +.fa-codiepie:before { + content: "\f284"; +} +.fa-modx:before { + content: "\f285"; +} +.fa-fort-awesome:before { + content: "\f286"; +} +.fa-usb:before { + content: "\f287"; +} +.fa-product-hunt:before { + content: "\f288"; +} +.fa-mixcloud:before { + content: "\f289"; +} +.fa-scribd:before { + content: "\f28a"; +} +.fa-pause-circle:before { + content: "\f28b"; +} +.fa-pause-circle-o:before { + content: "\f28c"; +} +.fa-stop-circle:before { + content: "\f28d"; +} +.fa-stop-circle-o:before { + content: "\f28e"; +} +.fa-shopping-bag:before { + content: "\f290"; +} +.fa-shopping-basket:before { + content: "\f291"; +} +.fa-hashtag:before { + content: "\f292"; +} +.fa-bluetooth:before { + content: "\f293"; +} +.fa-bluetooth-b:before { + content: "\f294"; +} +.fa-percent:before { + content: "\f295"; +} +.fa-gitlab:before { + content: "\f296"; +} +.fa-wpbeginner:before { + content: "\f297"; +} +.fa-wpforms:before { + content: "\f298"; +} +.fa-envira:before { + content: "\f299"; +} +.fa-universal-access:before { + content: "\f29a"; +} +.fa-wheelchair-alt:before { + content: "\f29b"; +} +.fa-question-circle-o:before { + content: "\f29c"; +} +.fa-blind:before { + content: "\f29d"; +} +.fa-audio-description:before { + content: "\f29e"; +} +.fa-volume-control-phone:before { + content: "\f2a0"; +} +.fa-braille:before { + content: "\f2a1"; +} +.fa-assistive-listening-systems:before { + content: "\f2a2"; +} +.fa-asl-interpreting:before, +.fa-american-sign-language-interpreting:before { + content: "\f2a3"; +} +.fa-deafness:before, +.fa-hard-of-hearing:before, +.fa-deaf:before { + content: "\f2a4"; +} +.fa-glide:before { + content: "\f2a5"; +} +.fa-glide-g:before { + content: "\f2a6"; +} +.fa-signing:before, +.fa-sign-language:before { + content: "\f2a7"; +} +.fa-low-vision:before { + content: "\f2a8"; +} +.fa-viadeo:before { + content: "\f2a9"; +} +.fa-viadeo-square:before { + content: "\f2aa"; +} +.fa-snapchat:before { + content: "\f2ab"; +} +.fa-snapchat-ghost:before { + content: "\f2ac"; +} +.fa-snapchat-square:before { + content: "\f2ad"; +} +.fa-pied-piper:before { + content: "\f2ae"; +} +.fa-first-order:before { + content: "\f2b0"; +} +.fa-yoast:before { + content: "\f2b1"; +} +.fa-themeisle:before { + content: "\f2b2"; +} +.fa-google-plus-circle:before, +.fa-google-plus-official:before { + content: "\f2b3"; +} +.fa-fa:before, +.fa-font-awesome:before { + content: "\f2b4"; +} +.fa-handshake-o:before { + content: "\f2b5"; +} +.fa-envelope-open:before { + content: "\f2b6"; +} +.fa-envelope-open-o:before { + content: "\f2b7"; +} +.fa-linode:before { + content: "\f2b8"; +} +.fa-address-book:before { + content: "\f2b9"; +} +.fa-address-book-o:before { + content: "\f2ba"; +} +.fa-vcard:before, +.fa-address-card:before { + content: "\f2bb"; +} +.fa-vcard-o:before, +.fa-address-card-o:before { + content: "\f2bc"; +} +.fa-user-circle:before { + content: "\f2bd"; +} +.fa-user-circle-o:before { + content: "\f2be"; +} +.fa-user-o:before { + content: "\f2c0"; +} +.fa-id-badge:before { + content: "\f2c1"; +} +.fa-drivers-license:before, +.fa-id-card:before { + content: "\f2c2"; +} +.fa-drivers-license-o:before, +.fa-id-card-o:before { + content: "\f2c3"; +} +.fa-quora:before { + content: "\f2c4"; +} +.fa-free-code-camp:before { + content: "\f2c5"; +} +.fa-telegram:before { + content: "\f2c6"; +} +.fa-thermometer-4:before, +.fa-thermometer:before, +.fa-thermometer-full:before { + content: "\f2c7"; +} +.fa-thermometer-3:before, +.fa-thermometer-three-quarters:before { + content: "\f2c8"; +} +.fa-thermometer-2:before, +.fa-thermometer-half:before { + content: "\f2c9"; +} +.fa-thermometer-1:before, +.fa-thermometer-quarter:before { + content: "\f2ca"; +} +.fa-thermometer-0:before, +.fa-thermometer-empty:before { + content: "\f2cb"; +} +.fa-shower:before { + content: "\f2cc"; +} +.fa-bathtub:before, +.fa-s15:before, +.fa-bath:before { + content: "\f2cd"; +} +.fa-podcast:before { + content: "\f2ce"; +} +.fa-window-maximize:before { + content: "\f2d0"; +} +.fa-window-minimize:before { + content: "\f2d1"; +} +.fa-window-restore:before { + content: "\f2d2"; +} +.fa-times-rectangle:before, +.fa-window-close:before { + content: "\f2d3"; +} +.fa-times-rectangle-o:before, +.fa-window-close-o:before { + content: "\f2d4"; +} +.fa-bandcamp:before { + content: "\f2d5"; +} +.fa-grav:before { + content: "\f2d6"; +} +.fa-etsy:before { + content: "\f2d7"; +} +.fa-imdb:before { + content: "\f2d8"; +} +.fa-ravelry:before { + content: "\f2d9"; +} +.fa-eercast:before { + content: "\f2da"; +} +.fa-microchip:before { + content: "\f2db"; +} +.fa-snowflake-o:before { + content: "\f2dc"; +} +.fa-superpowers:before { + content: "\f2dd"; +} +.fa-wpexplorer:before { + content: "\f2de"; +} +.fa-meetup:before { + content: "\f2e0"; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} diff --git a/3.8.13/reference/html/css/site.css b/3.8.13/reference/html/css/site.css new file mode 100755 index 0000000000..1e660829dd --- /dev/null +++ b/3.8.13/reference/html/css/site.css @@ -0,0 +1,7 @@ +/* + +Source Available at https://github.com/spring-io/asciidoctor-spring-backend */ + + +:root{--html-font-size:1em;--pixel-to-rem:16 * 1rem;--font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--font-weight:400;--monospace-font-family:"SFMono-Regular","Consolas","Liberation Mono","Menlo",monospace;--body-background-color:#fff;--panel-background-color:#f6f8fa;--panel-group-background-color:#e1e8e8;--panel-border-color:#eaedf0;--color-accent-1:#ebf2f2;--color-accent-2:#d7e7e7;--color-accent-3:#6db33f;--body-font-color:#191e1e;--body-font-light-color:#273030;--body-font-dark-color:#141818;--link-font-color:#1565c0;--hover-link-font-color:#104d92;--scrollbar-thumb-color:silver;--mark-background-color:#39ff14;--selected-background-color:#191e1e;--layout-banner-logo-offset:18px;--layout-banner-logo-height:50px;--layout-max-width:1400px;--layout-banner-height:80px;--layout-border-color:var(--color-accent-1);--layout-switchtheme-invert-filter:invert();--layout-switchtheme-background-color:var(--body-background-color);--layout-switchtheme-button-color:var(--body-background-color);--layout-switchtheme-button-hover-color:var(--color-accent-1);--asciidoctor-doc-embellishment-margin-width:250px;--asciidoctor-doc-background-embellishment-height:147px;--asciidoctor-details-background:var(--color-accent-1);--asciidoctor-details-font-color:var(--body-font-light-color);--asciidoctor-author-separator-color:var(--color-accent-3);--asciidoctor-panel-background:var(--panel-background-color);--asciidoctor-panel-border-color:var(--panel-border-color);--asciidoctor-font-color:var(--body-font-color);--asciidoctor-heading-font-color:var(--body-font-dark-color);--asciidoctor-heading-font-weight:600;--asciidoctor-alt-heading-font-weight:600;--asciidoctor-section-divider-color:var(--color-accent-1);--asciidoctor-link-font-color:var(--link-font-color);--asciidoctor-hover-link-font-color:var(--hover-link-font-color);--asciidoctor-unresolved-link-font-color:#d32f2f;--asciidoctor-code-font-color:var(--asciidoctor-font-color);--asciidoctor-code-link-font-color:var(--link-font-color);--asciidoctor-code-background:rgba(27,31,35,.05);--asciidoctor-code-data-lang-color:#999;--asciidoctor-table-border-color:var(--asciidoctor-panel-border-color);--asciidoctor-table-header-footer-background:var(--color-accent-1);--asciidoctor-table-stripe-background:var(--color-accent-1);--asciidoctor-table-footer-background:linear-gradient(to bottom,var(--color-accent-1) 0%,var(--body-background-color) 100%);--asciidoctor-admonition-background:var(--color-accent-1);--asciidoctor-admonition-pre-background:var(--color-accent-2);--asciidoctor-admonition-label-font-weight:500;--asciidoctor-admonition-font-color:#f0f0f0;--asciidoctor-admonition-caution-background:#561164;--asciidoctor-admonition-important-background:#960000;--asciidoctor-admonition-note-background:#015785;--asciidoctor-admonition-tip-background:#3e6b1f;--asciidoctor-admonition-warning-background:#bd7400;--asciidoctor-abstract-background:var(--asciidoctor-panel-background);--asciidoctor-abstract-border-color:var(--asciidoctor-panel-border-color);--asciidoctor-quote-background:var(--color-accent-1);--asciidoctor-quote-border-color:var(--color-accent-3);--asciidoctor-quote-attribution-font-color:var(--color-accent-3);--asciidoctor-caption-font-color:var(--body-font-light-color);--asciidoctor-caption-font-weight:400;--asciidoctor-example-background:var(--asciidoctor-panel-background);--asciidoctor-example-border-color:var(--asciidoctor-panel-border-color);--asciidoctor-sidebar-background:var(--color-accent-1);--asciidoctor-pre-background:var(--asciidoctor-panel-background);--asciidoctor-pre-border-color:var(--asciidoctor-panel-border-color);--asciidoctor-callout-background:var(--body-font-dark-color);--asciidoctor-callout-font-color:var(--body-background-color);--asciidoctor-footer-font-color:#b6b6b6;--highlight-background-color:var(--asciidoctor-pre-background);--highlight-font-color:#24292e;--highlight-keyword-font-color:#d73a49;--highlight-comment-font-color:#6a737d;--highlight-string-font-color:#032f62;--highlight-meta-font-color:#6a737d;--highlight-constant-font-color:#032f62;--highlight-variable-font-color:#005cc5;--highlight-tag-font-color:#22863a;--highlight-tag-attribute-font-color:#6f42c1;--highlight-type-font-color:#6f42c1;--highlight-link-font-color:var(--link-font-color);--highlight-addition-font-color:#22863a;--highlight-deletion-font-color:#24292e;--highlight-regex-font-color:#032f62;--tabs-border-color:var(--selected-background-color);--tabs-background-color:var(--body-background-color);--tabs-font-color:var(--body-font-color);--tabs-selected-background-color:var(--selected-background-color);--tabs-selected-font-color:var(--body-background-color);--tabs-hover-font-color:var(--hover-link-font-color);--tabs-hover-background:var(--color-accent-1);--tabs-group-background-color:var(--panel-group-background-color);--toc-width:24rem;--toc-display:block;--toc-font-color:var(--body-font-color);--toc-hover-background-color:var(--color-accent-1);--toc-active-background-color:var(--selected-background-color);--toc-active-font-color:var(--body-background-color);--toc-back-to-index-filter:none;--toc-bar-display:none;--toc-bar-height:0;--toc-bar-button-filter:none;--codetools-button-filter:none;--codetools-button-active-filter:invert();--codetools-background-color:var(--body-background-color);--codetools-border-color:rgba(0,0,0,.3);--codetools-hover-background-color:var(--color-accent-1);--codetools-divider-color:var(--codetools-border-color);--codetools-popup-background-color:var(--selected-background-color);--codetools-popup-font-color:var(--body-background-color)}@media screen and (max-width:1024px){:root{--toc-width:16rem;--asciidoctor-doc-embellishment-margin-width:140px}}@media screen and (max-width:800px){:root{--layout-banner-height:51px;--layout-banner-logo-height:30px;--layout-banner-logo-offset:10px;--layout-border-color:var(--body-background-color);--toc-bar-display:block;--toc-bar-height:24px;--toc-width:0;--toc-display:none;--asciidoctor-doc-embellishment-margin-width:0}}html.dark-theme{--font-weight:300;--body-background-color:#1b1f23;--panel-background-color:#262a2d;--panel-group-background-color:#303741;--panel-border-color:#2c3135;--color-accent-1:#272c33;--color-accent-1-invert:#d8d3cc;--color-accent-2:#2d333a;--color-accent-3:#6db33f;--body-font-color:#bbbcbe;--body-font-light-color:#abacaf;--body-font-dark-color:#cecfd1;--link-font-color:#086dc3;--hover-link-font-color:#107ddd;--scrollbar-thumb-color:#5f5f5f;--mark-background-color:#2eca12;--selected-background-color:#8d8d8d;--layout-switchtheme-invert-filter:none;--layout-switchtheme-background-color:var(--selected-background-color);--asciidoctor-code-background:rgba(177,209,241,.15);--asciidoctor-code-data-lang-color:#6e6e6e;--asciidoctor-admonition-font-color:#f0f0f0;--asciidoctor-admonition-caution-background:#603668;--asciidoctor-admonition-important-background:#924040;--asciidoctor-admonition-note-background:#355463;--asciidoctor-admonition-tip-background:#4d6340;--asciidoctor-admonition-warning-background:#967745;--asciidoctor-footer-font-color:#5e5e5e;--highlight-background-color:var(--asciidoctor-pre-background);--highlight-font-color:#f6f8fa;--highlight-keyword-font-color:#ea4a5a;--highlight-comment-font-color:#959da5;--highlight-string-font-color:#79b8ff;--highlight-meta-font-color:#959da5;--highlight-constant-font-color:#79b8ff;--highlight-variable-font-color:#c8e1ff;--highlight-tag-font-color:#7bcc72;--highlight-tag-attribute-font-color:#b392f0;--highlight-type-font-color:#b392f0;--highlight-link-font-color:#1565c0;--highlight-addition-font-color:#7bcc72;--highlight-deletion-font-color:#f6f8fa;--highlight-regex-font-color:#79b8ff;--toc-back-to-index-filter:invert();--toc-bar-button-filter:invert();--codetools-button-filter:invert();--codetools-button-active-filter:none;--codetools-hover-background-color:var(--color-accent-1-invert);--codetools-border-color:hsla(0,0%,100%,.274);--codetools-divider-color:rgba(44,44,44,.274)}html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}body{-webkit-text-size-adjust:none;-moz-text-size-adjust:none;text-size-adjust:none}html{font-size:var(--html-font-size);height:100%;min-width:340px;scroll-behavior:smooth}body{background-color:var(--body-background-color);color:var(--body-font-color);font-family:var(--font-family);font-weight:var(--font-weight);margin:0;overflow-wrap:anywhere;overscroll-behavior:none}a{text-decoration:none}a:hover{text-decoration:underline}a:active{background-color:none}code,kbd,pre{font-family:var(--monospace-font-family)}@supports (scrollbar-width:thin){body *{scrollbar-color:var(--scrollbar-thumb-color) transparent;scrollbar-width:thin}}table{word-wrap:normal;border-collapse:collapse}mark{background:var(--mark-background-color)}#banner-container{border-bottom:1px solid var(--layout-border-color);height:var(--layout-banner-height);overflow:hidden}#banner{background:no-repeat top var(--layout-banner-logo-offset) left var(--layout-banner-logo-offset) /auto var(--layout-banner-logo-height);background-image:url(../img/banner-logo.svg);height:100%}#doc{overflow:auto}.contained{margin:0 auto;max-width:var(--layout-max-width)}#switch-theme label,div#switch-theme{display:none}html.js div#switch-theme{display:block;float:right;margin:8px 6px 0 0}#switch-theme input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:no-repeat url(../img/octicons-16.svg#view-sun) 90% 50%/16px 16px,no-repeat url(../img/octicons-16.svg#view-moon) 10% 50%/16px 16px;background-color:var(--layout-switchtheme-background-color);height:22px;outline:none;position:relative;width:40px}#switch-theme input,#switch-theme input:before{border-radius:25px;filter:var(--layout-switchtheme-invert-filter)}#switch-theme input:before{background-color:var(--layout-switchtheme-button-color);content:"";height:18px;left:2px;position:absolute;top:2px;transition:transform .2s;width:18px}#switch-theme:hover input:before{background-color:var(--layout-switchtheme-button-hover-color)}#switch-theme input:checked:before{transform:translateX(18px)}.doc{color:var(--asciidoctor-font-color);-webkit-hyphens:none;hyphens:none;letter-spacing:-.0027777778rem;line-height:1.6;margin:0}.doc #content,.doc #footer{margin:0 2rem}.doc #header>:not(#toc){margin-left:2rem;margin-right:2rem}.doc #content{padding-bottom:4rem}#doc{background:no-repeat 100% 0/305px 147px;background-image:url(../img/doc-background.svg)}.doc #header{margin-right:var(--asciidoctor-doc-embellishment-margin-width)}.doc #header .details{background:var(--asciidoctor-details-background);color:var(--asciidoctor-details-font-color);font-size:.8em;font-weight:600;padding:1rem 1.5rem}.doc #header div.details{display:flex;flex-wrap:wrap}#header .details br{display:none}.doc #header .details span.author:not(:last-of-type):after{color:var(--asciidoctor-author-separator-color);content:"\2022";font-weight:400;margin:.4em}.doc #header .details span.last-author:after{display:none}.doc #header .details #revnumber{flex-basis:100%;font-weight:200;margin-top:.5rem;text-transform:capitalize}.doc #preamble+.sect1,.doc .sect1+.sect1{margin-top:2rem}.doc .sect1+.sect1{border-top:1px solid var(--asciidoctor-section-divider-color)}.doc h1{font-size:2.3em}.doc h2{font-size:2em}.doc h3{font-size:1.7em}.doc h4{font-size:1.6em}.doc h5{font-size:1.4em}.doc h6{font-size:1.3em}.doc h1,.doc h2,.doc h3,.doc h4,.doc h5,.doc h6{color:var(--asciidoctor-heading-font-color);font-weight:var(--asciidoctor-heading-font-weight);-webkit-hyphens:none;hyphens:none;line-height:1.3;margin:1.3rem 0 0;padding-top:1.8rem}.doc h1.sect0{background:var(--asciidoctor-abstract-background);font-size:1.8em;margin:1.5rem -1rem 0;padding:.5rem 1rem}.doc h1:first-child{margin:1.3rem 0}.doc h2:not(.discrete){margin-left:-1rem;margin-right:-1rem;padding:1.8rem 1rem .1rem}.doc h3:not(.discrete){font-weight:var(--asciidoctor-alt-heading-font-weight)}.doc h1 .anchor,.doc h2 .anchor,.doc h3 .anchor,.doc h4 .anchor,.doc h5 .anchor,.doc h6 .anchor{font-weight:400;margin-left:-2ex;padding-left:.5ex;position:absolute;text-decoration:none;visibility:hidden;width:2.25ex}.doc h1 .anchor:before,.doc h2 .anchor:before,.doc h3 .anchor:before,.doc h4 .anchor:before,.doc h5 .anchor:before,.doc h6 .anchor:before{content:"\0023"}.doc h1:hover .anchor,.doc h2:hover .anchor,.doc h3:hover .anchor,.doc h4:hover .anchor,.doc h5:hover .anchor,.doc h6:hover .anchor{visibility:visible}.doc dl,.doc p{margin:0}.doc a.bare{-webkit-hyphens:none;hyphens:none}.doc a{color:var(--asciidoctor-link-font-color)}.doc a:hover{color:var(--asciidoctor-hover-link-font-color)}.doc a.unresolved{color:var(--asciidoctor-unresolved-link-font-color)}.doc .admonitionblock code,.doc p code,.doc thead code{background:var(--asciidoctor-code-background);border-radius:.25em;color:var(--asciidoctor-code-font-color);font-size:.95em;padding:.125em .25em}.doc .admonitionblock a code,.doc p a code,.doc thead a code{color:var(--asciidoctor-code-link-font-color)}.doc code,.doc pre{-webkit-hyphens:none;hyphens:none}.doc pre{font-size:calc(14/var(--pixel-to-rem));line-height:1.3;margin:0}.doc .listingblock pre:not(.highlight),.doc .literalblock pre,.doc pre.highlight code{background:var(--asciidoctor-pre-background);border-radius:4px;box-shadow:inset 0 0 1.75px var(--asciidoctor-pre-border-color);display:block;overflow-x:auto;padding:.95rem}.doc pre.highlight code[data-lang]:before{color:var(--asciidoctor-code-data-lang-color);content:attr(data-lang);display:block;font-size:.65em;line-height:1;position:absolute;right:.3rem;text-transform:uppercase;top:.3rem}.doc pre.highlight{position:relative}.doc table pre.highlight code[data-lang]:before{display:none}.doc blockquote{margin:0}.doc .paragraph.lead>p{font-size:calc(18/var(--pixel-to-rem))}.doc .dlist,.doc .exampleblock,.doc .hdlist,.doc .imageblock,.doc .listingblock,.doc .literalblock,.doc .olist,.doc .paragraph,.doc .partintro,.doc .quoteblock,.doc .sidebarblock,.doc .ulist,.doc .verseblock,.doc details,.doc hr{margin:1rem 0 0}.doc table.tableblock{display:block;overflow-x:auto;width:100%}.doc table.tableblock td{min-width:6rem}.doc table.tableblock{font-size:calc(15/var(--pixel-to-rem));margin:1.5rem 0 0}.doc table.tableblock+*{margin-top:2rem}.doc td.tableblock>.content>:first-child{margin-top:0}.doc table.tableblock td,.doc table.tableblock th{padding:.5rem}.doc table.tableblock thead th{border-bottom:2.5px solid var(--asciidoctor-table-border-color)}.doc table.tableblock td,.doc table.tableblock>:not(thead) th{border-bottom:1px solid var(--asciidoctor-table-border-color);border-top:1px solid var(--asciidoctor-table-border-color)}.doc table.stripes-all>tbody>tr,.doc table.stripes-even>tbody>tr:nth-of-type(2n),.doc table.stripes-hover>tbody>tr:hover,.doc table.stripes-odd>tbody>tr:nth-of-type(odd){background:var(--asciidoctor-table-stripe-background)}.doc table.tableblock>tfoot{background:var(--asciidoctor-table-footer-background)}.doc .listingblock.wrap pre,.doc .tableblock code,.doc .tableblock pre{white-space:pre-wrap}.doc td:first-child .listingblock.wrap pre,.doc td:first-child .tableblock code,.doc td:first-child .tableblock pre{white-space:nowrap}.doc .admonitionblock{margin:2.5rem 0}.doc .admonitionblock p,.doc .admonitionblock td.content{font-size:calc(16/var(--pixel-to-rem))}.doc .admonitionblock td.content>.title+*,.doc .admonitionblock td.content>:not(.title):first-child{margin-top:0}.doc .admonitionblock pre{border:none;font-size:calc(14/var(--pixel-to-rem))}.doc .admonitionblock>table{position:relative;table-layout:fixed;width:100%}.doc .admonitionblock td.content{word-wrap:anywhere;background:var(--asciidoctor-admonition-background);padding:1rem 1rem .75rem;width:100%}.doc .admonitionblock td.icon{border-radius:.45rem;font-size:calc(16/var(--pixel-to-rem));left:0;line-height:1;padding:.25em .075em;position:absolute;top:0;transform:translate(-.5rem,-50%)}.doc .admonitionblock .icon i{align-items:center;background-position:.5em 0;background-repeat:no-repeat;display:inline-flex;filter:invert(100%);height:16px;padding-left:2em;vertical-align:initial;width:auto}.doc .admonitionblock .icon i:after{border-left:1px solid hsla(0,0%,100%,.3);color:var(--asciidoctor-admonition-font-color);content:attr(title);filter:invert(100%);font-style:normal;font-weight:var(--asciidoctor-admonition-label-font-weight);-webkit-hyphens:none;hyphens:none;margin:-.05em;padding:0 .5em;text-transform:capitalize}i.fa{background-size:16px 16px}i.fa.icon-caution{background-image:url(../img/octicons-16.svg#view-flame)}i.fa.icon-important{background-image:url(../img/octicons-16.svg#view-stop)}i.fa.icon-note{background-image:url(../img/octicons-16.svg#view-info)}i.fa.icon-tip{background-image:url(../img/octicons-16.svg#view-light-bulb)}i.fa.icon-warning{background-image:url(../img/octicons-16.svg#view-alert)}.doc .admonitionblock.caution td.icon{background:var(--asciidoctor-admonition-caution-background)}.doc .admonitionblock.important td.icon{background:var(--asciidoctor-admonition-important-background)}.doc .admonitionblock.note .icon{background:var(--asciidoctor-admonition-note-background)}.doc .admonitionblock.tip .icon{background:var(--asciidoctor-admonition-tip-background)}.doc .admonitionblock.warning .icon{background-color:var(--asciidoctor-admonition-warning-background)}.doc .imageblock{align-items:center;display:flex;flex-direction:column}.doc .image>img,.doc .imageblock img{display:inline-block;height:auto;max-width:100%;vertical-align:middle}.doc .image:not(.left):not(.right)>img{margin-top:-.2em}.doc #preamble .abstract blockquote{background:var(--asciidoctor-abstract-background);border-left:5px solid var(--asciidoctor-abstract-border-color);font-size:calc(16/var(--pixel-to-rem));padding:.75em 1em}.doc .quoteblock,.doc .verseblock{background:var(--asciidoctor-quote-background);border-left:5px solid var(--asciidoctor-quote-border-color)}.doc .quoteblock{padding:.25rem 2rem 1.25rem}.doc .quoteblock .attribution{color:var(--asciidoctor-quote-attribution-font-color);font-size:calc(15/var(--pixel-to-rem));margin-top:.75rem}.doc .quoteblock blockquote{margin-top:1rem}.doc .quoteblock .paragraph{font-style:italic}.doc .quoteblock cite{padding-left:1em}.doc .verseblock{font-size:1.15em;padding:1rem 2rem}.doc .verseblock pre{font-family:inherit;font-size:inherit}.doc ol,.doc ul{margin:0;padding:0 0 0 2rem}.doc ol.none,.doc ol.unnumbered,.doc ol.unstyled,.doc ul.checklist,.doc ul.no-bullet,.doc ul.none,.doc ul.unstyled{list-style-type:none}.doc ol.unnumbered,.doc ul.no-bullet{padding-left:1.25rem}.doc ol.unstyled,.doc ul.unstyled{padding-left:0}.doc ul.circle,.doc ul.disc,.doc ul.square{list-style-type:square}.doc ol.arabic{list-style-type:decimal}.doc ol.decimal{list-style-type:decimal-leading-zero}.doc ol.loweralpha{list-style-type:lower-alpha}.doc ol.upperalpha{list-style-type:upper-alpha}.doc ol.lowerroman{list-style-type:lower-roman}.doc ol.upperroman{list-style-type:upper-roman}.doc ol.lowergreek{list-style-type:lower-greek}.doc ul.checklist{padding-left:.5rem}.doc ul.checklist p>i.fa-check-square-o:first-child,.doc ul.checklist p>i.fa-square-o:first-child{display:inline-flex;justify-content:center;width:1.25rem}.doc ul.checklist i.fa-check-square-o:before{content:"\2713"}.doc ul.checklist i.fa-square-o:before{content:"\274f"}.doc .dlist .dlist,.doc .dlist .olist,.doc .dlist .ulist,.doc .olist .dlist,.doc .olist .olist,.doc .olist .ulist,.doc .ulist .dlist,.doc .ulist .olist,.doc .ulist .ulist{margin-top:.5rem}.doc .olist li,.doc .ulist li{margin-bottom:.3rem}.doc .admonitionblock .listingblock,.doc .olist .listingblock,.doc .ulist .listingblock{padding:0}.doc .admonitionblock .title,.doc .exampleblock .title,.doc .imageblock .title,.doc .listingblock .title,.doc .literalblock .title,.doc .openblock .title,.doc .tableblock caption{color:var(--asciidoctor-caption-font-color);font-size:calc(14/var(--pixel-to-rem));font-style:italic;font-weight:var(--asciidoctor-caption-font-weight);-webkit-hyphens:none;hyphens:none;letter-spacing:.01em;padding-bottom:.075rem;text-align:left}.doc .imageblock .title{margin-top:.5rem;padding-bottom:0}.doc .exampleblock>.content{background:var(--asciidoctor-example-background);border:1px solid var(--asciidoctor-example-border-color);border-radius:4px;padding:.75rem}.doc .exampleblock>.content>:first-child{margin-top:0}.doc .sidebarblock{background:var(--asciidoctor-sidebar-background);padding:2.2rem}.doc .sidebarblock>.content>.title{font-size:calc(23/var(--pixel-to-rem));font-weight:var(--asciidoctor-alt-heading-font-weight);line-height:1.3;margin-bottom:1.2rem}.doc .sidebarblock>.content>:not(.title):first-child{margin-top:0}.doc b.button{white-space:nowrap}.doc b.button:before{content:"[";padding-right:.25em}.doc b.button:after{content:"]";padding-left:.25em}.doc .menuseq,.doc .path{-webkit-hyphens:none;hyphens:none}.doc .menuseq i.caret:before{content:"\203a";font-size:1.1em;font-weight:var(--asciidoctor-body-font-weight-bold);line-height:.90909}.doc kbd{background:var(--asciidoctor-kbd-background);border:1px solid var(--asciidoctor-kbd-border-color);border-radius:.25em;box-shadow:0 1px 0 var(--asciidoctor-kbd-border-color),0 0 0 .1em var(--body-background) inset;display:inline-block;font-size:calc(12/var(--pixel-to-rem));padding:.25em .5em;vertical-align:text-bottom;white-space:nowrap}.doc .keyseq,.doc kbd{line-height:1}.doc .keyseq{font-size:calc(16/var(--pixel-to-rem))}.doc .keyseq kbd{margin:0 .125em}.doc .keyseq kbd:first-child{margin-left:0}.doc .keyseq kbd:last-child{margin-right:0}.doc i.fa{font-style:normal;-webkit-hyphens:none;hyphens:none}.doc .language-console .hljs-meta{-webkit-user-select:none;-moz-user-select:none;user-select:none}.doc .dlist dt{font-style:italic}.doc .dlist dd{margin:0 0 .25rem 1.5rem}.doc .dlist dd:last-of-type{margin-bottom:0}.doc td.hdlist1,.doc td.hdlist2{padding:.5rem 0 0;vertical-align:top}.doc tr:first-child>.hdlist1,.doc tr:first-child>.hdlist2{padding-top:0}.doc td.hdlist1{font-weight:var(--body-font-weight-bold);padding-right:.25rem}.doc td.hdlist2{padding-left:.25rem}.doc .colist{font-size:calc(16/var(--pixel-to-rem));margin:.25rem 0 -.25rem}.doc .colist>table>tbody>tr>:first-child,.doc .colist>table>tr>:first-child{padding:.25em .5rem 0;vertical-align:top}.doc .colist>table>tbody>tr>:last-child,.doc .colist>table>tr>:last-child{padding:.25rem 0}.doc .conum[data-value]{background:var(--asciidoctor-callout-background);border-radius:100%;color:var(--asciidoctor-callout-font-color);display:inline-block;font-family:var(--monospace-font-family);font-size:calc(12.5/var(--pixel-to-rem));font-style:normal;height:1.25em;letter-spacing:-.25ex;line-height:1.2;text-align:center;text-indent:-.25ex;width:1.25em}.doc .conum[data-value]:after{content:attr(data-value)}.doc .conum[data-value]+b{display:none}.doc hr{border:solid var(--asciidoctor-section-divider-color);border-width:2px 0 0;height:0}.doc :not(pre).nowrap{white-space:nowrap}.doc .nobreak{word-wrap:normal;-webkit-hyphens:none;hyphens:none}.doc .right{float:right}.doc .left{float:left}.doc .stretch{width:100%}.doc .underline{text-decoration:underline}.doc .line-through{text-decoration:line-through}.doc .halign-left{text-align:left}.doc .halign-right{text-align:right}.doc .halign-center{text-align:center}.doc .valign-top{vertical-align:top}.doc .valign-bottom{vertical-align:bottom}.doc .valign-middle{vertical-align:middle}#footer #footer-text{border-top:1px solid var(--asciidoctor-section-divider-color);color:var(--asciidoctor-footer-font-color);font-size:calc(14/var(--pixel-to-rem));padding:2rem 0}html.dark-theme #doc{background:no-repeat 100% 0/305px 147px;background-image:url(../img/doc-background-dark.svg)}@media screen and (max-width:1024px){#doc{background:no-repeat 100% 0/203px 95px;background-image:url(../img/doc-background.svg)}html.dark-theme #doc{background:no-repeat 100% 0/203px 95px;background-image:url(../img/doc-background-dark.svg)}}@media screen and (max-width:800px){#doc,html.dark-theme #doc{background:none}}.hljs{background:var(--highlight-background-color);color:var(--highlight-font-color);display:block;overflow-x:auto;padding:.5em}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:var(--highlight-keyword-font-color)}.hljs-comment,.hljs-quote{color:var(--highlight-comment-font-color)}.hljs-doctag,.hljs-string{color:var(--highlight-string-font-color)}.hljs-meta{color:var(--highlight-meta-font-color)}.hljs-built_in,.hljs-builtin-name,.hljs-literal,.hljs-number,.hljs-symbol{color:var(--highlight-constant-font-color)}.hljs-template-variable,.hljs-variable{color:var(--highlight-variable-font-color)}.hljs-attribute,.hljs-name,.hljs-tag{color:var(--highlight-tag-font-color)}.hljs-tag .hljs-attr{color:var(--highlight-tag-attribute-font-color)}.hljs-class .hljs-title,.hljs-type{color:var(--highlight-type-font-color)}.hljs-regexp{color:var(--highlight-regex-font-color)}.hljs-link{color:var(--highlight-link-font-color);text-decoration:underline}.hljs-addition{color:var(--highlight-addition-font-color)}.hljs-deletion{color:var(--highlight-deletion-font-color)}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.language-json .hljs-literal,.language-json .hljs-number{color:var(--highlight-variable-font-color)}.language-json .hljs-attr{color:var(--highlight-string-font-color)}.hidden{display:none}.doc .tabs{border-style:none;bottom:0;display:inline-block;font-size:calc(12/var(--pixel-to-rem));font-weight:700;margin-bottom:calc(2/var(--pixel-to-rem));margin-top:.5rem;position:relative}.doc .tab,.doc .tab:not(:first-child){border:1px solid var(--tabs-border-color)}.doc .tab{background-color:var(--tabs-background-color);border-radius:0;color:var(--tabs-font-color);cursor:pointer;display:inline-block;margin-bottom:calc(2/var(--pixel-to-rem));padding:.3rem .6rem;transition:background-color .2s}.doc .tab:hover{background-color:var(--tabs-hover-background);color:var(--tabs-hover-font-color);text-decoration:underline}.doc .tab.selected{background-color:var(--tabs-selected-background-color);border-color:var(--tabs-selected-background-color);color:var(--tabs-selected-font-color)}.doc .tab.selected:hover{color:var(--tabs-selected-font-color);text-decoration:none}.doc div.openblock.tabs-content>.content{background-color:var(--tabs-group-background-color);padding:1rem}body.toc-left #doc{border-left:1px solid var(--layout-border-color);margin-left:var(--toc-width);overflow:auto}#toc{border-right:1px solid var(--layout-border-color);display:var(--toc-display);font-size:.95rem;line-height:1.1;margin-left:calc(var(--toc-width)*-1);padding:1.7rem 1rem 0;position:absolute;top:var(--layout-banner-height);width:var(--toc-width)}#toctitle{display:none}#toc ol,#toc ul{padding:0}#toc ul ol,#toc ul ul{padding-left:.8rem}#toc li{display:block;list-style:none}#toc a{border-radius:4px;color:var(--toc-font-color);display:block;padding:.4rem .6rem;text-decoration:none}#toc a:hover{background-color:var(--toc-hover-background-color)}body.fixed-toc #toc{height:100%;overflow-x:hidden;position:fixed;top:0}#toc li.active>a{background-color:var(--toc-active-background-color);color:var(--toc-active-font-color)}#toc>ol ol,#toc>ul ul{display:none}#toc li.active>ol,#toc li.active>ul,#toc ol.expanded,#toc ul.expanded{display:block}#back-to-index{display:block;margin-bottom:.6rem}#back-to-index a{margin-bottom:.6rem;margin-top:-.9rem;padding-left:1.6rem}#back-to-index a:before{background:no-repeat 50%/16px 16px;background-image:url(../img/octicons-16.svg#view-chevron-left);content:"";display:block;filter:var(--toc-back-to-index-filter);left:1.4rem;min-height:16px;min-width:16px;position:absolute}#tocbar-container{background-color:var(--body-background-color);border-bottom:1px solid var(--panel-border-color);display:var(--toc-bar-display);height:var(--tocbar-height);width:100%;z-index:10000}#tocbar{height:100%;padding-left:6px;width:100%}body.fixed-toc #tocbar-container{position:fixed;top:0}button#toggle-toc{background:no-repeat 50%/16px 16px;background-image:url(../img/octicons-16.svg#view-three-bars);border:none;display:block;filter:var(--toc-bar-button-filter);height:var(--toc-bar-height);outline:none;padding:0;width:var(--toc-bar-height)}body.show-toc button#toggle-toc{background-image:url(../img/octicons-16.svg#view-x)}@media screen and (max-width:800px){body.fixed-toc #toc{top:var(--toc-bar-height)}#toc{background-color:var(--body-background-color);height:100%;left:0;top:calc(var(--layout-banner-height) + var(--toc-bar-height));width:100%;z-index:10000}body.show-toc #toc{display:block}}div.codetools{--button-width:28px;--button-height:24px;--arrow-size:5px;background:var(--codetools-background-color);border:1px solid var(--codetools-border-color);border-radius:2px;bottom:9px;display:flex;opacity:0;padding:0;position:absolute;right:8px;transition:opacity .15s ease-in-out}.doc pre.highlight:hover div.codetools{opacity:1}div.codetools button{background:no-repeat 50%/16px 16px;border:none;filter:var(--codetools-button-filter);height:var(--button-height);outline:none;padding:0;width:var(--button-width)}div.codetools button:not(:last-child){border-right:1px solid var(--codetools-divider-color)}div.codetools button:hover{background-color:var(--codetools-hover-background-color);transition:filter .3s}div.codetools button:active{filter:var(--codetools-button-active-filter);transition:filter none}div.codetools button span.label{display:none}div.codetools button.copy-button{background-image:url(../img/octicons-16.svg#view-clippy)}div.codetools button.unfold-button{background-image:url(../img/octicons-16.svg#view-unfold)}div.codetools button.fold-button{background-image:url(../img/octicons-16.svg#view-fold)}div.codetools span.copied{content:"";display:block;height:var(--button-height);opacity:0;position:relative;transition:opacity .5s;width:var(--button-width);z-index:1000000}div.codetools button:active span.copied{filter:invert();transition:filter none}div.codetools span.copied:before{border:var(--arrow-size) solid var(--codetools-popup-background-color);border-color:transparent transparent var(--codetools-popup-background-color) transparent;bottom:calc(var(--arrow-size)*-1);content:"";left:50%;margin-left:calc(var(--arrow-size)/-2);position:absolute}div.codetools span.copied:after{background-color:var(--codetools-popup-background-color);border-radius:3px;color:var(--codetools-popup-font-color);content:"Copied to clipboard!";font-weight:700;margin-right:calc(var(--button-width)*-1);padding:5px 8px;position:absolute;right:100%;top:calc(var(--button-height) + var(--arrow-size))}div.codetools button.clicked span.copied{opacity:1}span.fold-block{clear:left;float:left;overflow:hidden;padding-right:.75rem;position:relative}code.unfolded span.fold-block.hide-when-folded,code:not(.unfolded) span.fold-block.hide-when-unfolded{max-height:99999px;opacity:1}code.unfolded span.fold-block.hide-when-unfolded,code:not(.unfolded) span.fold-block.hide-when-folded{max-height:0;opacity:0}code.unfolding span.fold-block.hide-when-folded{max-height:600px;opacity:1}code.folding span.fold-block.hide-when-unfolded{max-height:400px;opacity:1}code.folding span.fold-block.hide-when-folded,code.unfolding span.fold-block.hide-when-unfolded{max-height:0;opacity:0}code.unfolding span.fold-block.hide-when-unfolded{transition:max-height .2s cubic-bezier(0,1,0,1),opacity .2s linear}code.folding span.fold-block.hide-when-unfolded,code.unfolding span.fold-block.hide-when-folded{transition:max-height .2s cubic-bezier(1,0,1,0),opacity .2s linear}code.folding span.fold-block.hide-when-folded{transition:max-height .2s cubic-bezier(0,1,0,1),opacity .2s linear} +/*# sourceMappingURL=site.css.map */ diff --git a/3.8.13/reference/html/css/site.css.map b/3.8.13/reference/html/css/site.css.map new file mode 100755 index 0000000000..da9ab6958a --- /dev/null +++ b/3.8.13/reference/html/css/site.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["settings.css","settings-dark.css","generic.css","elements.css","layout.css","asciidoctor.css","highlight.css","tabs.css","toc.css","codetools.css"],"names":[],"mappings":"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","file":"site.css","sourcesContent":["/*\n * Copyright 2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n:root {\n /* NOTE: Don't use relative `url()` values in here. Safari load them properly */\n\n /* General */\n --html-font-size: 1em;\n --pixel-to-rem: 16 * 1rem;\n --font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto,\n Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\",\n \"Segoe UI Symbol\";\n --font-weight: 400;\n --monospace-font-family: \"SFMono-Regular\", \"Consolas\", \"Liberation Mono\",\n \"Menlo\", monospace;\n --body-background-color: white;\n --panel-background-color: #f6f8fa;\n --panel-group-background-color: #e1e8e8;\n --panel-border-color: #eaedf0;\n --color-accent-1: #ebf2f2;\n --color-accent-2: #d7e7e7;\n --color-accent-3: #6db33f;\n --body-font-color: #191e1e;\n --body-font-light-color: #273030;\n --body-font-dark-color: #141818;\n --link-font-color: #1565c0;\n --hover-link-font-color: #104d92;\n --scrollbar-thumb-color: silver;\n --mark-background-color: #39ff14;\n --selected-background-color: #191e1e;\n\n /* Layout */\n --layout-banner-logo-offset: 18px;\n --layout-banner-logo-height: 50px;\n --layout-max-width: 1400px;\n --layout-banner-height: 80px;\n --layout-border-color: var(--color-accent-1);\n --layout-switchtheme-invert-filter: invert();\n --layout-switchtheme-background-color: var(--body-background-color);\n --layout-switchtheme-button-color: var(--body-background-color);\n --layout-switchtheme-button-hover-color: var(--color-accent-1);\n\n /* Asciidoctor */\n --asciidoctor-doc-embellishment-margin-width: 250px;\n --asciidoctor-doc-background-embellishment-height: 147px;\n --asciidoctor-details-background: var(--color-accent-1);\n --asciidoctor-details-font-color: var(--body-font-light-color);\n --asciidoctor-author-separator-color: var(--color-accent-3);\n --asciidoctor-panel-background: var(--panel-background-color);\n --asciidoctor-panel-border-color: var(--panel-border-color);\n --asciidoctor-font-color: var(--body-font-color);\n --asciidoctor-heading-font-color: var(--body-font-dark-color);\n --asciidoctor-heading-font-weight: 600;\n --asciidoctor-alt-heading-font-weight: 600;\n --asciidoctor-section-divider-color: var(--color-accent-1);\n --asciidoctor-link-font-color: var(--link-font-color);\n --asciidoctor-hover-link-font-color: var(--hover-link-font-color);\n --asciidoctor-unresolved-link-font-color: #d32f2f;\n --asciidoctor-code-font-color: var(--asciidoctor-font-color);\n --asciidoctor-code-link-font-color: var(--link-font-color);\n --asciidoctor-code-background: rgba(27, 31, 35, 0.05);\n --asciidoctor-code-data-lang-color: #999;\n --asciidoctor-table-border-color: var(--asciidoctor-panel-border-color);\n --asciidoctor-table-header-footer-background: var(--color-accent-1);\n --asciidoctor-table-stripe-background: var(--color-accent-1);\n --asciidoctor-table-footer-background: linear-gradient(\n to bottom,\n var(--color-accent-1) 0%,\n var(--body-background-color) 100%\n );\n --asciidoctor-admonition-background: var(--color-accent-1);\n --asciidoctor-admonition-pre-background: var(--color-accent-2);\n --asciidoctor-admonition-label-font-weight: 500;\n --asciidoctor-admonition-font-color: #f0f0f0;\n --asciidoctor-admonition-caution-background: #561164;\n --asciidoctor-admonition-important-background: #960000;\n --asciidoctor-admonition-note-background: #015785;\n --asciidoctor-admonition-tip-background: #3e6b1f;\n --asciidoctor-admonition-warning-background: #bd7400;\n --asciidoctor-abstract-background: var(--asciidoctor-panel-background);\n --asciidoctor-abstract-border-color: var(--asciidoctor-panel-border-color);\n --asciidoctor-quote-background: var(--color-accent-1);\n --asciidoctor-quote-border-color: var(--color-accent-3);\n --asciidoctor-quote-attribution-font-color: var(--color-accent-3);\n --asciidoctor-caption-font-color: var(--body-font-light-color);\n --asciidoctor-caption-font-weight: 400;\n --asciidoctor-example-background: var(--asciidoctor-panel-background);\n --asciidoctor-example-border-color: var(--asciidoctor-panel-border-color);\n --asciidoctor-sidebar-background: var(--color-accent-1);\n --asciidoctor-pre-background: var(--asciidoctor-panel-background);\n --asciidoctor-pre-border-color: var(--asciidoctor-panel-border-color);\n --asciidoctor-callout-background: var(--body-font-dark-color);\n --asciidoctor-callout-font-color: var(--body-background-color);\n --asciidoctor-footer-font-color: #b6b6b6;\n\n /* Highlight JS (colors based on https://github.com/primer/github-syntax-light) */\n --highlight-background-color: var(--asciidoctor-pre-background);\n --highlight-font-color: #24292e;\n --highlight-keyword-font-color: #d73a49;\n --highlight-comment-font-color: #6a737d;\n --highlight-string-font-color: #032f62;\n --highlight-meta-font-color: #6a737d;\n --highlight-constant-font-color: #032f62;\n --highlight-variable-font-color: #005cc5;\n --highlight-tag-font-color: #22863a;\n --highlight-tag-attribute-font-color: #6f42c1;\n --highlight-type-font-color: #6f42c1;\n --highlight-link-font-color: var(--link-font-color);\n --highlight-addition-font-color: #22863a;\n --highlight-deletion-font-color: #24292e;\n --highlight-regex-font-color: #032f62;\n\n /* Tabs */\n --tabs-border-color: var(--selected-background-color);\n --tabs-background-color: var(--body-background-color);\n --tabs-font-color: var(--body-font-color);\n --tabs-selected-background-color: var(--selected-background-color);\n --tabs-selected-font-color: var(--body-background-color);\n --tabs-hover-font-color: var(--hover-link-font-color);\n --tabs-hover-background: var(--color-accent-1);\n --tabs-group-background-color: var(--panel-group-background-color);\n\n /* TOC */\n --toc-width: 24rem;\n --toc-display: block;\n --toc-font-color: var(--body-font-color);\n --toc-hover-background-color: var(--color-accent-1);\n --toc-active-background-color: var(--selected-background-color);\n --toc-active-font-color: var(--body-background-color);\n --toc-back-to-index-filter: none;\n --toc-bar-display: none;\n --toc-bar-height: 0;\n --toc-bar-button-filter: none;\n\n /* Code Tools */\n --codetools-button-filter: none;\n --codetools-button-active-filter: invert();\n --codetools-background-color: var(--body-background-color);\n --codetools-border-color: rgba(0, 0, 0, 0.3);\n --codetools-hover-background-color: var(--color-accent-1);\n --codetools-divider-color: var(--codetools-border-color);\n --codetools-popup-background-color: var(--selected-background-color);\n --codetools-popup-font-color: var(--body-background-color);\n}\n\n/* Responsive Overrides */\n\n@media screen and (max-width: 1024px) {\n :root {\n --toc-width: 16rem;\n --asciidoctor-doc-embellishment-margin-width: 140px;\n }\n}\n\n@media screen and (max-width: 800px) {\n :root {\n --layout-banner-height: 51px;\n --layout-banner-logo-height: 30px;\n --layout-banner-logo-offset: 10px;\n --layout-border-color: var(--body-background-color);\n --toc-bar-display: block;\n --toc-bar-height: 24px;\n --toc-width: 0;\n --toc-display: none;\n --asciidoctor-doc-embellishment-margin-width: 0;\n }\n}\n","html.dark-theme {\n /* General */\n --font-weight: 300;\n --body-background-color: #1b1f23;\n --panel-background-color: #262a2d;\n --panel-group-background-color: #303741;\n --panel-border-color: #2c3135;\n --color-accent-1: #272c33;\n --color-accent-1-invert: #d8d3cc;\n --color-accent-2: #2d333a;\n --color-accent-3: #6db33f;\n --body-font-color: #bbbcbe;\n --body-font-light-color: #abacaf;\n --body-font-dark-color: #cecfd1;\n --link-font-color: #086dc3;\n --hover-link-font-color: #107ddd;\n --scrollbar-thumb-color: #5f5f5f;\n --mark-background-color: #2eca12;\n --selected-background-color: #8d8d8d;\n\n /* Layout */\n --layout-switchtheme-invert-filter: none;\n --layout-switchtheme-background-color: var(--selected-background-color);\n\n /* Asciidoctor */\n --asciidoctor-code-background: rgba(177, 209, 241, 0.15);\n --asciidoctor-code-data-lang-color: #6e6e6e;\n --asciidoctor-admonition-font-color: #f0f0f0;\n --asciidoctor-admonition-caution-background: #603668;\n --asciidoctor-admonition-important-background: #924040;\n --asciidoctor-admonition-note-background: #355463;\n --asciidoctor-admonition-tip-background: #4d6340;\n --asciidoctor-admonition-warning-background: #967745;\n --asciidoctor-footer-font-color: #5e5e5e;\n\n /* Highlight JS (colors based on https://github.com/primer/github-syntax-dark) */\n --highlight-background-color: var(--asciidoctor-pre-background);\n --highlight-font-color: #f6f8fa;\n --highlight-keyword-font-color: #ea4a5a;\n --highlight-comment-font-color: #959da5;\n --highlight-string-font-color: #79b8ff;\n --highlight-meta-font-color: #959da5;\n --highlight-constant-font-color: #79b8ff;\n --highlight-variable-font-color: #c8e1ff;\n --highlight-tag-font-color: #7bcc72;\n --highlight-tag-attribute-font-color: #b392f0;\n --highlight-type-font-color: #b392f0;\n --highlight-link-font-color: #1565c0;\n --highlight-addition-font-color: #7bcc72;\n --highlight-deletion-font-color: #f6f8fa;\n --highlight-regex-font-color: #79b8ff;\n\n /* TOC */\n --toc-back-to-index-filter: invert();\n --toc-bar-button-filter: invert();\n\n /* Code Tools */\n --codetools-button-filter: invert();\n --codetools-button-active-filter: none;\n --codetools-hover-background-color: var(--color-accent-1-invert);\n --codetools-border-color: rgba(255, 255, 255, 0.274);\n --codetools-divider-color: rgba(44, 44, 44, 0.274);\n}\n","/*\n * Copyright 2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nhtml {\n box-sizing: border-box;\n}\n\n*,\n*:before,\n*:after {\n box-sizing: inherit;\n}\n\nbody {\n text-size-adjust: none;\n}\n","/*\n * Copyright 2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nhtml {\n height: 100%;\n font-size: var(--html-font-size);\n scroll-behavior: smooth;\n min-width: 340px;\n}\n\nbody {\n margin: 0;\n overflow-wrap: anywhere;\n overscroll-behavior: none;\n font-family: var(--font-family);\n font-weight: var(--font-weight);\n color: var(--body-font-color);\n background-color: var(--body-background-color);\n}\n\na {\n text-decoration: none;\n}\n\na:hover {\n text-decoration: underline;\n}\n\na:active {\n background-color: none;\n}\n\ncode,\nkbd,\npre {\n font-family: var(--monospace-font-family);\n}\n\n@supports (scrollbar-width: thin) {\n body * {\n scrollbar-width: thin;\n scrollbar-color: var(--scrollbar-thumb-color) transparent;\n }\n}\n\ntable {\n border-collapse: collapse;\n word-wrap: normal;\n}\n\nmark {\n background: var(--mark-background-color);\n}\n","/*\n * Copyright 2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#banner-container {\n height: var(--layout-banner-height);\n overflow: hidden;\n border-bottom: 1px solid var(--layout-border-color);\n}\n\n#banner {\n height: 100%;\n background: no-repeat top var(--layout-banner-logo-offset) left\n var(--layout-banner-logo-offset) / auto var(--layout-banner-logo-height);\n background-image: url(\"../img/banner-logo.svg\");\n}\n\n#doc {\n overflow: auto;\n}\n\n.contained {\n max-width: var(--layout-max-width);\n margin: 0 auto;\n}\n\ndiv#switch-theme,\n#switch-theme label {\n display: none;\n}\n\nhtml.js div#switch-theme {\n display: block;\n float: right;\n margin: 8px 6px 0 0;\n}\n\n#switch-theme input {\n appearance: none;\n position: relative;\n width: 40px;\n height: 22px;\n filter: var(--layout-switchtheme-invert-filter);\n background: no-repeat url(\"../img/octicons-16.svg#view-sun\") 90% 50% / 16px\n 16px,\n no-repeat url(\"../img/octicons-16.svg#view-moon\") 10% 50% / 16px 16px;\n background-color: var(--layout-switchtheme-background-color);\n border-radius: 25px;\n outline: none;\n}\n\n#switch-theme input::before {\n filter: var(--layout-switchtheme-invert-filter);\n content: \"\";\n position: absolute;\n left: 2px;\n top: 2px;\n height: 18px;\n width: 18px;\n border-radius: 25px;\n background-color: var(--layout-switchtheme-button-color);\n transition: transform 200ms;\n}\n\n#switch-theme:hover input::before {\n background-color: var(--layout-switchtheme-button-hover-color);\n}\n\n#switch-theme input:checked::before {\n transform: translateX(18px);\n}\n","/*\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/.\n */\n\n/* Asciidoctor styling based on https://gitlab.com/antora/antora-ui-default/-/blob/8751b86b76d6779fbbcf0fccd58fafcf73c49260/src/css/doc.css */\n\n/* Container element styling */\n\n.doc {\n color: var(--asciidoctor-font-color);\n hyphens: none;\n line-height: 1.6;\n letter-spacing: -0.0027777778rem;\n margin: 0;\n}\n\n/* Gutter and margins */\n\n.doc #content,\n.doc #footer {\n margin: 0 2rem;\n}\n\n.doc #header > *:not(#toc) {\n /* Gutter is not applied directly to #header of toc positioning */\n margin-left: 2rem;\n margin-right: 2rem;\n}\n\n.doc #content {\n padding-bottom: 4rem;\n}\n\n/* Doc background embellishment */\n\n#doc {\n background: no-repeat top right / 305px 147px;\n background-image: url(\"../img/doc-background.svg\");\n}\n\n.doc #header {\n margin-right: var(--asciidoctor-doc-embellishment-margin-width);\n}\n\n/* Header Details */\n\n.doc #header .details {\n background: var(--asciidoctor-details-background);\n color: var(--asciidoctor-details-font-color);\n padding: 1rem 1.5rem;\n font-weight: 600;\n font-size: 0.8em;\n}\n\n.doc #header div.details {\n display: flex;\n flex-wrap: wrap;\n}\n\n#header .details br {\n display: none;\n}\n\n.doc #header .details span.author:not(:last-of-type)::after {\n content: \"\\2022\";\n font-weight: 400;\n margin: 0.4em;\n color: var(--asciidoctor-author-separator-color);\n}\n\n.doc #header .details span.last-author::after {\n display: none;\n}\n\n.doc #header .details #revnumber {\n flex-basis: 100%;\n margin-top: 0.5rem;\n text-transform: capitalize;\n font-weight: 200;\n}\n\n/* Section setup */\n\n.doc #preamble + .sect1,\n.doc .sect1 + .sect1 {\n margin-top: 2rem;\n}\n\n.doc .sect1 + .sect1 {\n border-top: 1px solid var(--asciidoctor-section-divider-color);\n}\n\n/* Headings */\n\n.doc h1 {\n font-size: 2.3em;\n}\n\n.doc h2 {\n font-size: 2em;\n}\n\n.doc h3 {\n font-size: 1.7em;\n}\n\n.doc h4 {\n font-size: 1.6em;\n}\n\n.doc h5 {\n font-size: 1.4em;\n}\n\n.doc h6 {\n font-size: 1.3em;\n}\n\n.doc h1,\n.doc h2,\n.doc h3,\n.doc h4,\n.doc h5,\n.doc h6 {\n color: var(--asciidoctor-heading-font-color);\n font-weight: var(--asciidoctor-heading-font-weight);\n hyphens: none;\n line-height: 1.3;\n margin: 1.3rem 0 0;\n padding-top: 1.8rem;\n}\n\n.doc h1.sect0 {\n background: var(--asciidoctor-abstract-background);\n font-size: 1.8em;\n margin: 1.5rem -1rem 0;\n padding: 0.5rem 1rem;\n}\n\n.doc h1:first-child {\n margin: 1.3rem 0;\n}\n\n.doc h2:not(.discrete) {\n margin-left: -1rem;\n margin-right: -1rem;\n padding: 1.8rem 1rem 0.1rem;\n}\n\n.doc h3:not(.discrete) {\n font-weight: var(--asciidoctor-alt-heading-font-weight);\n}\n\n/* Header hover anchors */\n\n.doc h1 .anchor,\n.doc h2 .anchor,\n.doc h3 .anchor,\n.doc h4 .anchor,\n.doc h5 .anchor,\n.doc h6 .anchor {\n position: absolute;\n text-decoration: none;\n width: 2.25ex;\n margin-left: -2ex;\n padding-left: 0.5ex;\n visibility: hidden;\n font-weight: normal;\n}\n\n.doc h1 .anchor::before,\n.doc h2 .anchor::before,\n.doc h3 .anchor::before,\n.doc h4 .anchor::before,\n.doc h5 .anchor::before,\n.doc h6 .anchor::before {\n content: \"\\0023\";\n}\n\n.doc h1:hover .anchor,\n.doc h2:hover .anchor,\n.doc h3:hover .anchor,\n.doc h4:hover .anchor,\n.doc h5:hover .anchor,\n.doc h6:hover .anchor {\n visibility: visible;\n}\n\n.doc p,\n.doc dl {\n margin: 0;\n}\n\n/* General anchors */\n\n.doc a.bare {\n hyphens: none;\n}\n\n.doc a {\n color: var(--asciidoctor-link-font-color);\n}\n\n.doc a:hover {\n color: var(--asciidoctor-hover-link-font-color);\n}\n\n.doc a.unresolved {\n color: var(--asciidoctor-unresolved-link-font-color);\n}\n\n/* Code and Pre */\n\n.doc p code,\n.doc thead code,\n.doc .admonitionblock code {\n color: var(--asciidoctor-code-font-color);\n background: var(--asciidoctor-code-background);\n border-radius: 0.25em;\n font-size: 0.95em;\n padding: 0.125em 0.25em;\n}\n\n.doc p a code,\n.doc thead a code,\n.doc .admonitionblock a code {\n color: var(--asciidoctor-code-link-font-color);\n}\n\n.doc code,\n.doc pre {\n hyphens: none;\n}\n\n.doc pre {\n font-size: calc(14 / var(--pixel-to-rem));\n line-height: 1.3;\n margin: 0;\n}\n\n.doc pre.highlight code,\n.doc .listingblock pre:not(.highlight),\n.doc .literalblock pre {\n background: var(--asciidoctor-pre-background);\n box-shadow: inset 0 0 1.75px var(--asciidoctor-pre-border-color);\n display: block;\n overflow-x: auto;\n padding: 0.95rem;\n border-radius: 4px;\n}\n\n.doc pre.highlight code[data-lang]:before {\n content: attr(data-lang);\n text-transform: uppercase;\n display: block;\n position: absolute;\n top: 0.3rem;\n right: 0.3rem;\n line-height: 1;\n font-size: 0.65em;\n color: var(--asciidoctor-code-data-lang-color);\n}\n\n.doc pre.highlight {\n position: relative;\n}\n\n.doc table pre.highlight code[data-lang]:before {\n display: none;\n}\n\n/* General margin and fonts sizing */\n\n.doc blockquote {\n margin: 0;\n}\n\n.doc .paragraph.lead > p {\n font-size: calc(18 / var(--pixel-to-rem));\n}\n\n.doc .paragraph,\n.doc .dlist,\n.doc .hdlist,\n.doc .olist,\n.doc .ulist,\n.doc .exampleblock,\n.doc .imageblock,\n.doc .listingblock,\n.doc .literalblock,\n.doc .sidebarblock,\n.doc .verseblock,\n.doc .quoteblock,\n.doc .partintro,\n.doc details,\n.doc hr {\n margin: 1rem 0 0;\n}\n\n/* Tables */\n\n.doc table.tableblock {\n display: block;\n width: 100%;\n overflow-x: auto;\n}\n\n.doc table.tableblock td {\n min-width: 6rem;\n}\n\n.doc table.tableblock {\n font-size: calc(15 / var(--pixel-to-rem));\n margin: 1.5rem 0 0;\n}\n\n.doc table.tableblock + * {\n margin-top: 2rem;\n}\n\n.doc td.tableblock > .content > :first-child {\n margin-top: 0;\n}\n\n.doc table.tableblock th,\n.doc table.tableblock td {\n padding: 0.5rem;\n}\n\n.doc table.tableblock thead th {\n border-bottom: 2.5px solid var(--asciidoctor-table-border-color);\n}\n\n.doc table.tableblock td,\n.doc table.tableblock > :not(thead) th {\n border-top: 1px solid var(--asciidoctor-table-border-color);\n border-bottom: 1px solid var(--asciidoctor-table-border-color);\n}\n\n.doc table.stripes-all > tbody > tr,\n.doc table.stripes-odd > tbody > tr:nth-of-type(odd),\n.doc table.stripes-even > tbody > tr:nth-of-type(even),\n.doc table.stripes-hover > tbody > tr:hover {\n background: var(--asciidoctor-table-stripe-background);\n}\n\n.doc table.tableblock > tfoot {\n background: var(--asciidoctor-table-footer-background);\n}\n\n.doc .tableblock pre,\n.doc .tableblock code,\n.doc .listingblock.wrap pre {\n white-space: pre-wrap;\n}\n\n.doc td:nth-child(1) .tableblock pre,\n.doc td:nth-child(1) .tableblock code,\n.doc td:nth-child(1) .listingblock.wrap pre {\n white-space: nowrap;\n}\n\n/* Admonition blocks */\n\n.doc .admonitionblock {\n margin: 2.5rem 0;\n}\n\n.doc .admonitionblock p,\n.doc .admonitionblock td.content {\n font-size: calc(16 / var(--pixel-to-rem));\n}\n\n.doc .admonitionblock td.content > :not(.title):first-child,\n.doc .admonitionblock td.content > .title + * {\n margin-top: 0;\n}\n\n.doc .admonitionblock pre {\n font-size: calc(14 / var(--pixel-to-rem));\n border: none;\n}\n\n.doc .admonitionblock > table {\n table-layout: fixed;\n position: relative;\n width: 100%;\n}\n\n.doc .admonitionblock td.content {\n padding: 1rem 1rem 0.75rem;\n background: var(--asciidoctor-admonition-background);\n width: 100%;\n word-wrap: anywhere;\n}\n\n.doc .admonitionblock td.icon {\n position: absolute;\n top: 0;\n left: 0;\n font-size: calc(16 / var(--pixel-to-rem));\n line-height: 1;\n transform: translate(-0.5rem, -50%);\n border-radius: 0.45rem;\n padding: 0.25em 0.075em;\n}\n\n.doc .admonitionblock .icon i {\n display: inline-flex;\n align-items: center;\n width: auto;\n height: 16px;\n background-repeat: no-repeat;\n background-position: 0.5em 0;\n filter: invert(100%);\n padding-left: 2em;\n vertical-align: initial;\n}\n\n.doc .admonitionblock .icon i::after {\n border-left: 1px solid rgba(255, 255, 255, 0.3);\n content: attr(title);\n text-transform: capitalize;\n filter: invert(100%);\n font-weight: var(--asciidoctor-admonition-label-font-weight);\n color: var(--asciidoctor-admonition-font-color);\n font-style: normal;\n hyphens: none;\n padding: 0 0.5em;\n margin: -0.05em;\n}\n\ni.fa {\n background-size: 16px 16px;\n}\n\ni.fa.icon-caution {\n background-image: url(\"../img/octicons-16.svg#view-flame\");\n}\n\ni.fa.icon-important {\n background-image: url(\"../img/octicons-16.svg#view-stop\");\n}\n\ni.fa.icon-note {\n background-image: url(\"../img/octicons-16.svg#view-info\");\n}\n\ni.fa.icon-tip {\n background-image: url(\"../img/octicons-16.svg#view-light-bulb\");\n}\n\ni.fa.icon-warning {\n background-image: url(\"../img/octicons-16.svg#view-alert\");\n}\n\n.doc .admonitionblock.caution td.icon {\n background: var(--asciidoctor-admonition-caution-background);\n}\n\n.doc .admonitionblock.important td.icon {\n background: var(--asciidoctor-admonition-important-background);\n}\n\n.doc .admonitionblock.note .icon {\n background: var(--asciidoctor-admonition-note-background);\n}\n\n.doc .admonitionblock.tip .icon {\n background: var(--asciidoctor-admonition-tip-background);\n}\n\n.doc .admonitionblock.warning .icon {\n background-color: var(--asciidoctor-admonition-warning-background);\n}\n\n/* Images and image blocks */\n\n.doc .imageblock {\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n.doc .imageblock img,\n.doc .image > img {\n display: inline-block;\n height: auto;\n max-width: 100%;\n vertical-align: middle;\n}\n\n.doc .image:not(.left):not(.right) > img {\n margin-top: -0.2em;\n}\n\n/* Quote blocks */\n\n.doc #preamble .abstract blockquote {\n background: var(--asciidoctor-abstract-background);\n border-left: 5px solid var(--asciidoctor-abstract-border-color);\n font-size: calc(16 / var(--pixel-to-rem));\n padding: 0.75em 1em;\n}\n\n.doc .quoteblock,\n.doc .verseblock {\n background: var(--asciidoctor-quote-background);\n border-left: 5px solid var(--asciidoctor-quote-border-color);\n}\n\n.doc .quoteblock {\n padding: 0.25rem 2rem 1.25rem;\n}\n\n.doc .quoteblock .attribution {\n color: var(--asciidoctor-quote-attribution-font-color);\n font-size: calc(15 / var(--pixel-to-rem));\n margin-top: 0.75rem;\n}\n\n.doc .quoteblock blockquote {\n margin-top: 1rem;\n}\n\n.doc .quoteblock .paragraph {\n font-style: italic;\n}\n\n.doc .quoteblock cite {\n padding-left: 1em;\n}\n\n/* Verse blocks */\n\n.doc .verseblock {\n font-size: 1.15em;\n padding: 1rem 2rem;\n}\n\n.doc .verseblock pre {\n font-family: inherit;\n font-size: inherit;\n}\n\n/* Lists */\n\n.doc ol,\n.doc ul {\n margin: 0;\n padding: 0 0 0 2rem;\n}\n\n.doc ul.checklist,\n.doc ul.none,\n.doc ol.none,\n.doc ul.no-bullet,\n.doc ol.unnumbered,\n.doc ul.unstyled,\n.doc ol.unstyled {\n list-style-type: none;\n}\n\n.doc ul.no-bullet,\n.doc ol.unnumbered {\n padding-left: 1.25rem;\n}\n\n.doc ul.unstyled,\n.doc ol.unstyled {\n padding-left: 0;\n}\n\n.doc ul.circle {\n list-style-type: square;\n}\n\n.doc ul.disc {\n list-style-type: square;\n}\n\n.doc ul.square {\n list-style-type: square;\n}\n\n.doc ol.arabic {\n list-style-type: decimal;\n}\n\n.doc ol.decimal {\n list-style-type: decimal-leading-zero;\n}\n\n.doc ol.loweralpha {\n list-style-type: lower-alpha;\n}\n\n.doc ol.upperalpha {\n list-style-type: upper-alpha;\n}\n\n.doc ol.lowerroman {\n list-style-type: lower-roman;\n}\n\n.doc ol.upperroman {\n list-style-type: upper-roman;\n}\n\n.doc ol.lowergreek {\n list-style-type: lower-greek;\n}\n\n.doc ul.checklist {\n padding-left: 0.5rem;\n}\n\n.doc ul.checklist p > i.fa-check-square-o:first-child,\n.doc ul.checklist p > i.fa-square-o:first-child {\n display: inline-flex;\n justify-content: center;\n width: 1.25rem;\n}\n\n.doc ul.checklist i.fa-check-square-o::before {\n content: \"\\2713\";\n}\n\n.doc ul.checklist i.fa-square-o::before {\n content: \"\\274f\";\n}\n\n.doc .dlist .dlist,\n.doc .dlist .olist,\n.doc .dlist .ulist,\n.doc .olist .dlist,\n.doc .olist .olist,\n.doc .olist .ulist,\n.doc .ulist .dlist,\n.doc .ulist .olist,\n.doc .ulist .ulist {\n margin-top: 0.5rem;\n}\n\n.doc .olist li,\n.doc .ulist li {\n margin-bottom: 0.3rem;\n}\n\n.doc .ulist .listingblock,\n.doc .olist .listingblock,\n.doc .admonitionblock .listingblock {\n padding: 0;\n}\n\n/* Block Titles */\n\n.doc .admonitionblock .title,\n.doc .exampleblock .title,\n.doc .imageblock .title,\n.doc .literalblock .title,\n.doc .listingblock .title,\n.doc .openblock .title,\n.doc .tableblock caption {\n color: var(--asciidoctor-caption-font-color);\n font-size: calc(14 / var(--pixel-to-rem));\n font-weight: var(--asciidoctor-caption-font-weight);\n font-style: italic;\n hyphens: none;\n letter-spacing: 0.01em;\n padding-bottom: 0.075rem;\n text-align: left;\n}\n\n.doc .imageblock .title {\n margin-top: 0.5rem;\n padding-bottom: 0;\n}\n\n/* Block content */\n\n.doc .exampleblock > .content {\n background: var(--asciidoctor-example-background);\n border: 1px solid var(--asciidoctor-example-border-color);\n border-radius: 4px;\n padding: 0.75rem;\n}\n\n.doc .exampleblock > .content > :first-child {\n margin-top: 0;\n}\n\n/* Sidebars */\n\n.doc .sidebarblock {\n background: var(--asciidoctor-sidebar-background);\n padding: 2.2rem 2.2rem;\n}\n\n.doc .sidebarblock > .content > .title {\n font-size: calc(23 / var(--pixel-to-rem));\n font-weight: var(--asciidoctor-alt-heading-font-weight);\n line-height: 1.3;\n margin-bottom: 1.2rem;\n}\n\n.doc .sidebarblock > .content > :not(.title):first-child {\n margin-top: 0;\n}\n\n/* Buttons (https://docs.asciidoctor.org/asciidoc/latest/macros/ui-macros/#button-macro-syntax) */\n\n.doc b.button {\n white-space: nowrap;\n}\n\n.doc b.button::before {\n content: \"[\";\n padding-right: 0.25em;\n}\n\n.doc b.button::after {\n content: \"]\";\n padding-left: 0.25em;\n}\n\n/* Menu (https://docs.asciidoctor.org/asciidoc/latest/macros/ui-macros/#menu-macro-syntax) */\n\n.doc .menuseq,\n.doc .path {\n hyphens: none;\n}\n\n.doc .menuseq i.caret::before {\n content: \"\\203a\";\n font-size: 1.1em;\n font-weight: var(--asciidoctor-body-font-weight-bold);\n line-height: calc(1 / 1.1);\n}\n\n/* Keyboard (https://docs.asciidoctor.org/asciidoc/latest/macros/keyboard-macro/) */\n\n.doc kbd {\n display: inline-block;\n font-size: calc(12 / var(--pixel-to-rem));\n background: var(--asciidoctor-kbd-background);\n border: 1px solid var(--asciidoctor-kbd-border-color);\n border-radius: 0.25em;\n box-shadow: 0 1px 0 var(--asciidoctor-kbd-border-color),\n 0 0 0 0.1em var(--body-background) inset;\n padding: 0.25em 0.5em;\n vertical-align: text-bottom;\n white-space: nowrap;\n}\n\n.doc kbd,\n.doc .keyseq {\n line-height: 1;\n}\n\n.doc .keyseq {\n font-size: calc(16 / var(--pixel-to-rem));\n}\n\n.doc .keyseq kbd {\n margin: 0 0.125em;\n}\n\n.doc .keyseq kbd:first-child {\n margin-left: 0;\n}\n\n.doc .keyseq kbd:last-child {\n margin-right: 0;\n}\n\n/* Misc */\n\n.doc i.fa {\n hyphens: none;\n font-style: normal;\n}\n\n.doc .language-console .hljs-meta {\n user-select: none;\n}\n\n.doc .dlist dt {\n font-style: italic;\n}\n\n.doc .dlist dd {\n margin: 0 0 0.25rem 1.5rem;\n}\n\n.doc .dlist dd:last-of-type {\n margin-bottom: 0;\n}\n\n.doc td.hdlist1,\n.doc td.hdlist2 {\n padding: 0.5rem 0 0;\n vertical-align: top;\n}\n\n.doc tr:first-child > .hdlist1,\n.doc tr:first-child > .hdlist2 {\n padding-top: 0;\n}\n\n.doc td.hdlist1 {\n font-weight: var(--body-font-weight-bold);\n padding-right: 0.25rem;\n}\n\n.doc td.hdlist2 {\n padding-left: 0.25rem;\n}\n\n.doc .colist {\n font-size: calc(16 / var(--pixel-to-rem));\n margin: 0.25rem 0 -0.25rem;\n}\n\n.doc .colist > table > tr > :first-child,\n.doc .colist > table > tbody > tr > :first-child {\n padding: 0.25em 0.5rem 0;\n vertical-align: top;\n}\n\n.doc .colist > table > tr > :last-child,\n.doc .colist > table > tbody > tr > :last-child {\n padding: 0.25rem 0;\n}\n\n.doc .conum[data-value] {\n /* border: 1px solid currentColor; */\n font-family: var(--monospace-font-family);\n border-radius: 100%;\n display: inline-block;\n font-size: calc(12.5 / var(--pixel-to-rem));\n font-style: normal;\n line-height: 1.2;\n text-align: center;\n width: 1.25em;\n height: 1.25em;\n letter-spacing: -0.25ex;\n text-indent: -0.25ex;\n background: var(--asciidoctor-callout-background);\n color: var(--asciidoctor-callout-font-color);\n}\n\n.doc .conum[data-value]::after {\n content: attr(data-value);\n}\n\n.doc .conum[data-value] + b {\n display: none;\n}\n\n.doc hr {\n border: solid var(--asciidoctor-section-divider-color);\n border-width: 2px 0 0;\n height: 0;\n}\n\n/* Pass-though Classes */\n\n.doc :not(pre).nowrap {\n white-space: nowrap;\n}\n\n.doc .nobreak {\n hyphens: none;\n word-wrap: normal;\n}\n\n.doc .right {\n float: right;\n}\n\n.doc .left {\n float: left;\n}\n\n.doc .stretch {\n width: 100%;\n}\n\n.doc .underline {\n text-decoration: underline;\n}\n\n.doc .line-through {\n text-decoration: line-through;\n}\n\n.doc .halign-left {\n text-align: left;\n}\n\n.doc .halign-right {\n text-align: right;\n}\n\n.doc .halign-center {\n text-align: center;\n}\n\n.doc .valign-top {\n vertical-align: top;\n}\n\n.doc .valign-bottom {\n vertical-align: bottom;\n}\n\n.doc .valign-middle {\n vertical-align: middle;\n}\n\n/* Footer */\n\n#footer #footer-text {\n font-size: calc(14 / var(--pixel-to-rem));\n color: var(--asciidoctor-footer-font-color);\n padding: 2rem 0;\n border-top: 1px solid var(--asciidoctor-section-divider-color);\n}\n\n/* Responsive and Dark Theme Overrides */\n\nhtml.dark-theme #doc {\n background: no-repeat top right / 305px 147px;\n background-image: url(\"../img/doc-background-dark.svg\");\n}\n\n@media screen and (max-width: 1024px) {\n #doc {\n background: no-repeat top right / 203px 95px;\n background-image: url(\"../img/doc-background.svg\");\n }\n html.dark-theme #doc {\n background: no-repeat top right / 203px 95px;\n background-image: url(\"../img/doc-background-dark.svg\");\n }\n}\n\n@media screen and (max-width: 800px) {\n html.dark-theme #doc,\n #doc {\n background: none;\n }\n}\n","/*\n * Copyright 2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n.hljs {\n display: block;\n overflow-x: auto;\n padding: 0.5em;\n background: var(--highlight-background-color);\n color: var(--highlight-font-color);\n}\n\n.hljs-keyword,\n.hljs-selector-tag,\n.hljs-subst {\n color: var(--highlight-keyword-font-color);\n}\n\n.hljs-comment,\n.hljs-quote {\n color: var(--highlight-comment-font-color);\n}\n\n.hljs-string,\n.hljs-doctag {\n color: var(--highlight-string-font-color);\n}\n\n.hljs-meta {\n color: var(--highlight-meta-font-color);\n}\n\n.hljs-built_in,\n.hljs-builtin-name,\n.hljs-number,\n.hljs-symbol,\n.hljs-literal {\n color: var(--highlight-constant-font-color);\n}\n\n.hljs-variable,\n.hljs-template-variable {\n color: var(--highlight-variable-font-color);\n}\n\n.hljs-tag,\n.hljs-name,\n.hljs-attribute {\n color: var(--highlight-tag-font-color);\n}\n\n.hljs-tag .hljs-attr {\n color: var(--highlight-tag-attribute-font-color);\n}\n\n.hljs-type,\n.hljs-class .hljs-title {\n color: var(--highlight-type-font-color);\n}\n\n.hljs-regexp {\n color: var(--highlight-regex-font-color);\n}\n\n.hljs-link {\n text-decoration: underline;\n color: var(--highlight-link-font-color);\n}\n\n.hljs-addition {\n color: var(--highlight-addition-font-color);\n}\n\n.hljs-deletion {\n color: var(--highlight-deletion-font-color);\n}\n\n.hljs-emphasis {\n font-style: italic;\n}\n\n.hljs-strong {\n font-weight: bold;\n}\n\n.language-json .hljs-number,\n.language-json .hljs-literal {\n color: var(--highlight-variable-font-color);\n}\n\n.language-json .hljs-attr {\n color: var(--highlight-string-font-color);\n}\n","/*\n * Copyright 2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* Block Switching */\n\n.hidden {\n display: none;\n}\n\n.doc .tabs {\n font-weight: bold;\n font-size: calc(12 / var(--pixel-to-rem));\n border-style: none;\n display: inline-block;\n position: relative;\n bottom: 0;\n margin-top: 0.5rem;\n margin-bottom: calc(2 / var(--pixel-to-rem));\n}\n\n.doc .tab:not(:first-child) {\n border: 1px solid var(--tabs-border-color);\n}\n\n.doc .tab {\n padding: 0.3rem 0.6rem;\n background-color: var(--tabs-background-color);\n color: var(--tabs-font-color);\n display: inline-block;\n cursor: pointer;\n border: 1px solid var(--tabs-border-color);\n margin-bottom: calc(2 / var(--pixel-to-rem));\n border-radius: 0;\n transition: background-color 200ms;\n}\n\n.doc .tab:hover {\n color: var(--tabs-hover-font-color);\n background-color: var(--tabs-hover-background);\n text-decoration: underline;\n}\n\n.doc .tab.selected {\n background-color: var(--tabs-selected-background-color);\n border-color: var(--tabs-selected-background-color);\n color: var(--tabs-selected-font-color);\n}\n\n.doc .tab.selected:hover {\n color: var(--tabs-selected-font-color);\n text-decoration: none;\n}\n\n.doc div.openblock.tabs-content > .content {\n padding: 1rem;\n background-color: var(--tabs-group-background-color);\n}\n","/*\n * Copyright 2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nbody.toc-left #doc {\n border-left: 1px solid var(--layout-border-color);\n overflow: auto;\n margin-left: var(--toc-width);\n}\n\n#toc {\n display: var(--toc-display);\n position: absolute;\n top: var(--layout-banner-height);\n width: var(--toc-width);\n margin-left: calc(var(--toc-width) * -1);\n border-right: 1px solid var(--layout-border-color);\n padding: 1.7rem 1rem 0 1rem;\n font-size: 0.95rem;\n line-height: 1.1;\n}\n\n#toctitle {\n display: none;\n}\n\n#toc ul,\n#toc ol {\n padding: 0;\n}\n\n#toc ul ul,\n#toc ul ol {\n padding-left: 0.8rem;\n}\n\n#toc li {\n display: block;\n list-style: none;\n}\n\n#toc a {\n display: block;\n text-decoration: none;\n color: var(--toc-font-color);\n padding: 0.4rem 0.6rem;\n border-radius: 4px;\n}\n\n#toc a:hover {\n background-color: var(--toc-hover-background-color);\n}\n\nbody.fixed-toc #toc {\n position: fixed;\n top: 0;\n overflow-x: hidden;\n height: 100%;\n}\n\n#toc li.active > a {\n background-color: var(--toc-active-background-color);\n color: var(--toc-active-font-color);\n}\n\n#toc > ul ul,\n#toc > ol ol {\n display: none;\n}\n\n#toc li.active > ul,\n#toc li.active > ol,\n#toc ul.expanded,\n#toc ol.expanded {\n display: block;\n}\n\n#back-to-index {\n display: block;\n margin-bottom: 0.6rem;\n}\n\n#back-to-index a {\n padding-left: 1.6rem;\n margin-bottom: 0.6rem;\n margin-top: -0.9rem;\n}\n\n#back-to-index a::before {\n content: \"\";\n filter: var(--toc-back-to-index-filter);\n background: no-repeat center / 16px 16px;\n background-image: url(\"../img/octicons-16.svg#view-chevron-left\");\n display: block;\n position: absolute;\n min-width: 16px;\n min-height: 16px;\n left: 1.4rem;\n}\n\n#tocbar-container {\n display: var(--toc-bar-display);\n width: 100%;\n}\n\n#tocbar-container {\n height: var(--tocbar-height);\n background-color: var(--body-background-color);\n border-bottom: 1px solid var(--panel-border-color);\n z-index: 10000;\n}\n\n#tocbar {\n width: 100%;\n height: 100%;\n padding-left: 6px;\n}\n\nbody.fixed-toc #tocbar-container {\n position: fixed;\n top: 0;\n}\n\nbutton#toggle-toc {\n width: var(--toc-bar-height);\n height: var(--toc-bar-height);\n filter: var(--toc-bar-button-filter);\n background: no-repeat center / 16px 16px;\n background-image: url(\"../img/octicons-16.svg#view-three-bars\");\n border: none;\n outline: none;\n padding: 0;\n display: block;\n}\n\nbody.show-toc button#toggle-toc {\n background-image: url(\"../img/octicons-16.svg#view-x\");\n}\n\n@media screen and (max-width: 800px) {\n body.fixed-toc #toc {\n top: var(--toc-bar-height);\n }\n\n #toc {\n top: calc(var(--layout-banner-height) + var(--toc-bar-height));\n width: 100%;\n height: 100%;\n left: 0;\n background-color: var(--body-background-color);\n z-index: 10000;\n }\n\n body.show-toc #toc {\n display: block;\n }\n}\n","/*\n * Copyright 2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\ndiv.codetools {\n --button-width: 28px;\n --button-height: 24px;\n --arrow-size: 5px;\n display: flex;\n position: absolute;\n bottom: 9px;\n right: 8px;\n background: var(--codetools-background-color);\n padding: 0;\n border-radius: 2px;\n border: 1px solid var(--codetools-border-color);\n opacity: 0;\n transition: opacity 150ms ease-in-out;\n}\n\n.doc pre.highlight:hover div.codetools {\n opacity: 1;\n}\n\ndiv.codetools button {\n width: var(--button-width);\n height: var(--button-height);\n filter: var(--codetools-button-filter);\n background: no-repeat center / 16px 16px;\n border: none;\n padding: 0;\n outline: none;\n}\n\ndiv.codetools button:not(:last-child) {\n border-right: 1px solid var(--codetools-divider-color);\n}\n\ndiv.codetools button:hover {\n background-color: var(--codetools-hover-background-color);\n transition: filter 300ms;\n}\n\ndiv.codetools button:active {\n filter: var(--codetools-button-active-filter);\n transition: filter none;\n}\n\ndiv.codetools button span.label {\n display: none;\n}\n\ndiv.codetools button.copy-button {\n background-image: url(\"../img/octicons-16.svg#view-clippy\");\n}\n\ndiv.codetools button.unfold-button {\n background-image: url(\"../img/octicons-16.svg#view-unfold\");\n}\n\ndiv.codetools button.fold-button {\n background-image: url(\"../img/octicons-16.svg#view-fold\");\n}\n\ndiv.codetools span.copied {\n opacity: 0;\n display: block;\n content: \"\";\n position: relative;\n width: var(--button-width);\n height: var(--button-height);\n z-index: 1000000;\n transition: opacity 500ms;\n}\n\ndiv.codetools button:active span.copied {\n filter: invert();\n transition: filter none;\n}\n\ndiv.codetools span.copied:before {\n position: absolute;\n bottom: calc(var(--arrow-size) * -1);\n left: 50%;\n margin-left: calc(var(--arrow-size) / -2);\n content: \"\";\n border: var(--arrow-size) solid var(--codetools-popup-background-color);\n border-color: transparent transparent var(--codetools-popup-background-color)\n transparent;\n}\n\ndiv.codetools span.copied:after {\n content: \"Copied to clipboard!\";\n position: absolute;\n top: calc(var(--button-height) + var(--arrow-size));\n right: 100%;\n margin-right: calc(var(--button-width) * -1);\n background-color: var(--codetools-popup-background-color);\n color: var(--codetools-popup-font-color);\n padding: 5px 8px;\n border-radius: 3px;\n font-weight: bold;\n}\n\ndiv.codetools button.clicked span.copied {\n opacity: 1;\n}\n\nspan.fold-block {\n position: relative;\n float: left;\n clear: left;\n padding-right: 0.75rem;\n overflow: hidden;\n}\n\ncode.unfolded span.fold-block.hide-when-folded,\ncode:not(.unfolded) span.fold-block.hide-when-unfolded {\n max-height: 99999px;\n opacity: 1;\n}\n\ncode.unfolded span.fold-block.hide-when-unfolded,\ncode:not(.unfolded) span.fold-block.hide-when-folded {\n max-height: 0px;\n opacity: 0;\n}\n\ncode.unfolding span.fold-block.hide-when-folded {\n max-height: 600px;\n opacity: 1;\n}\n\ncode.folding span.fold-block.hide-when-unfolded {\n max-height: 400px;\n opacity: 1;\n}\n\ncode.unfolding span.fold-block.hide-when-unfolded,\ncode.folding span.fold-block.hide-when-folded {\n max-height: 0;\n opacity: 0;\n}\n\ncode.unfolding span.fold-block.hide-when-unfolded {\n transition: max-height 200ms cubic-bezier(0, 1, 0, 1), opacity 200ms linear;\n}\n\ncode.folding span.fold-block.hide-when-unfolded {\n transition: max-height 200ms cubic-bezier(1, 0, 1, 0), opacity 200ms linear;\n}\n\ncode.unfolding span.fold-block.hide-when-folded {\n transition: max-height 200ms cubic-bezier(1, 0, 1, 0), opacity 200ms linear;\n}\n\ncode.folding span.fold-block.hide-when-folded {\n transition: max-height 200ms cubic-bezier(0, 1, 0, 1), opacity 200ms linear;\n}\n"]} \ No newline at end of file diff --git a/3.8.13/reference/html/datastore.html b/3.8.13/reference/html/datastore.html new file mode 100644 index 0000000000..fee31912ad --- /dev/null +++ b/3.8.13/reference/html/datastore.html @@ -0,0 +1,2210 @@ + + + + + + + +Spring Data Cloud Datastore + + + + + + + + + +
+
+
+ +
+
+

Spring Data Cloud Datastore

+
+
+ + + + + +
+ + +This integration is fully compatible with Firestore in Datastore Mode, but not with Firestore in Native Mode. +
+
+
+

Spring Data is an abstraction for storing and retrieving POJOs in numerous storage technologies. +Spring Framework on Google Cloud adds Spring Data support for Google Cloud Firestore in Datastore mode.

+
+
+

Maven coordinates for this module only, using Spring Framework on Google Cloud BOM:

+
+
+
+
<dependency>
+    <groupId>com.google.cloud</groupId>
+    <artifactId>spring-cloud-gcp-data-datastore</artifactId>
+</dependency>
+
+
+
+

Gradle coordinates:

+
+
+
+
dependencies {
+    implementation("com.google.cloud:spring-cloud-gcp-data-datastore")
+}
+
+
+
+

We provide a Spring Boot Starter for Spring Data Datastore, with which you can use our recommended auto-configuration setup. +To use the starter, see the coordinates below.

+
+
+

Maven:

+
+
+
+
<dependency>
+    <groupId>com.google.cloud</groupId>
+    <artifactId>spring-cloud-gcp-starter-data-datastore</artifactId>
+</dependency>
+
+
+
+

Gradle:

+
+
+
+
dependencies {
+    implementation("com.google.cloud:spring-cloud-gcp-starter-data-datastore")
+}
+
+
+
+

This setup takes care of bringing in the latest compatible version of Cloud Java Cloud Datastore libraries as well.

+
+
+

Configuration

+
+

To setup Spring Data Cloud Datastore, you have to configure the following:

+
+
+
    +
  • +

    Setup the connection details to Google Cloud Datastore.

    +
  • +
+
+
+

Cloud Datastore settings

+
+

You can use the Spring Boot Starter for Spring Data Datastore to autoconfigure Google Cloud Datastore in your Spring application. +It contains all the necessary setup that makes it easy to authenticate with your Google Cloud project. +The following configuration options are available:

+
+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Name

Description

Required

Default value

spring.cloud.gcp.datastore.enabled

Enables the Cloud Datastore client

No

true

spring.cloud.gcp.datastore.project-id

Google Cloud project ID where the Google Cloud Datastore API is hosted, if different from the one in the Spring Framework on Google Cloud Core Module

No

spring.cloud.gcp.datastore.database-id

Google Cloud project can host multiple databases. You can specify which database will be used. If not specified, the database id will be "(default)".

No

spring.cloud.gcp.datastore.credentials.location

OAuth2 credentials for authenticating with the Google Cloud Datastore API, if different from the ones in the Spring Framework on Google Cloud Core Module

No

spring.cloud.gcp.datastore.credentials.encoded-key

Base64-encoded OAuth2 credentials for authenticating with the Google Cloud Datastore API, if different from the ones in the Spring Framework on Google Cloud Core Module

No

spring.cloud.gcp.datastore.credentials.scopes

OAuth2 scope for Spring Framework on Google CloudDatastore credentials

No

https://www.googleapis.com/auth/datastore

spring.cloud.gcp.datastore.namespace

The Cloud Datastore namespace to use

No

the Default namespace of Cloud Datastore in your Google Cloud project

spring.cloud.gcp.datastore.host

The hostname:port of the datastore service or emulator to connect to. Can be used to connect to a manually started Datastore Emulator. If the autoconfigured emulator is enabled, this property will be ignored and localhost:<emulator_port> will be used.

No

spring.cloud.gcp.datastore.emulator.enabled

To enable the auto configuration to start a local instance of the Datastore Emulator.

No

false

spring.cloud.gcp.datastore.emulator.port

The local port to use for the Datastore Emulator

No

8081

spring.cloud.gcp.datastore.emulator.consistency

The consistency to use for the Datastore Emulator instance

No

0.9

spring.cloud.gcp.datastore.emulator.store-on-disk

Configures whether or not the emulator should persist any data to disk.

No

true

spring.cloud.gcp.datastore.emulator.data-dir

The directory to be used to store/retrieve data/config for an emulator run.

No

The default value is <USER_CONFIG_DIR>/emulators/datastore. See the gcloud documentation for finding your USER_CONFIG_DIR.

+
+
+

Repository settings

+
+

Spring Data Repositories can be configured via the @EnableDatastoreRepositories annotation on your main @Configuration class. +With our Spring Boot Starter for Spring Data Cloud Datastore, @EnableDatastoreRepositories is automatically added. +It is not required to add it to any other class, unless there is a need to override finer grain configuration parameters provided by @EnableDatastoreRepositories.

+
+
+
+

Autoconfiguration

+
+

Our Spring Boot autoconfiguration creates the following beans available in the Spring application context:

+
+
+
    +
  • +

    an instance of DatastoreTemplate

    +
  • +
  • +

    an instance of all user defined repositories extending CrudRepository, PagingAndSortingRepository, and DatastoreRepository (an extension of PagingAndSortingRepository with additional Cloud Datastore features) when repositories are enabled

    +
  • +
  • +

    an instance of Datastore from the Google Cloud Java Client for Datastore, for convenience and lower level API access

    +
  • +
+
+
+
+

Datastore Emulator Autoconfiguration

+
+

This Spring Boot autoconfiguration can also configure and start a local Datastore Emulator server if enabled by property.

+
+
+

It is useful for integration testing, but not for production.

+
+
+

When enabled, the spring.cloud.gcp.datastore.host property will be ignored and the Datastore autoconfiguration itself will be forced to connect to the autoconfigured local emulator instance.

+
+
+

It will create an instance of LocalDatastoreHelper as a bean that stores the DatastoreOptions to get the Datastore client connection to the emulator for convenience and lower level API for local access. +The emulator will be properly stopped after the Spring application context shutdown.

+
+
+
+
+

Object Mapping

+
+

Spring Data Cloud Datastore allows you to map domain POJOs to Cloud Datastore kinds and entities via annotations:

+
+
+
+
@Entity(name = "traders")
+public class Trader {
+
+	@Id
+	@Field(name = "trader_id")
+	String traderId;
+
+	String firstName;
+
+	String lastName;
+
+	@Transient
+	Double temporaryNumber;
+}
+
+
+
+
+

Spring Data Cloud Datastore will ignore any property annotated with @Transient. +These properties will not be written to or read from Cloud Datastore.

+
+
+

Constructors

+
+

Simple constructors are supported on POJOs. +The constructor arguments can be a subset of the persistent properties. +Every constructor argument needs to have the same name and type as a persistent property on the entity and the constructor should set the property from the given argument. +Arguments that are not directly set to properties are not supported.

+
+
+
+
@Entity(name = "traders")
+public class Trader {
+
+	@Id
+	@Field(name = "trader_id")
+	String traderId;
+
+	String firstName;
+
+	String lastName;
+
+	@Transient
+	Double temporaryNumber;
+
+	public Trader(String traderId, String firstName) {
+	    this.traderId = traderId;
+	    this.firstName = firstName;
+	}
+}
+
+
+
+
+
+

Kind

+
+

The @Entity annotation can provide the name of the Cloud Datastore kind that stores instances of the annotated class, one per row.

+
+
+
+

Keys

+
+

@Id identifies the property corresponding to the ID value.

+
+
+

You must annotate one of your POJO’s fields as the ID value, because every entity in Cloud Datastore requires a single ID value:

+
+
+
+
@Entity(name = "trades")
+public class Trade {
+	@Id
+	@Field(name = "trade_id")
+	String tradeId;
+
+	@Field(name = "trader_id")
+	String traderId;
+
+	String action;
+
+	Double price;
+
+	Double shares;
+
+	String symbol;
+}
+
+
+
+
+

Datastore can automatically allocate integer ID values. +If a POJO instance with a Long ID property is written to Cloud Datastore with null as the ID value, then Spring Data Cloud Datastore will obtain a newly allocated ID value from Cloud Datastore and set that in the POJO for saving. +Because primitive long ID properties cannot be null and default to 0, keys will not be allocated.

+
+
+
+

Fields

+
+

All accessible properties on POJOs are automatically recognized as a Cloud Datastore field. +Field naming is generated by the PropertyNameFieldNamingStrategy by default defined on the DatastoreMappingContext bean. +The @Field annotation optionally provides a different field name than that of the property.

+
+
+
+

Supported Types

+
+

Spring Data Cloud Datastore supports the following types for regular fields and elements of collections:

+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeStored as

com.google.cloud.Timestamp

com.google.cloud.datastore.TimestampValue

com.google.cloud.datastore.Blob

com.google.cloud.datastore.BlobValue

com.google.cloud.datastore.LatLng

com.google.cloud.datastore.LatLngValue

java.lang.Boolean, boolean

com.google.cloud.datastore.BooleanValue

java.lang.Double, double

com.google.cloud.datastore.DoubleValue

java.lang.Long, long

com.google.cloud.datastore.LongValue

java.lang.Integer, int

com.google.cloud.datastore.LongValue

java.lang.String

com.google.cloud.datastore.StringValue

com.google.cloud.datastore.Entity

com.google.cloud.datastore.EntityValue

com.google.cloud.datastore.Key

com.google.cloud.datastore.KeyValue

byte[]

com.google.cloud.datastore.BlobValue

Java enum values

com.google.cloud.datastore.StringValue

+
+

In addition, all types that can be converted to the ones listed in the table by +org.springframework.core.convert.support.DefaultConversionService are supported.

+
+
+
+

Custom types

+
+

Custom converters can be used extending the type support for user defined types.

+
+
+
    +
  1. +

    Converters need to implement the org.springframework.core.convert.converter.Converter interface in both directions.

    +
  2. +
  3. +

    The user defined type needs to be mapped to one of the basic types supported by Cloud Datastore.

    +
  4. +
  5. +

    An instance of both Converters (read and write) needs to be passed to the DatastoreCustomConversions constructor, which then has to be made available as a @Bean for DatastoreCustomConversions.

    +
  6. +
+
+
+

For example:

+
+
+

We would like to have a field of type Album on our Singer POJO and want it to be stored as a string property:

+
+
+
+
@Entity
+public class Singer {
+
+	@Id
+	String singerId;
+
+	String name;
+
+	Album album;
+}
+
+
+
+
+

Where Album is a simple class:

+
+
+
+
public class Album {
+	String albumName;
+
+	LocalDate date;
+}
+
+
+
+
+

We have to define the two converters:

+
+
+
+
	// Converter to write custom Album type
+	static final Converter<Album, String> ALBUM_STRING_CONVERTER =
+			new Converter<Album, String>() {
+				@Override
+				public String convert(Album album) {
+					return album.getAlbumName() + " " + album.getDate().format(DateTimeFormatter.ISO_DATE);
+				}
+			};
+
+	// Converters to read custom Album type
+	static final Converter<String, Album> STRING_ALBUM_CONVERTER =
+			new Converter<String, Album>() {
+				@Override
+				public Album convert(String s) {
+					String[] parts = s.split(" ");
+					return new Album(parts[0], LocalDate.parse(parts[parts.length - 1], DateTimeFormatter.ISO_DATE));
+				}
+			};
+
+
+
+
+

That will be configured in our @Configuration file:

+
+
+
+
@Configuration
+public class ConverterConfiguration {
+	@Bean
+	public DatastoreCustomConversions datastoreCustomConversions() {
+		return new DatastoreCustomConversions(
+				Arrays.asList(
+						ALBUM_STRING_CONVERTER,
+						STRING_ALBUM_CONVERTER));
+	}
+}
+
+
+
+
+
+

Collections and arrays

+
+

Arrays and collections (types that implement java.util.Collection) of supported types are supported. +They are stored as com.google.cloud.datastore.ListValue. +Elements are converted to Cloud Datastore supported types individually. byte[] is an exception, it is converted to +com.google.cloud.datastore.Blob.

+
+
+
+

Custom Converter for collections

+
+

Users can provide converters from List<?> to the custom collection type. +Only read converter is necessary, the Collection API is used on the write side to convert a collection to the internal list type.

+
+
+

Collection converters need to implement the org.springframework.core.convert.converter.Converter interface.

+
+
+

Example:

+
+
+

Let’s improve the Singer class from the previous example. +Instead of a field of type Album, we would like to have a field of type Set<Album>:

+
+
+
+
@Entity
+public class Singer {
+
+	@Id
+	String singerId;
+
+	String name;
+
+	Set<Album> albums;
+}
+
+
+
+
+

We have to define a read converter only:

+
+
+
+
static final Converter<List<?>, Set<?>> LIST_SET_CONVERTER =
+			new Converter<List<?>, Set<?>>() {
+				@Override
+				public Set<?> convert(List<?> source) {
+					return Collections.unmodifiableSet(new HashSet<>(source));
+				}
+			};
+
+
+
+
+

And add it to the list of custom converters:

+
+
+
+
@Configuration
+public class ConverterConfiguration {
+	@Bean
+	public DatastoreCustomConversions datastoreCustomConversions() {
+		return new DatastoreCustomConversions(
+				Arrays.asList(
+						LIST_SET_CONVERTER,
+						ALBUM_STRING_CONVERTER,
+						STRING_ALBUM_CONVERTER));
+	}
+}
+
+
+
+
+
+

Inheritance Hierarchies

+
+

Java entity types related by inheritance can be stored in the same Kind. +When reading and querying entities using DatastoreRepository or DatastoreTemplate with a superclass as the type parameter, you can receive instances of subclasses if you annotate the superclass and its subclasses with DiscriminatorField and DiscriminatorValue:

+
+
+
+
@Entity(name = "pets")
+@DiscriminatorField(field = "pet_type")
+abstract class Pet {
+	@Id
+	Long id;
+
+	abstract String speak();
+}
+
+@DiscriminatorValue("cat")
+class Cat extends Pet {
+	@Override
+	String speak() {
+		return "meow";
+	}
+}
+
+@DiscriminatorValue("dog")
+class Dog extends Pet {
+	@Override
+	String speak() {
+		return "woof";
+	}
+}
+
+@DiscriminatorValue("pug")
+class Pug extends Dog {
+	@Override
+	String speak() {
+		return "woof woof";
+	}
+}
+
+
+
+
+

Instances of all 3 types are stored in the pets Kind. +Because a single Kind is used, all classes in the hierarchy must share the same ID property and no two instances of any type in the hierarchy can share the same ID value.

+
+
+

Entity rows in Cloud Datastore store their respective types' DiscriminatorValue in a field specified by the root superclass’s DiscriminatorField (pet_type in this case). +Reads and queries using a given type parameter will match each entity with its specific type. +For example, reading a List<Pet> will produce a list containing instances of all 3 types. +However, reading a List<Dog> will produce a list containing only Dog and Pug instances. +You can include the pet_type discrimination field in your Java entities, but its type must be convertible to a collection or array of String. +Any value set in the discrimination field will be overwritten upon write to Cloud Datastore.

+
+
+
+
+

Relationships

+
+

There are three ways to represent relationships between entities that are described in this section:

+
+
+
    +
  • +

    Embedded entities stored directly in the field of the containing entity

    +
  • +
  • +

    @Descendant annotated properties for one-to-many relationships

    +
  • +
  • +

    @Reference annotated properties for general relationships without hierarchy

    +
  • +
  • +

    @LazyReference similar to @Reference, but the entities are lazy-loaded when the property is accessed. +(Note that the keys of the children are retrieved when the parent entity is loaded.)

    +
  • +
+
+
+

Embedded Entities

+
+

Fields whose types are also annotated with @Entity are converted to EntityValue and stored inside the parent entity.

+
+
+

Here is an example of Cloud Datastore entity containing an embedded entity in JSON:

+
+
+
+
{
+  "name" : "Alexander",
+  "age" : 47,
+  "child" : {"name" : "Philip"  }
+}
+
+
+
+

This corresponds to a simple pair of Java entities:

+
+
+
+
import com.google.cloud.spring.data.datastore.core.mapping.Entity;
+import org.springframework.data.annotation.Id;
+
+@Entity("parents")
+public class Parent {
+  @Id
+  String name;
+
+  Child child;
+}
+
+@Entity
+public class Child {
+  String name;
+}
+
+
+
+
+

Child entities are not stored in their own kind. +They are stored in their entirety in the child field of the parents kind.

+
+
+

Multiple levels of embedded entities are supported.

+
+
+ + + + + +
+ + +Embedded entities don’t need to have @Id field, it is only required for top level entities. +
+
+
+

Example:

+
+
+

Entities can hold embedded entities that are their own type. +We can store trees in Cloud Datastore using this feature:

+
+
+
+
import com.google.cloud.spring.data.datastore.core.mapping.Embedded;
+import com.google.cloud.spring.data.datastore.core.mapping.Entity;
+import org.springframework.data.annotation.Id;
+
+@Entity
+public class EmbeddableTreeNode {
+  @Id
+  long value;
+
+  EmbeddableTreeNode left;
+
+  EmbeddableTreeNode right;
+
+  Map<String, Long> longValues;
+
+  Map<String, List<Timestamp>> listTimestamps;
+
+  public EmbeddableTreeNode(long value, EmbeddableTreeNode left, EmbeddableTreeNode right) {
+    this.value = value;
+    this.left = left;
+    this.right = right;
+  }
+}
+
+
+
+
+
Maps
+
+

Maps will be stored as embedded entities where the key values become the field names in the embedded entity. +The value types in these maps can be any regularly supported property type, and the key values will be converted to String using the configured converters.

+
+
+

Also, a collection of entities can be embedded; it will be converted to ListValue on write.

+
+
+

Example:

+
+
+

Instead of a binary tree from the previous example, we would like to store a general tree +(each node can have an arbitrary number of children) in Cloud Datastore. +To do that, we need to create a field of type List<EmbeddableTreeNode>:

+
+
+
+
import com.google.cloud.spring.data.datastore.core.mapping.Embedded;
+import org.springframework.data.annotation.Id;
+
+public class EmbeddableTreeNode {
+  @Id
+  long value;
+
+  List<EmbeddableTreeNode> children;
+
+  Map<String, EmbeddableTreeNode> siblingNodes;
+
+  Map<String, Set<EmbeddableTreeNode>> subNodeGroups;
+
+  public EmbeddableTreeNode(List<EmbeddableTreeNode> children) {
+    this.children = children;
+  }
+}
+
+
+
+
+

Because Maps are stored as entities, they can further hold embedded entities:

+
+
+
    +
  • +

    Singular embedded objects in the value can be stored in the values of embedded Maps.

    +
  • +
  • +

    Collections of embedded objects in the value can also be stored as the values of embedded Maps.

    +
  • +
  • +

    Maps in the value are further stored as embedded entities with the same rules applied recursively for their values.

    +
  • +
+
+
+
+
+

Ancestor-Descendant Relationships

+
+

Parent-child relationships are supported via the @Descendants annotation.

+
+
+

Unlike embedded children, descendants are fully-formed entities residing in their own kinds. +The parent entity does not have an extra field to hold the descendant entities. +Instead, the relationship is captured in the descendants' keys, which refer to their parent entities:

+
+
+
+
import com.google.cloud.spring.data.datastore.core.mapping.Descendants;
+import com.google.cloud.spring.data.datastore.core.mapping.Entity;
+import org.springframework.data.annotation.Id;
+
+@Entity("orders")
+public class ShoppingOrder {
+  @Id
+  long id;
+
+  @Descendants
+  List<Item> items;
+}
+
+@Entity("purchased_item")
+public class Item {
+  @Id
+  Key purchasedItemKey;
+
+  String name;
+
+  Timestamp timeAddedToOrder;
+}
+
+
+
+
+

For example, an instance of a GQL key-literal representation for Item would also contain the parent ShoppingOrder ID value:

+
+
+
+
Key(orders, '12345', purchased_item, 'eggs')
+
+
+
+

The GQL key-literal representation for the parent ShoppingOrder would be:

+
+
+
+
Key(orders, '12345')
+
+
+
+

The Cloud Datastore entities exist separately in their own kinds.

+
+
+

The ShoppingOrder:

+
+
+
+
{
+  "id" : 12345
+}
+
+
+
+

The two items inside that order:

+
+
+
+
{
+  "purchasedItemKey" : Key(orders, '12345', purchased_item, 'eggs'),
+  "name" : "eggs",
+  "timeAddedToOrder" : "2014-09-27 12:30:00.45-8:00"
+}
+
+{
+  "purchasedItemKey" : Key(orders, '12345', purchased_item, 'sausage'),
+  "name" : "sausage",
+  "timeAddedToOrder" : "2014-09-28 11:30:00.45-9:00"
+}
+
+
+
+

The parent-child relationship structure of objects is stored in Cloud Datastore using Datastore’s ancestor relationships. +Because the relationships are defined by the Ancestor mechanism, there is no extra column needed in either the parent or child entity to store this relationship. +The relationship link is part of the descendant entity’s key value. +These relationships can be many levels deep.

+
+
+

Properties holding child entities must be collection-like, but they can be any of the supported inter-convertible collection-like types that are supported for regular properties such as List, arrays, Set, etc…​ +Child items must have Key as their ID type because Cloud Datastore stores the ancestor relationship link inside the keys of the children.

+
+
+

Reading or saving an entity automatically causes all subsequent levels of children under that entity to be read or saved, respectively. +If a new child is created and added to a property annotated @Descendants and the key property is left null, then a new key will be allocated for that child. +The ordering of the retrieved children may not be the same as the ordering in the original property that was saved.

+
+
+

Child entities cannot be moved from the property of one parent to that of another unless the child’s key property is set to null or a value that contains the new parent as an ancestor. +Since Cloud Datastore entity keys can have multiple parents, it is possible that a child entity appears in the property of multiple parent entities. +Because entity keys are immutable in Cloud Datastore, to change the key of a child you must delete the existing one and re-save it with the new key.

+
+
+
+

Key Reference Relationships

+
+

General relationships can be stored using the @Reference annotation.

+
+
+
+
import org.springframework.data.annotation.Reference;
+import org.springframework.data.annotation.Id;
+
+@Entity
+public class ShoppingOrder {
+  @Id
+  long id;
+
+  @Reference
+  List<Item> items;
+
+  @Reference
+  Item specialSingleItem;
+}
+
+@Entity
+public class Item {
+  @Id
+  Key purchasedItemKey;
+
+  String name;
+
+  Timestamp timeAddedToOrder;
+}
+
+
+
+
+

@Reference relationships are between fully-formed entities residing in their own kinds. +The relationship between ShoppingOrder and Item entities are stored as a Key field inside ShoppingOrder, which are resolved to the underlying Java entity type by Spring Data Cloud Datastore:

+
+
+
+
{
+  "id" : 12345,
+  "specialSingleItem" : Key(item, "milk"),
+  "items" : [ Key(item, "eggs"), Key(item, "sausage") ]
+}
+
+
+
+

Reference properties can either be singular or collection-like. +These properties correspond to actual columns in the entity and Cloud Datastore Kind that hold the key values of the referenced entities. +The referenced entities are full-fledged entities of other Kinds.

+
+
+

Similar to the @Descendants relationships, reading or writing an entity will recursively read or write all of the referenced entities at all levels. +If referenced entities have null ID values, then they will be saved as new entities and will have ID values allocated by Cloud Datastore. +There are no requirements for relationships between the key of an entity and the keys that entity holds as references. +The order of collection-like reference properties is not preserved when reading back from Cloud Datastore.

+
+
+
+
+

Datastore Operations & Template

+
+

DatastoreOperations and its implementation, DatastoreTemplate, provides the Template pattern familiar to Spring developers.

+
+
+

Using the auto-configuration provided by Spring Boot Starter for Datastore, your Spring application context will contain a fully configured DatastoreTemplate object that you can autowire in your application:

+
+
+
+
@SpringBootApplication
+public class DatastoreTemplateExample {
+
+	@Autowired
+	DatastoreTemplate datastoreTemplate;
+
+	public void doSomething() {
+		this.datastoreTemplate.deleteAll(Trader.class);
+		//...
+		Trader t = new Trader();
+		//...
+		this.datastoreTemplate.save(t);
+		//...
+		List<Trader> traders = datastoreTemplate.findAll(Trader.class);
+		//...
+	}
+}
+
+
+
+
+

The Template API provides convenience methods for:

+
+
+
    +
  • +

    Write operations (saving and deleting)

    +
  • +
  • +

    Read-write transactions

    +
  • +
+
+
+

GQL Query

+
+

In addition to retrieving entities by their IDs, you can also submit queries.

+
+
+
+
  <T> Iterable<T> query(Query<? extends BaseEntity> query, Class<T> entityClass);
+
+  <A, T> Iterable<T> query(Query<A> query, Function<A, T> entityFunc);
+
+  Iterable<Key> queryKeys(Query<Key> query);
+
+
+
+
+

These methods, respectively, allow querying for:

+
+
+
    +
  • +

    entities mapped by a given entity class using all the same mapping and converting features

    +
  • +
  • +

    arbitrary types produced by a given mapping function

    +
  • +
  • +

    only the Cloud Datastore keys of the entities found by the query

    +
  • +
+
+
+
+

Find by ID(s)

+
+

Using DatastoreTemplate you can find entities by id. For example:

+
+
+
+
Trader trader = this.datastoreTemplate.findById("trader1", Trader.class);
+
+List<Trader> traders = this.datastoreTemplate.findAllById(Arrays.asList("trader1", "trader2"), Trader.class);
+
+List<Trader> allTraders = this.datastoreTemplate.findAll(Trader.class);
+
+
+
+
+

Cloud Datastore uses key-based reads with strong consistency, but queries with eventual consistency. +In the example above the first two reads utilize keys, while the third is run by using a query based on the corresponding Kind of Trader.

+
+
+
Indexes
+
+

By default, all fields are indexed. +To disable indexing on a particular field, @Unindexed annotation can be used.

+
+
+

Example:

+
+
+
+
import com.google.cloud.spring.data.datastore.core.mapping.Unindexed;
+
+public class ExampleItem {
+	long indexedField;
+
+	@Unindexed
+	long unindexedField;
+
+	@Unindexed
+	List<String> unindexedListField;
+}
+
+
+
+
+

When using queries directly or via Query Methods, Cloud Datastore requires composite custom indexes if the select statement is not SELECT * or if there is more than one filtering condition in the WHERE clause.

+
+
+
+
Read with offsets, limits, and sorting
+
+

DatastoreRepository and custom-defined entity repositories implement the Spring Data PagingAndSortingRepository, which supports offsets and limits using page numbers and page sizes. +Paging and sorting options are also supported in DatastoreTemplate by supplying a DatastoreQueryOptions to findAll.

+
+
+
+
Partial read
+
+

This feature is not supported yet.

+
+
+
+
+

Write / Update

+
+

The write methods of DatastoreOperations accept a POJO and writes all of its properties to Datastore. +The required Datastore kind and entity metadata is obtained from the given object’s actual type.

+
+
+

If a POJO was retrieved from Datastore and its ID value was changed and then written or updated, the operation will occur as if against a row with the new ID value. +The entity with the original ID value will not be affected.

+
+
+
+
Trader t = new Trader();
+this.datastoreTemplate.save(t);
+
+
+
+
+

The save method behaves as update-or-insert. +In contrast, the insert method will fail if an entity already exists.

+
+
+
Partial Update
+
+

This feature is not supported yet.

+
+
+
+
+

Transactions

+
+

Read and write transactions are provided by DatastoreOperations via the performTransaction method:

+
+
+
+
@Autowired
+DatastoreOperations myDatastoreOperations;
+
+public String doWorkInsideTransaction() {
+  return myDatastoreOperations.performTransaction(
+    transactionDatastoreOperations -> {
+      // Work with transactionDatastoreOperations here.
+      // It is also a DatastoreOperations object.
+
+      return "transaction completed";
+    }
+  );
+}
+
+
+
+
+

The performTransaction method accepts a Function that is provided an instance of a DatastoreOperations object. +The final returned value and type of the function is determined by the user. +You can use this object just as you would a regular DatastoreOperations with an exception:

+
+
+
    +
  • +

    It cannot perform sub-transactions.

    +
  • +
+
+
+

Because of Cloud Datastore’s consistency guarantees, there are limitations to the operations and relationships among entities used inside transactions.

+
+
+
Declarative Transactions with @Transactional Annotation
+
+

This feature requires a bean of DatastoreTransactionManager, which is provided when using spring-cloud-gcp-starter-data-datastore.

+
+
+

DatastoreTemplate and DatastoreRepository support running methods with the @Transactional annotation as transactions. +If a method annotated with @Transactional calls another method also annotated, then both methods will work within the same transaction. +performTransaction cannot be used in @Transactional annotated methods because Cloud Datastore does not support transactions within transactions.

+
+
+

Other Google Cloud database-related integrations like Spanner and Firestore can introduce PlatformTransactionManager beans, and can interfere with Datastore Transaction Manager. To disambiguate, explicitly specify the name of the transaction manager bean for such @Transactional methods. Example:

+
+
+
+
@Transactional(transactionManager = "datastoreTransactionManager")
+
+
+
+
+
+
+

Read-Write Support for Maps

+
+

You can work with Maps of type Map<String, ?> instead of with entity objects by directly reading and writing them to and from Cloud Datastore.

+
+
+ + + + + +
+ + +This is a different situation than using entity objects that contain Map properties. +
+
+
+

The map keys are used as field names for a Datastore entity and map values are converted to Datastore supported types. +Only simple types are supported (i.e. collections are not supported). +Converters for custom value types can be added (see Custom types section).

+
+
+

Example:

+
+
+
+
Map<String, Long> map = new HashMap<>();
+map.put("field1", 1L);
+map.put("field2", 2L);
+map.put("field3", 3L);
+
+keyForMap = datastoreTemplate.createKey("kindName", "id");
+
+// write a map
+datastoreTemplate.writeMap(keyForMap, map);
+
+// read a map
+Map<String, Long> loadedMap = datastoreTemplate.findByIdAsMap(keyForMap, Long.class);
+
+
+
+
+
+
+

Repositories

+
+

Spring Data Repositories are an abstraction that can reduce boilerplate code.

+
+
+

For example:

+
+
+
+
public interface TraderRepository extends DatastoreRepository<Trader, String> {
+}
+
+
+
+
+

Spring Data generates a working implementation of the specified interface, which can be autowired into an application.

+
+
+

The Trader type parameter to DatastoreRepository refers to the underlying domain type. +The second type parameter, String in this case, refers to the type of the key of the domain type.

+
+
+
+
public class MyApplication {
+
+	@Autowired
+	TraderRepository traderRepository;
+
+	public void demo() {
+
+		this.traderRepository.deleteAll();
+		String traderId = "demo_trader";
+		Trader t = new Trader();
+		t.traderId = traderId;
+		this.tradeRepository.save(t);
+
+		Iterable<Trader> allTraders = this.traderRepository.findAll();
+
+		int count = this.traderRepository.count();
+	}
+}
+
+
+
+
+

Repositories allow you to define custom Query Methods (detailed in the following sections) for retrieving, counting, and deleting based on filtering and paging parameters. +Filtering parameters can be of types supported by your configured custom converters.

+
+
+

Query methods by convention

+
+
+
public interface TradeRepository extends DatastoreRepository<Trade, String[]> {
+  List<Trader> findByAction(String action);
+
+  // throws an exception if no results
+  Trader findOneByAction(String action);
+
+  // because of the annotation, returns null if no results
+  @Nullable
+  Trader getByAction(String action);
+
+  Optional<Trader> getOneByAction(String action);
+
+  int countByAction(String action);
+
+  boolean existsByAction(String action);
+
+  List<Trade> findTop3ByActionAndSymbolAndPriceGreaterThanAndPriceLessThanOrEqualOrderBySymbolDesc(
+  			String action, String symbol, double priceFloor, double priceCeiling);
+
+  Page<TestEntity> findByAction(String action, Pageable pageable);
+
+  Slice<TestEntity> findBySymbol(String symbol, Pageable pageable);
+
+  List<TestEntity> findBySymbol(String symbol, Sort sort);
+
+  Stream<TestEntity> findBySymbol(String symbol);
+}
+
+
+
+
+

In the example above the query methods in TradeRepository are generated based on the name of the methods using the Spring Data Query creation naming convention.

+
+
+ + + + + +
+ + +You can refer to nested fields using Spring Data JPA Property Expressions +
+
+
+

Cloud Datastore only supports filter components joined by AND, and the following operations:

+
+
+
    +
  • +

    equals

    +
  • +
  • +

    greater than or equals

    +
  • +
  • +

    greater than

    +
  • +
  • +

    less than or equals

    +
  • +
  • +

    less than

    +
  • +
  • +

    is null

    +
  • +
+
+
+

After writing a custom repository interface specifying just the signatures of these methods, implementations are generated for you and can be used with an auto-wired instance of the repository. +Because of Cloud Datastore’s requirement that explicitly selected fields must all appear in a composite index together, find name-based query methods are run as SELECT *.

+
+
+

Delete queries are also supported. +For example, query methods such as deleteByAction or removeByAction delete entities found by findByAction. +Delete queries are run as separate read and delete operations instead of as a single transaction because Cloud Datastore cannot query in transactions unless ancestors for queries are specified. +As a result, removeBy and deleteBy name-convention query methods cannot be used inside transactions via either performInTransaction or @Transactional annotation.

+
+
+

Delete queries can have the following return types:

+
+
+
    +
  • +

    An integer type that is the number of entities deleted

    +
  • +
  • +

    A collection of entities that were deleted

    +
  • +
  • +

    'void'

    +
  • +
+
+
+

Methods can have org.springframework.data.domain.Pageable parameter to control pagination and sorting, or org.springframework.data.domain.Sort parameter to control sorting only. +See Spring Data documentation for details.

+
+
+

For returning multiple items in a repository method, we support Java collections as well as org.springframework.data.domain.Page and org.springframework.data.domain.Slice. +If a method’s return type is org.springframework.data.domain.Page, the returned object will include current page, total number of results and total number of pages.

+
+
+ + + + + +
+ + +Methods that return Page run an additional query to compute total number of pages. +Methods that return Slice, on the other hand, do not run any additional queries and, therefore, are much more efficient. +
+
+
+
+

Empty result handling in repository methods

+
+

Java java.util.Optional can be used to indicate the potential absence of a return value.

+
+
+

Alternatively, query methods can return the result without a wrapper. +In that case the absence of a query result is indicated by returning null. +Repository methods returning collections are guaranteed never to return null but rather the corresponding empty collection.

+
+
+ + + + + +
+ + +You can enable nullability checks. For more details please see Spring Framework’s nullability docs. +
+
+
+
+

Query by example

+
+

Query by Example is an alternative querying technique. +It enables dynamic query generation based on a user-provided object. See Spring Data Documentation for details.

+
+
+
Unsupported features:
+
+
    +
  1. +

    Currently, only equality queries are supported (no ignore-case matching, regexp matching, etc.).

    +
  2. +
  3. +

    Per-field matchers are not supported.

    +
  4. +
  5. +

    Embedded entities matching is not supported.

    +
  6. +
  7. +

    Projection is not supported.

    +
  8. +
+
+
+

For example, if you want to find all users with the last name "Smith", you would use the following code:

+
+
+
+
userRepository.findAll(
+    Example.of(new User(null, null, "Smith"))
+
+
+
+
+

null fields are not used in the filter by default. If you want to include them, you would use the following code:

+
+
+
+
userRepository.findAll(
+    Example.of(new User(null, null, "Smith"), ExampleMatcher.matching().withIncludeNullValues())
+
+
+
+
+

You can also extend query specification initially defined by an example in FluentQuery’s chaining style:

+
+
+
+
userRepository.findBy(
+    Example.of(new User(null, null, "Smith")), q -> q.sortBy(Sort.by("firstName")).firstValue());
+
+userRepository.findBy(
+    Example.of(new User(null, null, "Smith")), FetchableFluentQuery::stream);
+
+
+
+
+
+

Custom GQL query methods

+
+

Custom GQL queries can be mapped to repository methods in one of two ways:

+
+
+
    +
  • +

    namedQueries properties file

    +
  • +
  • +

    using the @Query annotation

    +
  • +
+
+
+
Query methods with annotation
+
+

Using the @Query annotation:

+
+
+

The names of the tags of the GQL correspond to the @Param annotated names of the method parameters.

+
+
+
+
public interface TraderRepository extends DatastoreRepository<Trader, String> {
+
+  @Query("SELECT * FROM traders WHERE name = @trader_name")
+  List<Trader> tradersByName(@Param("trader_name") String traderName);
+
+  @Query("SELECT * FROM traders WHERE name = @trader_name")
+  Stream<Trader> tradersStreamByName(@Param("trader_name") String traderName);
+
+  @Query("SELECT * FROM  test_entities_ci WHERE name = @trader_name")
+  TestEntity getOneTestEntity(@Param("trader_name") String traderName);
+
+  @Query("SELECT * FROM traders WHERE name = @trader_name")
+  List<Trader> tradersByNameSort(@Param("trader_name") String traderName, Sort sort);
+
+  @Query("SELECT * FROM traders WHERE name = @trader_name")
+  Slice<Trader> tradersByNameSlice(@Param("trader_name") String traderName, Pageable pageable);
+
+  @Query("SELECT * FROM traders WHERE name = @trader_name")
+  Page<Trader> tradersByNamePage(@Param("trader_name") String traderName, Pageable pageable);
+}
+
+
+
+
+

When the return type is Slice or Pageable, the result set cursor that points to the position just after the page is preserved in the returned Slice or Page object. To take advantage of the cursor to query for the next page or slice, use result.getPageable().next().

+
+
+ + + + + +
+ + +Page requires the total count of entities produced by the query. Therefore, the first query will have to retrieve all of the records just to count them. Instead, we recommend using the Slice return type, because it does not require an additional count query. +
+
+
+
+
 Slice<Trader> slice1 = tradersByNamePage("Dave", PageRequest.of(0, 5));
+ Slice<Trader> slice2 = tradersByNamePage("Dave", slice1.getPageable().next());
+
+
+
+
+ + + + + +
+ + +You cannot use these Query Methods in repositories where the type parameter is a subclass of another class +annotated with DiscriminatorField. +
+
+
+

The following parameter types are supported:

+
+
+
    +
  • +

    com.google.cloud.Timestamp

    +
  • +
  • +

    com.google.cloud.datastore.Blob

    +
  • +
  • +

    com.google.cloud.datastore.Key

    +
  • +
  • +

    com.google.cloud.datastore.Cursor

    +
  • +
  • +

    java.lang.Boolean

    +
  • +
  • +

    java.lang.Double

    +
  • +
  • +

    java.lang.Long

    +
  • +
  • +

    java.lang.String

    +
  • +
  • +

    enum values. +These are queried as String values.

    +
  • +
+
+
+

With the exception of Cursor, array forms of each of the types are also supported.

+
+
+

If you would like to obtain the count of items of a query or if there are any items returned by the query, set the count = true or exists = true properties of the @Query annotation, respectively. +The return type of the query method in these cases should be an integer type or a boolean type.

+
+
+

Cloud Datastore provides provides the SELECT __key__ FROM …​ special column for all kinds that retrieves the Key of each row. +Selecting this special __key__ column is especially useful and efficient for count and exists queries.

+
+
+

You can also query for non-entity types:

+
+
+
+
@Query(value = "SELECT __key__ from test_entities_ci")
+List<Key> getKeys();
+
+@Query(value = "SELECT __key__ from test_entities_ci limit 1")
+Key getKey();
+
+
+
+
+

In order to use @Id annotated fields in custom queries, use __key__ keyword for the field name. The parameter type should be of Key, as in the following example.

+
+
+

Repository method:

+
+
+
+
@Query("select * from  test_entities_ci where size = @size and __key__ = @id")
+LinkedList<TestEntity> findEntities(@Param("size") long size, @Param("id") Key id);
+
+
+
+
+

Generate a key from id value using DatastoreTemplate.createKey method and use it as a parameter for the repository method:

+
+
+
+
this.testEntityRepository.findEntities(1L, datastoreTemplate.createKey(TestEntity.class, 1L))
+
+
+
+
+

SpEL can be used to provide GQL parameters:

+
+
+
+
@Query("SELECT * FROM |com.example.Trade| WHERE trades.action = @act
+  AND price > :#{#priceRadius * -1} AND price < :#{#priceRadius * 2}")
+List<Trade> fetchByActionNamedQuery(@Param("act") String action, @Param("priceRadius") Double r);
+
+
+
+
+

Kind names can be directly written in the GQL annotations. +Kind names can also be resolved from the @Entity annotation on domain classes.

+
+
+

In this case, the query should refer to table names with fully qualified class names surrounded by | characters: |fully.qualified.ClassName|. +This is useful when SpEL expressions appear in the kind name provided to the @Entity annotation. +For example:

+
+
+
+
@Query("SELECT * FROM |com.example.Trade| WHERE trades.action = @act")
+List<Trade> fetchByActionNamedQuery(@Param("act") String action);
+
+
+
+
+
+
Query methods with named queries properties
+
+

You can also specify queries with Cloud Datastore parameter tags and SpEL expressions in properties files.

+
+
+

By default, the namedQueriesLocation attribute on @EnableDatastoreRepositories points to the META-INF/datastore-named-queries.properties file. +You can specify the query for a method in the properties file by providing the GQL as the value for the "interface.method" property:

+
+
+ + + + + +
+ + +You cannot use these Query Methods in repositories where the type parameter is a subclass of another class +annotated with DiscriminatorField. +
+
+
+
+
Trader.fetchByName=SELECT * FROM traders WHERE name = @tag0
+
+
+
+
+
public interface TraderRepository extends DatastoreRepository<Trader, String> {
+
+	// This method uses the query from the properties file instead of one generated based on name.
+	List<Trader> fetchByName(@Param("tag0") String traderName);
+
+}
+
+
+
+
+
+
+

Transactions

+
+

These transactions work very similarly to those of DatastoreOperations, but is specific to the repository’s domain type and provides repository functions instead of template functions.

+
+
+

For example, this is a read-write transaction:

+
+
+
+
@Autowired
+DatastoreRepository myRepo;
+
+public String doWorkInsideTransaction() {
+  return myRepo.performTransaction(
+    transactionDatastoreRepo -> {
+      // Work with the single-transaction transactionDatastoreRepo here.
+      // This is a DatastoreRepository object.
+
+      return "transaction completed";
+    }
+  );
+}
+
+
+
+
+
+

Projections

+
+

Spring Data Cloud Datastore supports projections. +You can define projection interfaces based on domain types and add query methods that return them in your repository:

+
+
+
+
public interface TradeProjection {
+
+	String getAction();
+
+	@Value("#{target.symbol + ' ' + target.action}")
+	String getSymbolAndAction();
+}
+
+public interface TradeRepository extends DatastoreRepository<Trade, Key> {
+
+	List<Trade> findByTraderId(String traderId);
+
+	List<TradeProjection> findByAction(String action);
+
+	@Query("SELECT action, symbol FROM trades WHERE action = @action")
+	List<TradeProjection> findByQuery(String action);
+}
+
+
+
+
+

Projections can be provided by name-convention-based query methods as well as by custom GQL queries. +If using custom GQL queries, you can further restrict the fields retrieved from Cloud Datastore to just those required by the projection. +However, custom select statements (those not using SELECT *) require composite indexes containing the selected fields.

+
+
+

Properties of projection types defined using SpEL use the fixed name target for the underlying domain object. +As a result, accessing underlying properties take the form target.<property-name>.

+
+
+
+

REST Repositories

+
+

When running with Spring Boot, repositories can be exposed as REST services by simply adding this dependency to your pom file:

+
+
+
+
<dependency>
+  <groupId>org.springframework.boot</groupId>
+  <artifactId>spring-boot-starter-data-rest</artifactId>
+</dependency>
+
+
+
+

If you prefer to configure parameters (such as path), you can use @RepositoryRestResource annotation:

+
+
+
+
@RepositoryRestResource(collectionResourceRel = "trades", path = "trades")
+public interface TradeRepository extends DatastoreRepository<Trade, String[]> {
+}
+
+
+
+
+

For example, you can retrieve all Trade objects in the repository by using curl http://<server>:<port>/trades, or any specific trade via curl http://<server>:<port>/trades/<trader_id>.

+
+
+

You can also write trades using curl -XPOST -H"Content-Type: application/json" -d@test.json http://<server>:<port>/trades/ where the file test.json holds the JSON representation of a Trade object.

+
+
+

To delete trades, you can use curl -XDELETE http://<server>:<port>/trades/<trader_id>

+
+
+
+
+

Events

+
+

Spring Data Cloud Datastore publishes events extending the Spring Framework’s ApplicationEvent to the context that can be received by ApplicationListener beans you register.

+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDescriptionContents

AfterFindByKeyEvent

Published immediately after read by-key operations are run by DatastoreTemplate

The entities read from Cloud Datastore and the original keys in the request.

AfterQueryEvent

Published immediately after read byquery operations are run by DatastoreTemplate

The entities read from Cloud Datastore and the original query in the request.

BeforeSaveEvent

Published immediately before save operations are run by DatastoreTemplate

The entities to be sent to Cloud Datastore and the original Java objects being saved.

AfterSaveEvent

Published immediately after save operations are run by DatastoreTemplate

The entities sent to Cloud Datastore and the original Java objects being saved.

BeforeDeleteEvent

Published immediately before delete operations are run by DatastoreTemplate

The keys to be sent to Cloud Datastore. The target entities, ID values, or entity type originally specified for the delete operation.

AfterDeleteEvent

Published immediately after delete operations are run by DatastoreTemplate

The keys sent to Cloud Datastore. The target entities, ID values, or entity type originally specified for the delete operation.

+
+
+

Auditing

+
+

Spring Data Cloud Datastore supports the @LastModifiedDate and @LastModifiedBy auditing annotations for properties:

+
+
+
+
@Entity
+public class SimpleEntity {
+    @Id
+    String id;
+
+    @LastModifiedBy
+    String lastUser;
+
+    @LastModifiedDate
+    DateTime lastTouched;
+}
+
+
+
+
+

Upon insert, update, or save, these properties will be set automatically by the framework before Datastore entities are generated and saved to Cloud Datastore.

+
+
+

To take advantage of these features, add the @EnableDatastoreAuditing annotation to your configuration class and provide a bean for an AuditorAware<A> implementation where the type A is the desired property type annotated by @LastModifiedBy:

+
+
+
+
@Configuration
+@EnableDatastoreAuditing
+public class Config {
+
+    @Bean
+    public AuditorAware<String> auditorProvider() {
+        return () -> Optional.of("YOUR_USERNAME_HERE");
+    }
+}
+
+
+
+
+

The AuditorAware interface contains a single method that supplies the value for fields annotated by @LastModifiedBy and can be of any type. +One alternative is to use Spring Security’s User type:

+
+
+
+
class SpringSecurityAuditorAware implements AuditorAware<User> {
+
+  public Optional<User> getCurrentAuditor() {
+
+    return Optional.ofNullable(SecurityContextHolder.getContext())
+			  .map(SecurityContext::getAuthentication)
+			  .filter(Authentication::isAuthenticated)
+			  .map(Authentication::getPrincipal)
+			  .map(User.class::cast);
+  }
+}
+
+
+
+
+

You can also set a custom provider for properties annotated @LastModifiedDate by providing a bean for DateTimeProvider and providing the bean name to @EnableDatastoreAuditing(dateTimeProviderRef = "customDateTimeProviderBean").

+
+
+
+

Partitioning Data by Namespace

+
+

You can partition your data by using more than one namespace. +This is the recommended method for multi-tenancy in Cloud Datastore.

+
+
+
+
    @Bean
+    public DatastoreNamespaceProvider namespaceProvider() {
+        // return custom Supplier of a namespace string.
+    }
+
+
+
+
+

The DatastoreNamespaceProvider is a synonym for Supplier<String>. +By providing a custom implementation of this bean (for example, supplying a thread-local namespace name), you can direct your application to use multiple namespaces. +Every read, write, query, and transaction you perform will utilize the namespace provided by this supplier.

+
+
+

Note that your provided namespace in application.properties will be ignored if you define a namespace provider bean.

+
+
+
+

Spring Boot Actuator Support

+
+

Cloud Datastore Health Indicator

+
+

If you are using Spring Boot Actuator, you can take advantage of the Cloud Datastore health indicator called datastore. +The health indicator will verify whether Cloud Datastore is up and accessible by your application. +To enable it, all you need to do is add the Spring Boot Actuator to your project.

+
+
+
+
<dependency>
+    <groupId>org.springframework.boot</groupId>
+    <artifactId>spring-boot-starter-actuator</artifactId>
+</dependency>
+
+
+
+
+
+

Sample

+
+

A Simple Spring Boot Application and more advanced Sample Spring Boot Application are provided to show how to use the Spring Data Cloud Datastore starter and template.

+
+
+
+

Test

+
+

Testcontainers provides a gcloud module which offers DatastoreEmulatorContainer. See more at the docs

+
+
+
+
+
+ + +
+
+
+ + \ No newline at end of file diff --git a/3.8.13/reference/html/firestore.html b/3.8.13/reference/html/firestore.html new file mode 100644 index 0000000000..b8526b5dee --- /dev/null +++ b/3.8.13/reference/html/firestore.html @@ -0,0 +1,1208 @@ + + + + + + + +Spring Data Cloud Firestore + + + + + + + + + +
+
+
+ +
+
+

Spring Data Cloud Firestore

+
+
+ + + + + +
+ + +Currently some features are not supported: query by example, projections, and auditing. +
+
+
+

Spring Data is an abstraction for storing and retrieving POJOs in numerous storage technologies. +Spring Framework on Google Cloud adds Spring Data Reactive Repositories support for Google Cloud Firestore in native mode, providing reactive template and repositories support. +To begin using this library, add the spring-cloud-gcp-data-firestore artifact to your project.

+
+
+

Maven coordinates for this module only, using Spring Framework on Google Cloud BOM:

+
+
+
+
<dependency>
+  <groupId>com.google.cloud</groupId>
+  <artifactId>spring-cloud-gcp-data-firestore</artifactId>
+</dependency>
+
+
+
+

Gradle coordinates:

+
+
+
+
dependencies {
+  implementation("com.google.cloud:spring-cloud-gcp-data-firestore")
+}
+
+
+
+

We provide a Spring Boot Starter for Spring Data Firestore, with which you can use our recommended auto-configuration setup. To use the starter, see the coordinates below.

+
+
+
+
<dependency>
+  <groupId>com.google.cloud</groupId>
+  <artifactId>spring-cloud-gcp-starter-data-firestore</artifactId>
+</dependency>
+
+
+
+

Gradle coordinates:

+
+
+
+
dependencies {
+  implementation("com.google.cloud:spring-cloud-gcp-starter-data-firestore")
+}
+
+
+
+

Configuration

+
+

Properties

+
+

The Spring Boot starter for Google Cloud Firestore provides the following configuration options:

+
+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Name

Description

Required

Default value

spring.cloud.gcp.firestore.enabled

Enables or disables Firestore auto-configuration

No

true

spring.cloud.gcp.firestore.project-id

Google Cloud project ID where the Google Cloud Firestore API is hosted, if different from the one in the Spring Framework on Google Cloud Core Module

No

spring.cloud.gcp.firestore.database-id

Google Cloud project can host multiple databases. You can specify which database will be used. If not specified, the database id will be "(default)".

No

spring.cloud.gcp.firestore.emulator.enabled

Enables the usage of an emulator. If this is set to true, then you should set the spring.cloud.gcp.firestore.host-port to the host:port of your locally running emulator instance

No

false

spring.cloud.gcp.firestore.host-port

The host and port of the Firestore service; can be overridden to specify connecting to an already-running Firestore emulator instance.

No

firestore.googleapis.com:443 (the host/port of official Firestore service)

spring.cloud.gcp.firestore.credentials.location

OAuth2 credentials for authenticating with the Google Cloud Firestore API, if different from the ones in the Spring Framework on Google Cloud Core Module

No

spring.cloud.gcp.firestore.credentials.encoded-key

Base64-encoded OAuth2 credentials for authenticating with the Google Cloud Firestore API, if different from the ones in the Spring Framework on Google Cloud Core Module

No

spring.cloud.gcp.firestore.credentials.scopes

OAuth2 scope for Spring Framework on Google CloudFirestore credentials

No

https://www.googleapis.com/auth/datastore

+
+
+

Supported types

+
+

You may use the following field types when defining your persistent entities or when binding query parameters:

+
+
+
    +
  • +

    Long

    +
  • +
  • +

    Integer

    +
  • +
  • +

    Double

    +
  • +
  • +

    Float

    +
  • +
  • +

    String

    +
  • +
  • +

    Boolean

    +
  • +
  • +

    Character

    +
  • +
  • +

    Date

    +
  • +
  • +

    Map

    +
  • +
  • +

    List

    +
  • +
  • +

    Enum

    +
  • +
  • +

    com.google.cloud.Timestamp

    +
  • +
  • +

    com.google.cloud.firestore.GeoPoint

    +
  • +
  • +

    com.google.cloud.firestore.Blob

    +
  • +
+
+
+
+

Reactive Repository settings

+
+

Spring Data Repositories can be configured via the @EnableReactiveFirestoreRepositories annotation on your main @Configuration class. +With our Spring Boot Starter for Spring Data Cloud Firestore, @EnableReactiveFirestoreRepositories is automatically added. +It is not required to add it to any other class, unless there is a need to override finer grain configuration parameters provided by @EnableReactiveFirestoreRepositories.

+
+
+
+

Autoconfiguration

+
+

Our Spring Boot autoconfiguration creates the following beans available in the Spring application context:

+
+
+
    +
  • +

    an instance of FirestoreTemplate

    +
  • +
  • +

    instances of all user defined repositories extending FirestoreReactiveRepository (an extension of ReactiveCrudRepository with additional Cloud Firestore features) when repositories are enabled

    +
  • +
  • +

    an instance of Firestore from the Google Cloud Java Client for Firestore, for convenience and lower level API access

    +
  • +
+
+
+
+
+

Object Mapping

+
+

Spring Data Cloud Firestore allows you to map domain POJOs to Cloud Firestore collections and documents via annotations:

+
+
+
+
import com.google.cloud.firestore.annotation.DocumentId;
+import com.google.cloud.spring.data.firestore.Document;
+
+@Document(collectionName = "usersCollection")
+public class User {
+  /** Used to test @PropertyName annotation on a field. */
+  @PropertyName("drink")
+  public String favoriteDrink;
+
+  @DocumentId private String name;
+
+  private Integer age;
+
+  public User() {}
+
+  public String getName() {
+    return this.name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public Integer getAge() {
+    return this.age;
+  }
+
+  public void setAge(Integer age) {
+    this.age = age;
+  }
+}
+
+
+
+
+

@Document(collectionName = "usersCollection") annotation configures the collection name for the documents of this type. +This annotation is optional, by default the collection name is derived from the class name.

+
+
+

@DocumentId annotation marks a field to be used as document id. +This annotation is required and the annotated field can only be of String type.

+
+
+ + + + + +
+ + +If the property annotated with @DocumentId is null the document id is generated automatically when the entity is saved. +
+
+
+ + + + + +
+ + +Internally we use Firestore client library object mapping. See the documentation for supported annotations. +
+
+
+

Embedded entities and lists

+
+

Spring Data Cloud Firestore supports embedded properties of custom types and lists. +Given a custom POJO definition, you can have properties of this type or lists of this type in your entities. +They are stored as embedded documents (or arrays, correspondingly) in the Cloud Firestore.

+
+
+

Example:

+
+
+
+
@Document(collectionName = "usersCollection")
+public class User {
+  /** Used to test @PropertyName annotation on a field. */
+  @PropertyName("drink")
+  public String favoriteDrink;
+
+  @DocumentId private String name;
+
+  private Integer age;
+
+  private List<String> pets;
+
+  private List<Address> addresses;
+
+  private Address homeAddress;
+
+  public List<String> getPets() {
+    return this.pets;
+  }
+
+  public void setPets(List<String> pets) {
+    this.pets = pets;
+  }
+
+  public List<Address> getAddresses() {
+    return this.addresses;
+  }
+
+  public void setAddresses(List<Address> addresses) {
+    this.addresses = addresses;
+  }
+
+  public Timestamp getUpdateTime() {
+    return updateTime;
+  }
+
+  public void setUpdateTime(Timestamp updateTime) {
+    this.updateTime = updateTime;
+  }
+
+  @PropertyName("address")
+  public Address getHomeAddress() {
+    return this.homeAddress;
+  }
+
+  @PropertyName("address")
+  public void setHomeAddress(Address homeAddress) {
+    this.homeAddress = homeAddress;
+  }
+  public static class Address {
+    String streetAddress;
+    String country;
+
+    public Address() {}
+  }
+}
+
+
+
+
+
+
+

Reactive Repositories

+
+

Spring Data Repositories is an abstraction that can reduce boilerplate code.

+
+
+

For example:

+
+
+
+
public interface UserRepository extends FirestoreReactiveRepository<User> {
+  Flux<User> findBy(Pageable pageable);
+
+  Flux<User> findByAge(Integer age);
+
+  Flux<User> findByAge(Integer age, Sort sort);
+
+  Flux<User> findByAgeOrderByNameDesc(Integer age);
+
+  Flux<User> findAllByOrderByAge();
+
+  Flux<User> findByAgeNot(Integer age);
+
+  Flux<User> findByNameAndAge(String name, Integer age);
+
+  Flux<User> findByHomeAddressCountry(String country);
+
+  Flux<User> findByFavoriteDrink(String drink);
+
+  Flux<User> findByAgeGreaterThanAndAgeLessThan(Integer age1, Integer age2);
+
+  Flux<User> findByAgeGreaterThan(Integer age);
+
+  Flux<User> findByAgeGreaterThan(Integer age, Sort sort);
+
+  Flux<User> findByAgeGreaterThan(Integer age, Pageable pageable);
+
+  Flux<User> findByAgeIn(List<Integer> ages);
+
+  Flux<User> findByAgeNotIn(List<Integer> ages);
+
+  Flux<User> findByAgeAndPetsContains(Integer age, List<String> pets);
+
+  Flux<User> findByNameAndPetsContains(String name, List<String> pets);
+
+  Flux<User> findByPetsContains(List<String> pets);
+
+  Flux<User> findByPetsContainsAndAgeIn(String pets, List<Integer> ages);
+
+  Mono<Long> countByAgeIsGreaterThan(Integer age);
+}
+
+
+
+
+

Spring Data generates a working implementation of the specified interface, which can be autowired into an application.

+
+
+

The User type parameter to FirestoreReactiveRepository refers to the underlying domain type.

+
+
+ + + + + +
+ + +You can refer to nested fields using Spring Data JPA Property Expressions +
+
+
+
+
public class MyApplication {
+
+  @Autowired UserRepository userRepository;
+
+  void writeReadDeleteTest() {
+    List<User.Address> addresses =
+        Arrays.asList(
+            new User.Address("123 Alice st", "US"), new User.Address("1 Alice ave", "US"));
+    User.Address homeAddress = new User.Address("10 Alice blvd", "UK");
+    User alice = new User("Alice", 29, null, addresses, homeAddress);
+    User bob = new User("Bob", 60);
+
+    this.userRepository.save(alice).block();
+    this.userRepository.save(bob).block();
+
+    assertThat(this.userRepository.count().block()).isEqualTo(2);
+    assertThat(this.userRepository.findAll().map(User::getName).collectList().block())
+        .containsExactlyInAnyOrder("Alice", "Bob");
+
+    User aliceLoaded = this.userRepository.findById("Alice").block();
+    assertThat(aliceLoaded.getAddresses()).isEqualTo(addresses);
+    assertThat(aliceLoaded.getHomeAddress()).isEqualTo(homeAddress);
+
+    // cast to SimpleFirestoreReactiveRepository for method be reachable with Spring Boot 2.4
+    SimpleFirestoreReactiveRepository repository =
+        AopTestUtils.getTargetObject(this.userRepository);
+    StepVerifier.create(
+            repository
+                .deleteAllById(Arrays.asList("Alice", "Bob"))
+                .then(this.userRepository.count()))
+        .expectNext(0L)
+        .verifyComplete();
+  }
+}
+
+
+
+
+

Repositories allow you to define custom Query Methods (detailed in the following sections) for retrieving and counting based on filtering and paging parameters.

+
+
+ + + + + +
+ + +Custom queries with @Query annotation are not supported since there is no query language in Cloud Firestore +
+
+
+
+

Firestore Operations & Template

+
+

FirestoreOperations and its implementation, FirestoreTemplate, provides the Template pattern familiar to Spring developers.

+
+
+

Using the auto-configuration provided by Spring Data Cloud Firestore, your Spring application context will contain a fully configured FirestoreTemplate object that you can autowire in your application:

+
+
+
+
@SpringBootApplication
+public class FirestoreTemplateExample {
+
+	@Autowired
+	FirestoreOperations firestoreOperations;
+
+	public Mono<User> createUsers() {
+		return this.firestoreOperations.save(new User("Alice", 29))
+			.then(this.firestoreOperations.save(new User("Bob", 60)));
+	}
+
+	public Flux<User> findUsers() {
+		return this.firestoreOperations.findAll(User.class);
+	}
+
+	public Mono<Long> removeAllUsers() {
+		return this.firestoreOperations.deleteAll(User.class);
+	}
+}
+
+
+
+
+

The Template API provides support for:

+
+
+ +
+
+
+

Query methods by convention

+
+
+
public class MyApplication {
+  void partTreeRepositoryMethodTest() {
+    User u1 = new User("Cloud", 22, null, null, new Address("1 First st., NYC", "USA"));
+    u1.favoriteDrink = "tea";
+    User u2 =
+        new User(
+            "Squall",
+            17,
+            Arrays.asList("cat", "dog"),
+            null,
+            new Address("2 Second st., London", "UK"));
+    u2.favoriteDrink = "wine";
+    Flux<User> users = Flux.fromArray(new User[] {u1, u2});
+
+    this.userRepository.saveAll(users).blockLast();
+
+    assertThat(this.userRepository.count().block()).isEqualTo(2);
+    assertThat(this.userRepository.findBy(PageRequest.of(0, 10)).collectList().block())
+        .containsExactly(u1, u2);
+    assertThat(this.userRepository.findByAge(22).collectList().block()).containsExactly(u1);
+    assertThat(this.userRepository.findByAgeNot(22).collectList().block()).containsExactly(u2);
+    assertThat(this.userRepository.findByHomeAddressCountry("USA").collectList().block())
+        .containsExactly(u1);
+    assertThat(this.userRepository.findByFavoriteDrink("wine").collectList().block())
+        .containsExactly(u2);
+    assertThat(this.userRepository.findByAgeGreaterThanAndAgeLessThan(20, 30).collectList().block())
+        .containsExactly(u1);
+    assertThat(this.userRepository.findByAgeGreaterThan(10).collectList().block())
+        .containsExactlyInAnyOrder(u1, u2);
+    assertThat(this.userRepository.findByNameAndAge("Cloud", 22).collectList().block())
+        .containsExactly(u1);
+    assertThat(
+            this.userRepository
+                .findByNameAndPetsContains("Squall", Collections.singletonList("cat"))
+                .collectList()
+                .block())
+        .containsExactly(u2);
+  }
+}
+
+
+
+
+

In the example above the query method implementations in UserRepository are generated based on the name of the methods using the Spring Data Query creation naming convention.

+
+
+

Cloud Firestore only supports filter components joined by AND, and the following operations:

+
+
+
    +
  • +

    equals

    +
  • +
  • +

    is not equal

    +
  • +
  • +

    greater than or equals

    +
  • +
  • +

    greater than

    +
  • +
  • +

    less than or equals

    +
  • +
  • +

    less than

    +
  • +
  • +

    is null

    +
  • +
  • +

    contains (accepts a List with up to 10 elements, or a singular value)

    +
  • +
  • +

    in (accepts a List with up to 10 elements)

    +
  • +
  • +

    not in (accepts a List with up to 10 elements)

    +
  • +
+
+
+ + + + + +
+ + +If in operation is used in combination with contains operation, the argument to contains operation has to be a singular value. +
+
+
+

After writing a custom repository interface specifying just the signatures of these methods, implementations are generated for you and can be used with an auto-wired instance of the repository.

+
+
+
+

Transactions

+
+

Read-only and read-write transactions are provided by TransactionalOperator (see this blog post on reactive transactions for details). +In order to use it, you would need to autowire ReactiveFirestoreTransactionManager like this:

+
+
+
+
public class MyApplication {
+  @Autowired ReactiveFirestoreTransactionManager txManager;
+}
+
+
+
+
+

After that you will be able to use it to create an instance of TransactionalOperator. +Note that you can switch between read-only and read-write transactions using TransactionDefinition object:

+
+
+
+
DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
+transactionDefinition.setReadOnly(false);
+TransactionalOperator operator =
+    TransactionalOperator.create(this.txManager, transactionDefinition);
+
+
+
+
+

When you have an instance of TransactionalOperator, you can invoke a sequence of Firestore operations in a transaction by using operator::transactional:

+
+
+
+
User alice = new User("Alice", 29);
+User bob = new User("Bob", 60);
+
+this.userRepository
+    .save(alice)
+    .then(this.userRepository.save(bob))
+    .as(operator::transactional)
+    .block();
+
+this.userRepository
+    .findAll()
+    .flatMap(
+        a -> {
+          a.setAge(a.getAge() - 1);
+          return this.userRepository.save(a);
+        })
+    .as(operator::transactional)
+    .collectList()
+    .block();
+
+assertThat(this.userRepository.findAll().map(User::getAge).collectList().block())
+    .containsExactlyInAnyOrder(28, 59);
+
+
+
+
+ + + + + +
+ + +Read operations in a transaction can only happen before write operations. +All write operations are applied atomically. +Read documents are locked until the transaction finishes with a commit or a rollback, which are handled by Spring Data. +If an Exception is thrown within a transaction, the rollback operation is performed. +Otherwise, the commit operation is performed. +
+
+
+

Declarative Transactions with @Transactional Annotation

+
+

This feature requires a bean of SpannerTransactionManager, which is provided when using spring-cloud-gcp-starter-data-firestore.

+
+
+

FirestoreTemplate and FirestoreReactiveRepository support running methods with the @Transactional annotation as transactions. +If a method annotated with @Transactional calls another method also annotated, then both methods will work within the same transaction.

+
+
+

One way to use this feature is illustrated here. You would need to do the following:

+
+
+
    +
  1. +

    Annotate your configuration class with the @EnableTransactionManagement annotation.

    +
  2. +
  3. +

    Create a service class that has methods annotated with @Transactional:

    +
  4. +
+
+
+
+
class UserService {
+  @Autowired private UserRepository userRepository;
+
+  @Transactional
+  public Mono<Void> updateUsers() {
+    return this.userRepository
+        .findAll()
+        .flatMap(
+            a -> {
+              a.setAge(a.getAge() - 1);
+              return this.userRepository.save(a);
+            })
+        .then();
+  }
+}
+
+
+
+
+
    +
  1. +

    Make a Spring Bean provider that creates an instance of that class:

    +
  2. +
+
+
+
+
@Bean
+public UserService userService() {
+  return new UserService();
+}
+
+
+
+
+

After that, you can autowire your service like so:

+
+
+
+
public class MyApplication {
+  @Autowired UserService userService;
+}
+
+
+
+
+

Now when you call the methods annotated with @Transactional on your service object, a transaction will be automatically started. +If an error occurs during the execution of a method annotated with @Transactional, the transaction will be rolled back. +If no error occurs, the transaction will be committed.

+
+
+
+
+

Subcollections

+
+

A subcollection is a collection associated with a specific entity. +Documents in subcollections can contain subcollections as well, allowing you to further nest data. You can nest data up to 100 levels deep.

+
+
+ + + + + +
+ + +Deleting a document does not delete its subcollections! +
+
+
+

To use subcollections you will need to create a FirestoreReactiveOperations object with a parent entity using FirestoreReactiveOperations.withParent call. +You can use this object to save, query and remove entities associated with this parent. +The parent doesn’t have to exist in Firestore, but should have a non-empty id field.

+
+
+

Autowire FirestoreReactiveOperations:

+
+
+
+
@Autowired
+FirestoreReactiveOperations firestoreTemplate;
+
+
+
+
+

Then you can use this object to create a FirestoreReactiveOperations object with a custom parent:

+
+
+
+
FirestoreReactiveOperations bobTemplate =
+    this.firestoreTemplate.withParent(new User("Bob", 60));
+
+PhoneNumber phoneNumber = new PhoneNumber("111-222-333");
+bobTemplate.save(phoneNumber).block();
+assertThat(bobTemplate.findAll(PhoneNumber.class).collectList().block())
+    .containsExactly(phoneNumber);
+bobTemplate.deleteAll(PhoneNumber.class).block();
+assertThat(bobTemplate.findAll(PhoneNumber.class).collectList().block()).isEmpty();
+
+
+
+
+
+

Update Time and Optimistic Locking

+
+

Firestore stores update time for every document. +If you would like to retrieve it, you can add a field of com.google.cloud.Timestamp type to your entity and annotate it with @UpdateTime annotation.

+
+
+
+
@UpdateTime
+Timestamp updateTime;
+
+
+
+
+
Using update time for optimistic locking
+
+

A field annotated with @UpdateTime can be used for optimistic locking. +To enable that, you need to set version parameter to true:

+
+
+
+
@UpdateTime(version = true)
+Timestamp updateTime;
+
+
+
+
+

When you enable optimistic locking, a precondition will be automatically added to the write request to ensure that the document you are updating was not changed since your last read. +It uses this field’s value as a document version and checks that the version of the document you write is the same as the one you’ve read.

+
+
+

If the field is empty, a precondition would check that the document with the same id does not exist to ensure you don’t overwrite existing documents unintentionally.

+
+
+
+
+

Cloud Firestore Spring Boot Starter

+
+

If you prefer using Firestore client only, Spring Framework on Google Cloud provides a convenience starter which automatically configures authentication settings and client objects needed to begin using Google Cloud Firestore in native mode.

+
+
+

See documentation to learn more about Cloud Firestore.

+
+
+

To begin using this library, add the spring-cloud-gcp-starter-firestore artifact to your project.

+
+
+

Maven coordinates, using Spring Framework on Google Cloud BOM:

+
+
+
+
<dependency>
+    <groupId>com.google.cloud</groupId>
+    <artifactId>spring-cloud-gcp-starter-firestore</artifactId>
+</dependency>
+
+
+
+

Gradle coordinates:

+
+
+
+
dependencies {
+  implementation("com.google.cloud:spring-cloud-gcp-starter-firestore")
+}
+
+
+
+

Using Cloud Firestore

+
+

The starter automatically configures and registers a Firestore bean in the Spring application context. To start using it, simply use the @Autowired annotation.

+
+
+
+
@Autowired
+Firestore firestore;
+
+ void writeDocumentFromObject() throws ExecutionException, InterruptedException {
+   // Add document data with id "joe" using a custom User class
+   User data =
+       new User(
+           "Joe",
+           Arrays.asList(new Phone(12345, PhoneType.CELL), new Phone(54321, PhoneType.WORK)));
+
+   // .get() blocks on response
+   WriteResult writeResult = this.firestore.document("users/joe").set(data).get();
+
+   LOGGER.info("Update time: " + writeResult.getUpdateTime());
+ }
+
+ User readDocumentToObject() throws ExecutionException, InterruptedException {
+   ApiFuture<DocumentSnapshot> documentFuture = this.firestore.document("users/joe").get();
+
+   User user = documentFuture.get().toObject(User.class);
+
+   LOGGER.info("read: " + user);
+
+   return user;
+ }
+
+
+
+
+
+
+

Emulator Usage

+
+

The Google Cloud Firebase SDK provides a local, in-memory emulator for Cloud Firestore, which you can use to develop and test your application.

+
+
+

First follow the Firebase emulator installation steps to install, configure, and run the emulator.

+
+
+ + + + + +
+ + +By default, the emulator is configured to run on port 8080; you will need to ensure that the emulator does not run on the same port as your Spring application. +
+
+
+

Once the Firestore emulator is running, ensure that the following properties are set in your application.properties of your Spring application:

+
+
+
+
spring.cloud.gcp.firestore.emulator.enabled=true
+spring.cloud.gcp.firestore.host-port=${EMULATOR_HOSTPORT}
+
+
+
+

From this point onward, your application will connect to your locally running emulator instance instead of the real Firestore service.

+
+
+
+

Samples

+
+

Spring Framework on Google Cloud provides Firestore sample applications to demonstrate API usage:

+
+ +
+
+

Test

+
+

Testcontainers provides a gcloud module which offers FirestoreEmulatorContainer. See more at the docs

+
+
+
+
+
+ + +
+
+
+ + \ No newline at end of file diff --git a/3.8.13/reference/html/first-page.html b/3.8.13/reference/html/first-page.html new file mode 100644 index 0000000000..d31542825c --- /dev/null +++ b/3.8.13/reference/html/first-page.html @@ -0,0 +1,263 @@ + + + + + + + + +Spring Framework on Google Cloud + + + + + + + + + +
+
+
+ +
+
+
+
+

3.8.13

+
+
+
+
+

1. Introduction

+
+
+

The Spring Framework on Google Cloud project makes the Spring Framework a first-class citizen of Google Cloud .

+
+
+

Spring Framework on Google Cloud lets you leverage the power and simplicity of the Spring Framework to:

+
+
+
    +
  • +

    Publish and subscribe to Google Cloud Pub/Sub topics

    +
  • +
  • +

    Configure Spring JDBC with a few properties to use Google Cloud SQL

    +
  • +
  • +

    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

    +
  • +
  • +

    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

    +
  • +
  • +

    Analyze your images for text, objects, and other content with Google Cloud Vision

    +
  • +
+
+
+
+
+ + +
+
+
+ + \ No newline at end of file diff --git a/3.8.13/reference/html/fonts/FontAwesome.otf b/3.8.13/reference/html/fonts/FontAwesome.otf new file mode 100755 index 0000000000000000000000000000000000000000..401ec0f36e4f73b8efa40bd6f604fe80d286db70 GIT binary patch literal 134808 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 literal 0 HcmV?d00001 diff --git a/3.8.13/reference/html/fonts/fontawesome-webfont.eot b/3.8.13/reference/html/fonts/fontawesome-webfont.eot new file mode 100755 index 0000000000000000000000000000000000000000..e9f60ca953f93e35eab4108bd414bc02ddcf3928 GIT binary patch literal 165742 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 G*yFsbY~cLNXLP73aA74Mq6M9f^&YV`isWW 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

5q!RY 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 + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/3.8.13/reference/html/fonts/fontawesome-webfont.ttf b/3.8.13/reference/html/fonts/fontawesome-webfont.ttf new file mode 100755 index 0000000000000000000000000000000000000000..35acda2fa1196aad98c2adf4378a7611dd713aa3 GIT binary patch literal 165548 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 z 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 d0mRO}xo^G_%I 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 SdSQM28?+M|D(2r_;{O0|uQ74} literal 0 HcmV?d00001 diff --git a/3.8.13/reference/html/fonts/fontawesome-webfont.woff2 b/3.8.13/reference/html/fonts/fontawesome-webfont.woff2 new file mode 100755 index 0000000000000000000000000000000000000000..4d13fc60404b91e398a37200c4a77b645cfd9586 GIT binary patch literal 77160 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 literal 0 HcmV?d00001 diff --git a/3.8.13/reference/html/getting-started.html b/3.8.13/reference/html/getting-started.html new file mode 100644 index 0000000000..49e893622b --- /dev/null +++ b/3.8.13/reference/html/getting-started.html @@ -0,0 +1,524 @@ + + + + + + + +Getting Started + + + + + + + +

+ +
+
+
+ +
+
+

Getting Started

+
+
+

This section describes how to get up to speed with Spring Framework on Google Cloud libraries.

+
+
+

Compatibility with Spring Project Versions

+
+

Spring Framework on Google Cloud has dependency and transitive dependencies on Spring Projects. The table below outlines the versions of Spring Cloud, Spring Boot and Spring Framework versions that are compatible with certain Spring Framework on Google Cloud version.

+
+ ++++++ + + + + + + + + + + + + + + + + + + + + + + +
Spring Framework on Google CloudSpring CloudSpring BootSpring Framework

2.x

2020.0.x (3.0/Illford)

2.4.x, 2.5.x

5.3.x

3.x

2021.0.x (3.1/Jubilee)

2.6.x, 2.7.x

5.3.x

+
+
+

Setting up Dependencies

+
+

All Spring Framework on Google Cloud artifacts are made available through Maven Central. +The following resources are provided to help you setup the libraries for your project:

+
+
+
    +
  • +

    Maven Bill of Materials for dependency management

    +
  • +
  • +

    Starter Dependencies for depending on Spring Framework on Google Cloud modules

    +
  • +
+
+
+

You may also consult our Github project to examine the code or build directly from source.

+
+
+

Bill of Materials

+
+

The Spring Framework on Google Cloud Bill of Materials (BOM) contains the versions of all the dependencies it uses.

+
+
+

If you’re a Maven user, adding the following to your pom.xml file will allow you omit any Spring Framework on Google Cloud dependency version numbers from your configuration. +Instead, the version of the BOM you’re using determines the versions of the used dependencies.

+
+
+
+
<dependencyManagement>
+   <dependencies>
+       <dependency>
+           <groupId>com.google.cloud</groupId>
+           <artifactId>spring-cloud-gcp-dependencies</artifactId>
+           <version>3.8.13</version>
+           <type>pom</type>
+           <scope>import</scope>
+       </dependency>
+   </dependencies>
+</dependencyManagement>
+
+
+
+

Or, if you’re a Gradle user:

+
+
+
+
dependencies {
+    implementation platform("com.google.cloud:spring-cloud-gcp-dependencies:3.8.13")
+}
+
+
+
+

In the following sections, it will be assumed you are using the Spring Framework on Google Cloud BOM and the dependency snippets will not contain versions.

+
+
+
+

Starter Dependencies

+
+

Spring Framework on Google Cloud offers starter dependencies through Maven to easily depend on different modules of the library. +Each starter contains all the dependencies and transitive dependencies needed to begin using their corresponding Spring Framework on Google Cloud module.

+
+
+

For example, if you wish to write a Spring application with Cloud Pub/Sub, you would include the spring-cloud-gcp-starter-pubsub dependency in your project. +You do not need to include the underlying spring-cloud-gcp-pubsub dependency, because the starter dependency includes it.

+
+
+

A summary of these artifacts are provided below.

+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Spring Framework on Google Cloud StarterDescriptionMaven Artifact Name

Core

Automatically configure authentication and Google project settings

com.google.cloud:spring-cloud-gcp-starter

Cloud Spanner

Provides integrations with Google Cloud Spanner

com.google.cloud:spring-cloud-gcp-starter-data-spanner

Cloud Datastore

Provides integrations with Google Cloud Datastore

com.google.cloud:spring-cloud-gcp-starter-data-datastore

Cloud Pub/Sub

Provides integrations with Google Cloud Pub/Sub

com.google.cloud:spring-cloud-gcp-starter-pubsub

Logging

Enables Cloud Logging

com.google.cloud:spring-cloud-gcp-starter-logging

SQL - MySQL

Cloud SQL integrations with MySQL

com.google.cloud:spring-cloud-gcp-starter-sql-mysql

SQL - PostgreSQL

Cloud SQL integrations with PostgreSQL

com.google.cloud:spring-cloud-gcp-starter-sql-postgresql

Storage

Provides integrations with Google Cloud Storage and Spring Resource

com.google.cloud:spring-cloud-gcp-starter-storage

Config

Enables usage of Google Runtime Configuration API as a Spring Cloud Config server

com.google.cloud:spring-cloud-gcp-starter-config

Trace

Enables instrumentation with Google Cloud Trace

com.google.cloud:spring-cloud-gcp-starter-trace

Vision

Provides integrations with Google Cloud Vision

com.google.cloud:spring-cloud-gcp-starter-vision

Security - IAP

Provides a security layer over applications deployed to Google Cloud

com.google.cloud:spring-cloud-gcp-starter-security-iap

Security - Firebase

Provides a security layer over applications deployed to Firebase

com.google.cloud:spring-cloud-gcp-starter-security-firebase

+
+
+

Spring Initializr

+
+

Spring Initializr is a tool which generates the scaffolding code for a new Spring Boot project. +It handles the work of generating the Maven or Gradle build file so you do not have to manually add the dependencies yourself.

+
+
+

Spring Initializr offers three modules from Spring Framework on Google Cloud that you can use to generate your project.

+
+
+
    +
  • +

    GCP Support: The GCP Support module contains auto-configuration support for every Spring Framework on Google Cloud integration. +Most of the autoconfiguration code is only enabled if the required dependency is added to your project.

    +
  • +
  • +

    GCP Messaging: Google Cloud Pub/Sub integrations work out of the box.

    +
  • +
  • +

    GCP Storage: Google Cloud Storage integrations work out of the box.

    +
  • +
+
+
+
+
+

Learning Spring Framework on Google Cloud

+
+

There are a variety of resources to help you learn how to use Spring Framework on Google Cloud libraries.

+
+
+

Sample Applications

+
+

The easiest way to learn how to use Spring Framework on Google Cloud is to consult the sample applications on Github. +Spring Framework on Google Cloud provides sample applications which demonstrate how to use every integration in the library. +The table below highlights several samples of the most commonly used integrations in Spring Framework on Google Cloud.

+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Google Cloud IntegrationSample Application

Cloud Pub/Sub

spring-cloud-gcp-pubsub-sample

Cloud Spanner

spring-cloud-gcp-data-spanner-repository-sample

+

spring-cloud-gcp-data-spanner-template-sample

Datastore

spring-cloud-gcp-data-datastore-sample

Cloud SQL (w/ MySQL)

spring-cloud-gcp-sql-mysql-sample

Cloud Storage

spring-cloud-gcp-storage-resource-sample

Cloud Logging

spring-cloud-gcp-logging-sample

Trace

spring-cloud-gcp-trace-sample

Cloud Vision

spring-cloud-gcp-vision-api-sample

Cloud Security - IAP

spring-cloud-gcp-security-iap-sample

Cloud Security - Firebase

spring-cloud-gcp-security-firebase-sample

+
+

Each sample application demonstrates how to use Spring Framework on Google Cloud libraries in context and how to setup the dependencies for the project. +The applications are fully functional and can be deployed to Google Cloud as well. +If you are interested, you may consult guides for deploying an application to AppEngine and to Google Kubernetes Engine.

+
+
+
+

Codelabs

+
+

For a more hands-on approach, there are several guides and codelabs to help you get up to speed. +These guides provide step-by-step instructions for building an application using Spring Framework on Google Cloud.

+
+
+

Some examples include:

+
+ +
+

The full collection of Spring codelabs can be found on the Google Developer Codelabs page.

+
+
+
+
+
+
+ + +
+
+
+ + \ No newline at end of file diff --git a/3.8.13/reference/html/images/GCP on Spring Initializr.png b/3.8.13/reference/html/images/GCP on Spring Initializr.png new file mode 100644 index 0000000000000000000000000000000000000000..f2a545c73fc4e6fae839b8ef85f15975d20677fd GIT binary patch literal 189333 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 literal 0 HcmV?d00001 diff --git a/3.8.13/reference/html/images/Stackdrive trace.png b/3.8.13/reference/html/images/Stackdrive trace.png new file mode 100644 index 0000000000000000000000000000000000000000..b024ed922f4174f5949933bbde4421bca0a32045 GIT binary patch literal 125360 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 F{{o<3Q@sEH literal 0 HcmV?d00001 diff --git a/3.8.13/reference/html/img/banner-logo.svg b/3.8.13/reference/html/img/banner-logo.svg new file mode 100755 index 0000000000..23e7e980c0 --- /dev/null +++ b/3.8.13/reference/html/img/banner-logo.svg @@ -0,0 +1,16 @@ + + + Spring Logos and Graphics + + + + + + + + + + + + + diff --git a/3.8.13/reference/html/img/doc-background-dark.svg b/3.8.13/reference/html/img/doc-background-dark.svg new file mode 100755 index 0000000000..3e7b63c3ee --- /dev/null +++ b/3.8.13/reference/html/img/doc-background-dark.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/3.8.13/reference/html/img/doc-background.svg b/3.8.13/reference/html/img/doc-background.svg new file mode 100755 index 0000000000..301ff34f1f --- /dev/null +++ b/3.8.13/reference/html/img/doc-background.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/3.8.13/reference/html/img/octicons-16.svg b/3.8.13/reference/html/img/octicons-16.svg new file mode 100755 index 0000000000..9e8fe2847c --- /dev/null +++ b/3.8.13/reference/html/img/octicons-16.svg @@ -0,0 +1,109 @@ + + Octicons (24px subset) + Octicons v12.1.0 by GitHub - https://primer.style/octicons/ - License: MIT + + + + @primer/octicons + 12.1.0 + A scalable set of icons handcrafted with <3 by GitHub + image/svg+xml + + + GitHub + + + + + Copyright (c) 2020 GitHub Inc. + + + + https://primer.style/octicons/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/3.8.13/reference/html/index.html b/3.8.13/reference/html/index.html new file mode 100644 index 0000000000..4b0cced68a --- /dev/null +++ b/3.8.13/reference/html/index.html @@ -0,0 +1,12334 @@ + + + + + + + + +Spring Framework on Google Cloud + + + + + + + + + +
    +
    +
    + +
    +
    +
    +
    +

    3.8.13

    +
    +
    +
    +
    +

    1. Introduction

    +
    +
    +

    The Spring Framework on Google Cloud project makes the Spring Framework a first-class citizen of Google Cloud .

    +
    +
    +

    Spring Framework on Google Cloud lets you leverage the power and simplicity of the Spring Framework to:

    +
    +
    +
      +
    • +

      Publish and subscribe to Google Cloud Pub/Sub topics

      +
    • +
    • +

      Configure Spring JDBC with a few properties to use Google Cloud SQL

      +
    • +
    • +

      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

      +
    • +
    • +

      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

      +
    • +
    • +

      Analyze your images for text, objects, and other content with Google Cloud Vision

      +
    • +
    +
    +
    +
    +
    +

    2. Getting Started

    +
    +
    +

    This section describes how to get up to speed with Spring Framework on Google Cloud libraries.

    +
    +
    +

    2.1. Compatibility with Spring Project Versions

    +
    +

    Spring Framework on Google Cloud has dependency and transitive dependencies on Spring Projects. The table below outlines the versions of Spring Cloud, Spring Boot and Spring Framework versions that are compatible with certain Spring Framework on Google Cloud version.

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + +
    Spring Framework on Google CloudSpring CloudSpring BootSpring Framework

    2.x

    2020.0.x (3.0/Illford)

    2.4.x, 2.5.x

    5.3.x

    3.x

    2021.0.x (3.1/Jubilee)

    2.6.x, 2.7.x

    5.3.x

    +
    +
    +

    2.2. Setting up Dependencies

    +
    +

    All Spring Framework on Google Cloud artifacts are made available through Maven Central. +The following resources are provided to help you setup the libraries for your project:

    +
    +
    +
      +
    • +

      Maven Bill of Materials for dependency management

      +
    • +
    • +

      Starter Dependencies for depending on Spring Framework on Google Cloud modules

      +
    • +
    +
    +
    +

    You may also consult our Github project to examine the code or build directly from source.

    +
    +
    +

    2.2.1. Bill of Materials

    +
    +

    The Spring Framework on Google Cloud Bill of Materials (BOM) contains the versions of all the dependencies it uses.

    +
    +
    +

    If you’re a Maven user, adding the following to your pom.xml file will allow you omit any Spring Framework on Google Cloud dependency version numbers from your configuration. +Instead, the version of the BOM you’re using determines the versions of the used dependencies.

    +
    +
    +
    +
    <dependencyManagement>
    +   <dependencies>
    +       <dependency>
    +           <groupId>com.google.cloud</groupId>
    +           <artifactId>spring-cloud-gcp-dependencies</artifactId>
    +           <version>3.8.13</version>
    +           <type>pom</type>
    +           <scope>import</scope>
    +       </dependency>
    +   </dependencies>
    +</dependencyManagement>
    +
    +
    +
    +

    Or, if you’re a Gradle user:

    +
    +
    +
    +
    dependencies {
    +    implementation platform("com.google.cloud:spring-cloud-gcp-dependencies:3.8.13")
    +}
    +
    +
    +
    +

    In the following sections, it will be assumed you are using the Spring Framework on Google Cloud BOM and the dependency snippets will not contain versions.

    +
    +
    +
    +

    2.2.2. Starter Dependencies

    +
    +

    Spring Framework on Google Cloud offers starter dependencies through Maven to easily depend on different modules of the library. +Each starter contains all the dependencies and transitive dependencies needed to begin using their corresponding Spring Framework on Google Cloud module.

    +
    +
    +

    For example, if you wish to write a Spring application with Cloud Pub/Sub, you would include the spring-cloud-gcp-starter-pubsub dependency in your project. +You do not need to include the underlying spring-cloud-gcp-pubsub dependency, because the starter dependency includes it.

    +
    +
    +

    A summary of these artifacts are provided below.

    +
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Spring Framework on Google Cloud StarterDescriptionMaven Artifact Name

    Core

    Automatically configure authentication and Google project settings

    com.google.cloud:spring-cloud-gcp-starter

    Cloud Spanner

    Provides integrations with Google Cloud Spanner

    com.google.cloud:spring-cloud-gcp-starter-data-spanner

    Cloud Datastore

    Provides integrations with Google Cloud Datastore

    com.google.cloud:spring-cloud-gcp-starter-data-datastore

    Cloud Pub/Sub

    Provides integrations with Google Cloud Pub/Sub

    com.google.cloud:spring-cloud-gcp-starter-pubsub

    Logging

    Enables Cloud Logging

    com.google.cloud:spring-cloud-gcp-starter-logging

    SQL - MySQL

    Cloud SQL integrations with MySQL

    com.google.cloud:spring-cloud-gcp-starter-sql-mysql

    SQL - PostgreSQL

    Cloud SQL integrations with PostgreSQL

    com.google.cloud:spring-cloud-gcp-starter-sql-postgresql

    Storage

    Provides integrations with Google Cloud Storage and Spring Resource

    com.google.cloud:spring-cloud-gcp-starter-storage

    Config

    Enables usage of Google Runtime Configuration API as a Spring Cloud Config server

    com.google.cloud:spring-cloud-gcp-starter-config

    Trace

    Enables instrumentation with Google Cloud Trace

    com.google.cloud:spring-cloud-gcp-starter-trace

    Vision

    Provides integrations with Google Cloud Vision

    com.google.cloud:spring-cloud-gcp-starter-vision

    Security - IAP

    Provides a security layer over applications deployed to Google Cloud

    com.google.cloud:spring-cloud-gcp-starter-security-iap

    Security - Firebase

    Provides a security layer over applications deployed to Firebase

    com.google.cloud:spring-cloud-gcp-starter-security-firebase

    +
    +
    +

    2.2.3. Spring Initializr

    +
    +

    Spring Initializr is a tool which generates the scaffolding code for a new Spring Boot project. +It handles the work of generating the Maven or Gradle build file so you do not have to manually add the dependencies yourself.

    +
    +
    +

    Spring Initializr offers three modules from Spring Framework on Google Cloud that you can use to generate your project.

    +
    +
    +
      +
    • +

      GCP Support: The GCP Support module contains auto-configuration support for every Spring Framework on Google Cloud integration. +Most of the autoconfiguration code is only enabled if the required dependency is added to your project.

      +
    • +
    • +

      GCP Messaging: Google Cloud Pub/Sub integrations work out of the box.

      +
    • +
    • +

      GCP Storage: Google Cloud Storage integrations work out of the box.

      +
    • +
    +
    +
    +
    +
    +

    2.3. Learning Spring Framework on Google Cloud

    +
    +

    There are a variety of resources to help you learn how to use Spring Framework on Google Cloud libraries.

    +
    +
    +

    2.3.1. Sample Applications

    +
    +

    The easiest way to learn how to use Spring Framework on Google Cloud is to consult the sample applications on Github. +Spring Framework on Google Cloud provides sample applications which demonstrate how to use every integration in the library. +The table below highlights several samples of the most commonly used integrations in Spring Framework on Google Cloud.

    +
    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Google Cloud IntegrationSample Application

    Cloud Pub/Sub

    spring-cloud-gcp-pubsub-sample

    Cloud Spanner

    spring-cloud-gcp-data-spanner-repository-sample

    +

    spring-cloud-gcp-data-spanner-template-sample

    Datastore

    spring-cloud-gcp-data-datastore-sample

    Cloud SQL (w/ MySQL)

    spring-cloud-gcp-sql-mysql-sample

    Cloud Storage

    spring-cloud-gcp-storage-resource-sample

    Cloud Logging

    spring-cloud-gcp-logging-sample

    Trace

    spring-cloud-gcp-trace-sample

    Cloud Vision

    spring-cloud-gcp-vision-api-sample

    Cloud Security - IAP

    spring-cloud-gcp-security-iap-sample

    Cloud Security - Firebase

    spring-cloud-gcp-security-firebase-sample

    +
    +

    Each sample application demonstrates how to use Spring Framework on Google Cloud libraries in context and how to setup the dependencies for the project. +The applications are fully functional and can be deployed to Google Cloud as well. +If you are interested, you may consult guides for deploying an application to AppEngine and to Google Kubernetes Engine.

    +
    +
    +
    +

    2.3.2. Codelabs

    +
    +

    For a more hands-on approach, there are several guides and codelabs to help you get up to speed. +These guides provide step-by-step instructions for building an application using Spring Framework on Google Cloud.

    +
    +
    +

    Some examples include:

    +
    + +
    +

    The full collection of Spring codelabs can be found on the Google Developer Codelabs page.

    +
    +
    +
    +
    +
    +
    +

    3. Spring Framework on Google Cloud Core

    +
    +
    +

    Each Spring Framework on Google Cloud module uses GcpProjectIdProvider and CredentialsProvider to get the Google Cloud project ID and access credentials.

    +
    +
    +

    Spring Framework on Google Cloud provides a Spring Boot starter to auto-configure the core components.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter")
    +}
    +
    +
    +
    +

    3.1. Configuration

    +
    +

    The following options may be configured with Spring Cloud core.

    +
    + ++++++ + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.core.enabled

    Enables or disables Google Cloud core auto configuration

    No

    true

    +
    +
    +

    3.2. Project ID

    +
    +

    GcpProjectIdProvider is a functional interface that returns a Google Cloud project ID string.

    +
    +
    +
    +
    public interface GcpProjectIdProvider {
    +    String getProjectId();
    +}
    +
    +
    +
    +
    +

    The Spring Framework on Google Cloud starter auto-configures a GcpProjectIdProvider. +If a spring.cloud.gcp.project-id property is specified, the provided GcpProjectIdProvider returns that property value.

    +
    +
    +
    +
    spring.cloud.gcp.project-id=my-gcp-project-id
    +
    +
    +
    +
    +

    Otherwise, the project ID is discovered based on an +ordered list of rules:

    +
    +
    +
      +
    1. +

      The project ID specified by the GOOGLE_CLOUD_PROJECT environment variable

      +
    2. +
    3. +

      The Google App Engine project ID

      +
    4. +
    5. +

      The project ID specified in the JSON credentials file pointed by the GOOGLE_APPLICATION_CREDENTIALS environment variable

      +
    6. +
    7. +

      The Google Cloud SDK project ID

      +
    8. +
    9. +

      The Google Compute Engine project ID, from the Google Compute Engine Metadata Server

      +
    10. +
    +
    +
    +
    +

    3.3. Credentials

    +
    +

    CredentialsProvider is a functional interface that returns the credentials to authenticate and authorize calls to Google Cloud Client Libraries.

    +
    +
    +
    +
    public interface CredentialsProvider {
    +  Credentials getCredentials() throws IOException;
    +}
    +
    +
    +
    +
    +

    The Spring Framework on Google Cloud starter auto-configures a CredentialsProvider. +It uses the spring.cloud.gcp.credentials.location property to locate the OAuth2 private key of a Google service account. +Keep in mind this property is a Spring Resource, so the credentials file can be obtained from a number of different locations such as the file system, classpath, URL, etc. +The next example specifies the credentials location property in the file system.

    +
    +
    +
    +
    spring.cloud.gcp.credentials.location=file:/usr/local/key.json
    +
    +
    +
    +

    Alternatively, you can set the credentials by directly specifying the spring.cloud.gcp.credentials.encoded-key property. +The value should be the base64-encoded account private key in JSON format.

    +
    +
    +

    If that credentials aren’t specified through properties, the starter tries to discover credentials from a number of places:

    +
    +
    +
      +
    1. +

      Credentials file pointed to by the GOOGLE_APPLICATION_CREDENTIALS environment variable

      +
    2. +
    3. +

      Credentials provided by the Google Cloud SDK gcloud auth application-default login command

      +
    4. +
    5. +

      Google App Engine built-in credentials

      +
    6. +
    7. +

      Google Cloud Shell built-in credentials

      +
    8. +
    9. +

      Google Compute Engine built-in credentials

      +
    10. +
    +
    +
    +

    If your app is running on Google App Engine or Google Compute Engine, in most cases, you should omit the spring.cloud.gcp.credentials.location property and, instead, let the Spring Framework on Google Cloud Starter get the correct credentials for those environments. +On App Engine Standard, the App Identity service account credentials are used, on App Engine Flexible, the Flexible service account credential are used and on Google Compute Engine, the Compute Engine Default Service Account is used.

    +
    +
    +

    3.3.1. Scopes

    +
    +

    By default, the credentials provided by the Spring Framework on Google Cloud Starter contain scopes for every service supported by Spring Framework on Google Cloud.

    +
    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Service

    Scope

    Spanner

    www.googleapis.com/auth/spanner.admin, www.googleapis.com/auth/spanner.data

    Datastore

    www.googleapis.com/auth/datastore

    Pub/Sub

    www.googleapis.com/auth/pubsub

    Storage (Read Only)

    www.googleapis.com/auth/devstorage.read_only

    Storage (Read/Write)

    www.googleapis.com/auth/devstorage.read_write

    Runtime Config

    www.googleapis.com/auth/cloudruntimeconfig

    Trace (Append)

    www.googleapis.com/auth/trace.append

    Cloud Platform

    www.googleapis.com/auth/cloud-platform

    Vision

    www.googleapis.com/auth/cloud-vision

    +
    +

    The Spring Framework on Google Cloud starter allows you to configure a custom scope list for the provided credentials. +To do that, specify a comma-delimited list of Google OAuth2 scopes in the spring.cloud.gcp.credentials.scopes property.

    +
    +
    +

    spring.cloud.gcp.credentials.scopes is a comma-delimited list of Google OAuth2 scopes for Google Cloud services that the credentials returned by the provided CredentialsProvider support.

    +
    +
    +
    +
    spring.cloud.gcp.credentials.scopes=https://www.googleapis.com/auth/pubsub,https://www.googleapis.com/auth/sqlservice.admin
    +
    +
    +
    +

    You can also use DEFAULT_SCOPES placeholder as a scope to represent the starters default scopes, and append the additional scopes you need to add.

    +
    +
    +
    +
    spring.cloud.gcp.credentials.scopes=DEFAULT_SCOPES,https://www.googleapis.com/auth/cloud-vision
    +
    +
    +
    +
    +
    +

    3.4. Environment

    +
    +

    GcpEnvironmentProvider is a functional interface, auto-configured by the Spring Framework on Google Cloud starter, that returns a GcpEnvironment enum. +The provider can help determine programmatically in which Google Cloud environment (App Engine Flexible, App Engine Standard, Kubernetes Engine or Compute Engine) the application is deployed.

    +
    +
    +
    +
    public interface GcpEnvironmentProvider {
    +    GcpEnvironment getCurrentEnvironment();
    +}
    +
    +
    +
    +
    +
    +

    3.5. Customizing bean scope

    +
    +

    Spring Framework on Google Cloud starters autoconfigure all necessary beans in the default singleton scope. +If you need a particular bean or set of beans to be recreated dynamically (for example, to rotate credentials), there are two options:

    +
    +
    +
      +
    1. +

      Annotate custom beans of the necessary types with @RefreshScope. +This makes the most sense if your application is already redefining those beans.

      +
    2. +
    3. +

      Override the scope for autoconfigured beans by listing them in the Spring Cloud property spring.cloud.refresh.extra-refreshable.

      +
      +

      For example, the beans involved in Cloud Pub/Sub subscription could be marked as refreshable as follows:

      +
      +
    4. +
    +
    +
    +
    +
    spring.cloud.refresh.extra-refreshable=com.google.cloud.spring.pubsub.support.SubscriberFactory,\
    +  com.google.cloud.spring.pubsub.core.subscriber.PubSubSubscriberTemplate
    +
    +
    +
    + + + + + +
    + + +
    +

    SmartLifecycle beans, such as Spring Integration adapters, do not currently support @RefreshScope. +If your application refreshes any beans used by such SmartLifecycle objects, it may also have to restart the beans manually when RefreshScopeRefreshedEvent is detected, such as in the Cloud Pub/Sub example below:

    +
    +
    +
    +
    @Autowired
    +private PubSubInboundChannelAdapter pubSubAdapter;
    +
    +@EventListener(RefreshScopeRefreshedEvent.class)
    +public void onRefreshScope(RefreshScopeRefreshedEvent event) {
    +  this.pubSubAdapter.stop();
    +  this.pubSubAdapter.start();
    +}
    +
    +
    +
    +
    +
    +
    +
    +

    3.6. Spring Initializr

    +
    +

    This starter is available from Spring Initializr through the GCP Support entry.

    +
    +
    +
    +
    +
    +

    4. Cloud Storage

    +
    +
    +

    Google Cloud Storage allows storing any types of files in single or multiple regions. +A Spring Boot starter is provided to auto-configure the various Storage components.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-storage</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-storage")
    +}
    +
    +
    +
    +

    This starter is also available from Spring Initializr through the GCP Storage entry.

    +
    +
    +

    4.1. Using Cloud Storage

    +
    +

    The starter automatically configures and registers a Storage bean in the Spring application context. +The Storage bean (Javadoc) can be used to list/create/update/delete buckets (a group of objects with similar permissions and resiliency requirements) and objects.

    +
    +
    +
    +
    @Autowired
    +private Storage storage;
    +
    +public void createFile() {
    +    Bucket bucket = storage.create(BucketInfo.of("my-app-storage-bucket"));
    +
    +    storage.create(
    +        BlobInfo.newBuilder("my-app-storage-bucket", "subdirectory/my-file").build(),
    +            "file contents".getBytes()
    +    );
    +}
    +
    +
    +
    +
    +
    +

    4.2. Cloud Storage Objects As Spring Resources

    +
    +

    Spring Resources are an abstraction for a number of low-level resources, such as file system files, classpath files, servlet context-relative files, etc. +Spring Framework on Google Cloud adds a new resource type: a Google Cloud Storage (GCS) object.

    +
    +
    +

    The Spring Resource Abstraction for Google Cloud Storage allows GCS objects to be accessed by their GCS URL using the @Value annotation:

    +
    +
    +
    +
    @Value("gs://[YOUR_GCS_BUCKET]/[GCS_FILE_NAME]")
    +private Resource gcsResource;
    +
    +
    +
    +
    +

    …​or the Spring application context

    +
    +
    +
    +
    SpringApplication.run(...).getResource("gs://[YOUR_GCS_BUCKET]/[GCS_FILE_NAME]");
    +
    +
    +
    +
    +

    This creates a Resource object that can be used to read the object, among other possible operations.

    +
    +
    +

    It is also possible to write to a Resource, although a WriteableResource is required.

    +
    +
    +
    +
    @Value("gs://[YOUR_GCS_BUCKET]/[GCS_FILE_NAME]")
    +private Resource gcsResource;
    +...
    +try (OutputStream os = ((WritableResource) gcsResource).getOutputStream()) {
    +  os.write("foo".getBytes());
    +}
    +
    +
    +
    +
    +

    To work with the Resource as a Google Cloud Storage resource, cast it to GoogleStorageResource.

    +
    +
    +

    If the resource path refers to an object on Google Cloud Storage (as opposed to a bucket), then the getBlob method can be called to obtain a Blob. +This type represents a GCS file, which has associated metadata, such as content-type, that can be set. +The createSignedUrl method can also be used to obtain signed URLs for GCS objects. +However, creating signed URLs requires that the resource was created using service account credentials.

    +
    +
    + + + + + +
    + + +
    +

    As of v2.0.2+, the GoogleStorageResource.getURL() method returns the Bucket or Blob 's selfLink value, rather than attempting to convert the URI a URL object that nearly-always threw a MalformedURLException. +This value is notably different from GoogleStorageResource.getURI(), which returns the more commonly used gs://my-bucket/my-object identifier. +Returning a valid URL is necessary to support some features in the Spring ecosystem, such as spring.resources.static-locations.

    +
    +
    +
    +
    +

    The Spring Boot Starter for Google Cloud Storage auto-configures the Storage bean required by the spring-cloud-gcp-storage module, based on the CredentialsProvider provided by the Spring Framework on Google Cloud Starter.

    +
    +
    +

    4.2.1. Setting the Content Type

    +
    +

    You can set the content-type of Google Cloud Storage files from their corresponding Resource objects:

    +
    +
    +
    +
    ((GoogleStorageResource)gcsResource).getBlob().toBuilder().setContentType("text/html").build().update();
    +
    +
    +
    +
    +
    +
    +

    4.3. Configuration

    +
    +

    The Spring Boot Starter for Google Cloud Storage provides the following configuration options:

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.storage.enabled

    Enables the Google Cloud storage APIs.

    No

    true

    spring.cloud.gcp.storage.auto-create-files

    Creates files and buckets on Google Cloud Storage when writes are made to non-existent files

    No

    true

    spring.cloud.gcp.storage.credentials.location

    OAuth2 credentials for authenticating with the Google Cloud Storage API, if different from the ones in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.storage.credentials.encoded-key

    Base64-encoded contents of OAuth2 account private key for authenticating with the Google Cloud Storage API, if different from the ones in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.storage.credentials.scopes

    OAuth2 scope for Spring Framework on Google Cloud Storage credentials

    No

    www.googleapis.com/auth/devstorage.read_write

    +
    +
    +

    4.4. Sample

    +
    +

    A sample application and a codelab are available.

    +
    +
    +
    +
    +
    +

    5. Cloud SQL

    +
    +
    +

    Spring Framework on Google Cloud adds integrations with +Spring JDBC and Spring R2DBC so you can run your MySQL or PostgreSQL databases in 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.

    +
    +
    +

    5.1. JDBC Support

    +
    +

    Maven and Gradle coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +

    To use MySQL:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-sql-mysql</artifactId>
    +</dependency>
    +
    +
    +
    +
    +
    dependencies {
    +implementation("com.google.cloud:spring-cloud-gcp-starter-sql-mysql")
    +}
    +
    +
    +
    +

    To use PostgreSQL:

    +
    +
    +
    +
    <dependency>
    +<groupId>com.google.cloud</groupId>
    +<artifactId>spring-cloud-gcp-starter-sql-postgresql</artifactId>
    +</dependency>
    +
    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-sql-postgresql")
    +}
    +
    +
    +
    +

    5.1.1. Prerequisites

    +
    +

    In order to use the Spring Boot Starters for Google Cloud SQL, the Google Cloud SQL API must be enabled in your Google Cloud project.

    +
    +
    +

    To do that, go to the API library page of the Google Cloud Console, search for "Cloud SQL API" and enable the option that is called "Cloud SQL" .

    +
    +
    +
    +

    5.1.2. Spring Boot Starter for Google Cloud SQL

    +
    +

    The Spring Boot Starters for Google Cloud SQL provide an auto-configured DataSource object. +Coupled with Spring JDBC, it provides a +JdbcTemplate object bean that allows for operations such as querying and modifying a database.

    +
    +
    +
    +
    public List<Map<String, Object>> listUsers() {
    +    return jdbcTemplate.queryForList("SELECT * FROM user;");
    +}
    +
    +
    +
    +
    +

    You can rely on +Spring Boot data source auto-configuration 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).

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Property name

    Description

    Required

    Default value

    spring.datasource.username

    Database username

    No

    MySQL: root; PostgreSQL: postgres

    spring.datasource.password

    Database password

    No

    null

    spring.datasource.driver-class-name

    JDBC driver to use.

    No

    MySQL: com.mysql.cj.jdbc.Driver; PostgreSQL: org.postgresql.Driver

    +
    + + + + + +
    + + +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. +
    +
    +
    +
    DataSource creation flow
    +
    +

    Spring Boot starter for Google Cloud SQL registers a CloudSqlEnvironmentPostProcessor that provides a correctly formatted spring.datasource.url property to the environment based on the properties mentioned above. +It also provides defaults for spring.datasource.username and spring.datasource.driver-class-name, which can be overridden. +The starter also configures credentials for the JDBC connection based on the properties below.

    +
    +
    +

    The user properties and the properties provided by the CloudSqlEnvironmentPostProcessor are then used by Spring Boot to create the DataSource. +You can select the type of connection pool (e.g., Tomcat, HikariCP, etc.) by adding their dependency to the classpath.

    +
    +
    +

    Using the created DataSource in conjunction with Spring JDBC provides you with a fully configured and operational JdbcTemplate object that you can use to interact with your SQL database. +You can connect to your database with as little as a database and instance names.

    +
    +
    +
    +
    +
    +

    5.2. R2DBC Support

    +
    +

    Maven and Gradle coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +

    To use MySQL:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-sql-mysql-r2dbc</artifactId>
    +</dependency>
    +
    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-sql-mysql-r2dbc")
    +}
    +
    +
    +
    +

    To use PostgreSQL with Spring Boot 2.6:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-sql-postgres-r2dbc</artifactId>
    +</dependency>
    +
    +
    +
    +
    +
    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):

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-sql-postgres-r2dbc</artifactId>
    +    <exclusions>
    +        <exclusion>
    +            <groupId>io.r2dbc</groupId>
    +            <artifactId>r2dbc-postgresql</artifactId>
    +        </exclusion>
    +    </exclusions>
    +</dependency>
    +
    +<dependency>
    +    <groupId>org.postgresql</groupId>
    +    <artifactId>r2dbc-postgresql</artifactId>
    +    <version>0.9.1.RELEASE</version>
    +</dependency>
    +
    +
    +
    +

    5.2.1. Prerequisites

    +
    +

    In order to use the Spring Boot Starters for Google Cloud SQL, the Google Cloud SQL API must be enabled in your Google Cloud project.

    +
    +
    +

    To do that, go to the API library page of the Google Cloud Console, search for "Cloud SQL API" and enable the option that is called "Cloud SQL".

    +
    +
    +
    +

    5.2.2. Spring Boot Starter for Google Cloud SQL

    +
    +

    The Cloud SQL R2DBC starter provides a customized io.r2dbc.spi.ConnectionFactory bean for connecting to Cloud SQL with the help of the Cloud SQL Socket Factory. +Similar to the JDBC support, you can connect to your database with as little as a database and instance names.

    +
    +
    +

    A higher level convenience object +R2dbcEntityTemplate is also provided for operations such as querying and modifying a database.

    +
    +
    +
    +
    @Autowired R2dbcEntityTemplate template;
    +
    +public Flux<String> listUsers() {
    +  return template.select(User.class).all().map(user -> user.toString());
    +}
    +
    +
    +
    +
    +

    Standard R2DBC properties like the SQL username, spring.r2dbc.username, and password, spring.r2dbc.password can be 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

    +
    + + + + + +
    + + +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 . +
    +
    +
    +
    ConnectionFactory creation flow
    +
    +

    Spring Framework on Google Cloud starter for Google Cloud SQL registers a R2dbcCloudSqlEnvironmentPostProcessor that provides a correctly formatted spring.r2dbc.url property to the environment based on the properties mentioned above. +It also provides a default value for spring.r2dbc.username, which can be overridden. +The starter also configures credentials for the R2DBC connection based on the properties below.

    +
    +
    +

    The user properties and the properties provided by the R2dbcCloudSqlEnvironmentPostProcessor are then used by Spring Boot to create the ConnectionFactory.

    +
    +
    +

    The customized ConnectionFactory is then ready to connect to Cloud SQL. The rest of Spring Data R2DBC objects built on it ( R2dbcEntityTemplate, DatabaseClient) are automatically configured and operational, ready to interact with your SQL database.

    +
    +
    +
    +
    +
    +

    5.3. Cloud SQL IAM database authentication

    +
    +

    Currently, Cloud SQL only supports IAM database authentication for PostgreSQL. +It allows you to connect to the database using an IAM account, rather than a predefined database username and password. +You will need to do the following to enable it:

    +
    +
    +
      +
    1. +

      In your database instance settings, turn on the cloudsql.iam_authentication flag.

      +
    2. +
    3. +

      Add the IAM user or service account to the list of database users.

      +
    4. +
    5. +

      In the application settings, set spring.cloud.gcp.sql.enableIamAuth to true. Note that this will also set the database protocol sslmode to disabled, as it’s required for IAM authentication to work. +However, it doesn’t compromise the security of the communication because the connection is always encrypted.

      +
    6. +
    7. +

      Set spring.datasource.username to the IAM user or service account created in step 2. Note that IAM user or service account still needs to be granted permissions before modifying or querying the database.

      +
    8. +
    +
    +
    +
    +

    5.4. Cloud SQL Configuration Properties

    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Property name

    Description

    Required

    Default value

    spring.cloud.gcp.sql.enabled

    Enables or disables Cloud SQL auto configuration

    No

    true

    spring.cloud.gcp.sql.jdbc.enabled

    Enables or disables Cloud SQL auto-configuration for JDBC

    No

    true

    spring.cloud.gcp.sql.r2dbc.enabled

    Enables or disables Cloud SQL auto-configuration for R2DBC

    No

    true

    spring.cloud.gcp.sql.database-name

    Name of the database to connect to.

    Yes

    spring.cloud.gcp.sql.instance-connection-name

    A string containing a Google Cloud SQL instance’s project ID, region and name, each separated by a colon.

    Yes

    For example, my-project-id:my-region:my-instance-name.

    spring.cloud.gcp.sql.ip-types

    Allows you to specify a comma delimited list of preferred IP types for connecting to a Cloud SQL instance. Left unconfigured Cloud SQL Socket Factory will default it to PUBLIC,PRIVATE. See Cloud SQL Socket Factory - Specifying IP Types

    No

    PUBLIC,PRIVATE

    spring.cloud.gcp.sql.credentials.location

    File system path to the Google OAuth2 credentials private key file. +Used to authenticate and authorize new connections to a Google Cloud SQL instance.

    No

    Default credentials provided by the Spring Framework on Google Cloud Core Starter

    spring.cloud.gcp.sql.credentials.encoded-key

    Base64-encoded contents of OAuth2 account private key in JSON format. +Used to authenticate and authorize new connections to a Google Cloud SQL instance.

    No

    Default credentials provided by the Spring Framework on Google Cloud Core Starter

    spring.cloud.gcp.sql.enableIamAuth

    Specifies whether to enable IAM database authentication (PostgreSQL only).

    No

    False

    +
    +
    +

    5.5. Troubleshooting tips

    +
    +

    5.5.1. Connection issues

    +
    +

    If you’re not able to connect to a database and see an endless loop of Connecting to Cloud SQL instance […​] on IP […​], it’s likely that exceptions are being thrown and logged at a level lower than your logger’s level. +This may be the case with HikariCP, if your logger is set to INFO or higher level.

    +
    +
    +

    To see what’s going on in the background, you should add a logback.xml file to your application resources folder, that looks like this:

    +
    +
    +
    +
    <?xml version="1.0" encoding="UTF-8"?>
    +<configuration>
    +  <include resource="org/springframework/boot/logging/logback/base.xml"/>
    +  <logger name="com.zaxxer.hikari.pool" level="DEBUG"/>
    +</configuration>
    +
    +
    +
    +
    +

    5.5.2. Errors like c.g.cloud.sql.core.SslSocketFactory : Re-throwing cached exception due to attempt to refresh instance information too soon after error

    +
    +

    If you see a lot of errors like this in a loop and can’t connect to your database, this is usually a symptom that something isn’t right with the permissions of your credentials or the Google Cloud SQL API is not enabled. +Verify that the Google Cloud SQL API is enabled in the Cloud Console and that your service account has the necessary IAM roles.

    +
    +
    +

    To find out what’s causing the issue, you can enable DEBUG logging level as mentioned above.

    +
    +
    +
    +

    5.5.3. PostgreSQL: java.net.SocketException: already connected issue

    +
    +

    We found this exception to be common if your Maven project’s parent is spring-boot version 1.5.x, or in any other circumstance that would cause the version of the org.postgresql:postgresql dependency to be an older one (e.g., 9.4.1212.jre7).

    +
    +
    +

    To fix this, re-declare the dependency in its correct version. +For example, in Maven:

    +
    +
    +
    +
    <dependency>
    +  <groupId>org.postgresql</groupId>
    +  <artifactId>postgresql</artifactId>
    +  <version>42.1.1</version>
    +</dependency>
    +
    +
    +
    +
    + +
    +
    +
    +

    6. Cloud Pub/Sub

    +
    +
    +

    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.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-pubsub</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-pubsub")
    +}
    +
    +
    +
    +

    This starter is also available from Spring Initializr through the GCP Messaging entry.

    +
    +
    +

    6.1. Configuration

    +
    +

    The Spring Boot starter for Google Cloud Pub/Sub provides the following configuration options.

    +
    +
    +

    6.1.1. Spring Framework on Google Cloud Pub/Sub API Configuration

    +
    +

    This section describes options for enabling the integration, specifying the Google Cloud project and credentials, and setting whether the APIs should connect to an emulator for local testing.

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.pubsub.enabled

    Enables or disables Pub/Sub auto-configuration

    No

    true

    spring.cloud.gcp.pubsub.project-id

    Google Cloud project ID where the Google Cloud Pub/Sub API is hosted, if different from the one in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.pubsub.credentials.location

    OAuth2 credentials for authenticating with the +Google Cloud Pub/Sub API, if different from the ones in the +Spring Framework on Google Cloud Core Module

    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 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 +Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.pubsub.credentials.scopes

    OAuth2 scope for Spring Framework on Google Cloud +Pub/Sub credentials

    No

    www.googleapis.com/auth/pubsub

    +
    +
    +

    6.1.2. Publisher/Subscriber Configuration

    +
    +

    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.

    +
    +
    + + + + + +
    + + +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 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. +However, if a per-subscription configuration is not set then the global or default configuration will be used. +
    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.pubsub.subscriber.parallel-pull-count

    The number of pull workers

    No

    1

    spring.cloud.gcp.pubsub.subscriber.max-ack-extension-period

    The maximum period a message ack deadline will be extended, in seconds

    No

    0

    spring.cloud.gcp.pubsub.subscriber.min-duration-per-ack-extension

    The lower bound for a single mod ack extension period, in seconds

    No

    0

    spring.cloud.gcp.pubsub.subscriber.max-duration-per-ack-extension

    The upper bound for a single mod ack extension period, in seconds

    No

    0

    spring.cloud.gcp.pubsub.subscriber.pull-endpoint

    The endpoint for pulling messages

    No

    pubsub.googleapis.com:443

    spring.cloud.gcp.pubsub.[subscriber,publisher].executor-threads

    Number of threads used by Subscriber instances created by SubscriberFactory

    No

    4

    spring.cloud.gcp.pubsub.[subscriber,publisher.batching].flow-control.max-outstanding-element-count

    Maximum number of outstanding elements to keep in memory before enforcing flow control.

    No

    unlimited

    spring.cloud.gcp.pubsub.[subscriber,publisher.batching].flow-control.max-outstanding-request-bytes

    Maximum number of outstanding bytes to keep in memory before enforcing flow control.

    No

    unlimited

    spring.cloud.gcp.pubsub.[subscriber,publisher.batching].flow-control.limit-exceeded-behavior

    The behavior when the specified limits are exceeded.

    No

    Block

    spring.cloud.gcp.pubsub.publisher.batching.element-count-threshold

    The element count threshold to use for batching.

    No

    1 (batching off)

    spring.cloud.gcp.pubsub.publisher.batching.request-byte-threshold

    The request byte threshold to use for batching.

    No

    1 byte (batching off)

    spring.cloud.gcp.pubsub.publisher.batching.delay-threshold-seconds

    The delay threshold to use for batching. +After this amount of time has elapsed (counting from the first element added), the elements will be wrapped up in a batch and sent.

    No

    1 ms (batching off)

    spring.cloud.gcp.pubsub.publisher.batching.enabled

    Enables batching.

    No

    false

    spring.cloud.gcp.pubsub.publisher.enable-message-ordering

    Enables message ordering.

    No

    false

    spring.cloud.gcp.pubsub.publisher.endpoint

    The publisher endpoint. +Example: "us-east1-pubsub.googleapis.com:443". +This is useful in conjunction with enabling message ordering because sending messages to the same region ensures they are received in order even when multiple publishers are used.

    No

    pubsub.googleapis.com:443

    +
    +
    Subscription-specific Configurations
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.pubsub.subscription.[subscription-name].fully-qualified-name

    The fully-qualified subscription name in the projects/[PROJECT]/subscriptions/[SUBSCRIPTION] format. When this property is present, the [subscription-name] key does not have to match any actual resources; it’s used only for logical grouping.

    No

    1

    spring.cloud.gcp.pubsub.subscription.[subscription-name].parallel-pull-count

    The number of pull workers.

    No

    1

    spring.cloud.gcp.pubsub.subscription.[subscription-name].max-ack-extension-period

    The maximum period a message ack deadline will be extended, in seconds.

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].min-duration-per-ack-extension

    The lower bound for a single mod ack extension period, in seconds

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].max-duration-per-ack-extension

    The upper bound for a single mod ack extension period, in seconds

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].pull-endpoint

    The endpoint for pulling messages.

    No

    pubsub.googleapis.com:443

    spring.cloud.gcp.pubsub.subscription.[subscription-name].executor-threads

    Number of threads used by Subscriber instances created by SubscriberFactory. Note that configuring per-subscription executor-threads will result in the creation of thread pools for both global/default and per-subscription configurations.

    No

    4

    spring.cloud.gcp.pubsub.subscription.[subscription-name].flow-control.max-outstanding-element-count

    Maximum number of outstanding elements to keep in memory before enforcing flow control.

    No

    unlimited

    spring.cloud.gcp.pubsub.subscription.[subscription-name].flow-control.max-outstanding-request-bytes

    Maximum number of outstanding bytes to keep in memory before enforcing flow control.

    No

    unlimited

    spring.cloud.gcp.pubsub.subscription.[subscription-name].flow-control.limit-exceeded-behavior

    The behavior when the specified limits are exceeded.

    No

    Block

    +
    + + + + + +
    + + +By default, subscription-specific threads are named after fully-qualified subscription name, ex: gcp-pubsub-subscriber-projects/project-id/subscriptions/subscription-name. +This can be customized, by registering a SelectiveSchedulerThreadNameProvider bean. +
    +
    +
    +
    +
    +

    6.1.3. GRPC Connection Settings

    +
    +

    The Pub/Sub API uses the GRPC protocol to send API requests to the Pub/Sub service. +This section describes configuration options for customizing the GRPC behavior.

    +
    +
    + + + + + +
    + + +The properties that refer to retry control the RPC retries for transient failures during the gRPC call to Cloud Pub/Sub server. +They do not control message redelivery; only message acknowledgement deadline can be used to extend or shorten the amount of time until Pub/Sub attempts redelivery. +
    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.pubsub.keepAliveIntervalMinutes

    Determines frequency of keepalive gRPC ping

    No

    5 minutes

    spring.cloud.gcp.pubsub.subscriber.retryableCodes

    RPC status codes that should be retried when pulling messages.

    No

    UNKNOWN,ABORTED,UNAVAILABLE

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.total-timeout-seconds

    TotalTimeout has ultimate control over how long the logic should keep trying the remote call until it gives up completely. +The higher the total timeout, the more retries can be attempted.

    No

    0

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.initial-retry-delay-second

    InitialRetryDelay controls the delay before the first retry. +Subsequent retries will use this value adjusted according to the RetryDelayMultiplier.

    No

    0

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.retry-delay-multiplier

    RetryDelayMultiplier controls the change in retry delay. +The retry delay of the previous call is multiplied by the RetryDelayMultiplier to calculate the retry delay for the next call.

    No

    1

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.max-retry-delay-seconds

    MaxRetryDelay puts a limit on the value of the retry delay, so that the RetryDelayMultiplier +can’t increase the retry delay higher than this amount.

    No

    0

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.max-attempts

    MaxAttempts defines the maximum number of attempts to perform. +If this value is greater than 0, and the number of attempts reaches this limit, the logic will give up retrying even if the total retry time is still lower than TotalTimeout.

    No

    0

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.jittered

    Jitter determines if the delay time should be randomized.

    No

    true

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.initial-rpc-timeout-seconds

    InitialRpcTimeout controls the timeout for the initial RPC. +Subsequent calls will use this value adjusted according to the RpcTimeoutMultiplier.

    No

    0

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.rpc-timeout-multiplier

    RpcTimeoutMultiplier controls the change in RPC timeout. +The timeout of the previous call is multiplied by the RpcTimeoutMultiplier to calculate the timeout for the next call.

    No

    1

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.max-rpc-timeout-seconds

    MaxRpcTimeout puts a limit on the value of the RPC timeout, so that the RpcTimeoutMultiplier +can’t increase the RPC timeout higher than this amount.

    No

    0

    +
    +

    Subscription-specific Configuration

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retryableCodes

    RPC status codes that should be retried when pulling messages.

    No

    UNKNOWN,ABORTED,UNAVAILABLE

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.total-timeout-seconds

    TotalTimeout has ultimate control over how long the logic should keep trying the remote call until it gives up completely. The higher the total timeout, the more retries can be attempted.

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.initial-retry-delay-second

    InitialRetryDelay controls the delay before the first retry. +Subsequent retries will use this value adjusted according to the RetryDelayMultiplier.

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.retry-delay-multiplier

    RetryDelayMultiplier controls the change in retry delay. +The retry delay of the previous call is multiplied by the RetryDelayMultiplier to calculate the retry delay for the next call.

    No

    1

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.max-retry-delay-seconds

    MaxRetryDelay puts a limit on the value of the retry delay, so that the RetryDelayMultiplier +can’t increase the retry delay higher than this amount.

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.max-attempts

    MaxAttempts defines the maximum number of attempts to perform. +If this value is greater than 0, and the number of attempts reaches this limit, the logic will give up retrying even if the total retry time is still lower than TotalTimeout.

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.jittered

    Jitter determines if the delay time should be randomized.

    No

    true

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.initial-rpc-timeout-seconds

    InitialRpcTimeout controls the timeout for the initial RPC. +Subsequent calls will use this value adjusted according to the RpcTimeoutMultiplier.

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.rpc-timeout-multiplier

    RpcTimeoutMultiplier controls the change in RPC timeout. +The timeout of the previous call is multiplied by the RpcTimeoutMultiplier to calculate the timeout for the next call.

    No

    1

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.max-rpc-timeout-seconds

    MaxRpcTimeout puts a limit on the value of the RPC timeout, so that the RpcTimeoutMultiplier +can’t increase the RPC timeout higher than this amount.

    No

    0

    +
    +
    +

    6.1.4. Programmatic Configuration

    +
    +

    To apply publishing customizations not covered by the properties above, you may provide custom beans of type PublisherCustomizer to post-process the Publisher.Builder object right before it is built into a Publisher. +The PublisherCustomizer beans may be annotated with Spring Framework’s @Order annotation to ensure they are applied in a particular sequence.

    +
    +
    +
    +
    +

    6.2. Spring Boot Actuator Support

    +
    +

    6.2.1. Cloud Pub/Sub Health Indicator

    +
    +

    If you are using Spring Boot Actuator, you can take advantage of the Cloud Pub/Sub health indicator called pubsub. +The health indicator will verify whether Cloud Pub/Sub is up and accessible by your application. +To enable it, all you need to do is add the Spring Boot Actuator to your project.

    +
    +
    +

    The pubsub indicator will then roll up to the overall application status visible at localhost:8080/actuator/health (use the management.endpoint.health.show-details property to view per-indicator details).

    +
    +
    +
    +
    <dependency>
    +    <groupId>org.springframework.boot</groupId>
    +    <artifactId>spring-boot-starter-actuator</artifactId>
    +</dependency>
    +
    +
    +
    + + + + + +
    + + +If your application already has actuator and Cloud Pub/Sub starters, this health indicator is enabled by default. +To disable the Cloud Pub/Sub indicator, set management.health.pubsub.enabled to false. +
    +
    +
    +

    The health indicator validates the connection to Pub/Sub by pulling messages from a Pub/Sub subscription.

    +
    +
    +

    If no subscription has been specified via spring.cloud.gcp.pubsub.health.subscription, it will pull messages from a random subscription that is expected not to exist. +It will signal "up" if it is able to connect to Spring Framework on Google Cloud Pub/Sub APIs, i.e. the pull results in a response of NOT_FOUND or PERMISSION_DENIED.

    +
    +
    +

    If a custom subscription has been specified, this health indicator will signal "up" if messages are successfully pulled and (optionally) acknowledged, or when a successful pull is performed but no messages are returned from Pub/Sub. +Note that messages pulled from the subscription will not be acknowledged, unless you set the spring.cloud.gcp.pubsub.health.acknowledge-messages option to true. +So, take care not to configure a subscription that has a business impact, or instead leave the custom subscription out completely.

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    management.health.pubsub.enabled

    Whether to enable the Pub/Sub health indicator

    No

    true with Spring Boot Actuator, false otherwise

    spring.cloud.gcp.pubsub.health.subscription

    Subscription to health check against by pulling a message

    No

    Random non-existent

    spring.cloud.gcp.pubsub.health.timeout-millis

    Milliseconds to wait for response from Pub/Sub before timing out

    No

    2000

    spring.cloud.gcp.pubsub.health.acknowledge-messages

    Whether to acknowledge messages pulled from the optionally specified subscription

    No

    false

    +
    +
    +

    6.2.2. Cloud Pub/Sub Subscription Health Indicator

    +
    +

    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 Spring Boot Actuator to your project and the Google Cloud 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 localhost:8080/actuator/health (use the management.endpoint.health.show-details property to view per-indicator details).

    +
    +
    +
    +
    <dependency>
    +    <groupId>org.springframework.boot</groupId>
    +    <artifactId>spring-boot-starter-actuator</artifactId>
    +</dependency>
    +<dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>google-cloud-monitoring</artifactId>
    +</dependency>
    +
    +
    +
    +

    The health indicator validates a subscriber’s health by checking the subscription’s message backlog and the last processed message. +A subscription’s backlog is retrieved using Google Cloud’s Monitoring Metrics. The metric used is the num_undelivered_messages for a subscription.

    +
    +
    +

    If a message has been recently processed in a reasonable time threshold, then the subscriber is healthy. +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.

    +
    +
    + + + + + +
    + + +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. +
    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    management.health.pubsub-subscriber.enabled

    Whether to enable the Pub/Sub Subscription health indicator

    No

    true with Spring Boot Actuator, false otherwise

    spring.cloud.gcp.pubsub.health.lagThreshold

    Threshold in seconds over message processing lag

    Yes

    Provided

    spring.cloud.gcp.pubsub.health.backlogThreshold

    The threshold number of messages for a subscription backlog

    Yes

    Provided

    spring.cloud.gcp.pubsub.health.lookUpInterval

    The optional interval in seconds for subscription backlog lookup

    No

    1

    spring.cloud.gcp.pubsub.health.executorThreads

    Number of threads used for Health Check Executors

    No

    4

    +
    +
    +
    +

    6.3. Pub/Sub Operations & Template

    +
    +

    PubSubOperations is an abstraction that allows Spring users to use Google Cloud Pub/Sub without depending on any Google Cloud Pub/Sub API semantics. +It provides the common set of operations needed to interact with Google Cloud Pub/Sub. +PubSubTemplate is the default implementation of PubSubOperations and it uses the Google Cloud Java Client for Pub/Sub to interact with Google Cloud Pub/Sub.

    +
    +
    +

    6.3.1. Publishing to a topic

    +
    +

    PubSubTemplate provides asynchronous methods to publish messages to a Google Cloud Pub/Sub topic. +The publish() method takes in a topic name to post the message to, a payload of a generic type and, optionally, a map with the message headers. +The topic name could either be a short topic name within the current project, or the fully-qualified name referring to a topic in a different project using the projects/[project_name]/topics/[topic_name] format.

    +
    +
    +

    Here is an example of how to publish a message to a Google Cloud Pub/Sub topic:

    +
    +
    +
    +
    Map<String, String> headers = Collections.singletonMap("key1", "val1");
    +pubSubTemplate.publish(topicName, "message", headers).get();
    +
    +
    +
    +
    +

    By default, the SimplePubSubMessageConverter is used to convert payloads of type byte[], ByteString, ByteBuffer, and String to Pub/Sub messages.

    +
    +
    +
    Ordering messages
    +
    +

    If you are relying on message converters and would like to provide an ordering key, use the GcpPubSubHeaders.ORDERING_KEY header. +You will also need to make sure to enable message ordering on the publisher via the spring.cloud.gcp.pubsub.publisher.enable-message-ordering property. +Additionally, if you are using multiple publishers, you will want to set the spring.cloud.gcp.pubsub.publisher.endpoint to a regional endpoint such as "us-east1-pubsub.googleapis.com:443" so that messages are sent to the same region and received in order.

    +
    +
    +
    +
    Map<String, String> headers =
    +    Collections.singletonMap(GcpPubSubHeaders.ORDERING_KEY, "key1");
    +pubSubTemplate.publish(topicName, "message1", headers).get();
    +pubSubTemplate.publish(topicName, "message2", headers).get();
    +
    +
    +
    +
    +
    +
    +

    6.3.2. Subscribing to a subscription

    +
    +

    Google Cloud Pub/Sub allows many subscriptions to be associated to the same topic. +PubSubTemplate allows you to listen to subscriptions via the subscribe() method. +When listening to a subscription, messages will be pulled from Google Cloud Pub/Sub asynchronously and passed to a user provided message handler. +The subscription name could either be a short subscription name within the current project, or the fully-qualified name referring to a subscription in a different project using the projects/[project_name]/subscriptions/[subscription_name] format.

    +
    +
    +
    Example
    +
    +

    Subscribe to a subscription with a message handler:

    +
    +
    +
    +
    Subscriber subscriber =
    +    pubSubTemplate.subscribe(
    +        subscriptionName,
    +        message -> {
    +          logger.info(
    +              "Message received from "
    +                  + subscriptionName
    +                  + " subscription: "
    +                  + message.getPubsubMessage().getData().toStringUtf8());
    +          message.ack();
    +        });
    +
    +
    +
    +
    +
    +
    Subscribe methods
    +
    +

    PubSubTemplate provides the following subscribe methods:

    +
    + ++++ + + + + + + + + + + +

    subscribe(String subscription, Consumer<BasicAcknowledgeablePubsubMessage> messageConsumer)

    asynchronously pulls messages and passes them to messageConsumer

    subscribeAndConvert(String subscription, + Consumer<ConvertedBasicAcknowledgeablePubsubMessage<T>> messageConsumer, + Class<T> payloadType)

    same as pull, but converts message payload to payloadType using the converter configured in the template

    +
    + + + + + +
    + + +
    +

    As of version 1.2, subscribing by itself is not enough to keep an application running. +For a command-line application, a way to keep the application running is to have a user thread(non-daemon thread) started up. A fake scheduled task creates a threadpool with non-daemon threads:

    +
    +
    +
    +
    @Scheduled (fixedRate = 1, timeUnit = TimeUnit.MINUTES)
    +public void fakeScheduledTask() {
    +    // do nothing
    +}
    +
    +
    +
    +
    +

    Another option is to pull in spring-boot-starter-web or spring-boot-starter-webflux as a dependency which will start an embedded servlet container or reactive server keeping the application running in the background

    +
    +
    +
    +
    +
    +
    +

    6.3.3. Pulling messages from a subscription

    +
    +

    Google Cloud Pub/Sub supports synchronous pulling of messages from a subscription. +This is different from subscribing to a subscription, in the sense that subscribing is an asynchronous task.

    +
    +
    +
    Example
    +
    +

    Pull up to 10 messages:

    +
    +
    +
    +
    int maxMessages = 10;
    +boolean returnImmediately = false;
    +List<AcknowledgeablePubsubMessage> messages =
    +    pubSubTemplate.pull(subscriptionName, maxMessages, returnImmediately);
    +
    +// acknowledge the messages
    +pubSubTemplate.ack(messages);
    +
    +messages.forEach(
    +    message ->
    +        logger.info(message.getPubsubMessage().getData().toStringUtf8()));
    +
    +
    +
    +
    +
    +
    Pull methods
    +
    +

    PubsubTemplate provides the following pull methods:

    +
    + ++++ + + + + + + + + + + + + + + + + + + +

    pull(String subscription, Integer maxMessages, + Boolean returnImmediately)

    Pulls a number of messages from a subscription, allowing for the retry settings to be configured. + Any messages received by pull() are not automatically acknowledged. See Acknowledging messages.

    +

    The maxMessages parameter is the maximum limit of how many messages to pull from a subscription in a single call; this value must be greater than 0. + You may omit this parameter by passing in null; this means there will be no limit on the number of messages pulled (maxMessages will be Integer.MAX_INTEGER).

    +

    If returnImmediately is true, the system will respond immediately even if it there are no messages available to return in the Pull response. Otherwise, the system may wait (for a bounded amount of time) until at least one message is available, rather than returning no messages.

    pullAndAck

    Works the same as the pull method and, additionally, acknowledges all received messages.

    pullNext

    Allows for a single message to be pulled and automatically acknowledged from a subscription.

    pullAndConvert

    Works the same as the pull method and, additionally, converts the Pub/Sub binary payload to an object of the desired type, using the converter configured in the template.

    +
    + + + + + +
    + + +We do not recommend setting returnImmediately to true, as it may result in delayed message delivery. +"Immediately" really means 1 second, and if Pub/Sub cannot retrieve any messages from the backend in that time, it will return 0 messages, despite having messages queue up on the topic. +Therefore, we recommend setting returnImmediately to false, or using subscribe methods from the previous section. +
    +
    +
    +
    +
    Acknowledging messages
    +
    +

    There are two ways to acknowledge messages.

    +
    +
    +
      +
    1. +

      To acknowledge multiple messages at once, you can use the PubSubTemplate.ack() method. +You can also use the PubSubTemplate.nack() for negatively acknowledging messages. +Using these methods for acknowledging messages in batches is more efficient than acknowledging messages individually, but they require the collection of messages to be from the same project.

      +
    2. +
    3. +

      To acknowledge messages individually you can use the ack() or nack() method on each of them (to acknowledge or negatively acknowledge, correspondingly).

      +
    4. +
    +
    +
    + + + + + +
    + + +All ack(), nack(), and modifyAckDeadline() methods on messages, as well as PubSubSubscriberTemplate, are implemented asynchronously, returning a ListenableFuture<Void> to enable asynchronous processing. +
    +
    +
    +
    +
    Dead Letter Topics
    +
    +

    Your application may occasionally receive a message it cannot process. +If you create your Subscription passing the Subscription.Builder argument, you can specify a DeadLetterPolicy that will forward all nack()-ed and non-ack()-ed messages after a configurable amount of redelivery attempts. +See here for more information.

    +
    +
    +
    +
    public Subscription newSubscription() {
    +    // Must use the fully-qualified topic name.
    +    String fullDeadLetterTopic = PubSubTopicUtils
    +                        .toTopicName(DEAD_LETTER_TOPIC, gcpProjectIdProvider.getProjectId())
    +                        .toString();
    +    return pubSubAdmin.createSubscription(Subscription.newBuilder()
    +            .setName(SUBSCRIPTION_NAME)
    +            .setTopic(TOPIC_NAME)
    +            .setDeadLetterPolicy(DeadLetterPolicy.newBuilder()
    +                    .setDeadLetterTopic(fullDeadLetterTopic)
    +                    .setMaxDeliveryAttempts(6)
    +                    .build()));
    +}
    +
    +
    +
    +
    +

    Dead letter topics are no different than any other topic, though some 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.

    +
    +
    +
    +
    +

    6.3.4. JSON support

    +
    +

    For serialization and deserialization of POJOs using Jackson JSON, configure a PubSubMessageConverter bean, and the Spring Boot starter for Spring Framework on Google Cloud Pub/Sub will automatically wire it into the PubSubTemplate.

    +
    +
    +
    +
    // Note: The ObjectMapper is used to convert Java POJOs to and from JSON.
    +// You will have to configure your own instance if you are unable to depend
    +// on the ObjectMapper provided by Spring Boot starters.
    +@Bean
    +public PubSubMessageConverter pubSubMessageConverter() {
    +  return new JacksonPubSubMessageConverter(new ObjectMapper());
    +}
    +
    +
    +
    +
    + + + + + +
    + + +Alternatively, you can set it directly by calling the setMessageConverter() method on the PubSubTemplate. +Other implementations of the PubSubMessageConverter can also be configured in the same manner. +
    +
    +
    +

    Assuming you have the following class defined:

    +
    +
    +
    +
    static class TestUser {
    +
    +  String username;
    +
    +  String password;
    +
    +  public String getUsername() {
    +    return this.username;
    +  }
    +
    +  void setUsername(String username) {
    +    this.username = username;
    +  }
    +
    +  public String getPassword() {
    +    return this.password;
    +  }
    +
    +  void setPassword(String password) {
    +    this.password = password;
    +  }
    +}
    +
    +
    +
    +
    +

    You can serialize objects to JSON on publish automatically:

    +
    +
    +
    +
    TestUser user = new TestUser();
    +user.setUsername("John");
    +user.setPassword("password");
    +pubSubTemplate.publish(topicName, user);
    +
    +
    +
    +
    +

    And that’s how you convert messages to objects on pull:

    +
    +
    +
    +
    int maxMessages = 1;
    +boolean returnImmediately = false;
    +List<ConvertedAcknowledgeablePubsubMessage<TestUser>> messages =
    +    pubSubTemplate.pullAndConvert(
    +        subscriptionName, maxMessages, returnImmediately, TestUser.class);
    +
    +ConvertedAcknowledgeablePubsubMessage<TestUser> message = messages.get(0);
    +
    +// acknowledge the message
    +message.ack();
    +
    +TestUser receivedTestUser = message.getPayload();
    +
    +
    +
    +
    +

    Please refer to our Pub/Sub JSON Payload Sample App as a reference for using this functionality.

    +
    +
    +
    +
    +

    6.4. Reactive Stream Subscriber

    +
    +

    It is also possible to acquire a reactive stream backed by a subscription. +To do so, a Project Reactor dependency (io.projectreactor:reactor-core) must be added to the project. +The combination of the Pub/Sub starter and the Project Reactor dependencies will then make a PubSubReactiveFactory bean available, which can then be used to get a Publisher.

    +
    +
    +
    +
    @Autowired
    +PubSubReactiveFactory reactiveFactory;
    +
    +// ...
    +
    +Flux<AcknowledgeablePubsubMessage> flux
    +                = reactiveFactory.poll("exampleSubscription", 1000);
    +
    +
    +
    +
    +

    The Flux then represents an infinite stream of Spring Framework on Google Cloud Pub/Sub messages coming in through the specified subscription. +For unlimited demand, the Pub/Sub subscription will be polled regularly, at intervals determined by pollingPeriodMs parameter passed in when creating the Flux. +For bounded demand, the pollingPeriodMs parameter is unused. +Instead, as many messages as possible (up to the requested number) are delivered immediately, with the remaining messages delivered as they become available.

    +
    +
    +

    Any exceptions thrown by the underlying message retrieval logic will be passed as an error to the stream. +The error handling operators (Flux#retry(), Flux#onErrorResume() etc.) can be used to recover.

    +
    +
    +

    The full range of Project Reactor operations can be applied to the stream. +For example, if you only want to fetch 5 messages, you can use limitRequest operation to turn the infinite stream into a finite one:

    +
    +
    +
    +
    Flux<AcknowledgeablePubsubMessage> fiveMessageFlux = flux.limitRequest(5);
    +
    +
    +
    +
    +

    Messages flowing through the Flux should be manually acknowledged.

    +
    +
    +
    +
    flux.doOnNext(AcknowledgeablePubsubMessage::ack);
    +
    +
    +
    +
    +
    +

    6.5. Pub/Sub management

    +
    +

    PubSubAdmin is the abstraction provided by Spring Framework on Google Cloud to manage Google Cloud Pub/Sub resources. +It allows for the creation, deletion and listing of topics and subscriptions.

    +
    +
    + + + + + +
    + + +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)/<name> 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.

    +
    +
    +

    6.5.1. Creating a topic

    +
    +

    PubSubAdmin implements a method to create topics:

    +
    +
    +
    +
    public Topic createTopic(String topicName)
    +
    +
    +
    +
    +

    Here is an example of how to create a Google Cloud Pub/Sub topic:

    +
    +
    +
    +
    public void newTopic() {
    +    pubSubAdmin.createTopic("topicName");
    +}
    +
    +
    +
    +
    +
    +

    6.5.2. Deleting a topic

    +
    +

    PubSubAdmin implements a method to delete topics:

    +
    +
    +
    +
    public void deleteTopic(String topicName)
    +
    +
    +
    +
    +

    Here is an example of how to delete a Google Cloud Pub/Sub topic:

    +
    +
    +
    +
    public void deleteTopic() {
    +    pubSubAdmin.deleteTopic("topicName");
    +}
    +
    +
    +
    +
    +
    +

    6.5.3. Listing topics

    +
    +

    PubSubAdmin implements a method to list topics:

    +
    +
    +
    +
    public List<Topic> listTopics
    +
    +
    +
    +
    +

    Here is an example of how to list every Google Cloud Pub/Sub topic name in a project:

    +
    +
    +
    +
    List<String> topics =
    +    pubSubAdmin.listTopics().stream().map(Topic::getName).collect(Collectors.toList());
    +
    +
    +
    +
    +
    +

    6.5.4. Creating a subscription

    +
    +

    PubSubAdmin implements several methods to create subscriptions to existing topics:

    +
    +
    +
    +
    public Subscription createSubscription(String subscriptionName, String topicName)
    +
    +public Subscription createSubscription(String subscriptionName, String topicName, Integer ackDeadline)
    +
    +public Subscription createSubscription(String subscriptionName, String topicName, Integer ackDeadline, String pushEndpoint)
    +
    +public Subscription createSubscription(Subscriber.Builder builder)
    +
    +
    +
    +
    +

    The default value for ackDeadline is 10 seconds. +If pushEndpoint isn’t specified, the subscription uses message pulling, instead. +You can also pass a Subscription.Builder for full control over any options or features available in the client library.

    +
    +
    +

    Here is an example of how to create a Google Cloud Pub/Sub subscription:

    +
    +
    +
    +
    public Subscription newSubscription() {
    +    return pubSubAdmin.createSubscription("subscriptionName", "topicName", 15);
    +}
    +
    +
    +
    +
    +
    +

    6.5.5. Deleting a subscription

    +
    +

    PubSubAdmin implements a method to delete subscriptions:

    +
    +
    +
    +
    public void deleteSubscription(String subscriptionName)
    +
    +
    +
    +
    +

    Here is an example of how to delete a Google Cloud Pub/Sub subscription:

    +
    +
    +
    +
    public void deleteSubscription() {
    +    pubSubAdmin.deleteSubscription("subscriptionName");
    +}
    +
    +
    +
    +
    +
    +

    6.5.6. Listing subscriptions

    +
    +

    PubSubAdmin implements a method to list subscriptions:

    +
    +
    +
    +
    public List<Subscription> listSubscriptions()
    +
    +
    +
    +
    +

    Here is an example of how to list every subscription name in a project:

    +
    +
    +
    +
    List<String> subscriptions =
    +    pubSubAdmin.listSubscriptions().stream()
    +        .map(Subscription::getName)
    +        .collect(Collectors.toList());
    +
    +
    +
    +
    +
    +
    +

    6.6. Sample

    +
    +

    Sample applications for using the template and using a subscription-backed reactive stream are available.

    +
    +
    +
    +

    6.7. Test

    +
    +

    Testcontainers provides a gcloud module which offers PubSubEmulatorContainer. See more at the docs

    +
    +
    +
    +
    +
    +

    7. Spring Integration

    +
    +
    +

    Spring Framework on Google Cloud provides Spring Integration adapters that allow your applications to use Enterprise Integration Patterns backed up by Google Cloud services.

    +
    +
    +

    7.1. Channel Adapters for Cloud Pub/Sub

    +
    +

    The channel adapters for Google Cloud Pub/Sub connect your Spring 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.

    +
    +
    +

    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.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-pubsub</artifactId>
    +</dependency>
    +<dependency>
    +    <groupId>org.springframework.integration</groupId>
    +    <artifactId>spring-integration-core</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-pubsub")
    +    implementation("org.springframework.integration:spring-integration-core")
    +}
    +
    +
    +
    +

    7.1.1. Inbound channel adapter (using Pub/Sub Streaming Pull)

    +
    +

    PubSubInboundChannelAdapter is the inbound channel adapter for Spring Framework on Google Cloud Pub/Sub that listens to a Spring Framework on Google Cloud Pub/Sub subscription for new messages. +It converts new messages to an internal Spring Message and then sends it to the bound output channel.

    +
    +
    +

    Google Pub/Sub treats message payloads as byte arrays. +So, by default, the inbound channel adapter will construct the Spring Message with byte[] as the payload. +However, you can change the desired payload type by setting the payloadType property of the PubSubInboundChannelAdapter. +The PubSubInboundChannelAdapter delegates the conversion to the desired payload type to the PubSubMessageConverter configured in the PubSubTemplate.

    +
    +
    +

    To use the inbound channel adapter, a PubSubInboundChannelAdapter must be provided and configured on the user application side.

    +
    +
    + + + + + +
    + + +The subscription name could either be a short subscription name within the current project, or the fully-qualified name referring to a subscription in a different project using the projects/[project_name]/subscriptions/[subscription_name] format. +
    +
    +
    +
    +
    @Bean
    +public MessageChannel pubsubInputChannel() {
    +    return new PublishSubscribeChannel();
    +}
    +
    +@Bean
    +public PubSubInboundChannelAdapter messageChannelAdapter(
    +    @Qualifier("pubsubInputChannel") MessageChannel inputChannel,
    +    PubSubTemplate pubsubTemplate) {
    +    PubSubInboundChannelAdapter adapter =
    +        new PubSubInboundChannelAdapter(pubsubTemplate, "subscriptionName");
    +    adapter.setOutputChannel(inputChannel);
    +    adapter.setAckMode(AckMode.MANUAL);
    +
    +    return adapter;
    +}
    +
    +
    +
    +
    +

    In the example, we first specify the MessageChannel where the adapter is going to write incoming messages to. +The MessageChannel implementation isn’t important here. +Depending on your use case, you might want to use a MessageChannel other than PublishSubscribeChannel.

    +
    +
    +

    Then, we declare a PubSubInboundChannelAdapter bean. +It requires the channel we just created and a SubscriberFactory, which creates Subscriber objects from the Google Cloud Java Client for Pub/Sub. +The Spring Boot starter for Spring Framework on Google Cloud Pub/Sub provides a configured PubSubSubscriberOperations object.

    +
    +
    +
    Acknowledging messages and handling failures
    +
    +

    When working with Cloud Pub/Sub, it is important to understand the concept of ackDeadline — the amount of time Cloud Pub/Sub will wait until attempting redelivery of an outstanding message. +Each subscription has a default ackDeadline applied to all messages sent to it. +Additionally, the Cloud Pub/Sub client library can extend each streamed message’s ackDeadline until the message processing completes, fails or until the maximum extension period elapses.

    +
    +
    + + + + + +
    + + +In the Pub/Sub client library, default maximum extension period is an hour. However, Spring Framework on Google Cloud disables this auto-extension behavior. +Use the spring.cloud.gcp.pubsub.subscriber.max-ack-extension-period property to re-enable it. +
    +
    +
    +

    Acknowledging (acking) a message removes it from Pub/Sub’s known outstanding messages. Nacking a message resets its acknowledgement deadline to 0, forcing immediate redelivery. +This could be useful in a load balanced architecture, where one of the subscribers is having issues but others are available to process messages.

    +
    +
    +

    The PubSubInboundChannelAdapter supports three acknowledgement modes: the default AckMode.AUTO (automatic acking on processing success and nacking on exception), as well as two modes for additional manual control: AckMode.AUTO_ACK (automatic acking on success but no action on exception) and AckMode.MANUAL (no automatic actions at all; both acking and nacking have to be done manually).

    +
    + + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Table 1. Acknowledgement mode behavior
    AUTOAUTO_ACKMANUAL

    Message processing completes successfully

    ack, no redelivery

    ack, no redelivery

    <no action>*

    Message processing fails, but error handler completes successfully**

    ack, no redelivery

    ack, no redelivery

    <no action>*

    Message processing fails; no error handler present

    nack, immediate redelivery

    <no action>*

    <no action>*

    Message processing fails, and error handler throws an exception

    nack, immediate redelivery

    <no action>*

    <no action>*

    +
    +

    * <no action> means that the message will be neither acked nor nacked. +Cloud Pub/Sub will attempt redelivery according to subscription ackDeadline setting and the max-ack-extension-period client library setting.

    +
    +
    +

    ** For the adapter, "success" means the Spring Integration flow processed without raising an exception, so successful message processing and the successful completion of an error handler both result in the same behavior (message will be acknowledged). +To trigger default error behavior (nacking in AUTO mode; neither acking nor nacking in AUTO_ACK mode), propagate the error back to the adapter by throwing an exception from the Error Handling flow.

    +
    +
    +
    Manual acking/nacking
    +
    +

    The adapter attaches a BasicAcknowledgeablePubsubMessage object to the Message headers. +Users can extract the BasicAcknowledgeablePubsubMessage using the GcpPubSubHeaders.ORIGINAL_MESSAGE key and use it to ack (or nack) a message.

    +
    +
    +
    +
    @Bean
    +@ServiceActivator(inputChannel = "pubsubInputChannel")
    +public MessageHandler messageReceiver() {
    +    return message -> {
    +        LOGGER.info("Message arrived! Payload: " + new String((byte[]) message.getPayload()));
    +        BasicAcknowledgeablePubsubMessage originalMessage =
    +              message.getHeaders().get(GcpPubSubHeaders.ORIGINAL_MESSAGE, BasicAcknowledgeablePubsubMessage.class);
    +        originalMessage.ack();
    +    };
    +}
    +
    +
    +
    +
    +
    +
    Error Handling
    +
    +

    If you want to have more control over message processing in case of an error, you need to associate the PubSubInboundChannelAdapter with a Spring Integration error channel and specify the behavior to be invoked with @ServiceActivator.

    +
    +
    + + + + + +
    + + +In order to activate the default behavior (nacking in AUTO mode; neither acking nor nacking in AUTO_ACK mode), your error handler has to throw an exception. +Otherwise, the adapter will assume that processing completed successfully and will ack the message. +
    +
    +
    +
    +
    @Bean
    +public MessageChannel pubsubInputChannel() {
    +    return new PublishSubscribeChannel();
    +}
    +
    +@Bean
    +public PubSubInboundChannelAdapter messageChannelAdapter(
    +    @Qualifier("pubsubInputChannel") MessageChannel inputChannel,
    +    PubSubTemplate pubsubTemplate) {
    +    PubSubInboundChannelAdapter adapter =
    +        new PubSubInboundChannelAdapter(pubsubTemplate, "subscriptionName");
    +    adapter.setOutputChannel(inputChannel);
    +    adapter.setAckMode(AckMode.AUTO_ACK);
    +    adapter.setErrorChannelName("pubsubErrors");
    +
    +    return adapter;
    +}
    +
    +@ServiceActivator(inputChannel =  "pubsubErrors")
    +public void pubsubErrorHandler(Message<MessagingException> message) {
    +    LOGGER.warn("This message will be automatically acked because error handler completes successfully");
    +}
    +
    +
    +
    +
    +

    If you would prefer to manually ack or nack the message, you can do it by retrieving the header of the exception payload:

    +
    +
    +
    +
    @ServiceActivator(inputChannel =  "pubsubErrors")
    +public void pubsubErrorHandler(Message<MessagingException> exceptionMessage) {
    +
    +    BasicAcknowledgeablePubsubMessage originalMessage =
    +      (BasicAcknowledgeablePubsubMessage)exceptionMessage.getPayload().getFailedMessage()
    +        .getHeaders().get(GcpPubSubHeaders.ORIGINAL_MESSAGE);
    +
    +    originalMessage.nack();
    +}
    +
    +
    +
    +
    +
    +
    +
    +

    7.1.2. Pollable Message Source (using Pub/Sub Synchronous Pull)

    +
    +

    While PubSubInboundChannelAdapter, through the underlying Asynchronous Pull Pub/Sub mechanism, provides the best performance for high-volume applications that receive a steady flow of messages, it can create load balancing anomalies due to message caching. +This behavior is most obvious when publishing a large batch of small messages that take a long time to process individually. +It manifests as one subscriber taking up most messages, even if multiple subscribers are available to take on the work. +For a more detailed explanation of this scenario, see Spring Framework on Google Cloud Pub/Sub documentation.

    +
    +
    +

    In such a scenario, a PubSubMessageSource can help spread the load between different subscribers more evenly.

    +
    +
    +

    As with the Inbound Channel Adapter, the message source has a configurable acknowledgement mode, payload type, and header mapping.

    +
    +
    +

    The default behavior is to return from the synchronous pull operation immediately if no messages are present. +This can be overridden by using setBlockOnPull() method to wait for at least one message to arrive.

    +
    +
    +

    By default, PubSubMessageSource pulls from the subscription one message at a time. +To pull a batch of messages on each request, use the setMaxFetchSize() method to set the batch size.

    +
    +
    + + + + + +
    + + +The subscription name could either be a short subscription name within the current project, or the fully-qualified name referring to a subscription in a different project using the projects/[project_name]/subscriptions/[subscription_name] format. +
    +
    +
    +
    +
    @Bean
    +@InboundChannelAdapter(channel = "pubsubInputChannel", poller = @Poller(fixedDelay = "100"))
    +public MessageSource<Object> pubsubAdapter(PubSubTemplate pubSubTemplate) {
    +    PubSubMessageSource messageSource = new PubSubMessageSource(pubSubTemplate,  "exampleSubscription");
    +    messageSource.setAckMode(AckMode.MANUAL);
    +    messageSource.setPayloadType(String.class);
    +    messageSource.setBlockOnPull(true);
    +    messageSource.setMaxFetchSize(100);
    +    return messageSource;
    +}
    +
    +
    +
    +
    +

    The @InboundChannelAdapter annotation above ensures that the configured MessageSource is polled for messages, which are then available for manipulation with any Spring Integration mechanism on the pubsubInputChannel message channel. +For example, messages can be retrieved in a method annotated with @ServiceActivator, as seen below.

    +
    +
    +

    For additional flexibility, PubSubMessageSource attaches an AcknowledgeablePubSubMessage object to the GcpPubSubHeaders.ORIGINAL_MESSAGE message header. +The object can be used for manually (n)acking the message.

    +
    +
    +
    +
    @ServiceActivator(inputChannel = "pubsubInputChannel")
    +public void messageReceiver(String payload,
    +        @Header(GcpPubSubHeaders.ORIGINAL_MESSAGE) AcknowledgeablePubsubMessage message)
    +            throws InterruptedException {
    +    LOGGER.info("Message arrived by Synchronous Pull! Payload: " + payload);
    +    message.ack();
    +}
    +
    +
    +
    +
    + + + + + +
    + + +AcknowledgeablePubSubMessage objects acquired by synchronous pull are aware of their own acknowledgement IDs. +Streaming pull does not expose this information due to limitations of the underlying API, and returns BasicAcknowledgeablePubsubMessage objects that allow acking/nacking individual messages, but not extracting acknowledgement IDs for future processing. +
    +
    +
    +
    +

    7.1.3. Outbound channel adapter

    +
    +

    PubSubMessageHandler is the outbound channel adapter for Spring Framework on Google Cloud Pub/Sub that listens for new messages on a Spring MessageChannel. +It uses PubSubTemplate to post them to a Spring Framework on Google Cloud Pub/Sub topic.

    +
    +
    +

    To construct a Pub/Sub representation of the message, the outbound channel adapter needs to convert the Spring Message payload to a byte array representation expected by Pub/Sub. +It delegates this conversion to the PubSubTemplate. +To customize the conversion, you can specify a PubSubMessageConverter in the PubSubTemplate that should convert the Object payload and headers of the Spring Message to a PubsubMessage.

    +
    +
    +

    To use the outbound channel adapter, a PubSubMessageHandler bean must be provided and configured on the user application side.

    +
    +
    + + + + + +
    + + +The topic name could either be a short topic name within the current project, or the fully-qualified name referring to a topic in a different project using the projects/[project_name]/topics/[topic_name] format. +
    +
    +
    +
    +
    @Bean
    +@ServiceActivator(inputChannel = "pubsubOutputChannel")
    +public MessageHandler messageSender(PubSubTemplate pubsubTemplate) {
    +    return new PubSubMessageHandler(pubsubTemplate, "topicName");
    +}
    +
    +
    +
    +
    +

    The provided PubSubTemplate contains all the necessary configuration to publish messages to a Spring Framework on Google Cloud Pub/Sub topic.

    +
    +
    +

    PubSubMessageHandler publishes messages asynchronously by default. +A publish timeout can be configured for synchronous publishing. +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.

    +
    +
    +
    +
    adapter.setPublishCallback(
    +    new ListenableFutureCallback<String>() {
    +      @Override
    +      public void onFailure(Throwable ex) {}
    +
    +      @Override
    +      public void onSuccess(String result) {}
    +    });
    +
    +
    +
    +
    +

    To override the default topic you can use the GcpPubSubHeaders.TOPIC header.

    +
    +
    +
    +
    @Autowired
    +private MessageChannel pubsubOutputChannel;
    +
    +public void handleMessage(Message<?> msg) throws MessagingException {
    +    final Message<?> message = MessageBuilder
    +        .withPayload(msg.getPayload())
    +        .setHeader(GcpPubSubHeaders.TOPIC, "customTopic").build();
    +    pubsubOutputChannel.send(message);
    +}
    +
    +
    +
    +
    +

    It is also possible to set an SpEL expression for the topic with the setTopicExpression() or setTopicExpressionString() methods.

    +
    +
    +
    +
    PubSubMessageHandler adapter = new PubSubMessageHandler(pubSubTemplate, "myDefaultTopic");
    +adapter.setTopicExpressionString("headers['sendToTopic']");
    +
    +
    +
    +
    +
    +

    7.1.4. Header mapping

    +
    +

    These channel adapters contain header mappers that allow you to map, or filter out, headers from Spring to Google Cloud Pub/Sub messages, and vice-versa. +By default, the inbound channel adapter maps every header on the Google Cloud Pub/Sub messages to the Spring messages produced by the adapter. +The outbound channel adapter maps every header from Spring messages into Google Cloud Pub/Sub ones, except the ones added by Spring and some special headers, like headers with key "id", "timestamp", "gcp_pubsub_acknowledgement", and "gcp_pubsub_ordering_key". +In the process, the outbound mapper also converts the value of the headers into string.

    +
    +
    +

    Note that you can provide the GcpPubSubHeaders.ORDERING_KEY ("gcp_pubsub_ordering_key") header, which will be automatically mapped to PubsubMessage.orderingKey property, and excluded from the headers in the published message. +Remember to set spring.cloud.gcp.pubsub.publisher.enable-message-ordering to true, if you are publishing messages with this header.

    +
    +
    +

    Each adapter declares a setHeaderMapper() method to let you further customize which headers you want to map from Spring to Google Cloud Pub/Sub, and vice-versa.

    +
    +
    +

    For example, to filter out headers "foo", "bar" and all headers starting with the prefix "prefix_", you can use setHeaderMapper() along with the PubSubHeaderMapper implementation provided by this module.

    +
    +
    +
    +
    PubSubMessageHandler adapter = ...
    +...
    +PubSubHeaderMapper headerMapper = new PubSubHeaderMapper();
    +headerMapper.setOutboundHeaderPatterns("!foo", "!bar", "!prefix_*", "*");
    +adapter.setHeaderMapper(headerMapper);
    +
    +
    +
    +
    + + + + + +
    + + +The order in which the patterns are declared in PubSubHeaderMapper.setOutboundHeaderPatterns() and PubSubHeaderMapper.setInboundHeaderPatterns() matters. +The first patterns have precedence over the following ones. +
    +
    +
    +

    In the previous example, the "*" pattern means every header is mapped. +However, because it comes last in the list, the previous patterns take precedence.

    +
    +
    + +
    +
    +

    7.2. Channel Adapters for Google Cloud Storage

    +
    +

    The channel adapters for Google Cloud Storage allow you to read and write files to Google Cloud Storage through MessageChannels.

    +
    +
    +

    Spring Framework on Google Cloud provides two inbound adapters, GcsInboundFileSynchronizingMessageSource and GcsStreamingMessageSource, and one outbound adapter, GcsMessageHandler.

    +
    +
    +

    The Spring Integration Channel Adapters for Google Cloud Storage are included in the spring-cloud-gcp-storage module.

    +
    +
    +

    To use the Storage portion of Spring Integration for Spring Framework on Google Cloud, you must also provide the spring-integration-file dependency, since it isn’t pulled transitively.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-storage</artifactId>
    +</dependency>
    +<dependency>
    +    <groupId>org.springframework.integration</groupId>
    +    <artifactId>spring-integration-file</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-storage")
    +    implementation("org.springframework.integration:spring-integration-file")
    +}
    +
    +
    +
    +

    7.2.1. Inbound channel adapter

    +
    +

    The Google Cloud Storage inbound channel adapter polls a Google Cloud Storage bucket for new files and sends each of them in a Message payload to the MessageChannel specified in the @InboundChannelAdapter annotation. +The files are temporarily stored in a folder in the local file system.

    +
    +
    +

    Here is an example of how to configure a Google Cloud Storage inbound channel adapter.

    +
    +
    +
    +
    @Bean
    +@InboundChannelAdapter(channel = "new-file-channel", poller = @Poller(fixedDelay = "5000"))
    +public MessageSource<File> synchronizerAdapter(Storage gcs) {
    +  GcsInboundFileSynchronizer synchronizer = new GcsInboundFileSynchronizer(gcs);
    +  synchronizer.setRemoteDirectory("your-gcs-bucket");
    +
    +  GcsInboundFileSynchronizingMessageSource synchAdapter =
    +          new GcsInboundFileSynchronizingMessageSource(synchronizer);
    +  synchAdapter.setLocalDirectory(new File("local-directory"));
    +
    +  return synchAdapter;
    +}
    +
    +
    +
    +
    +
    +

    7.2.2. Inbound streaming channel adapter

    +
    +

    The inbound streaming channel adapter is similar to the normal inbound channel adapter, except it does not require files to be stored in the file system.

    +
    +
    +

    Here is an example of how to configure a Google Cloud Storage inbound streaming channel adapter.

    +
    +
    +
    +
    @Bean
    +@InboundChannelAdapter(channel = "streaming-channel", poller = @Poller(fixedDelay = "5000"))
    +public MessageSource<InputStream> streamingAdapter(Storage gcs) {
    +  GcsStreamingMessageSource adapter =
    +          new GcsStreamingMessageSource(new GcsRemoteFileTemplate(new GcsSessionFactory(gcs)));
    +  adapter.setRemoteDirectory("your-gcs-bucket");
    +  return adapter;
    +}
    +
    +
    +
    +
    +

    If you would like to process the files in your bucket in a specific order, you may pass in a Comparator<BlobInfo> to the constructor GcsStreamingMessageSource to sort the files being processed.

    +
    +
    +
    +

    7.2.3. Outbound channel adapter

    +
    +

    The outbound channel adapter allows files to be written to Google Cloud Storage. +When it receives a Message containing a payload of type File, it writes that file to the Google Cloud Storage bucket specified in the adapter.

    +
    +
    +

    Here is an example of how to configure a Google Cloud Storage outbound channel adapter.

    +
    +
    +
    +
    @Bean
    +@ServiceActivator(inputChannel = "writeFiles")
    +public MessageHandler outboundChannelAdapter(Storage gcs) {
    +  GcsMessageHandler outboundChannelAdapter = new GcsMessageHandler(new GcsSessionFactory(gcs));
    +  outboundChannelAdapter.setRemoteDirectoryExpression(new ValueExpression<>("your-gcs-bucket"));
    +
    +  return outboundChannelAdapter;
    +}
    +
    +
    +
    +
    + +
    +
    +
    +
    +

    8. Spring Cloud Stream

    +
    +
    +

    Spring Framework on Google Cloud provides a Spring Cloud Stream binder to Google Cloud Pub/Sub.

    +
    + +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-pubsub-stream-binder</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-pubsub-stream-binder")
    +}
    +
    +
    +
    +

    8.1. Overview

    +
    +

    This binder binds producers to Google Cloud Pub/Sub topics and consumers to subscriptions.

    +
    +
    + + + + + +
    + + +Partitioning is currently not supported by this binder. +
    +
    +
    +
    +

    8.2. 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.<channelName>.<consumer|producer>.auto-create-resources property, which is turned ON by default.

    +
    +
    +

    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 configuration section for other Pub/Sub parameters.

    +
    +
    + + + + + +
    + + +To use this binder with a running emulator, configure its host and port via spring.cloud.gcp.pubsub.emulator-host. +
    +
    +
    +

    8.2.1. Producer Synchronous Sending Configuration

    +
    +

    By default, this binder will send messages to Cloud Pub/Sub asynchronously. +If synchronous sending is preferred (for example, to allow propagating errors back to the sender), set spring.cloud.stream.gcp.pubsub.default.producer.sync property to true.

    +
    +
    +
    +

    8.2.2. Producer Destination Configuration

    +
    +

    If automatic resource creation is turned ON and the topic corresponding to the destination name does not exist, it will be created.

    +
    +
    +

    For example, for the following configuration, a topic called myEvents would be created.

    +
    +
    +
    application.properties
    +
    +
    spring.cloud.stream.bindings.events.destination=myEvents
    +spring.cloud.stream.gcp.pubsub.bindings.events.producer.auto-create-resources=true
    +
    +
    +
    +
    +

    8.2.3. Consumer Destination Configuration

    +
    +

    A PubSubInboundChannelAdapter will be configured for your consumer endpoint. +You may adjust the ack mode of the consumer endpoint using the ack-mode property. +The ack mode controls how messages will be acknowledged when they are successfully received. +The three possible options are: AUTO (default), AUTO_ACK, and MANUAL. +These options are described in detail in the Pub/Sub channel adapter documentation.

    +
    +
    +
    application.properties
    +
    +
    # How to set the ACK mode of the consumer endpoint.
    +spring.cloud.stream.gcp.pubsub.bindings.{CONSUMER_NAME}.consumer.ack-mode=AUTO_ACK
    +
    +
    +
    +

    With automatic resource creation turned ON for a consumer, the library creates a topic and/or a subscription if they do not exist. +The topic name becomes the same as the destination name, and the subscription name follows these rules (in order of precedence):

    +
    +
    +
      +
    • +

      A user-defined, pre-existing subscription (use spring.cloud.stream.gcp.pubsub.bindings.{CONSUMER_NAME}.consumer.subscriptionName)

      +
    • +
    • +

      A consumer group using the topic name (use spring.cloud.stream.bindings.events.group to create a subscription named <topicName>.<group>)

      +
    • +
    • +

      If neither of the above are specified, the library creates an anonymous subscription with the name anonymous.<destinationName>.<randomUUID>. +Then when the binder shuts down, the library automatically cleans up all Pub/Sub subscriptions created for anonymous consumer groups.

      +
    • +
    +
    +
    +

    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
    +
    +
    +
    +

    Only an anonymous subscription named anonymous.myEvents.a6d83782-c5a3-4861-ac38-e6e2af15a7be is created and later cleaned up.

    +
    +
    +

    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
    +
    +# specify consumer group, and avoid anonymous consumer group generation
    +spring.cloud.stream.bindings.events.group=consumerGroup1
    +
    +
    +
    +

    These resources will be created:

    +
    +
    +
      +
    • +

      A topic named myEvents

      +
    • +
    • +

      A subscription named myEvents.consumerGroup1

      +
    • +
    +
    +
    +
    +

    8.2.4. Header Mapping

    +
    +

    You can filter incoming and outgoing message headers with allowHeaders property. +For example, for a consumer to allow only two headers, provide a comma separated list like this:

    +
    +
    +
    application.properties
    +
    +
    spring.cloud.stream.gcp.pubsub.bindings.<consumerFunction>-in-0.consumer.allowedHeaders=allowed1, allowed2
    +
    +
    +
    +

    Where <consumerFunction> 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.

    +
    +
    +

    A similar style is applicable for producers as well. For example:

    +
    +
    +
    application.properties
    +
    +
    spring.cloud.stream.gcp.pubsub.bindings.<producerFunction>-out-0.producer.allowedHeaders=allowed3,allowed4
    +
    +
    +
    +

    Where <producerFunction> 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.

    +
    +
    +
    +

    8.2.5. Endpoint Customization

    +
    +

    You may customize channel routing by defining a ConsumerEndpointCustomizer in your autoconfiguration. This is useful if you want to customize the default configurations provided by the Pub/Sub Spring Cloud Stream Binder.

    +
    +
    +

    The example below demonstrates how to use a ConsumerEndpointCustomizer to override the default error channel configured by the binder.

    +
    +
    +
    +
    @Bean
    +public ConsumerEndpointCustomizer<PubSubInboundChannelAdapter> messageChannelAdapter() {
    +    return (endpoint, destinationName, group) -> {
    +        NamedComponent namedComponent = (NamedComponent) endpoint.getOutputChannel();
    +        String channelName = namedComponent.getBeanName();
    +        endpoint.setErrorChannelName(channelName + ".errors");
    +    };
    +}
    +
    +
    +
    +
    +
    +
    +

    8.3. Binding with Functions

    +
    +

    Since version 3.0, Spring Cloud Stream supports a functional programming model natively. +This means that the only requirement for turning your application into a sink is presence of a java.util.function.Consumer bean in the application context.

    +
    +
    +
    +
    @Bean
    +public Consumer<UserMessage> logUserMessage() {
    +  return userMessage -> {
    +    // process message
    +  }
    +};
    +
    +
    +
    +

    A source application is one where a Supplier bean is present. +It can return an object, in which case Spring Cloud Stream will invoke the supplier repeatedly. +Alternatively, the function can return a reactive stream, which will be used as is.

    +
    +
    +
    +
    @Bean
    +Supplier<Flux<UserMessage>> generateUserMessages() {
    +  return () -> /* flux creation logic */;
    +}
    +
    +
    +
    +

    A processor application works similarly to a source application, except it is triggered by presence of a Function bean.

    +
    +
    +
    +

    8.4. 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 */));
    +    }
    +}
    +
    +
    +
    +
    +

    8.5. 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.

    +
    +
    +
    +
    public interface PollableSink {
    +
    +    @Input("input")
    +    PollableMessageSource input();
    +}
    +
    +
    +
    +
    +

    The PollableMessageSource can then be injected and queried, as needed.

    +
    +
    +
    +
    @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.

    +
    +
    +
    +

    8.6. Sample

    +
    +

    Sample applications are available:

    +
    + +
    +
    +
    +
    +

    9. Spring Cloud Bus

    +
    +
    +

    Using Cloud Pub/Sub as the Spring Cloud Bus implementation is as simple as importing the spring-cloud-gcp-starter-bus-pubsub starter.

    +
    +
    +

    This starter brings in the Spring Cloud Stream binder for Cloud Pub/Sub, which is used to both publish and subscribe to the bus. +If the bus topic (named springCloudBus by default) does not exist, the binder automatically creates it. +The binder also creates anonymous subscriptions for each project using the spring-cloud-gcp-starter-bus-pubsub starter.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +  <groupId>com.google.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-starter-bus-pubsub</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-bus-pubsub")
    +}
    +
    +
    +
    +
    +

    9.1. Configuration Management with Spring Cloud Config and Spring Cloud Bus

    +
    +

    Spring Cloud Bus can be used to push configuration changes from a Spring Cloud Config server to the clients listening on the same bus.

    +
    +
    +

    To use Spring Framework on Google Cloud Pub/Sub as the bus implementation, both the configuration server and the configuration client need the spring-cloud-gcp-starter-bus-pubsub dependency.

    +
    +
    +

    All other configuration is standard to Spring Cloud Config.

    +
    +
    +
    +spring cloud bus over pubsub +
    +
    +
    +

    Spring Cloud Config Server typically runs on port 8888, and can read configuration from a variety of source control systems such as GitHub, and even from the local filesystem. +When the server is notified that new configuration is available, it fetches the updated configuration and sends a notification (RefreshRemoteApplicationEvent) out via Spring Cloud Bus.

    +
    +
    +

    When configuration is stored locally, config server polls the parent directory for changes. +With configuration stored in source control repository, such as GitHub, the config server needs to be notified that a new version of configuration is available. +In a deployed server, this would be done automatically through a GitHub webhook, but in a local testing scenario, the /monitor HTTP endpoint needs to be invoked manually.

    +
    +
    +
    +
    curl -X POST http://localhost:8888/monitor -H "X-Github-Event: push" -H "Content-Type: application/json" -d '{"commits": [{"modified": ["application.properties"]}]}'
    +
    +
    +
    +

    By adding the spring-cloud-gcp-starter-bus-pubsub dependency, you instruct Spring Cloud Bus to use Cloud Pub/Sub to broadcast configuration changes. +Spring Cloud Bus will then create a topic named springCloudBus, as well as a subscription for each configuration client.

    +
    +
    +

    The configuration server happens to also be a configuration client, subscribing to the configuration changes that it sends out. +Thus, in a scenario with one configuration server and one configuration client, two anonymous subscriptions to the springCloudBus topic are created. +However, a config server disables configuration refresh by default (see ConfigServerBootstrapApplicationListener for more details).

    +
    +
    +

    A demo application showing configuration management and distribution over a Cloud Pub/Sub-powered bus is available. +The sample contains two examples of configuration management with Spring Cloud Bus: one monitoring a local file system, and the other retrieving configuration from a GitHub repository.

    +
    +
    +
    +
    +
    +

    10. Cloud Trace

    +
    +
    +

    Google Cloud provides a managed distributed tracing service called Cloud Trace, and Spring Cloud Sleuth 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 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.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-trace</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-trace")
    +}
    +
    +
    +
    +

    You must enable Cloud Trace API from the Google Cloud Console in order to capture traces. +Navigate to the Cloud Trace API for your project and make sure it’s enabled.

    +
    +
    + + + + + +
    + + +
    +

    If you are already using a Zipkin server capturing trace information from multiple platform/frameworks, you can also use a Stackdriver Zipkin proxy to forward those traces to Cloud Trace without modifying existing applications.

    +
    +
    +
    +
    +

    10.1. Tracing

    +
    +

    Spring Cloud Sleuth uses the Brave tracer to generate traces. +This integration enables Brave to use the 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. +A canonical example of the propagation usage is a web server that receives an HTTP request, which triggers other HTTP requests from the server before returning an HTTP response to the original caller.

    +
    +
    +

    In the case of StackdriverTracePropagation, first it looks for trace context in the 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 CUSTOM propagation type.

    +
    +
    + + + + + +
    + + +
    +

    The value of the x-cloud-trace-context key can be formatted in three different ways:

    +
    +
    +
      +
    • +

      x-cloud-trace-context: TRACE_ID

      +
    • +
    • +

      x-cloud-trace-context: TRACE_ID/SPAN_ID

      +
    • +
    • +

      x-cloud-trace-context: TRACE_ID/SPAN_ID;o=TRACE_TRUE

      +
    • +
    +
    +
    +

    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 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.

    +
    +
    +
    +
    +
    +

    10.2. Spring Boot Starter for Cloud Trace

    +
    +

    Spring Boot Starter for Cloud Trace uses Spring Cloud Sleuth and auto-configures a StackdriverSender that sends the Sleuth’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.project-id

    Overrides the project ID from the Spring Framework on Google Cloud Module

    No

    spring.cloud.gcp.trace.credentials.location

    Overrides the credentials location from the Spring Framework on Google Cloud Module

    No

    spring.cloud.gcp.trace.credentials.encoded-key

    Overrides the credentials encoded key from the Spring Framework on Google Cloud Module

    No

    spring.cloud.gcp.trace.credentials.scopes

    Overrides the credentials scopes from the Spring Framework on Google Cloud Module

    No

    spring.cloud.gcp.trace.num-executor-threads

    Number of threads used by the Trace executor

    No

    4

    spring.cloud.gcp.trace.authority

    HTTP/2 authority the channel claims to be connecting to.

    No

    spring.cloud.gcp.trace.compression

    Name of the compression to use in Trace calls

    No

    spring.cloud.gcp.trace.deadline-ms

    Call deadline in milliseconds

    No

    spring.cloud.gcp.trace.max-inbound-size

    Maximum size for inbound messages

    No

    spring.cloud.gcp.trace.max-outbound-size

    Maximum size for outbound messages

    No

    spring.cloud.gcp.trace.wait-for-ready

    Waits for the channel to be ready in case of a transient failure

    No

    false

    spring.cloud.gcp.trace.messageTimeout

    Timeout in seconds before pending spans will be sent in batches to Google Cloud Trace. (previously spring.zipkin.messageTimeout)

    No

    1

    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 for more information on Sleuth 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
    +
    +
    +
    + + + + + +
    + + +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:

    +
    +
    +
      +
    • +

      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.

      +
    • +
    +
    +
    +
    +

    10.3. 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<Span> 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.

    +
    +
    +
    +

    10.4. Customizing spans

    +
    +

    You can add additional tags and annotations to spans by using the brave.SpanCustomizer, which is available in the application context.

    +
    +
    +

    Here’s an example that uses WebMvcConfigurer to configure an MVC interceptor that adds two extra tags to all web controller spans.

    +
    +
    +
    +
    @SpringBootApplication
    +public class Application implements WebMvcConfigurer {
    +
    +    public static void main(String[] args) {
    +        SpringApplication.run(Application.class, args);
    +    }
    +
    +    @Autowired
    +    private SpanCustomizer spanCustomizer;
    +
    +    @Override
    +    public void addInterceptors(InterceptorRegistry registry) {
    +        registry.addInterceptor(new HandlerInterceptor() {
    +            @Override
    +            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    +                spanCustomizer.tag("session-id", request.getSession().getId());
    +                spanCustomizer.tag("environment", "QA");
    +
    +                return true;
    +            }
    +        });
    +    }
    +}
    +
    +
    +
    +
    +

    You can then search and filter traces based on these additional tags in the Cloud Trace service.

    +
    +
    +
    +

    10.5. Integration with Logging

    +
    +

    Integration with Cloud Logging is available through the Cloud Logging Support. +If the Trace integration is used together with the Logging one, the request logs will be associated to the corresponding traces. +The trace logs can be viewed by going to the Google Cloud Console Trace List, selecting a trace and pressing the Logs → View link in the Details section.

    +
    +
    +
    +

    10.6. Pub/Sub Trace Instrumentation (Experimental)

    +
    +

    You can enable trace instrumentation and propagation for Pub/Sub messages by using the spring.cloud.gcp.trace.pubsub.enabled=true property. +It’s set to false by default, but when set to true, trace spans will be created and propagated to Cloud Trace whenever the application sends or receives messages through PubSubTemplate or any other integration that builds on top of PubSubTemplate, such as the 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
    +
    +
    +
    +
    +

    10.7. Sample

    +
    +

    A sample application and a codelab are available.

    +
    +
    +
    +
    +
    +

    11. Cloud Logging

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-logging</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-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 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. +This allows grouping of log messages by request, for example, in the Google Cloud Console Logs viewer.

    +
    +
    + + + + + +
    + + +Due to the way logging is set up, the Google Cloud project ID and credentials defined in application.properties are ignored. +Instead, you should set the GOOGLE_CLOUD_PROJECT and GOOGLE_APPLICATION_CREDENTIALS environment variables to the project ID and credentials private key location, respectively. +You can do this easily if you’re using the Google Cloud SDK, using the gcloud config set project [YOUR_PROJECT_ID] and gcloud auth application-default login commands, respectively. +
    +
    +
    +

    11.1. Web MVC Interceptor

    +
    +

    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.

    +
    +
    + + + + + +
    + + +If Spring Framework on Google Cloud Trace is enabled, the logging module disables itself and delegates log correlation to Spring Cloud Sleuth. +
    +
    +
    +

    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.

    +
    +
    +
    +

    11.2. Logback Support

    +
    +

    Currently, only Logback is supported and there are 2 possibilities to log to Cloud Logging via this library with Logback: via direct API calls and through JSON-formatted console logs.

    +
    +
    +

    11.2.1. Log via API

    +
    +

    A Cloud Logging appender is available using com/google/cloud/spring/logging/logback-appender.xml. +This appender builds a Cloud Logging log entry from a JUL or Logback log entry, adds a trace ID to it and sends it to Cloud Logging.

    +
    +
    +

    STACKDRIVER_LOG_NAME and STACKDRIVER_LOG_FLUSH_LEVEL environment variables can be used to customize the STACKDRIVER appender.

    +
    +
    +

    Your configuration may then look like this:

    +
    +
    +
    +
    <configuration>
    +  <include resource="com/google/cloud/spring/logging/logback-appender.xml" />
    +
    +  <root level="INFO">
    +    <appender-ref ref="STACKDRIVER" />
    +  </root>
    +</configuration>
    +
    +
    +
    +

    If you want to have more control over the log output, you can further configure the appender. +The following properties are available (see java-logging-logback project for the full list):

    +
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PropertyDefault ValueDescription

    log

    spring.log

    The Cloud Logging Log name. +This can also be set via the STACKDRIVER_LOG_NAME environmental variable.

    flushLevel

    WARN

    If a log entry with this level is encountered, trigger a flush of locally buffered log to Cloud Logging. +This can also be set via the STACKDRIVER_LOG_FLUSH_LEVEL environmental variable.

    enhancer

    Fully qualified class name for customizing a logging entry; must implement com.google.cloud.logging.LoggingEnhancer.

    loggingEventEnhancer

    Fully qualified class name for customizing a logging entry given an ILoggingEvent; must implement com.google.cloud.logging.logback.LoggingEventEnhancer.

    +
    +
    +

    11.2.2. Asynchronous Logging

    +
    +

    If you would like to send logs asynchronously to Cloud Logging, you can use the AsyncAppender.

    +
    +
    +

    Your configuration may then look like this:

    +
    +
    +
    +
    <configuration>
    +  <include resource="com/google/cloud/spring/logging/logback-appender.xml" />
    +
    +  <appender name="ASYNC_STACKDRIVER" class="ch.qos.logback.classic.AsyncAppender">
    +    <appender-ref ref="STACKDRIVER" />
    +  </appender>
    +
    +  <root level="INFO">
    +    <appender-ref ref="ASYNC_STACKDRIVER" />
    +  </root>
    +</configuration>
    +
    +
    +
    +
    +

    11.2.3. Log via Console

    +
    +

    For Logback, a com/google/cloud/spring/logging/logback-json-appender.xml file is made available for import to make it easier to configure the JSON Logback appender.

    +
    +
    +

    Your configuration may then look something like this:

    +
    +
    +
    +
    <configuration>
    +  <include resource="com/google/cloud/spring/logging/logback-json-appender.xml" />
    +
    +  <root level="INFO">
    +    <appender-ref ref="CONSOLE_JSON" />
    +  </root>
    +</configuration>
    +
    +
    +
    +

    If your application is running on Google Kubernetes Engine, Google Compute Engine or Google App Engine Flexible, your console logging is automatically saved to Google Cloud Logging. +Therefore, you can just include com/google/cloud/spring/logging/logback-json-appender.xml in your logging configuration, which logs JSON entries to the console. +The trace id will be set correctly.

    +
    +
    +

    If you want to have more control over the log output, you can further configure the appender. +The following properties are available:

    +
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PropertyDefault ValueDescription

    projectId

    +

    If not set, default value is determined in the following order:

    +
    +
    +
      +
    1. +

      SPRING_CLOUD_GCP_LOGGING_PROJECT_ID Environmental Variable.

      +
    2. +
    3. +

      Value of DefaultGcpProjectIdProvider.getProjectId()

      +
    4. +
    +
    +

    This is used to generate fully qualified Cloud Trace ID format: projects/[PROJECT-ID]/traces/[TRACE-ID].

    +
    +
    +

    This format is required to correlate trace between Cloud Trace and Cloud Logging.

    +
    +
    +

    If projectId is not set and cannot be determined, then it’ll log traceId without the fully qualified format.

    +

    traceIdMdcField

    traceId

    The MDC field name for retrieving a trace id

    spanIdMdcField

    spanId

    the MDC field name for retrieving a span id

    includeTraceId

    true

    Should the trace id be included

    includeSpanId

    true

    Should the span id be included

    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

    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. +This setting is only evaluated if includeFormattedMessage is true

    includeContextName

    true

    Should the logging context be included

    includeMessage

    false

    Should the log message with blank placeholders be included

    includeException

    false

    Should the stacktrace be included as a own field

    serviceContext

    none

    Define the Stackdriver service context data (service and version). This allows filtering of error reports for service and version in the Google Cloud Error Reporting View.

    customJson

    none

    Defines custom json data. Data will be added to the json output.

    loggingEventEnhancer

    none

    Name of a class implementing JsonLoggingEventEnhancer which modifies the JSON logging output. This tag is repeatable.

    +

    Examples are provided in the extensions package.

    +

    - Logstash Enhancer

    +
    +

    This is an example of such an Logback configuration:

    +
    +
    +
    +
    <configuration >
    +  <property name="projectId" value="${projectId:-${GOOGLE_CLOUD_PROJECT}}"/>
    +
    +  <appender name="CONSOLE_JSON" class="ch.qos.logback.core.ConsoleAppender">
    +    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
    +      <layout class="com.google.cloud.spring.logging.StackdriverJsonLayout">
    +        <projectId>${projectId}</projectId>
    +
    +        <!--<traceIdMdcField>traceId</traceIdMdcField>-->
    +        <!--<spanIdMdcField>spanId</spanIdMdcField>-->
    +        <!--<includeTraceId>true</includeTraceId>-->
    +        <!--<includeSpanId>true</includeSpanId>-->
    +        <!--<includeLevel>true</includeLevel>-->
    +        <!--<includeThreadName>true</includeThreadName>-->
    +        <!--<includeMDC>true</includeMDC>-->
    +        <!--<includeLoggerName>true</includeLoggerName>-->
    +        <!--<includeFormattedMessage>true</includeFormattedMessage>-->
    +        <!--<includeExceptionInMessage>true</includeExceptionInMessage>-->
    +        <!--<includeContextName>true</includeContextName>-->
    +        <!--<includeMessage>false</includeMessage>-->
    +        <!--<includeException>false</includeException>-->
    +        <!--<serviceContext>
    +              <service>service-name</service>
    +              <version>service-version</version>
    +            </serviceContext>-->
    +        <!--<customJson>{"custom-key": "custom-value"}</customJson>-->
    +        <!--<loggingEventEnhancer>your.package.YourLoggingEventEnhancer</loggingEventEnhancer> -->
    +      </layout>
    +    </encoder>
    +  </appender>
    +</configuration>
    +
    +
    +
    +
    +
    +

    11.3. Sample

    +
    +

    A Sample Spring Boot Application is provided to show how to use the Cloud logging starter.

    +
    +
    +
    +
    +
    +

    12. Cloud Monitoring

    +
    +
    +

    Google Cloud provides a service called Cloud Monitoring, and Micrometer can be used with it to easily instrument Spring Boot applications for observability.

    +
    +
    +

    Spring Boot already provides auto-configuration for Cloud Monitoring. +This module enables auto-detection of the project-id and credentials. +Also, it can be customized.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-metrics</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-metrics")
    +}
    +
    +
    +
    +

    You must enable Cloud Monitoring API from the Google Cloud Console in order to capture metrics. +Navigate to the Cloud Monitoring API for your project and make sure it’s enabled.

    +
    +
    +

    Spring Boot Starter for Cloud Monitoring uses Micrometer.

    +
    +
    +

    12.1. Configuration

    +
    +

    All configurations are optional:

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.metrics.enabled

    Auto-configure Micrometer to send metrics to Cloud Monitoring.

    No

    true

    spring.cloud.gcp.metrics.project-id

    Overrides the project ID from the Spring Framework on Google Cloud Module

    No

    spring.cloud.gcp.metrics.credentials.location

    Overrides the credentials location from the Spring Framework on Google Cloud Module

    No

    spring.cloud.gcp.metrics.credentials.encoded-key

    Overrides the credentials encoded key from the Spring Framework on Google Cloud Module

    No

    spring.cloud.gcp.metrics.credentials.scopes

    Overrides the credentials scopes from the Spring Framework on Google Cloud Module

    No

    +
    +

    You can use core Spring Boot Actuator properties to control reporting frequency, etc. +Read Spring Boot Actuator documentation for more information on Stackdriver Actuator configurations.

    +
    +
    +

    12.1.1. Metrics Disambiguation

    +
    +

    By default, spring-cloud-gcp-starter-metrics/the StackdriverMeterRegistry does not add any application/pod specific tags to the metrics, +thus google is unable to distinguish between multiple metric sources. +This could lead to the following warning inside your applications logs:

    +
    +
    +
    +
    2022-08-15 10:26:00.248  WARN 1 --- [trics-publisher] i.m.s.StackdriverMeterRegistry           : failed to send metrics to Stackdriver
    +
    +
    +
    +

    The actual cause message may vary:

    +
    +
    +
    +
    One or more TimeSeries could not be written:
    +One or more points were written more frequently than the maximum sampling period configured for the metric.: global{} timeSeries[4]: custom.googleapis.com/process/uptime{};
    +One or more points were written more frequently than the maximum sampling period configured for the metric.: global{} timeSeries[6]: custom.googleapis.com/system/load/average/1m{};
    +One or more points ...
    +
    +
    +
    +

    or even:

    +
    +
    +
    +
    Caused by: io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2Exception: Header size exceeded max allowed size (10240)
    +
    +
    +
    +

    (due to the error message being too long)

    +
    +
    +

    Google rejects metric updates for entries that it has received before (from another application) for the same interval. +To avoid these conflicts and in order to distinguish between applications/instances you should add a configuration similar to this:

    +
    +
    +
    +
    management:
    +  metrics:
    +    tags:
    +      app: my-foobar-service
    +      instance: ${random.uuid}
    +
    +
    +
    +

    Instead of the random uuid you could also use the pod id/the hostname or some other instance id. +Read more about custom tags here.

    +
    +
    +
    +
    +

    12.2. Sample

    +
    +

    A sample application is available.

    +
    +
    +
    +
    +
    +

    13. Spring Data Cloud Spanner

    +
    +
    +

    Spring Data is an abstraction for storing and retrieving POJOs in numerous storage technologies. +Spring Framework on Google Cloud adds Spring Data support for Google Cloud Spanner.

    +
    +
    +

    Maven coordinates for this module only, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-data-spanner</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-data-spanner")
    +}
    +
    +
    +
    +

    We provide a Spring Boot Starter for Spring Data Spanner, with which you can leverage our recommended auto-configuration setup. +To use the starter, see the coordinates see below.

    +
    +
    +

    Maven:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-data-spanner</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-data-spanner")
    +}
    +
    +
    +
    +

    This setup takes care of bringing in the latest compatible version of Cloud Java Cloud Spanner libraries as well.

    +
    +
    +

    13.1. Configuration

    +
    +

    To setup Spring Data Cloud Spanner, you have to configure the following:

    +
    +
    +
      +
    • +

      Setup the connection details to Google Cloud Spanner.

      +
    • +
    • +

      Enable Spring Data Repositories (optional).

      +
    • +
    +
    +
    +

    13.1.1. Cloud Spanner settings

    +
    +

    You can use the Spring Boot Starter for Spring Data Spanner to autoconfigure Google Cloud Spanner in your Spring application. +It contains all the necessary setup that makes it easy to authenticate with your Google Cloud project. +The following configuration options are available:

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.spanner.enabled

    Enables the Cloud Spanner client

    No

    true

    spring.cloud.gcp.spanner.instance-id

    Cloud Spanner instance to use

    Yes

    spring.cloud.gcp.spanner.database

    Cloud Spanner database to use

    Yes

    spring.cloud.gcp.spanner.project-id

    Google Cloud project ID where the Google Cloud Spanner API is hosted, if different from the one in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.spanner.credentials.location

    OAuth2 credentials for authenticating with the +Google Cloud Spanner API, if different from the ones in the +Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.spanner.credentials.encoded-key

    Base64-encoded OAuth2 credentials for authenticating with the +Google Cloud Spanner API, if different from the ones in the +Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.spanner.credentials.scopes

    OAuth2 scope for Spring Framework on Google +CloudSpanner credentials

    No

    www.googleapis.com/auth/spanner.data

    spring.cloud.gcp.spanner.createInterleavedTableDdlOnDeleteCascade

    If true, then schema statements generated by SpannerSchemaUtils for tables with interleaved parent-child relationships will be "ON DELETE CASCADE". +The schema for the tables will be "ON DELETE NO ACTION" if false.

    No

    true

    spring.cloud.gcp.spanner.numRpcChannels

    Number of gRPC channels used to connect to Cloud Spanner

    No

    4 - Determined by Cloud Spanner client library

    spring.cloud.gcp.spanner.prefetchChunks

    Number of chunks prefetched by Cloud Spanner for read and query

    No

    4 - Determined by Cloud Spanner client library

    spring.cloud.gcp.spanner.minSessions

    Minimum number of sessions maintained in the session pool

    No

    0 - Determined by Cloud Spanner client library

    spring.cloud.gcp.spanner.maxSessions

    Maximum number of sessions session pool can have

    No

    400 - Determined by Cloud Spanner client library

    spring.cloud.gcp.spanner.maxIdleSessions

    Maximum number of idle sessions session pool will maintain

    No

    0 - Determined by Cloud Spanner client library

    spring.cloud.gcp.spanner.writeSessionsFraction

    Fraction of sessions to be kept prepared for write transactions

    No

    0.2 - Determined by Cloud Spanner client library

    spring.cloud.gcp.spanner.keepAliveIntervalMinutes

    How long to keep idle sessions alive

    No

    30 - Determined by Cloud Spanner client library

    spring.cloud.gcp.spanner.failIfPoolExhausted

    If all sessions are in use, fail the request by throwing an exception. Otherwise, by default, block until a session becomes available.

    No

    false

    spring.cloud.gcp.spanner.emulator.enabled

    Enables the usage of an emulator. If this is set to true, then you should set the spring.cloud.gcp.spanner.emulator-host to the host:port of your locally running emulator instance.

    No

    false

    spring.cloud.gcp.spanner.emulator-host

    The host and port of the Spanner emulator; can be overridden to specify connecting to an already-running Spanner emulator instance.

    No

    localhost:9010

    +
    + + + + + +
    + + +For further customization of the client library SpannerOptions, provide a bean implementing SpannerOptionsCustomizer, with a single method that accepts a SpannerOptions.Builder and modifies it as necessary. +
    +
    +
    +
    +

    13.1.2. Repository settings

    +
    +

    Spring Data Repositories can be configured via the @EnableSpannerRepositories annotation on your main @Configuration class. +With our Spring Boot Starter for Spring Data Cloud Spanner, @EnableSpannerRepositories is automatically added. +It is not required to add it to any other class, unless there is a need to override finer grain configuration parameters provided by @EnableSpannerRepositories.

    +
    +
    +
    +

    13.1.3. Autoconfiguration

    +
    +

    Our Spring Boot autoconfiguration creates the following beans available in the Spring application context:

    +
    +
    +
      +
    • +

      an instance of SpannerTemplate

      +
    • +
    • +

      an instance of SpannerDatabaseAdminTemplate for generating table schemas from object hierarchies and creating and deleting tables and databases

      +
    • +
    • +

      an instance of all user-defined repositories extending SpannerRepository, CrudRepository, PagingAndSortingRepository, when repositories are enabled

      +
    • +
    • +

      an instance of DatabaseClient from the Google Cloud Java Client for Spanner, for convenience and lower level API access

      +
    • +
    +
    +
    +
    +
    +

    13.2. Object Mapping

    +
    +

    Spring Data Cloud Spanner allows you to map domain POJOs to Cloud Spanner tables via annotations:

    +
    +
    +
    +
    @Table(name = "traders")
    +public class Trader {
    +
    +    @PrimaryKey
    +    @Column(name = "trader_id")
    +    String traderId;
    +
    +    String firstName;
    +
    +    String lastName;
    +
    +    @NotMapped
    +    Double temporaryNumber;
    +}
    +
    +
    +
    +
    +

    Spring Data Cloud Spanner will ignore any property annotated with @NotMapped. +These properties will not be written to or read from Spanner.

    +
    +
    +

    13.2.1. Constructors

    +
    +

    Simple constructors are supported on POJOs. +The constructor arguments can be a subset of the persistent properties. +Every constructor argument needs to have the same name and type as a persistent property on the entity and the constructor should set the property from the given argument. +Arguments that are not directly set to properties are not supported.

    +
    +
    +
    +
    @Table(name = "traders")
    +public class Trader {
    +    @PrimaryKey
    +    @Column(name = "trader_id")
    +    String traderId;
    +
    +    String firstName;
    +
    +    String lastName;
    +
    +    @NotMapped
    +    Double temporaryNumber;
    +
    +    public Trader(String traderId, String firstName) {
    +        this.traderId = traderId;
    +        this.firstName = firstName;
    +    }
    +}
    +
    +
    +
    +
    +
    +

    13.2.2. Table

    +
    +

    The @Table annotation can provide the name of the Cloud Spanner table that stores instances of the annotated class, one per row. +This annotation is optional, and if not given, the name of the table is inferred from the class name with the first character uncapitalized.

    +
    +
    +
    SpEL expressions for table names
    +
    +

    In some cases, you might want the @Table table name to be determined dynamically. +To do that, you can use Spring Expression Language.

    +
    +
    +

    For example:

    +
    +
    +
    +
    @Table(name = "trades_#{tableNameSuffix}")
    +public class Trade {
    +    // ...
    +}
    +
    +
    +
    +
    +

    The table name will be resolved only if the tableNameSuffix value/bean in the Spring application context is defined. +For example, if tableNameSuffix has the value "123", the table name will resolve to trades_123.

    +
    +
    +
    +
    +

    13.2.3. Primary Keys

    +
    +

    For a simple table, you may only have a primary key consisting of a single column. +Even in that case, the @PrimaryKey annotation is required. +@PrimaryKey identifies the one or more ID properties corresponding to the primary key.

    +
    +
    +

    Spanner has first class support for composite primary keys of multiple columns. +You have to annotate all of your POJO’s fields that the primary key consists of with @PrimaryKey as below:

    +
    +
    +
    +
    @Table(name = "trades")
    +public class Trade {
    +    @PrimaryKey(keyOrder = 2)
    +    @Column(name = "trade_id")
    +    private String tradeId;
    +
    +    @PrimaryKey(keyOrder = 1)
    +    @Column(name = "trader_id")
    +    private String traderId;
    +
    +    private String action;
    +
    +    private BigDecimal price;
    +
    +    private Double shares;
    +
    +    private String symbol;
    +}
    +
    +
    +
    +
    +

    The keyOrder parameter of @PrimaryKey identifies the properties corresponding to the primary key columns in order, starting with 1 and increasing consecutively. +Order is important and must reflect the order defined in the Cloud Spanner schema. +In our example the DDL to create the table and its primary key is as follows:

    +
    +
    +
    +
    CREATE TABLE trades (
    +    trader_id STRING(MAX),
    +    trade_id STRING(MAX),
    +    action STRING(15),
    +    symbol STRING(10),
    +    price NUMERIC,
    +    shares FLOAT64
    +) PRIMARY KEY (trader_id, trade_id)
    +
    +
    +
    +

    Spanner does not have automatic ID generation. +For most use-cases, sequential IDs should be used with caution to avoid creating data hotspots in the system. +Read Spanner Primary Keys documentation for a better understanding of primary keys and recommended practices.

    +
    +
    +
    +

    13.2.4. Columns

    +
    +

    All accessible properties on POJOs are automatically recognized as a Cloud Spanner column. +Column naming is generated by the PropertyNameFieldNamingStrategy by default defined on the SpannerMappingContext bean. +The @Column annotation optionally provides a different column name than that of the property and some other settings:

    +
    +
    +
      +
    • +

      name is the optional name of the column

      +
    • +
    • +

      spannerTypeMaxLength specifies for STRING and BYTES columns the maximum length. +This setting is only used when generating DDL schema statements based on domain types.

      +
    • +
    • +

      nullable specifies if the column is created as NOT NULL. +This setting is only used when generating DDL schema statements based on domain types.

      +
    • +
    • +

      spannerType is the Cloud Spanner column type you can optionally specify. +If this is not specified then a compatible column type is inferred from the Java property type.

      +
    • +
    • +

      spannerCommitTimestamp is a boolean specifying if this property corresponds to an auto-populated commit timestamp column. +Any value set in this property will be ignored when writing to Cloud Spanner.

      +
    • +
    +
    +
    +
    +

    13.2.5. Embedded Objects

    +
    +

    If an object of type B is embedded as a property of A, then the columns of B will be saved in the same Cloud Spanner table as those of A.

    +
    +
    +

    If B has primary key columns, those columns will be included in the primary key of A. B can also have embedded properties. +Embedding allows reuse of columns between multiple entities, and can be useful for implementing parent-child situations, because Cloud Spanner requires child tables to include the key columns of their parents.

    +
    +
    +

    For example:

    +
    +
    +
    +
    class X {
    +  @PrimaryKey
    +  String grandParentId;
    +
    +  long age;
    +}
    +
    +class A {
    +  @PrimaryKey
    +  @Embedded
    +  X grandParent;
    +
    +  @PrimaryKey(keyOrder = 2)
    +  String parentId;
    +
    +  String value;
    +}
    +
    +@Table(name = "items")
    +class B {
    +  @PrimaryKey
    +  @Embedded
    +  A parent;
    +
    +  @PrimaryKey(keyOrder = 2)
    +  String id;
    +
    +  @Column(name = "child_value")
    +  String value;
    +}
    +
    +
    +
    +
    +

    Entities of B can be stored in a table defined as:

    +
    +
    +
    +
    CREATE TABLE items (
    +    grandParentId STRING(MAX),
    +    parentId STRING(MAX),
    +    id STRING(MAX),
    +    value STRING(MAX),
    +    child_value STRING(MAX),
    +    age INT64
    +) PRIMARY KEY (grandParentId, parentId, id)
    +
    +
    +
    +

    Note that the following restrictions apply when you use embedded objects:

    +
    +
    +
      +
    • +

      Embedded properties' column names must all be unique.

      +
    • +
    • +

      Embedded properties must not be passed through a constructor and the property must be mutable; otherwise you’ll get an error, such as SpannerDataException: Column not found. +Be careful about this restriction when you use Kotlin’s data class to hold an embedded property.

      +
    • +
    +
    +
    +
    +

    13.2.6. Relationships

    +
    +

    Spring Data Cloud Spanner supports parent-child relationships using the Cloud Spanner parent-child interleaved table mechanism. +Cloud Spanner interleaved tables enforce the one-to-many relationship and provide efficient queries and operations on entities of a single domain parent entity. +These relationships can be up to 7 levels deep. +Cloud Spanner also provides automatic cascading delete or enforces the deletion of child entities before parents.

    +
    +
    +

    While one-to-one and many-to-many relationships can be implemented in Cloud Spanner and Spring Data Cloud Spanner using constructs of interleaved parent-child tables, only the parent-child relationship is natively supported.

    +
    +
    +

    For example, the following Java entities:

    +
    +
    +
    +
    @Table(name = "Singers")
    +class Singer {
    +  @PrimaryKey
    +  long SingerId;
    +
    +  String FirstName;
    +
    +  String LastName;
    +
    +  byte[] SingerInfo;
    +
    +  @Interleaved
    +  List<Album> albums;
    +}
    +
    +@Table(name = "Albums")
    +class Album {
    +  @PrimaryKey
    +  long SingerId;
    +
    +  @PrimaryKey(keyOrder = 2)
    +  long AlbumId;
    +
    +  String AlbumTitle;
    +}
    +
    +
    +
    +
    +

    These classes can correspond to an existing pair of interleaved tables. +The @Interleaved annotation may be applied to Collection properties and the inner type is resolved as the child entity type. +The schema needed to create them can also be generated using the SpannerSchemaUtils and run by using the SpannerDatabaseAdminTemplate:

    +
    +
    +
    +
    @Autowired
    +SpannerSchemaUtils schemaUtils;
    +
    +@Autowired
    +SpannerDatabaseAdminTemplate databaseAdmin;
    +...
    +
    +// Get the create statmenets for all tables in the table structure rooted at Singer
    +List<String> createStrings = this.schemaUtils.getCreateTableDdlStringsForInterleavedHierarchy(Singer.class);
    +
    +// Create the tables and also create the database if necessary
    +this.databaseAdmin.executeDdlStrings(createStrings, true);
    +
    +
    +
    +
    +

    The createStrings list contains table schema statements using column names and types compatible with the provided Java type and any resolved child relationship types contained within based on the configured custom converters.

    +
    +
    +
    +
    CREATE TABLE Singers (
    +  SingerId   INT64 NOT NULL,
    +  FirstName  STRING(1024),
    +  LastName   STRING(1024),
    +  SingerInfo BYTES(MAX),
    +) PRIMARY KEY (SingerId);
    +
    +CREATE TABLE Albums (
    +  SingerId     INT64 NOT NULL,
    +  AlbumId      INT64 NOT NULL,
    +  AlbumTitle   STRING(MAX),
    +) PRIMARY KEY (SingerId, AlbumId),
    +  INTERLEAVE IN PARENT Singers ON DELETE CASCADE;
    +
    +
    +
    +

    The ON DELETE CASCADE clause indicates that Cloud Spanner will delete all Albums of a singer if the Singer is deleted. +The alternative is ON DELETE NO ACTION, where a Singer cannot be deleted until all of its Albums have already been deleted. +When using SpannerSchemaUtils to generate the schema strings, the spring.cloud.gcp.spanner.createInterleavedTableDdlOnDeleteCascade boolean setting determines if these schema are generated as ON DELETE CASCADE for true and ON DELETE NO ACTION for false.

    +
    +
    +

    Cloud Spanner restricts these relationships to 7 child layers. +A table may have multiple child tables.

    +
    +
    +

    On updating or inserting an object to Cloud Spanner, all of its referenced children objects are also updated or inserted in the same request, respectively. +On read, all of the interleaved child rows are also all read.

    +
    +
    +
    Lazy Fetch
    +
    +

    @Interleaved properties are retrieved eagerly by default, but can be fetched lazily for performance in both read and write:

    +
    +
    +
    +
    @Interleaved(lazy = true)
    +List<Album> albums;
    +
    +
    +
    +
    +

    Lazily-fetched interleaved properties are retrieved upon the first interaction with the property. +If a property marked for lazy fetching is never retrieved, then it is also skipped when saving the parent entity.

    +
    +
    +

    If used inside a transaction, subsequent operations on lazily-fetched properties use the same transaction context as that of the original parent entity.

    +
    +
    +
    +
    Declarative Filtering with @Where
    +
    +

    The @Where annotation could be applied to an entity class or to an interleaved property. +This annotation provides an SQL where clause that will be applied at the fetching of interleaved collections or the entity itself.

    +
    +
    +

    Let’s say we have an Agreement with a list of Participants which could be assigned to it. +We would like to fetch a list of currently active participants. +For security reasons, all records should remain in the database forever, even if participants become inactive. +That can be easily achieved with the @Where annotation, which is demonstrated by this example:

    +
    +
    +
    +
    @Table(name = "participants")
    +public class Participant {
    +  //...
    +  boolean active;
    +  //...
    +}
    +
    +@Table(name = "agreements")
    +public class Agreement {
    +  //...
    +  @Interleaved
    +  @Where("active = true")
    +  List<Participant> participants;
    +  Person person;
    +  //...
    +}
    +
    +
    +
    +
    +
    +
    +

    13.2.7. Supported Types

    +
    +

    Spring Data Cloud Spanner natively supports the following types for regular fields but also utilizes custom converters (detailed in following sections) and dozens of pre-defined Spring Data custom converters to handle other common Java types.

    +
    +
    +

    Natively supported types:

    +
    +
    +
      +
    • +

      com.google.cloud.ByteArray

      +
    • +
    • +

      com.google.cloud.Date

      +
    • +
    • +

      com.google.cloud.Timestamp

      +
    • +
    • +

      java.lang.Boolean, boolean

      +
    • +
    • +

      java.lang.Double, double

      +
    • +
    • +

      java.lang.Long, long

      +
    • +
    • +

      java.lang.Integer, int

      +
    • +
    • +

      java.lang.String

      +
    • +
    • +

      double[]

      +
    • +
    • +

      long[]

      +
    • +
    • +

      boolean[]

      +
    • +
    • +

      java.util.Date

      +
    • +
    • +

      java.time.Instant

      +
    • +
    • +

      java.sql.Date

      +
    • +
    • +

      java.time.LocalDate

      +
    • +
    • +

      java.time.LocalDateTime

      +
    • +
    +
    +
    +
    +

    13.2.8. JSON fields

    +
    +

    Spanner supports JSON and ARRAY<JSON> type for columns. Such property needs to be annotated with @Column(spannerType = TypeCode.JSON). JSON columns are mapped to custom POJOs and ARRAY<JSON> columns are mapped to List of custom POJOs. Read, write and query with custom SQL query are supported for JSON annotated fields.

    +
    +
    + + + + + +
    + + +Spring Boot autoconfigures a Gson bean by default. +This Gson instance is used by default to convert to and from JSON representation. +To customize, use spring.gson.* configuration properties or GsonBuilderCustomizer bean as instructed in Spring Boot documentation here. +Alternatively, you can also provide a customized bean of type Gson in your application. +
    +
    +
    +
    +
    @Table(name = "traders")
    +public class Trader {
    +
    +    @PrimaryKey
    +    @Column(name = "trader_id")
    +    String traderId;
    +
    +    @Column(spannerType = TypeCode.JSON)
    +    Details details;
    +}
    +
    +public class Details {
    +    String name;
    +    String affiliation;
    +    Boolean isActive;
    +}
    +
    +
    +
    +
    +
    +

    13.2.9. Lists

    +
    +

    Spanner supports ARRAY types for columns. +ARRAY columns are mapped to List fields in POJOs.

    +
    +
    +

    Example:

    +
    +
    +
    +
    List<Double> curve;
    +
    +
    +
    +
    +

    The types inside the lists can be any singular property type.

    +
    +
    +
    +

    13.2.10. Lists of Structs

    +
    +

    Cloud Spanner queries can construct STRUCT values that appear as columns in the result. +Cloud Spanner requires STRUCT values appear in ARRAYs at the root level: SELECT ARRAY(SELECT STRUCT(1 as val1, 2 as val2)) as pair FROM Users.

    +
    +
    +

    Spring Data Cloud Spanner will attempt to read the column STRUCT values into a property that is an Iterable of an entity type compatible with the schema of the column STRUCT value.

    +
    +
    +

    For the previous array-select example, the following property can be mapped with the constructed ARRAY<STRUCT> column: List<TwoInts> pair; where the TwoInts type is defined:

    +
    +
    +
    +
    class TwoInts {
    +
    +  int val1;
    +
    +  int val2;
    +}
    +
    +
    +
    +
    +
    +

    13.2.11. Custom types

    +
    +

    Custom converters can be used to extend the type support for user defined types.

    +
    +
    +
      +
    1. +

      Converters need to implement the org.springframework.core.convert.converter.Converter interface in both directions.

      +
    2. +
    3. +

      The user defined type needs to be mapped to one of the basic types supported by Spanner:

      +
      +
        +
      • +

        com.google.cloud.ByteArray

        +
      • +
      • +

        com.google.cloud.Date

        +
      • +
      • +

        com.google.cloud.Timestamp

        +
      • +
      • +

        java.lang.Boolean, boolean

        +
      • +
      • +

        java.lang.Double, double

        +
      • +
      • +

        java.lang.Long, long

        +
      • +
      • +

        java.lang.String

        +
      • +
      • +

        double[]

        +
      • +
      • +

        long[]

        +
      • +
      • +

        boolean[]

        +
      • +
      • +

        enum types

        +
      • +
      +
      +
    4. +
    5. +

      An instance of both Converters needs to be passed to a ConverterAwareMappingSpannerEntityProcessor, which then has to be made available as a @Bean for SpannerEntityProcessor.

      +
    6. +
    +
    +
    +

    For example:

    +
    +
    +

    We would like to have a field of type Person on our Trade POJO:

    +
    +
    +
    +
    @Table(name = "trades")
    +public class Trade {
    +  //...
    +  Person person;
    +  //...
    +}
    +
    +
    +
    +
    +

    Where Person is a simple class:

    +
    +
    +
    +
    public class Person {
    +
    +  public String firstName;
    +  public String lastName;
    +
    +}
    +
    +
    +
    +
    +

    We have to define the two converters:

    +
    +
    +
    +
      public class PersonWriteConverter implements Converter<Person, String> {
    +
    +    @Override
    +    public String convert(Person person) {
    +      return person.firstName + " " + person.lastName;
    +    }
    +  }
    +
    +  public class PersonReadConverter implements Converter<String, Person> {
    +
    +    @Override
    +    public Person convert(String s) {
    +      Person person = new Person();
    +      person.firstName = s.split(" ")[0];
    +      person.lastName = s.split(" ")[1];
    +      return person;
    +    }
    +  }
    +
    +
    +
    +
    +

    That will be configured in our @Configuration file:

    +
    +
    +
    +
    @Configuration
    +public class ConverterConfiguration {
    +
    +    @Bean
    +    public SpannerEntityProcessor spannerEntityProcessor(SpannerMappingContext spannerMappingContext) {
    +        return new ConverterAwareMappingSpannerEntityProcessor(spannerMappingContext,
    +                Arrays.asList(new PersonWriteConverter()),
    +                Arrays.asList(new PersonReadConverter()));
    +    }
    +}
    +
    +
    +
    +
    +

    Note that ConverterAwareMappingSpannerEntityProcessor takes a list of Converters for write and read operations to support multiple user-defined types. +When there are duplicate Converters for a user-defined class in the list, it chooses the first matching item in the lists. +This means that, for a user-defined class U, write operations use the first Converter<U, …​> from the write Converters and read operations use the first Converter<…​, U> from the read Converters.

    +
    +
    +
    +

    13.2.12. Custom Converter for Struct Array Columns

    +
    +

    If a Converter<Struct, A> is provided, then properties of type List<A> can be used in your entity types.

    +
    +
    +
    +
    +

    13.3. Spanner Operations & Template

    +
    +

    SpannerOperations and its implementation, SpannerTemplate, provides the Template pattern familiar to Spring developers. +It provides:

    +
    +
    +
      +
    • +

      Resource management

      +
    • +
    • +

      One-stop-shop to Spanner operations with the Spring Data POJO mapping and conversion features

      +
    • +
    • +

      Exception conversion

      +
    • +
    +
    +
    +

    Using the autoconfigure provided by our Spring Boot Starter for Spanner, your Spring application context will contain a fully configured SpannerTemplate object that you can easily autowire in your application:

    +
    +
    +
    +
    @SpringBootApplication
    +public class SpannerTemplateExample {
    +
    +    @Autowired
    +    SpannerTemplate spannerTemplate;
    +
    +    public void doSomething() {
    +        this.spannerTemplate.delete(Trade.class, KeySet.all());
    +        //...
    +        Trade t = new Trade();
    +        //...
    +        this.spannerTemplate.insert(t);
    +        //...
    +        List<Trade> tradesByAction = spannerTemplate.findAll(Trade.class);
    +        //...
    +    }
    +}
    +
    +
    +
    +
    +

    The Template API provides convenience methods for:

    +
    +
    +
      +
    • +

      Reads, and by providing SpannerReadOptions and +SpannerQueryOptions

      +
      +
        +
      • +

        Stale read

        +
      • +
      • +

        Read with secondary indices

        +
      • +
      • +

        Read with limits and offsets

        +
      • +
      • +

        Read with sorting

        +
      • +
      +
      +
    • +
    • +

      Queries

      +
    • +
    • +

      DML operations (delete, insert, update, upsert)

      +
    • +
    • +

      Partial reads

      +
      +
        +
      • +

        You can define a set of columns to be read into your entity

        +
      • +
      +
      +
    • +
    • +

      Partial writes

      +
      +
        +
      • +

        Persist only a few properties from your entity

        +
      • +
      +
      +
    • +
    • +

      Read-only transactions

      +
    • +
    • +

      Locking read-write transactions

      +
    • +
    +
    +
    +

    13.3.1. SQL Query

    +
    +

    Cloud Spanner has SQL support for running read-only queries. +All the query related methods start with query on SpannerTemplate. +By using SpannerTemplate, you can run SQL queries that map to POJOs:

    +
    +
    +
    +
    List<Trade> trades = this.spannerTemplate.query(Trade.class, Statement.of("SELECT * FROM trades"));
    +
    +
    +
    +
    +
    +

    13.3.2. Read

    +
    +

    Spanner exposes a Read API for reading single row or multiple rows in a table or in a secondary index.

    +
    +
    +

    Using SpannerTemplate you can run reads, as the following example shows:

    +
    +
    +
    +
    List<Trade> trades = this.spannerTemplate.readAll(Trade.class);
    +
    +
    +
    +
    +

    Main benefit of reads over queries is reading multiple rows of a certain pattern of keys is much easier using the features of the KeySet class.

    +
    +
    +
    +

    13.3.3. Advanced reads

    +
    +
    Stale read
    +
    +

    All reads and queries are strong reads by default. +A strong read is a read at a current time and is guaranteed to see all data that has been committed up until the start of this read. +An exact staleness read is read at a timestamp in the past. +Cloud Spanner allows you to determine how current the data should be when you read data. +With SpannerTemplate you can specify the Timestamp by setting it on SpannerQueryOptions or SpannerReadOptions to the appropriate read or query methods:

    +
    +
    +

    Reads:

    +
    +
    +
    +
    // a read with options:
    +SpannerReadOptions spannerReadOptions = new SpannerReadOptions().setTimestamp(myTimestamp);
    +List<Trade> trades = this.spannerTemplate.readAll(Trade.class, spannerReadOptions);
    +
    +
    +
    +
    +

    Queries:

    +
    +
    +
    +
    // a query with options:
    +SpannerQueryOptions spannerQueryOptions = new SpannerQueryOptions().setTimestamp(myTimestamp);
    +List<Trade> trades = this.spannerTemplate.query(Trade.class, Statement.of("SELECT * FROM trades"), spannerQueryOptions);
    +
    +
    +
    +
    +

    You can also read with bounded staleness by setting .setTimestampBound(TimestampBound.ofMinReadTimestamp(myTimestamp)) on the query and read options objects. +Bounded staleness lets Cloud Spanner choose any point in time later than or equal to the given timestampBound, but it cannot be used inside transactions.

    +
    +
    +
    +
    Read from a secondary index
    +
    +

    Using a secondary index is available for Reads via the Template API and it is also implicitly available via SQL for Queries.

    +
    +
    +

    The following shows how to read rows from a table using a secondary index simply by setting index on SpannerReadOptions:

    +
    +
    +
    +
    SpannerReadOptions spannerReadOptions = new SpannerReadOptions().setIndex("TradesByTrader");
    +List<Trade> trades = this.spannerTemplate.readAll(Trade.class, spannerReadOptions);
    +
    +
    +
    +
    +
    +
    Read with offsets and limits
    +
    +

    Limits and offsets are only supported by Queries. +The following will get only the first two rows of the query:

    +
    +
    +
    +
    SpannerQueryOptions spannerQueryOptions = new SpannerQueryOptions().setLimit(2).setOffset(3);
    +List<Trade> trades = this.spannerTemplate.query(Trade.class, Statement.of("SELECT * FROM trades"), spannerQueryOptions);
    +
    +
    +
    +
    +

    Note that the above is equivalent of running SELECT * FROM trades LIMIT 2 OFFSET 3.

    +
    +
    +
    +
    Sorting
    +
    +

    Reads by keys do not support sorting. +However, queries on the Template API support sorting through standard SQL and also via Spring Data Sort API:

    +
    +
    +
    +
    List<Trade> trades = this.spannerTemplate.queryAll(Trade.class, Sort.by("action"));
    +
    +
    +
    +
    +

    If the provided sorted field name is that of a property of the domain type, then the column name corresponding to that property will be used in the query. +Otherwise, the given field name is assumed to be the name of the column in the Cloud Spanner table. +Sorting on columns of Cloud Spanner types STRING and BYTES can be done while ignoring case:

    +
    +
    +
    +
    Sort.by(Order.desc("action").ignoreCase())
    +
    +
    +
    +
    +
    +
    Partial read
    +
    +

    Partial read is only possible when using Queries. +In case the rows returned by the query have fewer columns than the entity that it will be mapped to, Spring Data will map the returned columns only. +This setting also applies to nested structs and their corresponding nested POJO properties.

    +
    +
    +
    +
    List<Trade> trades = this.spannerTemplate.query(Trade.class, Statement.of("SELECT action, symbol FROM trades"),
    +    new SpannerQueryOptions().setAllowMissingResultSetColumns(true));
    +
    +
    +
    +
    +

    If the setting is set to false, then an exception will be thrown if there are missing columns in the query result.

    +
    +
    +
    +
    Summary of options for Query vs Read
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Feature

    Query supports it

    Read supports it

    SQL

    yes

    no

    Partial read

    yes

    no

    Limits

    yes

    no

    Offsets

    yes

    no

    Secondary index

    yes

    yes

    Read using index range

    no

    yes

    Sorting

    yes

    no

    +
    +
    +
    +

    13.3.4. Write / Update

    +
    +

    The write methods of SpannerOperations accept a POJO and writes all of its properties to Spanner. +The corresponding Spanner table and entity metadata is obtained from the given object’s actual type.

    +
    +
    +

    If a POJO was retrieved from Spanner and its primary key properties values were changed and then written or updated, the operation will occur as if against a row with the new primary key values. +The row with the original primary key values will not be affected.

    +
    +
    +
    Insert
    +
    +

    The insert method of SpannerOperations accepts a POJO and writes all of its properties to Spanner, which means the operation will fail if a row with the POJO’s primary key already exists in the table.

    +
    +
    +
    +
    Trade t = new Trade();
    +this.spannerTemplate.insert(t);
    +
    +
    +
    +
    +
    +
    Update
    +
    +

    The update method of SpannerOperations accepts a POJO and writes all of its properties to Spanner, which means the operation will fail if the POJO’s primary key does not already exist in the table.

    +
    +
    +
    +
    // t was retrieved from a previous operation
    +this.spannerTemplate.update(t);
    +
    +
    +
    +
    +
    +
    Upsert
    +
    +

    The upsert method of SpannerOperations accepts a POJO and writes all of its properties to Spanner using update-or-insert.

    +
    +
    +
    +
    // t was retrieved from a previous operation or it's new
    +this.spannerTemplate.upsert(t);
    +
    +
    +
    +
    +
    +
    Partial Update
    +
    +

    The update methods of SpannerOperations operate by default on all properties within the given object, but also accept String[] and Optional<Set<String>> of column names. +If the Optional of set of column names is empty, then all columns are written to Spanner. +However, if the Optional is occupied by an empty set, then no columns will be written.

    +
    +
    +
    +
    // t was retrieved from a previous operation or it's new
    +this.spannerTemplate.update(t, "symbol", "action");
    +
    +
    +
    +
    +
    +
    +

    13.3.5. DML

    +
    +

    DML statements can be run by using SpannerOperations.executeDmlStatement. +Inserts, updates, and deletions can affect any number of rows and entities.

    +
    +
    +

    You can run partitioned DML updates by using the executePartitionedDmlStatement method. +Partitioned DML queries have performance benefits but also have restrictions and cannot be used inside transactions.

    +
    +
    +
    +

    13.3.6. Transactions

    +
    +

    SpannerOperations provides methods to run java.util.Function objects within a single transaction while making available the read and write methods from SpannerOperations.

    +
    +
    +
    Read/Write Transaction
    +
    +

    Read and write transactions are provided by SpannerOperations via the performReadWriteTransaction method:

    +
    +
    +
    +
    @Autowired
    +SpannerOperations mySpannerOperations;
    +
    +public String doWorkInsideTransaction() {
    +  return mySpannerOperations.performReadWriteTransaction(
    +    transActionSpannerOperations -> {
    +      // Work with transActionSpannerOperations here.
    +      // It is also a SpannerOperations object.
    +
    +      return "transaction completed";
    +    }
    +  );
    +}
    +
    +
    +
    +
    +

    The performReadWriteTransaction method accepts a Function that is provided an instance of a SpannerOperations object. +The final returned value and type of the function is determined by the user. +You can use this object just as you would a regular SpannerOperations with a few exceptions:

    +
    +
    +
      +
    • +

      Its read functionality cannot perform stale reads, because all reads and writes happen at the single point in time of the transaction.

      +
    • +
    • +

      It cannot perform sub-transactions via performReadWriteTransaction or performReadOnlyTransaction.

      +
    • +
    +
    +
    +

    As these read-write transactions are locking, it is recommended that you use the performReadOnlyTransaction if your function does not perform any writes.

    +
    +
    +
    +
    Read-only Transaction
    +
    +

    The performReadOnlyTransaction method is used to perform read-only transactions using a SpannerOperations:

    +
    +
    +
    +
    @Autowired
    +SpannerOperations mySpannerOperations;
    +
    +public String doWorkInsideTransaction() {
    +  return mySpannerOperations.performReadOnlyTransaction(
    +    transActionSpannerOperations -> {
    +      // Work with transActionSpannerOperations here.
    +      // It is also a SpannerOperations object.
    +
    +      return "transaction completed";
    +    }
    +  );
    +}
    +
    +
    +
    +
    +

    The performReadOnlyTransaction method accepts a Function that is provided an instance of a +SpannerOperations object. +This method also accepts a ReadOptions object, but the only attribute used is the timestamp used to determine the snapshot in time to perform the reads in the transaction. +If the timestamp is not set in the read options the transaction is run against the current state of the database. +The final returned value and type of the function is determined by the user. +You can use this object just as you would a regular SpannerOperations with +a few exceptions:

    +
    +
    +
      +
    • +

      Its read functionality cannot perform stale reads (other than the staleness set on the entire transaction), because all reads happen at the single point in time of the transaction.

      +
    • +
    • +

      It cannot perform sub-transactions via performReadWriteTransaction or performReadOnlyTransaction

      +
    • +
    • +

      It cannot perform any write operations.

      +
    • +
    +
    +
    +

    Because read-only transactions are non-locking and can be performed on points in time in the past, these are recommended for functions that do not perform write operations.

    +
    +
    +
    +
    Declarative Transactions with @Transactional Annotation
    +
    +

    This feature requires a bean of SpannerTransactionManager, which is provided when using spring-cloud-gcp-starter-data-spanner.

    +
    +
    +

    SpannerTemplate and SpannerRepository support running methods with the @Transactional annotation as transactions. +If a method annotated with @Transactional calls another method also annotated, then both methods will work within the same transaction. +performReadOnlyTransaction and performReadWriteTransaction cannot be used in @Transactional annotated methods because Cloud Spanner does not support transactions within transactions.

    +
    +
    +
    +
    +

    13.3.7. DML Statements

    +
    +

    SpannerTemplate supports DML Statements. +DML statements can also be run in transactions by using performReadWriteTransaction or by using the @Transactional annotation.

    +
    +
    +
    +
    +

    13.4. Repositories

    +
    +

    Spring Data Repositories are a powerful abstraction that can save you a lot of boilerplate code.

    +
    +
    +

    For example:

    +
    +
    +
    +
    public interface TraderRepository extends SpannerRepository<Trader, String> {
    +}
    +
    +
    +
    +
    +

    Spring Data generates a working implementation of the specified interface, which can be conveniently autowired into an application.

    +
    +
    +

    The Trader type parameter to SpannerRepository refers to the underlying domain type. +The second type parameter, String in this case, refers to the type of the key of the domain type.

    +
    +
    +

    For POJOs with a composite primary key, this ID type parameter can be any descendant of Object[] compatible with all primary key properties, any descendant of Iterable, or com.google.cloud.spanner.Key. +If the domain POJO type only has a single primary key column, then the primary key property type can be used or the Key type.

    +
    +
    +

    For example in case of Trades, that belong to a Trader, TradeRepository would look like this:

    +
    +
    +
    +
    public interface TradeRepository extends SpannerRepository<Trade, String[]> {
    +
    +}
    +
    +
    +
    +
    +
    +
    public class MyApplication {
    +
    +    @Autowired
    +    SpannerTemplate spannerTemplate;
    +
    +    @Autowired
    +    StudentRepository studentRepository;
    +
    +    public void demo() {
    +
    +        this.tradeRepository.deleteAll();
    +        String traderId = "demo_trader";
    +        Trade t = new Trade();
    +        t.symbol = stock;
    +        t.action = action;
    +        t.traderId = traderId;
    +        t.price = new BigDecimal("100.0");
    +        t.shares = 12345.6;
    +        this.spannerTemplate.insert(t);
    +
    +        Iterable<Trade> allTrades = this.tradeRepository.findAll();
    +
    +        int count = this.tradeRepository.countByAction("BUY");
    +
    +    }
    +}
    +
    +
    +
    +
    +

    13.4.1. CRUD Repository

    +
    +

    CrudRepository methods work as expected, with one thing Spanner specific: the save and saveAll methods work as update-or-insert.

    +
    +
    +
    +

    13.4.2. Paging and Sorting Repository

    +
    +

    You can also use PagingAndSortingRepository with Spanner Spring Data. +The sorting and pageable findAll methods available from this interface operate on the current state of the Spanner database. +As a result, beware that the state of the database (and the results) might change when moving page to page.

    +
    +
    +
    +

    13.4.3. Spanner Repository

    +
    +

    The SpannerRepository extends the PagingAndSortingRepository, but adds the read-only and the read-write transaction functionality provided by Spanner. +These transactions work very similarly to those of SpannerOperations, but is specific to the repository’s domain type and provides repository functions instead of template functions.

    +
    +
    +

    For example, this is a read-only transaction:

    +
    +
    +
    +
    @Autowired
    +SpannerRepository myRepo;
    +
    +public String doWorkInsideTransaction() {
    +  return myRepo.performReadOnlyTransaction(
    +    transactionSpannerRepo -> {
    +      // Work with the single-transaction transactionSpannerRepo here.
    +      // This is a SpannerRepository object.
    +
    +      return "transaction completed";
    +    }
    +  );
    +}
    +
    +
    +
    +
    +

    When creating custom repositories for your own domain types and query methods, you can extend SpannerRepository to access Cloud Spanner-specific features as well as all features from PagingAndSortingRepository and CrudRepository.

    +
    +
    +
    +
    +

    13.5. Query Methods

    +
    +

    SpannerRepository supports Query Methods. +Described in the following sections, these are methods residing in your custom repository interfaces of which implementations are generated based on their names and annotations. +Query Methods can read, write, and delete entities in Cloud Spanner. +Parameters to these methods can be any Cloud Spanner data type supported directly or via custom configured converters. +Parameters can also be of type Struct or POJOs. +If a POJO is given as a parameter, it will be converted to a Struct with the same type-conversion logic as used to create write mutations. +Comparisons using Struct parameters are limited to what is available with Cloud Spanner.

    +
    +
    +

    13.5.1. Query methods by convention

    +
    +
    +
    public interface TradeRepository extends SpannerRepository<Trade, String[]> {
    +    List<Trade> findByAction(String action);
    +
    +    int countByAction(String action);
    +
    +    // Named methods are powerful, but can get unwieldy
    +    List<Trade> findTop3DistinctByActionAndSymbolIgnoreCaseOrTraderIdOrderBySymbolDesc(
    +            String action, String symbol, String traderId);
    +}
    +
    +
    +
    +
    +

    In the example above, the query methods in TradeRepository are generated based on the name of the methods, using the Spring Data Query creation naming convention.

    +
    +
    +

    List<Trade> findByAction(String action) would translate to a SELECT * FROM trades WHERE action = ?.

    +
    +
    +

    The function List<Trade> findTop3DistinctByActionAndSymbolIgnoreCaseOrTraderIdOrderBySymbolDesc(String action, String symbol, String traderId); will be translated as the equivalent of this SQL query:

    +
    +
    +
    +
    SELECT DISTINCT * FROM trades
    +WHERE ACTION = ? AND LOWER(SYMBOL) = LOWER(?) AND TRADER_ID = ?
    +ORDER BY SYMBOL DESC
    +LIMIT 3
    +
    +
    +
    +

    The following filter options are supported:

    +
    +
    +
      +
    • +

      Equality

      +
    • +
    • +

      Greater than or equals

      +
    • +
    • +

      Greater than

      +
    • +
    • +

      Less than or equals

      +
    • +
    • +

      Less than

      +
    • +
    • +

      Is null

      +
    • +
    • +

      Is not null

      +
    • +
    • +

      Is true

      +
    • +
    • +

      Is false

      +
    • +
    • +

      Like a string

      +
    • +
    • +

      Not like a string

      +
    • +
    • +

      Contains a string

      +
    • +
    • +

      Not contains a string

      +
    • +
    • +

      In

      +
    • +
    • +

      Not in

      +
    • +
    +
    +
    +

    Note that the phrase SymbolIgnoreCase is translated to LOWER(SYMBOL) = LOWER(?) indicating a non-case-sensitive matching. +The IgnoreCase phrase may only be appended to fields that correspond to columns of type STRING or BYTES. +The Spring Data "AllIgnoreCase" phrase appended at the end of the method name is not supported.

    +
    +
    +

    The Like or NotLike naming conventions:

    +
    +
    +
    +
    List<Trade> findBySymbolLike(String symbolFragment);
    +
    +
    +
    +
    +

    The param symbolFragment can contain wildcard characters for string matching such as _ and %.

    +
    +
    +

    The Contains and NotContains naming conventions:

    +
    +
    +
    +
    List<Trade> findBySymbolContains(String symbolFragment);
    +
    +
    +
    +
    +

    The param symbolFragment is a regular expression that is checked for occurrences.

    +
    +
    +

    The In and NotIn keywords must be used with Iterable corresponding parameters.

    +
    +
    +

    Delete queries are also supported. +For example, query methods such as deleteByAction or removeByAction delete entities found by findByAction. +The delete operation happens in a single transaction.

    +
    +
    +

    Delete queries can have the following return types: +* An integer type that is the number of entities deleted +* A collection of entities that were deleted +* void

    +
    +
    +
    +

    13.5.2. Custom SQL/DML query methods

    +
    +

    The example above for List<Trade> fetchByActionNamedQuery(String action) does not match the Spring Data Query creation naming convention, so we have to map a parametrized Spanner SQL query to it.

    +
    +
    +

    The SQL query for the method can be mapped to repository methods in one of two ways:

    +
    +
    +
      +
    • +

      namedQueries properties file

      +
    • +
    • +

      using the @Query annotation

      +
    • +
    +
    +
    +

    The names of the tags of the SQL correspond to the @Param annotated names of the method parameters.

    +
    +
    +

    Interleaved properties are loaded eagerly, unless they are annotated with @Interleaved(lazy = true).

    +
    +
    +

    Custom SQL query methods can accept a single Sort or Pageable parameter that is applied on top of the specified custom query. +It is the recommended way to control the sort order of the results, which is not guaranteed by the ORDER BY clause in the SQL query. +This is due to the fact that the user-provided query is used as a sub-query, and Cloud Spanner doesn’t preserve order in subquery results.

    +
    +
    +

    You might want to use ORDER BY with LIMIT to obtain the top records, according to a specified order. +However, to ensure the correct sort order of the final result set, sort options have to be passed in with a Pageable.

    +
    +
    +
    +
        @Query("SELECT * FROM trades")
    +    List<Trade> fetchTrades(Pageable pageable);
    +
    +    @Query("SELECT * FROM trades ORDER BY price DESC LIMIT 1")
    +    Trade topTrade(Pageable pageable);
    +
    +
    +
    +
    +

    This can be used:

    +
    +
    +
    +
        List<Trade> customSortedTrades = tradeRepository.fetchTrades(PageRequest
    +                .of(2, 2, org.springframework.data.domain.Sort.by(Order.asc("id"))));
    +
    +
    +
    +
    +

    The results would be sorted by "id" in ascending order.

    +
    +
    +

    Your query method can also return non-entity types:

    +
    +
    +
    +
        @Query("SELECT COUNT(1) FROM trades WHERE action = @action")
    +    int countByActionQuery(String action);
    +
    +    @Query("SELECT EXISTS(SELECT COUNT(1) FROM trades WHERE action = @action)")
    +    boolean existsByActionQuery(String action);
    +
    +    @Query("SELECT action FROM trades WHERE action = @action LIMIT 1")
    +    String getFirstString(@Param("action") String action);
    +
    +    @Query("SELECT action FROM trades WHERE action = @action")
    +    List<String> getFirstStringList(@Param("action") String action);
    +
    +
    +
    +
    +

    DML statements can also be run by query methods, but the only possible return value is a long representing the number of affected rows. +The dmlStatement boolean setting must be set on @Query to indicate that the query method is run as a DML statement.

    +
    +
    +
    +
        @Query(value = "DELETE FROM trades WHERE action = @action", dmlStatement = true)
    +    long deleteByActionQuery(String action);
    +
    +
    +
    +
    +
    Query methods with named queries properties
    +
    +

    By default, the namedQueriesLocation attribute on @EnableSpannerRepositories points to the META-INF/spanner-named-queries.properties file. +You can specify the query for a method in the properties file by providing the SQL as the value for the "interface.method" property:

    +
    +
    +
    +
    Trade.fetchByActionNamedQuery=SELECT * FROM trades WHERE trades.action = @tag0
    +
    +
    +
    +
    +
    public interface TradeRepository extends SpannerRepository<Trade, String[]> {
    +    // This method uses the query from the properties file instead of one generated based on name.
    +    List<Trade> fetchByActionNamedQuery(@Param("tag0") String action);
    +}
    +
    +
    +
    +
    +
    +
    Query methods with annotation
    +
    +

    Using the @Query annotation:

    +
    +
    +
    +
    public interface TradeRepository extends SpannerRepository<Trade, String[]> {
    +    @Query("SELECT * FROM trades WHERE trades.action = @tag0")
    +    List<Trade> fetchByActionNamedQuery(@Param("tag0") String action);
    +}
    +
    +
    +
    +
    +

    Table names can be used directly. +For example, "trades" in the above example. +Alternatively, table names can be resolved from the @Table annotation on domain classes as well. +In this case, the query should refer to table names with fully qualified class names between : +characters: :fully.qualified.ClassName:. +A full example would look like:

    +
    +
    +
    +
    @Query("SELECT * FROM :com.example.Trade: WHERE trades.action = @tag0")
    +List<Trade> fetchByActionNamedQuery(String action);
    +
    +
    +
    +
    +

    This allows table names evaluated with SpEL to be used in custom queries.

    +
    +
    +

    SpEL can also be used to provide SQL parameters:

    +
    +
    +
    +
    @Query("SELECT * FROM :com.example.Trade: WHERE trades.action = @tag0
    +  AND price > #{#priceRadius * -1} AND price < #{#priceRadius * 2}")
    +List<Trade> fetchByActionNamedQuery(String action, Double priceRadius);
    +
    +
    +
    +
    +

    When using the IN SQL clause, remember to use IN UNNEST(@iterableParam) to specify a single Iterable parameter. +You can also use a fixed number of singular parameters such as IN (@stringParam1, @stringParam2).

    +
    +
    +
    +
    +

    13.5.3. Projections

    +
    +

    Spring Data Spanner supports projections. +You can define projection interfaces based on domain types and add query methods that return them in your repository:

    +
    +
    +
    +
    public interface TradeProjection {
    +
    +    String getAction();
    +
    +    @Value("#{target.symbol + ' ' + target.action}")
    +    String getSymbolAndAction();
    +}
    +
    +public interface TradeRepository extends SpannerRepository<Trade, Key> {
    +
    +    List<Trade> findByTraderId(String traderId);
    +
    +    List<TradeProjection> findByAction(String action);
    +
    +    @Query("SELECT action, symbol FROM trades WHERE action = @action")
    +    List<TradeProjection> findByQuery(String action);
    +}
    +
    +
    +
    +
    +

    Projections can be provided by name-convention-based query methods as well as by custom SQL queries. +If using custom SQL queries, you can further restrict the columns retrieved from Spanner to just those required by the projection to improve performance.

    +
    +
    +

    Properties of projection types defined using SpEL use the fixed name target for the underlying domain object. +As a result accessing underlying properties take the form target.<property-name>.

    +
    +
    +
    +

    13.5.4. Empty result handling in repository methods

    +
    +

    Java java.util.Optional can be used to indicate the potential absence of a return value.

    +
    +
    +

    Alternatively, query methods can return the result without a wrapper. +In that case the absence of a query result is indicated by returning null. +Repository methods returning collections are guaranteed never to return null but rather the corresponding empty collection.

    +
    +
    + + + + + +
    + + +You can enable nullability checks. For more details please see Spring Framework’s nullability docs. +
    +
    +
    +
    +

    13.5.5. REST Repositories

    +
    +

    When running with Spring Boot, repositories can be exposed as REST services by simply adding this dependency to your pom file:

    +
    +
    +
    +
    <dependency>
    +  <groupId>org.springframework.boot</groupId>
    +  <artifactId>spring-boot-starter-data-rest</artifactId>
    +</dependency>
    +
    +
    +
    +

    If you prefer to configure parameters (such as path), you can use @RepositoryRestResource annotation:

    +
    +
    +
    +
    @RepositoryRestResource(collectionResourceRel = "trades", path = "trades")
    +public interface TradeRepository extends SpannerRepository<Trade, Key> {
    +}
    +
    +
    +
    +
    + + + + + +
    + + +For classes that have composite keys (multiple @PrimaryKey fields), only the Key type is supported for the repository ID type. +
    +
    +
    +

    For example, you can retrieve all Trade objects in the repository by using curl http://<server>:<port>/trades, or any specific trade via curl http://<server>:<port>/trades/<trader_id>,<trade_id>.

    +
    +
    +

    The separator between your primary key components, id and trader_id in this case, is a comma by default, but can be configured to any string not found in your key values by extending the SpannerKeyIdConverter class:

    +
    +
    +
    +
    @Component
    +class MySpecialIdConverter extends SpannerKeyIdConverter {
    +
    +    @Override
    +    protected String getUrlIdSeparator() {
    +        return ":";
    +    }
    +}
    +
    +
    +
    +
    +

    You can also write trades using curl -XPOST -H"Content-Type: application/json" -d@test.json http://<server>:<port>/trades/ where the file test.json holds the JSON representation of a Trade object.

    +
    +
    +
    +
    +

    13.6. Database and Schema Admin

    +
    +

    Databases and tables inside Spanner instances can be created automatically from SpannerPersistentEntity objects:

    +
    +
    +
    +
    @Autowired
    +private SpannerSchemaUtils spannerSchemaUtils;
    +
    +@Autowired
    +private SpannerDatabaseAdminTemplate spannerDatabaseAdminTemplate;
    +
    +public void createTable(SpannerPersistentEntity entity) {
    +    if(!spannerDatabaseAdminTemplate.tableExists(entity.tableName()){
    +
    +      // The boolean parameter indicates that the database will be created if it does not exist.
    +      spannerDatabaseAdminTemplate.executeDdlStrings(Arrays.asList(
    +            spannerSchemaUtils.getCreateTableDDLString(entity.getType())), true);
    +    }
    +}
    +
    +
    +
    +
    +

    Schemas can be generated for entire object hierarchies with interleaved relationships and composite keys.

    +
    +
    +
    +

    13.7. Events

    +
    +

    Spring Data Cloud Spanner publishes events extending the Spring Framework’s ApplicationEvent to the context that can be received by ApplicationListener beans you register.

    +
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeDescriptionContents

    AfterReadEvent

    Published immediately after entities are read by key from Cloud Spanner by SpannerTemplate

    The entities loaded. The read options and key-set originally specified for the load operation.

    AfterQueryEvent

    Published immediately after entities are read by query from Cloud Spanner by SpannerTemplate

    The entities loaded. The query options and query statement originally specified for the load operation.

    BeforeExecuteDmlEvent

    Published immediately before DML statements are executed by SpannerTemplate

    The DML statement to execute.

    AfterExecuteDmlEvent

    Published immediately after DML statements are executed by SpannerTemplate

    The DML statement to execute and the number of rows affected by the operation as reported by Cloud Spanner.

    BeforeSaveEvent

    Published immediately before upsert/update/insert operations are executed by SpannerTemplate

    The mutations to be sent to Cloud Spanner, the entities to be saved, and optionally the properties in those entities to save.

    AfterSaveEvent

    Published immediately after upsert/update/insert operations are executed by SpannerTemplate

    The mutations sent to Cloud Spanner, the entities to be saved, and optionally the properties in those entities to save.

    BeforeDeleteEvent

    Published immediately before delete operations are executed by SpannerTemplate

    The mutations to be sent to Cloud Spanner. The target entities, keys, or entity type originally specified for the delete operation.

    AfterDeleteEvent

    Published immediately after delete operations are executed by SpannerTemplate

    The mutations sent to Cloud Spanner. The target entities, keys, or entity type originally specified for the delete operation.

    +
    +
    +

    13.8. Auditing

    +
    +

    Spring Data Cloud Spanner supports the @LastModifiedDate and @LastModifiedBy auditing annotations for properties:

    +
    +
    +
    +
    @Table
    +public class SimpleEntity {
    +    @PrimaryKey
    +    String id;
    +
    +    @LastModifiedBy
    +    String lastUser;
    +
    +    @LastModifiedDate
    +    DateTime lastTouched;
    +}
    +
    +
    +
    +
    +

    Upon insert, update, or save, these properties will be set automatically by the framework before mutations are generated and saved to Cloud Spanner.

    +
    +
    +

    To take advantage of these features, add the @EnableSpannerAuditing annotation to your configuration class and provide a bean for an AuditorAware<A> implementation where the type A is the desired property type annotated by @LastModifiedBy:

    +
    +
    +
    +
    @Configuration
    +@EnableSpannerAuditing
    +public class Config {
    +
    +    @Bean
    +    public AuditorAware<String> auditorProvider() {
    +        return () -> Optional.of("YOUR_USERNAME_HERE");
    +    }
    +}
    +
    +
    +
    +
    +

    The AuditorAware interface contains a single method that supplies the value for fields annotated by @LastModifiedBy and can be of any type. +One alternative is to use Spring Security’s User type:

    +
    +
    +
    +
    class SpringSecurityAuditorAware implements AuditorAware<User> {
    +
    +  public Optional<User> getCurrentAuditor() {
    +
    +    return Optional.ofNullable(SecurityContextHolder.getContext())
    +              .map(SecurityContext::getAuthentication)
    +              .filter(Authentication::isAuthenticated)
    +              .map(Authentication::getPrincipal)
    +              .map(User.class::cast);
    +  }
    +}
    +
    +
    +
    +
    +

    You can also set a custom provider for properties annotated @LastModifiedDate by providing a bean for DateTimeProvider and providing the bean name to @EnableSpannerAuditing(dateTimeProviderRef = "customDateTimeProviderBean").

    +
    +
    +
    +

    13.9. Multi-Instance Usage

    +
    +

    Your application can be configured to use multiple Cloud Spanner instances or databases by providing a custom bean for DatabaseIdProvider. +The default bean uses the instance ID, database name, and project ID options you configured in application.properties.

    +
    +
    +
    +
        @Bean
    +    public DatabaseIdProvider databaseIdProvider() {
    +        // return custom connection options provider
    +    }
    +
    +
    +
    +
    +

    The DatabaseId given by this provider is used as the target database name and instance of each operation Spring Data Cloud Spanner executes. +By providing a custom implementation of this bean (for example, supplying a thread-local DatabaseId), you can direct your application to use multiple instances or databases.

    +
    +
    +

    Database administrative operations, such as creating tables using SpannerDatabaseAdminTemplate, will also utilize the provided DatabaseId.

    +
    +
    +

    If you would like to configure every aspect of each connection (such as pool size and retry settings), you can supply a bean for Supplier<DatabaseClient>.

    +
    +
    +
    +

    13.10. Spring Boot Actuator Support

    +
    +

    13.10.1. Cloud Spanner Health Indicator

    +
    +

    If you are using Spring Boot Actuator, you can take advantage of the Cloud Spanner health indicator called spanner. +The health indicator will verify whether Cloud Spanner is up and accessible by your application. +To enable it, all you need to do is add the Spring Boot Actuator to your project.

    +
    +
    +

    The spanner indicator will then roll up to the overall application status visible at localhost:8080/actuator/health (use the management.endpoint.health.show-details property to view per-indicator details).

    +
    +
    +
    +
    <dependency>
    +    <groupId>org.springframework.boot</groupId>
    +    <artifactId>spring-boot-starter-actuator</artifactId>
    +</dependency>
    +
    +
    +
    + + + + + +
    + + +If your application already has actuator and Cloud Spanner starters, this health indicator is enabled by default. +To disable the Cloud Spanner indicator, set management.health.spanner.enabled to false. +
    +
    +
    +

    The health indicator validates the connection to Spanner by executing a query. +A query to validate can be configured via spring.cloud.gcp.spanner.health.query property.

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    management.health.spanner.enabled

    Whether to enable the Spanner health indicator

    No

    true with Spring Boot Actuator, false otherwise

    spring.cloud.gcp.spanner.health.query

    A query to validate

    No

    SELECT 1

    +
    +
    +
    +

    13.11. Cloud Spanner Emulator

    +
    +

    The Cloud SDK provides a local, in-memory emulator for Cloud Spanner, which you can use to develop and test your application. As the emulator stores data only in memory, it will not persist data across runs. It is intended to help you use Cloud Spanner for local development and testing, not for production deployments.

    +
    +
    +

    In order to set up and start the emulator, you can follow these steps.

    +
    +
    +

    This command can be used to create Cloud Spanner instances:

    +
    +
    +
    +
    $ gcloud spanner instances create <instance-name> --config=emulator-config --description="<description>" --nodes=1
    +
    +
    +
    +

    Once the Spanner emulator is running, ensure that the following properties are set in your application.properties of your Spring application:

    +
    +
    +
    +
    spring.cloud.gcp.spanner.emulator.enabled=true
    +
    +
    +
    +

    Note that the default emulator hostname and port (i.e., localhost:9010) is used. If you prefer a customized value, ensure the following property is set in your application.properties of your Spring application:

    +
    +
    +
    +
    spring.cloud.gcp.spanner.emulator-host=ip:port
    +
    +
    +
    + +
    +

    13.13. Test

    +
    +

    Testcontainers provides a gcloud module which offers SpannerEmulatorContainer. See more at the docs

    +
    +
    +
    +
    +
    +

    14. Cloud Spanner Spring Data R2DBC

    +
    +
    +

    The Spring Data R2DBC Dialect for Cloud Spanner enables the usage of Spring Data R2DBC with Cloud Spanner.

    +
    +
    +

    The goal of the Spring Data project is to create easy and consistent ways of using data access technologies from Spring Framework applications.

    +
    +
    +

    14.1. Setup

    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
        <dependency>
    +        <groupId>com.google.cloud</groupId>
    +        <artifactId>spring-cloud-spanner-spring-data-r2dbc</artifactId>
    +    </dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-spanner-spring-data-r2dbc")
    +}
    +
    +
    +
    +
    +

    14.2. Overview

    +
    +

    Spring Data R2DBC allows you to use the convenient features of Spring Data in a reactive application. +These features include:

    +
    +
    +
      +
    • +

      Spring configuration support using Java based @Configuration classes.

      +
    • +
    • +

      Annotation based mapping metadata.

      +
    • +
    • +

      Automatic implementation of Repository interfaces.

      +
    • +
    • +

      Support for Reactive Transactions.

      +
    • +
    • +

      Schema and data initialization utilities.

      +
    • +
    +
    +
    +

    See the Spring Data R2DBC documentation for more information on how to use Spring Data R2DBC.

    +
    +
    +
    +

    14.3. Sample Application

    +
    +

    We provide a sample application which demonstrates using the Spring Data R2DBC framework with Cloud Spanner in Spring WebFlux.

    +
    +
    +
    +
    +
    +

    15. Spring Data Cloud Datastore

    +
    +
    + + + + + +
    + + +This integration is fully compatible with Firestore in Datastore Mode, but not with Firestore in Native Mode. +
    +
    +
    +

    Spring Data is an abstraction for storing and retrieving POJOs in numerous storage technologies. +Spring Framework on Google Cloud adds Spring Data support for Google Cloud Firestore in Datastore mode.

    +
    +
    +

    Maven coordinates for this module only, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-data-datastore</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-data-datastore")
    +}
    +
    +
    +
    +

    We provide a Spring Boot Starter for Spring Data Datastore, with which you can use our recommended auto-configuration setup. +To use the starter, see the coordinates below.

    +
    +
    +

    Maven:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-data-datastore</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-data-datastore")
    +}
    +
    +
    +
    +

    This setup takes care of bringing in the latest compatible version of Cloud Java Cloud Datastore libraries as well.

    +
    +
    +

    15.1. Configuration

    +
    +

    To setup Spring Data Cloud Datastore, you have to configure the following:

    +
    +
    +
      +
    • +

      Setup the connection details to Google Cloud Datastore.

      +
    • +
    +
    +
    +

    15.1.1. Cloud Datastore settings

    +
    +

    You can use the Spring Boot Starter for Spring Data Datastore to autoconfigure Google Cloud Datastore in your Spring application. +It contains all the necessary setup that makes it easy to authenticate with your Google Cloud project. +The following configuration options are available:

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.datastore.enabled

    Enables the Cloud Datastore client

    No

    true

    spring.cloud.gcp.datastore.project-id

    Google Cloud project ID where the Google Cloud Datastore API is hosted, if different from the one in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.datastore.database-id

    Google Cloud project can host multiple databases. You can specify which database will be used. If not specified, the database id will be "(default)".

    No

    spring.cloud.gcp.datastore.credentials.location

    OAuth2 credentials for authenticating with the Google Cloud Datastore API, if different from the ones in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.datastore.credentials.encoded-key

    Base64-encoded OAuth2 credentials for authenticating with the Google Cloud Datastore API, if different from the ones in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.datastore.credentials.scopes

    OAuth2 scope for Spring Framework on Google CloudDatastore credentials

    No

    www.googleapis.com/auth/datastore

    spring.cloud.gcp.datastore.namespace

    The Cloud Datastore namespace to use

    No

    the Default namespace of Cloud Datastore in your Google Cloud project

    spring.cloud.gcp.datastore.host

    The hostname:port of the datastore service or emulator to connect to. Can be used to connect to a manually started Datastore Emulator. If the autoconfigured emulator is enabled, this property will be ignored and localhost:<emulator_port> will be used.

    No

    spring.cloud.gcp.datastore.emulator.enabled

    To enable the auto configuration to start a local instance of the Datastore Emulator.

    No

    false

    spring.cloud.gcp.datastore.emulator.port

    The local port to use for the Datastore Emulator

    No

    8081

    spring.cloud.gcp.datastore.emulator.consistency

    The consistency to use for the Datastore Emulator instance

    No

    0.9

    spring.cloud.gcp.datastore.emulator.store-on-disk

    Configures whether or not the emulator should persist any data to disk.

    No

    true

    spring.cloud.gcp.datastore.emulator.data-dir

    The directory to be used to store/retrieve data/config for an emulator run.

    No

    The default value is <USER_CONFIG_DIR>/emulators/datastore. See the gcloud documentation for finding your USER_CONFIG_DIR.

    +
    +
    +

    15.1.2. Repository settings

    +
    +

    Spring Data Repositories can be configured via the @EnableDatastoreRepositories annotation on your main @Configuration class. +With our Spring Boot Starter for Spring Data Cloud Datastore, @EnableDatastoreRepositories is automatically added. +It is not required to add it to any other class, unless there is a need to override finer grain configuration parameters provided by @EnableDatastoreRepositories.

    +
    +
    +
    +

    15.1.3. Autoconfiguration

    +
    +

    Our Spring Boot autoconfiguration creates the following beans available in the Spring application context:

    +
    +
    +
      +
    • +

      an instance of DatastoreTemplate

      +
    • +
    • +

      an instance of all user defined repositories extending CrudRepository, PagingAndSortingRepository, and DatastoreRepository (an extension of PagingAndSortingRepository with additional Cloud Datastore features) when repositories are enabled

      +
    • +
    • +

      an instance of Datastore from the Google Cloud Java Client for Datastore, for convenience and lower level API access

      +
    • +
    +
    +
    +
    +

    15.1.4. Datastore Emulator Autoconfiguration

    +
    +

    This Spring Boot autoconfiguration can also configure and start a local Datastore Emulator server if enabled by property.

    +
    +
    +

    It is useful for integration testing, but not for production.

    +
    +
    +

    When enabled, the spring.cloud.gcp.datastore.host property will be ignored and the Datastore autoconfiguration itself will be forced to connect to the autoconfigured local emulator instance.

    +
    +
    +

    It will create an instance of LocalDatastoreHelper as a bean that stores the DatastoreOptions to get the Datastore client connection to the emulator for convenience and lower level API for local access. +The emulator will be properly stopped after the Spring application context shutdown.

    +
    +
    +
    +
    +

    15.2. Object Mapping

    +
    +

    Spring Data Cloud Datastore allows you to map domain POJOs to Cloud Datastore kinds and entities via annotations:

    +
    +
    +
    +
    @Entity(name = "traders")
    +public class Trader {
    +
    +    @Id
    +    @Field(name = "trader_id")
    +    String traderId;
    +
    +    String firstName;
    +
    +    String lastName;
    +
    +    @Transient
    +    Double temporaryNumber;
    +}
    +
    +
    +
    +
    +

    Spring Data Cloud Datastore will ignore any property annotated with @Transient. +These properties will not be written to or read from Cloud Datastore.

    +
    +
    +

    15.2.1. Constructors

    +
    +

    Simple constructors are supported on POJOs. +The constructor arguments can be a subset of the persistent properties. +Every constructor argument needs to have the same name and type as a persistent property on the entity and the constructor should set the property from the given argument. +Arguments that are not directly set to properties are not supported.

    +
    +
    +
    +
    @Entity(name = "traders")
    +public class Trader {
    +
    +    @Id
    +    @Field(name = "trader_id")
    +    String traderId;
    +
    +    String firstName;
    +
    +    String lastName;
    +
    +    @Transient
    +    Double temporaryNumber;
    +
    +    public Trader(String traderId, String firstName) {
    +        this.traderId = traderId;
    +        this.firstName = firstName;
    +    }
    +}
    +
    +
    +
    +
    +
    +

    15.2.2. Kind

    +
    +

    The @Entity annotation can provide the name of the Cloud Datastore kind that stores instances of the annotated class, one per row.

    +
    +
    +
    +

    15.2.3. Keys

    +
    +

    @Id identifies the property corresponding to the ID value.

    +
    +
    +

    You must annotate one of your POJO’s fields as the ID value, because every entity in Cloud Datastore requires a single ID value:

    +
    +
    +
    +
    @Entity(name = "trades")
    +public class Trade {
    +    @Id
    +    @Field(name = "trade_id")
    +    String tradeId;
    +
    +    @Field(name = "trader_id")
    +    String traderId;
    +
    +    String action;
    +
    +    Double price;
    +
    +    Double shares;
    +
    +    String symbol;
    +}
    +
    +
    +
    +
    +

    Datastore can automatically allocate integer ID values. +If a POJO instance with a Long ID property is written to Cloud Datastore with null as the ID value, then Spring Data Cloud Datastore will obtain a newly allocated ID value from Cloud Datastore and set that in the POJO for saving. +Because primitive long ID properties cannot be null and default to 0, keys will not be allocated.

    +
    +
    +
    +

    15.2.4. Fields

    +
    +

    All accessible properties on POJOs are automatically recognized as a Cloud Datastore field. +Field naming is generated by the PropertyNameFieldNamingStrategy by default defined on the DatastoreMappingContext bean. +The @Field annotation optionally provides a different field name than that of the property.

    +
    +
    +
    +

    15.2.5. Supported Types

    +
    +

    Spring Data Cloud Datastore supports the following types for regular fields and elements of collections:

    +
    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeStored as

    com.google.cloud.Timestamp

    com.google.cloud.datastore.TimestampValue

    com.google.cloud.datastore.Blob

    com.google.cloud.datastore.BlobValue

    com.google.cloud.datastore.LatLng

    com.google.cloud.datastore.LatLngValue

    java.lang.Boolean, boolean

    com.google.cloud.datastore.BooleanValue

    java.lang.Double, double

    com.google.cloud.datastore.DoubleValue

    java.lang.Long, long

    com.google.cloud.datastore.LongValue

    java.lang.Integer, int

    com.google.cloud.datastore.LongValue

    java.lang.String

    com.google.cloud.datastore.StringValue

    com.google.cloud.datastore.Entity

    com.google.cloud.datastore.EntityValue

    com.google.cloud.datastore.Key

    com.google.cloud.datastore.KeyValue

    byte[]

    com.google.cloud.datastore.BlobValue

    Java enum values

    com.google.cloud.datastore.StringValue

    +
    +

    In addition, all types that can be converted to the ones listed in the table by +org.springframework.core.convert.support.DefaultConversionService are supported.

    +
    +
    +
    +

    15.2.6. Custom types

    +
    +

    Custom converters can be used extending the type support for user defined types.

    +
    +
    +
      +
    1. +

      Converters need to implement the org.springframework.core.convert.converter.Converter interface in both directions.

      +
    2. +
    3. +

      The user defined type needs to be mapped to one of the basic types supported by Cloud Datastore.

      +
    4. +
    5. +

      An instance of both Converters (read and write) needs to be passed to the DatastoreCustomConversions constructor, which then has to be made available as a @Bean for DatastoreCustomConversions.

      +
    6. +
    +
    +
    +

    For example:

    +
    +
    +

    We would like to have a field of type Album on our Singer POJO and want it to be stored as a string property:

    +
    +
    +
    +
    @Entity
    +public class Singer {
    +
    +    @Id
    +    String singerId;
    +
    +    String name;
    +
    +    Album album;
    +}
    +
    +
    +
    +
    +

    Where Album is a simple class:

    +
    +
    +
    +
    public class Album {
    +    String albumName;
    +
    +    LocalDate date;
    +}
    +
    +
    +
    +
    +

    We have to define the two converters:

    +
    +
    +
    +
        // Converter to write custom Album type
    +    static final Converter<Album, String> ALBUM_STRING_CONVERTER =
    +            new Converter<Album, String>() {
    +                @Override
    +                public String convert(Album album) {
    +                    return album.getAlbumName() + " " + album.getDate().format(DateTimeFormatter.ISO_DATE);
    +                }
    +            };
    +
    +    // Converters to read custom Album type
    +    static final Converter<String, Album> STRING_ALBUM_CONVERTER =
    +            new Converter<String, Album>() {
    +                @Override
    +                public Album convert(String s) {
    +                    String[] parts = s.split(" ");
    +                    return new Album(parts[0], LocalDate.parse(parts[parts.length - 1], DateTimeFormatter.ISO_DATE));
    +                }
    +            };
    +
    +
    +
    +
    +

    That will be configured in our @Configuration file:

    +
    +
    +
    +
    @Configuration
    +public class ConverterConfiguration {
    +    @Bean
    +    public DatastoreCustomConversions datastoreCustomConversions() {
    +        return new DatastoreCustomConversions(
    +                Arrays.asList(
    +                        ALBUM_STRING_CONVERTER,
    +                        STRING_ALBUM_CONVERTER));
    +    }
    +}
    +
    +
    +
    +
    +
    +

    15.2.7. Collections and arrays

    +
    +

    Arrays and collections (types that implement java.util.Collection) of supported types are supported. +They are stored as com.google.cloud.datastore.ListValue. +Elements are converted to Cloud Datastore supported types individually. byte[] is an exception, it is converted to +com.google.cloud.datastore.Blob.

    +
    +
    +
    +

    15.2.8. Custom Converter for collections

    +
    +

    Users can provide converters from List<?> to the custom collection type. +Only read converter is necessary, the Collection API is used on the write side to convert a collection to the internal list type.

    +
    +
    +

    Collection converters need to implement the org.springframework.core.convert.converter.Converter interface.

    +
    +
    +

    Example:

    +
    +
    +

    Let’s improve the Singer class from the previous example. +Instead of a field of type Album, we would like to have a field of type Set<Album>:

    +
    +
    +
    +
    @Entity
    +public class Singer {
    +
    +    @Id
    +    String singerId;
    +
    +    String name;
    +
    +    Set<Album> albums;
    +}
    +
    +
    +
    +
    +

    We have to define a read converter only:

    +
    +
    +
    +
    static final Converter<List<?>, Set<?>> LIST_SET_CONVERTER =
    +            new Converter<List<?>, Set<?>>() {
    +                @Override
    +                public Set<?> convert(List<?> source) {
    +                    return Collections.unmodifiableSet(new HashSet<>(source));
    +                }
    +            };
    +
    +
    +
    +
    +

    And add it to the list of custom converters:

    +
    +
    +
    +
    @Configuration
    +public class ConverterConfiguration {
    +    @Bean
    +    public DatastoreCustomConversions datastoreCustomConversions() {
    +        return new DatastoreCustomConversions(
    +                Arrays.asList(
    +                        LIST_SET_CONVERTER,
    +                        ALBUM_STRING_CONVERTER,
    +                        STRING_ALBUM_CONVERTER));
    +    }
    +}
    +
    +
    +
    +
    +
    +

    15.2.9. Inheritance Hierarchies

    +
    +

    Java entity types related by inheritance can be stored in the same Kind. +When reading and querying entities using DatastoreRepository or DatastoreTemplate with a superclass as the type parameter, you can receive instances of subclasses if you annotate the superclass and its subclasses with DiscriminatorField and DiscriminatorValue:

    +
    +
    +
    +
    @Entity(name = "pets")
    +@DiscriminatorField(field = "pet_type")
    +abstract class Pet {
    +    @Id
    +    Long id;
    +
    +    abstract String speak();
    +}
    +
    +@DiscriminatorValue("cat")
    +class Cat extends Pet {
    +    @Override
    +    String speak() {
    +        return "meow";
    +    }
    +}
    +
    +@DiscriminatorValue("dog")
    +class Dog extends Pet {
    +    @Override
    +    String speak() {
    +        return "woof";
    +    }
    +}
    +
    +@DiscriminatorValue("pug")
    +class Pug extends Dog {
    +    @Override
    +    String speak() {
    +        return "woof woof";
    +    }
    +}
    +
    +
    +
    +
    +

    Instances of all 3 types are stored in the pets Kind. +Because a single Kind is used, all classes in the hierarchy must share the same ID property and no two instances of any type in the hierarchy can share the same ID value.

    +
    +
    +

    Entity rows in Cloud Datastore store their respective types' DiscriminatorValue in a field specified by the root superclass’s DiscriminatorField (pet_type in this case). +Reads and queries using a given type parameter will match each entity with its specific type. +For example, reading a List<Pet> will produce a list containing instances of all 3 types. +However, reading a List<Dog> will produce a list containing only Dog and Pug instances. +You can include the pet_type discrimination field in your Java entities, but its type must be convertible to a collection or array of String. +Any value set in the discrimination field will be overwritten upon write to Cloud Datastore.

    +
    +
    +
    +
    +

    15.3. Relationships

    +
    +

    There are three ways to represent relationships between entities that are described in this section:

    +
    +
    +
      +
    • +

      Embedded entities stored directly in the field of the containing entity

      +
    • +
    • +

      @Descendant annotated properties for one-to-many relationships

      +
    • +
    • +

      @Reference annotated properties for general relationships without hierarchy

      +
    • +
    • +

      @LazyReference similar to @Reference, but the entities are lazy-loaded when the property is accessed. +(Note that the keys of the children are retrieved when the parent entity is loaded.)

      +
    • +
    +
    +
    +

    15.3.1. Embedded Entities

    +
    +

    Fields whose types are also annotated with @Entity are converted to EntityValue and stored inside the parent entity.

    +
    +
    +

    Here is an example of Cloud Datastore entity containing an embedded entity in JSON:

    +
    +
    +
    +
    {
    +  "name" : "Alexander",
    +  "age" : 47,
    +  "child" : {"name" : "Philip"  }
    +}
    +
    +
    +
    +

    This corresponds to a simple pair of Java entities:

    +
    +
    +
    +
    import com.google.cloud.spring.data.datastore.core.mapping.Entity;
    +import org.springframework.data.annotation.Id;
    +
    +@Entity("parents")
    +public class Parent {
    +  @Id
    +  String name;
    +
    +  Child child;
    +}
    +
    +@Entity
    +public class Child {
    +  String name;
    +}
    +
    +
    +
    +
    +

    Child entities are not stored in their own kind. +They are stored in their entirety in the child field of the parents kind.

    +
    +
    +

    Multiple levels of embedded entities are supported.

    +
    +
    + + + + + +
    + + +Embedded entities don’t need to have @Id field, it is only required for top level entities. +
    +
    +
    +

    Example:

    +
    +
    +

    Entities can hold embedded entities that are their own type. +We can store trees in Cloud Datastore using this feature:

    +
    +
    +
    +
    import com.google.cloud.spring.data.datastore.core.mapping.Embedded;
    +import com.google.cloud.spring.data.datastore.core.mapping.Entity;
    +import org.springframework.data.annotation.Id;
    +
    +@Entity
    +public class EmbeddableTreeNode {
    +  @Id
    +  long value;
    +
    +  EmbeddableTreeNode left;
    +
    +  EmbeddableTreeNode right;
    +
    +  Map<String, Long> longValues;
    +
    +  Map<String, List<Timestamp>> listTimestamps;
    +
    +  public EmbeddableTreeNode(long value, EmbeddableTreeNode left, EmbeddableTreeNode right) {
    +    this.value = value;
    +    this.left = left;
    +    this.right = right;
    +  }
    +}
    +
    +
    +
    +
    +
    Maps
    +
    +

    Maps will be stored as embedded entities where the key values become the field names in the embedded entity. +The value types in these maps can be any regularly supported property type, and the key values will be converted to String using the configured converters.

    +
    +
    +

    Also, a collection of entities can be embedded; it will be converted to ListValue on write.

    +
    +
    +

    Example:

    +
    +
    +

    Instead of a binary tree from the previous example, we would like to store a general tree +(each node can have an arbitrary number of children) in Cloud Datastore. +To do that, we need to create a field of type List<EmbeddableTreeNode>:

    +
    +
    +
    +
    import com.google.cloud.spring.data.datastore.core.mapping.Embedded;
    +import org.springframework.data.annotation.Id;
    +
    +public class EmbeddableTreeNode {
    +  @Id
    +  long value;
    +
    +  List<EmbeddableTreeNode> children;
    +
    +  Map<String, EmbeddableTreeNode> siblingNodes;
    +
    +  Map<String, Set<EmbeddableTreeNode>> subNodeGroups;
    +
    +  public EmbeddableTreeNode(List<EmbeddableTreeNode> children) {
    +    this.children = children;
    +  }
    +}
    +
    +
    +
    +
    +

    Because Maps are stored as entities, they can further hold embedded entities:

    +
    +
    +
      +
    • +

      Singular embedded objects in the value can be stored in the values of embedded Maps.

      +
    • +
    • +

      Collections of embedded objects in the value can also be stored as the values of embedded Maps.

      +
    • +
    • +

      Maps in the value are further stored as embedded entities with the same rules applied recursively for their values.

      +
    • +
    +
    +
    +
    +
    +

    15.3.2. Ancestor-Descendant Relationships

    +
    +

    Parent-child relationships are supported via the @Descendants annotation.

    +
    +
    +

    Unlike embedded children, descendants are fully-formed entities residing in their own kinds. +The parent entity does not have an extra field to hold the descendant entities. +Instead, the relationship is captured in the descendants' keys, which refer to their parent entities:

    +
    +
    +
    +
    import com.google.cloud.spring.data.datastore.core.mapping.Descendants;
    +import com.google.cloud.spring.data.datastore.core.mapping.Entity;
    +import org.springframework.data.annotation.Id;
    +
    +@Entity("orders")
    +public class ShoppingOrder {
    +  @Id
    +  long id;
    +
    +  @Descendants
    +  List<Item> items;
    +}
    +
    +@Entity("purchased_item")
    +public class Item {
    +  @Id
    +  Key purchasedItemKey;
    +
    +  String name;
    +
    +  Timestamp timeAddedToOrder;
    +}
    +
    +
    +
    +
    +

    For example, an instance of a GQL key-literal representation for Item would also contain the parent ShoppingOrder ID value:

    +
    +
    +
    +
    Key(orders, '12345', purchased_item, 'eggs')
    +
    +
    +
    +

    The GQL key-literal representation for the parent ShoppingOrder would be:

    +
    +
    +
    +
    Key(orders, '12345')
    +
    +
    +
    +

    The Cloud Datastore entities exist separately in their own kinds.

    +
    +
    +

    The ShoppingOrder:

    +
    +
    +
    +
    {
    +  "id" : 12345
    +}
    +
    +
    +
    +

    The two items inside that order:

    +
    +
    +
    +
    {
    +  "purchasedItemKey" : Key(orders, '12345', purchased_item, 'eggs'),
    +  "name" : "eggs",
    +  "timeAddedToOrder" : "2014-09-27 12:30:00.45-8:00"
    +}
    +
    +{
    +  "purchasedItemKey" : Key(orders, '12345', purchased_item, 'sausage'),
    +  "name" : "sausage",
    +  "timeAddedToOrder" : "2014-09-28 11:30:00.45-9:00"
    +}
    +
    +
    +
    +

    The parent-child relationship structure of objects is stored in Cloud Datastore using Datastore’s ancestor relationships. +Because the relationships are defined by the Ancestor mechanism, there is no extra column needed in either the parent or child entity to store this relationship. +The relationship link is part of the descendant entity’s key value. +These relationships can be many levels deep.

    +
    +
    +

    Properties holding child entities must be collection-like, but they can be any of the supported inter-convertible collection-like types that are supported for regular properties such as List, arrays, Set, etc…​ +Child items must have Key as their ID type because Cloud Datastore stores the ancestor relationship link inside the keys of the children.

    +
    +
    +

    Reading or saving an entity automatically causes all subsequent levels of children under that entity to be read or saved, respectively. +If a new child is created and added to a property annotated @Descendants and the key property is left null, then a new key will be allocated for that child. +The ordering of the retrieved children may not be the same as the ordering in the original property that was saved.

    +
    +
    +

    Child entities cannot be moved from the property of one parent to that of another unless the child’s key property is set to null or a value that contains the new parent as an ancestor. +Since Cloud Datastore entity keys can have multiple parents, it is possible that a child entity appears in the property of multiple parent entities. +Because entity keys are immutable in Cloud Datastore, to change the key of a child you must delete the existing one and re-save it with the new key.

    +
    +
    +
    +

    15.3.3. Key Reference Relationships

    +
    +

    General relationships can be stored using the @Reference annotation.

    +
    +
    +
    +
    import org.springframework.data.annotation.Reference;
    +import org.springframework.data.annotation.Id;
    +
    +@Entity
    +public class ShoppingOrder {
    +  @Id
    +  long id;
    +
    +  @Reference
    +  List<Item> items;
    +
    +  @Reference
    +  Item specialSingleItem;
    +}
    +
    +@Entity
    +public class Item {
    +  @Id
    +  Key purchasedItemKey;
    +
    +  String name;
    +
    +  Timestamp timeAddedToOrder;
    +}
    +
    +
    +
    +
    +

    @Reference relationships are between fully-formed entities residing in their own kinds. +The relationship between ShoppingOrder and Item entities are stored as a Key field inside ShoppingOrder, which are resolved to the underlying Java entity type by Spring Data Cloud Datastore:

    +
    +
    +
    +
    {
    +  "id" : 12345,
    +  "specialSingleItem" : Key(item, "milk"),
    +  "items" : [ Key(item, "eggs"), Key(item, "sausage") ]
    +}
    +
    +
    +
    +

    Reference properties can either be singular or collection-like. +These properties correspond to actual columns in the entity and Cloud Datastore Kind that hold the key values of the referenced entities. +The referenced entities are full-fledged entities of other Kinds.

    +
    +
    +

    Similar to the @Descendants relationships, reading or writing an entity will recursively read or write all of the referenced entities at all levels. +If referenced entities have null ID values, then they will be saved as new entities and will have ID values allocated by Cloud Datastore. +There are no requirements for relationships between the key of an entity and the keys that entity holds as references. +The order of collection-like reference properties is not preserved when reading back from Cloud Datastore.

    +
    +
    +
    +
    +

    15.4. Datastore Operations & Template

    +
    +

    DatastoreOperations and its implementation, DatastoreTemplate, provides the Template pattern familiar to Spring developers.

    +
    +
    +

    Using the auto-configuration provided by Spring Boot Starter for Datastore, your Spring application context will contain a fully configured DatastoreTemplate object that you can autowire in your application:

    +
    +
    +
    +
    @SpringBootApplication
    +public class DatastoreTemplateExample {
    +
    +    @Autowired
    +    DatastoreTemplate datastoreTemplate;
    +
    +    public void doSomething() {
    +        this.datastoreTemplate.deleteAll(Trader.class);
    +        //...
    +        Trader t = new Trader();
    +        //...
    +        this.datastoreTemplate.save(t);
    +        //...
    +        List<Trader> traders = datastoreTemplate.findAll(Trader.class);
    +        //...
    +    }
    +}
    +
    +
    +
    +
    +

    The Template API provides convenience methods for:

    +
    +
    +
      +
    • +

      Write operations (saving and deleting)

      +
    • +
    • +

      Read-write transactions

      +
    • +
    +
    +
    +

    15.4.1. GQL Query

    +
    +

    In addition to retrieving entities by their IDs, you can also submit queries.

    +
    +
    +
    +
      <T> Iterable<T> query(Query<? extends BaseEntity> query, Class<T> entityClass);
    +
    +  <A, T> Iterable<T> query(Query<A> query, Function<A, T> entityFunc);
    +
    +  Iterable<Key> queryKeys(Query<Key> query);
    +
    +
    +
    +
    +

    These methods, respectively, allow querying for:

    +
    +
    +
      +
    • +

      entities mapped by a given entity class using all the same mapping and converting features

      +
    • +
    • +

      arbitrary types produced by a given mapping function

      +
    • +
    • +

      only the Cloud Datastore keys of the entities found by the query

      +
    • +
    +
    +
    +
    +

    15.4.2. Find by ID(s)

    +
    +

    Using DatastoreTemplate you can find entities by id. For example:

    +
    +
    +
    +
    Trader trader = this.datastoreTemplate.findById("trader1", Trader.class);
    +
    +List<Trader> traders = this.datastoreTemplate.findAllById(Arrays.asList("trader1", "trader2"), Trader.class);
    +
    +List<Trader> allTraders = this.datastoreTemplate.findAll(Trader.class);
    +
    +
    +
    +
    +

    Cloud Datastore uses key-based reads with strong consistency, but queries with eventual consistency. +In the example above the first two reads utilize keys, while the third is run by using a query based on the corresponding Kind of Trader.

    +
    +
    +
    Indexes
    +
    +

    By default, all fields are indexed. +To disable indexing on a particular field, @Unindexed annotation can be used.

    +
    +
    +

    Example:

    +
    +
    +
    +
    import com.google.cloud.spring.data.datastore.core.mapping.Unindexed;
    +
    +public class ExampleItem {
    +    long indexedField;
    +
    +    @Unindexed
    +    long unindexedField;
    +
    +    @Unindexed
    +    List<String> unindexedListField;
    +}
    +
    +
    +
    +
    +

    When using queries directly or via Query Methods, Cloud Datastore requires composite custom indexes if the select statement is not SELECT * or if there is more than one filtering condition in the WHERE clause.

    +
    +
    +
    +
    Read with offsets, limits, and sorting
    +
    +

    DatastoreRepository and custom-defined entity repositories implement the Spring Data PagingAndSortingRepository, which supports offsets and limits using page numbers and page sizes. +Paging and sorting options are also supported in DatastoreTemplate by supplying a DatastoreQueryOptions to findAll.

    +
    +
    +
    +
    Partial read
    +
    +

    This feature is not supported yet.

    +
    +
    +
    +
    +

    15.4.3. Write / Update

    +
    +

    The write methods of DatastoreOperations accept a POJO and writes all of its properties to Datastore. +The required Datastore kind and entity metadata is obtained from the given object’s actual type.

    +
    +
    +

    If a POJO was retrieved from Datastore and its ID value was changed and then written or updated, the operation will occur as if against a row with the new ID value. +The entity with the original ID value will not be affected.

    +
    +
    +
    +
    Trader t = new Trader();
    +this.datastoreTemplate.save(t);
    +
    +
    +
    +
    +

    The save method behaves as update-or-insert. +In contrast, the insert method will fail if an entity already exists.

    +
    +
    +
    Partial Update
    +
    +

    This feature is not supported yet.

    +
    +
    +
    +
    +

    15.4.4. Transactions

    +
    +

    Read and write transactions are provided by DatastoreOperations via the performTransaction method:

    +
    +
    +
    +
    @Autowired
    +DatastoreOperations myDatastoreOperations;
    +
    +public String doWorkInsideTransaction() {
    +  return myDatastoreOperations.performTransaction(
    +    transactionDatastoreOperations -> {
    +      // Work with transactionDatastoreOperations here.
    +      // It is also a DatastoreOperations object.
    +
    +      return "transaction completed";
    +    }
    +  );
    +}
    +
    +
    +
    +
    +

    The performTransaction method accepts a Function that is provided an instance of a DatastoreOperations object. +The final returned value and type of the function is determined by the user. +You can use this object just as you would a regular DatastoreOperations with an exception:

    +
    +
    +
      +
    • +

      It cannot perform sub-transactions.

      +
    • +
    +
    +
    +

    Because of Cloud Datastore’s consistency guarantees, there are limitations to the operations and relationships among entities used inside transactions.

    +
    +
    +
    Declarative Transactions with @Transactional Annotation
    +
    +

    This feature requires a bean of DatastoreTransactionManager, which is provided when using spring-cloud-gcp-starter-data-datastore.

    +
    +
    +

    DatastoreTemplate and DatastoreRepository support running methods with the @Transactional annotation as transactions. +If a method annotated with @Transactional calls another method also annotated, then both methods will work within the same transaction. +performTransaction cannot be used in @Transactional annotated methods because Cloud Datastore does not support transactions within transactions.

    +
    +
    +

    Other Google Cloud database-related integrations like Spanner and Firestore can introduce PlatformTransactionManager beans, and can interfere with Datastore Transaction Manager. To disambiguate, explicitly specify the name of the transaction manager bean for such @Transactional methods. Example:

    +
    +
    +
    +
    @Transactional(transactionManager = "datastoreTransactionManager")
    +
    +
    +
    +
    +
    +
    +

    15.4.5. Read-Write Support for Maps

    +
    +

    You can work with Maps of type Map<String, ?> instead of with entity objects by directly reading and writing them to and from Cloud Datastore.

    +
    +
    + + + + + +
    + + +This is a different situation than using entity objects that contain Map properties. +
    +
    +
    +

    The map keys are used as field names for a Datastore entity and map values are converted to Datastore supported types. +Only simple types are supported (i.e. collections are not supported). +Converters for custom value types can be added (see Custom types section).

    +
    +
    +

    Example:

    +
    +
    +
    +
    Map<String, Long> map = new HashMap<>();
    +map.put("field1", 1L);
    +map.put("field2", 2L);
    +map.put("field3", 3L);
    +
    +keyForMap = datastoreTemplate.createKey("kindName", "id");
    +
    +// write a map
    +datastoreTemplate.writeMap(keyForMap, map);
    +
    +// read a map
    +Map<String, Long> loadedMap = datastoreTemplate.findByIdAsMap(keyForMap, Long.class);
    +
    +
    +
    +
    +
    +
    +

    15.5. Repositories

    +
    +

    Spring Data Repositories are an abstraction that can reduce boilerplate code.

    +
    +
    +

    For example:

    +
    +
    +
    +
    public interface TraderRepository extends DatastoreRepository<Trader, String> {
    +}
    +
    +
    +
    +
    +

    Spring Data generates a working implementation of the specified interface, which can be autowired into an application.

    +
    +
    +

    The Trader type parameter to DatastoreRepository refers to the underlying domain type. +The second type parameter, String in this case, refers to the type of the key of the domain type.

    +
    +
    +
    +
    public class MyApplication {
    +
    +    @Autowired
    +    TraderRepository traderRepository;
    +
    +    public void demo() {
    +
    +        this.traderRepository.deleteAll();
    +        String traderId = "demo_trader";
    +        Trader t = new Trader();
    +        t.traderId = traderId;
    +        this.tradeRepository.save(t);
    +
    +        Iterable<Trader> allTraders = this.traderRepository.findAll();
    +
    +        int count = this.traderRepository.count();
    +    }
    +}
    +
    +
    +
    +
    +

    Repositories allow you to define custom Query Methods (detailed in the following sections) for retrieving, counting, and deleting based on filtering and paging parameters. +Filtering parameters can be of types supported by your configured custom converters.

    +
    +
    +

    15.5.1. Query methods by convention

    +
    +
    +
    public interface TradeRepository extends DatastoreRepository<Trade, String[]> {
    +  List<Trader> findByAction(String action);
    +
    +  // throws an exception if no results
    +  Trader findOneByAction(String action);
    +
    +  // because of the annotation, returns null if no results
    +  @Nullable
    +  Trader getByAction(String action);
    +
    +  Optional<Trader> getOneByAction(String action);
    +
    +  int countByAction(String action);
    +
    +  boolean existsByAction(String action);
    +
    +  List<Trade> findTop3ByActionAndSymbolAndPriceGreaterThanAndPriceLessThanOrEqualOrderBySymbolDesc(
    +            String action, String symbol, double priceFloor, double priceCeiling);
    +
    +  Page<TestEntity> findByAction(String action, Pageable pageable);
    +
    +  Slice<TestEntity> findBySymbol(String symbol, Pageable pageable);
    +
    +  List<TestEntity> findBySymbol(String symbol, Sort sort);
    +
    +  Stream<TestEntity> findBySymbol(String symbol);
    +}
    +
    +
    +
    +
    +

    In the example above the query methods in TradeRepository are generated based on the name of the methods using the Spring Data Query creation naming convention.

    +
    +
    + + + + + +
    + + +You can refer to nested fields using Spring Data JPA Property Expressions +
    +
    +
    +

    Cloud Datastore only supports filter components joined by AND, and the following operations:

    +
    +
    +
      +
    • +

      equals

      +
    • +
    • +

      greater than or equals

      +
    • +
    • +

      greater than

      +
    • +
    • +

      less than or equals

      +
    • +
    • +

      less than

      +
    • +
    • +

      is null

      +
    • +
    +
    +
    +

    After writing a custom repository interface specifying just the signatures of these methods, implementations are generated for you and can be used with an auto-wired instance of the repository. +Because of Cloud Datastore’s requirement that explicitly selected fields must all appear in a composite index together, find name-based query methods are run as SELECT *.

    +
    +
    +

    Delete queries are also supported. +For example, query methods such as deleteByAction or removeByAction delete entities found by findByAction. +Delete queries are run as separate read and delete operations instead of as a single transaction because Cloud Datastore cannot query in transactions unless ancestors for queries are specified. +As a result, removeBy and deleteBy name-convention query methods cannot be used inside transactions via either performInTransaction or @Transactional annotation.

    +
    +
    +

    Delete queries can have the following return types:

    +
    +
    +
      +
    • +

      An integer type that is the number of entities deleted

      +
    • +
    • +

      A collection of entities that were deleted

      +
    • +
    • +

      'void'

      +
    • +
    +
    +
    +

    Methods can have org.springframework.data.domain.Pageable parameter to control pagination and sorting, or org.springframework.data.domain.Sort parameter to control sorting only. +See Spring Data documentation for details.

    +
    +
    +

    For returning multiple items in a repository method, we support Java collections as well as org.springframework.data.domain.Page and org.springframework.data.domain.Slice. +If a method’s return type is org.springframework.data.domain.Page, the returned object will include current page, total number of results and total number of pages.

    +
    +
    + + + + + +
    + + +Methods that return Page run an additional query to compute total number of pages. +Methods that return Slice, on the other hand, do not run any additional queries and, therefore, are much more efficient. +
    +
    +
    +
    +

    15.5.2. Empty result handling in repository methods

    +
    +

    Java java.util.Optional can be used to indicate the potential absence of a return value.

    +
    +
    +

    Alternatively, query methods can return the result without a wrapper. +In that case the absence of a query result is indicated by returning null. +Repository methods returning collections are guaranteed never to return null but rather the corresponding empty collection.

    +
    +
    + + + + + +
    + + +You can enable nullability checks. For more details please see Spring Framework’s nullability docs. +
    +
    +
    +
    +

    15.5.3. Query by example

    +
    +

    Query by Example is an alternative querying technique. +It enables dynamic query generation based on a user-provided object. See Spring Data Documentation for details.

    +
    +
    +
    Unsupported features:
    +
    +
      +
    1. +

      Currently, only equality queries are supported (no ignore-case matching, regexp matching, etc.).

      +
    2. +
    3. +

      Per-field matchers are not supported.

      +
    4. +
    5. +

      Embedded entities matching is not supported.

      +
    6. +
    7. +

      Projection is not supported.

      +
    8. +
    +
    +
    +

    For example, if you want to find all users with the last name "Smith", you would use the following code:

    +
    +
    +
    +
    userRepository.findAll(
    +    Example.of(new User(null, null, "Smith"))
    +
    +
    +
    +
    +

    null fields are not used in the filter by default. If you want to include them, you would use the following code:

    +
    +
    +
    +
    userRepository.findAll(
    +    Example.of(new User(null, null, "Smith"), ExampleMatcher.matching().withIncludeNullValues())
    +
    +
    +
    +
    +

    You can also extend query specification initially defined by an example in FluentQuery’s chaining style:

    +
    +
    +
    +
    userRepository.findBy(
    +    Example.of(new User(null, null, "Smith")), q -> q.sortBy(Sort.by("firstName")).firstValue());
    +
    +userRepository.findBy(
    +    Example.of(new User(null, null, "Smith")), FetchableFluentQuery::stream);
    +
    +
    +
    +
    +
    +

    15.5.4. Custom GQL query methods

    +
    +

    Custom GQL queries can be mapped to repository methods in one of two ways:

    +
    +
    +
      +
    • +

      namedQueries properties file

      +
    • +
    • +

      using the @Query annotation

      +
    • +
    +
    +
    +
    Query methods with annotation
    +
    +

    Using the @Query annotation:

    +
    +
    +

    The names of the tags of the GQL correspond to the @Param annotated names of the method parameters.

    +
    +
    +
    +
    public interface TraderRepository extends DatastoreRepository<Trader, String> {
    +
    +  @Query("SELECT * FROM traders WHERE name = @trader_name")
    +  List<Trader> tradersByName(@Param("trader_name") String traderName);
    +
    +  @Query("SELECT * FROM traders WHERE name = @trader_name")
    +  Stream<Trader> tradersStreamByName(@Param("trader_name") String traderName);
    +
    +  @Query("SELECT * FROM  test_entities_ci WHERE name = @trader_name")
    +  TestEntity getOneTestEntity(@Param("trader_name") String traderName);
    +
    +  @Query("SELECT * FROM traders WHERE name = @trader_name")
    +  List<Trader> tradersByNameSort(@Param("trader_name") String traderName, Sort sort);
    +
    +  @Query("SELECT * FROM traders WHERE name = @trader_name")
    +  Slice<Trader> tradersByNameSlice(@Param("trader_name") String traderName, Pageable pageable);
    +
    +  @Query("SELECT * FROM traders WHERE name = @trader_name")
    +  Page<Trader> tradersByNamePage(@Param("trader_name") String traderName, Pageable pageable);
    +}
    +
    +
    +
    +
    +

    When the return type is Slice or Pageable, the result set cursor that points to the position just after the page is preserved in the returned Slice or Page object. To take advantage of the cursor to query for the next page or slice, use result.getPageable().next().

    +
    +
    + + + + + +
    + + +Page requires the total count of entities produced by the query. Therefore, the first query will have to retrieve all of the records just to count them. Instead, we recommend using the Slice return type, because it does not require an additional count query. +
    +
    +
    +
    +
     Slice<Trader> slice1 = tradersByNamePage("Dave", PageRequest.of(0, 5));
    + Slice<Trader> slice2 = tradersByNamePage("Dave", slice1.getPageable().next());
    +
    +
    +
    +
    + + + + + +
    + + +You cannot use these Query Methods in repositories where the type parameter is a subclass of another class +annotated with DiscriminatorField. +
    +
    +
    +

    The following parameter types are supported:

    +
    +
    +
      +
    • +

      com.google.cloud.Timestamp

      +
    • +
    • +

      com.google.cloud.datastore.Blob

      +
    • +
    • +

      com.google.cloud.datastore.Key

      +
    • +
    • +

      com.google.cloud.datastore.Cursor

      +
    • +
    • +

      java.lang.Boolean

      +
    • +
    • +

      java.lang.Double

      +
    • +
    • +

      java.lang.Long

      +
    • +
    • +

      java.lang.String

      +
    • +
    • +

      enum values. +These are queried as String values.

      +
    • +
    +
    +
    +

    With the exception of Cursor, array forms of each of the types are also supported.

    +
    +
    +

    If you would like to obtain the count of items of a query or if there are any items returned by the query, set the count = true or exists = true properties of the @Query annotation, respectively. +The return type of the query method in these cases should be an integer type or a boolean type.

    +
    +
    +

    Cloud Datastore provides provides the SELECT __key__ FROM …​ special column for all kinds that retrieves the Key of each row. +Selecting this special __key__ column is especially useful and efficient for count and exists queries.

    +
    +
    +

    You can also query for non-entity types:

    +
    +
    +
    +
    @Query(value = "SELECT __key__ from test_entities_ci")
    +List<Key> getKeys();
    +
    +@Query(value = "SELECT __key__ from test_entities_ci limit 1")
    +Key getKey();
    +
    +
    +
    +
    +

    In order to use @Id annotated fields in custom queries, use __key__ keyword for the field name. The parameter type should be of Key, as in the following example.

    +
    +
    +

    Repository method:

    +
    +
    +
    +
    @Query("select * from  test_entities_ci where size = @size and __key__ = @id")
    +LinkedList<TestEntity> findEntities(@Param("size") long size, @Param("id") Key id);
    +
    +
    +
    +
    +

    Generate a key from id value using DatastoreTemplate.createKey method and use it as a parameter for the repository method:

    +
    +
    +
    +
    this.testEntityRepository.findEntities(1L, datastoreTemplate.createKey(TestEntity.class, 1L))
    +
    +
    +
    +
    +

    SpEL can be used to provide GQL parameters:

    +
    +
    +
    +
    @Query("SELECT * FROM |com.example.Trade| WHERE trades.action = @act
    +  AND price > :#{#priceRadius * -1} AND price < :#{#priceRadius * 2}")
    +List<Trade> fetchByActionNamedQuery(@Param("act") String action, @Param("priceRadius") Double r);
    +
    +
    +
    +
    +

    Kind names can be directly written in the GQL annotations. +Kind names can also be resolved from the @Entity annotation on domain classes.

    +
    +
    +

    In this case, the query should refer to table names with fully qualified class names surrounded by | characters: |fully.qualified.ClassName|. +This is useful when SpEL expressions appear in the kind name provided to the @Entity annotation. +For example:

    +
    +
    +
    +
    @Query("SELECT * FROM |com.example.Trade| WHERE trades.action = @act")
    +List<Trade> fetchByActionNamedQuery(@Param("act") String action);
    +
    +
    +
    +
    +
    +
    Query methods with named queries properties
    +
    +

    You can also specify queries with Cloud Datastore parameter tags and SpEL expressions in properties files.

    +
    +
    +

    By default, the namedQueriesLocation attribute on @EnableDatastoreRepositories points to the META-INF/datastore-named-queries.properties file. +You can specify the query for a method in the properties file by providing the GQL as the value for the "interface.method" property:

    +
    +
    + + + + + +
    + + +You cannot use these Query Methods in repositories where the type parameter is a subclass of another class +annotated with DiscriminatorField. +
    +
    +
    +
    +
    Trader.fetchByName=SELECT * FROM traders WHERE name = @tag0
    +
    +
    +
    +
    +
    public interface TraderRepository extends DatastoreRepository<Trader, String> {
    +
    +    // This method uses the query from the properties file instead of one generated based on name.
    +    List<Trader> fetchByName(@Param("tag0") String traderName);
    +
    +}
    +
    +
    +
    +
    +
    +
    +

    15.5.5. Transactions

    +
    +

    These transactions work very similarly to those of DatastoreOperations, but is specific to the repository’s domain type and provides repository functions instead of template functions.

    +
    +
    +

    For example, this is a read-write transaction:

    +
    +
    +
    +
    @Autowired
    +DatastoreRepository myRepo;
    +
    +public String doWorkInsideTransaction() {
    +  return myRepo.performTransaction(
    +    transactionDatastoreRepo -> {
    +      // Work with the single-transaction transactionDatastoreRepo here.
    +      // This is a DatastoreRepository object.
    +
    +      return "transaction completed";
    +    }
    +  );
    +}
    +
    +
    +
    +
    +
    +

    15.5.6. Projections

    +
    +

    Spring Data Cloud Datastore supports projections. +You can define projection interfaces based on domain types and add query methods that return them in your repository:

    +
    +
    +
    +
    public interface TradeProjection {
    +
    +    String getAction();
    +
    +    @Value("#{target.symbol + ' ' + target.action}")
    +    String getSymbolAndAction();
    +}
    +
    +public interface TradeRepository extends DatastoreRepository<Trade, Key> {
    +
    +    List<Trade> findByTraderId(String traderId);
    +
    +    List<TradeProjection> findByAction(String action);
    +
    +    @Query("SELECT action, symbol FROM trades WHERE action = @action")
    +    List<TradeProjection> findByQuery(String action);
    +}
    +
    +
    +
    +
    +

    Projections can be provided by name-convention-based query methods as well as by custom GQL queries. +If using custom GQL queries, you can further restrict the fields retrieved from Cloud Datastore to just those required by the projection. +However, custom select statements (those not using SELECT *) require composite indexes containing the selected fields.

    +
    +
    +

    Properties of projection types defined using SpEL use the fixed name target for the underlying domain object. +As a result, accessing underlying properties take the form target.<property-name>.

    +
    +
    +
    +

    15.5.7. REST Repositories

    +
    +

    When running with Spring Boot, repositories can be exposed as REST services by simply adding this dependency to your pom file:

    +
    +
    +
    +
    <dependency>
    +  <groupId>org.springframework.boot</groupId>
    +  <artifactId>spring-boot-starter-data-rest</artifactId>
    +</dependency>
    +
    +
    +
    +

    If you prefer to configure parameters (such as path), you can use @RepositoryRestResource annotation:

    +
    +
    +
    +
    @RepositoryRestResource(collectionResourceRel = "trades", path = "trades")
    +public interface TradeRepository extends DatastoreRepository<Trade, String[]> {
    +}
    +
    +
    +
    +
    +

    For example, you can retrieve all Trade objects in the repository by using curl http://<server>:<port>/trades, or any specific trade via curl http://<server>:<port>/trades/<trader_id>.

    +
    +
    +

    You can also write trades using curl -XPOST -H"Content-Type: application/json" -d@test.json http://<server>:<port>/trades/ where the file test.json holds the JSON representation of a Trade object.

    +
    +
    +

    To delete trades, you can use curl -XDELETE http://<server>:<port>/trades/<trader_id>

    +
    +
    +
    +
    +

    15.6. Events

    +
    +

    Spring Data Cloud Datastore publishes events extending the Spring Framework’s ApplicationEvent to the context that can be received by ApplicationListener beans you register.

    +
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeDescriptionContents

    AfterFindByKeyEvent

    Published immediately after read by-key operations are run by DatastoreTemplate

    The entities read from Cloud Datastore and the original keys in the request.

    AfterQueryEvent

    Published immediately after read byquery operations are run by DatastoreTemplate

    The entities read from Cloud Datastore and the original query in the request.

    BeforeSaveEvent

    Published immediately before save operations are run by DatastoreTemplate

    The entities to be sent to Cloud Datastore and the original Java objects being saved.

    AfterSaveEvent

    Published immediately after save operations are run by DatastoreTemplate

    The entities sent to Cloud Datastore and the original Java objects being saved.

    BeforeDeleteEvent

    Published immediately before delete operations are run by DatastoreTemplate

    The keys to be sent to Cloud Datastore. The target entities, ID values, or entity type originally specified for the delete operation.

    AfterDeleteEvent

    Published immediately after delete operations are run by DatastoreTemplate

    The keys sent to Cloud Datastore. The target entities, ID values, or entity type originally specified for the delete operation.

    +
    +
    +

    15.7. Auditing

    +
    +

    Spring Data Cloud Datastore supports the @LastModifiedDate and @LastModifiedBy auditing annotations for properties:

    +
    +
    +
    +
    @Entity
    +public class SimpleEntity {
    +    @Id
    +    String id;
    +
    +    @LastModifiedBy
    +    String lastUser;
    +
    +    @LastModifiedDate
    +    DateTime lastTouched;
    +}
    +
    +
    +
    +
    +

    Upon insert, update, or save, these properties will be set automatically by the framework before Datastore entities are generated and saved to Cloud Datastore.

    +
    +
    +

    To take advantage of these features, add the @EnableDatastoreAuditing annotation to your configuration class and provide a bean for an AuditorAware<A> implementation where the type A is the desired property type annotated by @LastModifiedBy:

    +
    +
    +
    +
    @Configuration
    +@EnableDatastoreAuditing
    +public class Config {
    +
    +    @Bean
    +    public AuditorAware<String> auditorProvider() {
    +        return () -> Optional.of("YOUR_USERNAME_HERE");
    +    }
    +}
    +
    +
    +
    +
    +

    The AuditorAware interface contains a single method that supplies the value for fields annotated by @LastModifiedBy and can be of any type. +One alternative is to use Spring Security’s User type:

    +
    +
    +
    +
    class SpringSecurityAuditorAware implements AuditorAware<User> {
    +
    +  public Optional<User> getCurrentAuditor() {
    +
    +    return Optional.ofNullable(SecurityContextHolder.getContext())
    +              .map(SecurityContext::getAuthentication)
    +              .filter(Authentication::isAuthenticated)
    +              .map(Authentication::getPrincipal)
    +              .map(User.class::cast);
    +  }
    +}
    +
    +
    +
    +
    +

    You can also set a custom provider for properties annotated @LastModifiedDate by providing a bean for DateTimeProvider and providing the bean name to @EnableDatastoreAuditing(dateTimeProviderRef = "customDateTimeProviderBean").

    +
    +
    +
    +

    15.8. Partitioning Data by Namespace

    +
    +

    You can partition your data by using more than one namespace. +This is the recommended method for multi-tenancy in Cloud Datastore.

    +
    +
    +
    +
        @Bean
    +    public DatastoreNamespaceProvider namespaceProvider() {
    +        // return custom Supplier of a namespace string.
    +    }
    +
    +
    +
    +
    +

    The DatastoreNamespaceProvider is a synonym for Supplier<String>. +By providing a custom implementation of this bean (for example, supplying a thread-local namespace name), you can direct your application to use multiple namespaces. +Every read, write, query, and transaction you perform will utilize the namespace provided by this supplier.

    +
    +
    +

    Note that your provided namespace in application.properties will be ignored if you define a namespace provider bean.

    +
    +
    +
    +

    15.9. Spring Boot Actuator Support

    +
    +

    15.9.1. Cloud Datastore Health Indicator

    +
    +

    If you are using Spring Boot Actuator, you can take advantage of the Cloud Datastore health indicator called datastore. +The health indicator will verify whether Cloud Datastore is up and accessible by your application. +To enable it, all you need to do is add the Spring Boot Actuator to your project.

    +
    +
    +
    +
    <dependency>
    +    <groupId>org.springframework.boot</groupId>
    +    <artifactId>spring-boot-starter-actuator</artifactId>
    +</dependency>
    +
    +
    +
    +
    +
    +

    15.10. Sample

    +
    +

    A Simple Spring Boot Application and more advanced Sample Spring Boot Application are provided to show how to use the Spring Data Cloud Datastore starter and template.

    +
    +
    +
    +

    15.11. Test

    +
    +

    Testcontainers provides a gcloud module which offers DatastoreEmulatorContainer. See more at the docs

    +
    +
    +
    +
    +
    +

    16. Spring Data Cloud Firestore

    +
    +
    + + + + + +
    + + +Currently some features are not supported: query by example, projections, and auditing. +
    +
    +
    +

    Spring Data is an abstraction for storing and retrieving POJOs in numerous storage technologies. +Spring Framework on Google Cloud adds Spring Data Reactive Repositories support for Google Cloud Firestore in native mode, providing reactive template and repositories support. +To begin using this library, add the spring-cloud-gcp-data-firestore artifact to your project.

    +
    +
    +

    Maven coordinates for this module only, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +  <groupId>com.google.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-data-firestore</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +  implementation("com.google.cloud:spring-cloud-gcp-data-firestore")
    +}
    +
    +
    +
    +

    We provide a Spring Boot Starter for Spring Data Firestore, with which you can use our recommended auto-configuration setup. To use the starter, see the coordinates below.

    +
    +
    +
    +
    <dependency>
    +  <groupId>com.google.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-starter-data-firestore</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +  implementation("com.google.cloud:spring-cloud-gcp-starter-data-firestore")
    +}
    +
    +
    +
    +

    16.1. Configuration

    +
    +

    16.1.1. Properties

    +
    +

    The Spring Boot starter for Google Cloud Firestore provides the following configuration options:

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.firestore.enabled

    Enables or disables Firestore auto-configuration

    No

    true

    spring.cloud.gcp.firestore.project-id

    Google Cloud project ID where the Google Cloud Firestore API is hosted, if different from the one in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.firestore.database-id

    Google Cloud project can host multiple databases. You can specify which database will be used. If not specified, the database id will be "(default)".

    No

    spring.cloud.gcp.firestore.emulator.enabled

    Enables the usage of an emulator. If this is set to true, then you should set the spring.cloud.gcp.firestore.host-port to the host:port of your locally running emulator instance

    No

    false

    spring.cloud.gcp.firestore.host-port

    The host and port of the Firestore service; can be overridden to specify connecting to an already-running Firestore emulator instance.

    No

    firestore.googleapis.com:443 (the host/port of official Firestore service)

    spring.cloud.gcp.firestore.credentials.location

    OAuth2 credentials for authenticating with the Google Cloud Firestore API, if different from the ones in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.firestore.credentials.encoded-key

    Base64-encoded OAuth2 credentials for authenticating with the Google Cloud Firestore API, if different from the ones in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.firestore.credentials.scopes

    OAuth2 scope for Spring Framework on Google CloudFirestore credentials

    No

    www.googleapis.com/auth/datastore

    +
    +
    +

    16.1.2. Supported types

    +
    +

    You may use the following field types when defining your persistent entities or when binding query parameters:

    +
    +
    +
      +
    • +

      Long

      +
    • +
    • +

      Integer

      +
    • +
    • +

      Double

      +
    • +
    • +

      Float

      +
    • +
    • +

      String

      +
    • +
    • +

      Boolean

      +
    • +
    • +

      Character

      +
    • +
    • +

      Date

      +
    • +
    • +

      Map

      +
    • +
    • +

      List

      +
    • +
    • +

      Enum

      +
    • +
    • +

      com.google.cloud.Timestamp

      +
    • +
    • +

      com.google.cloud.firestore.GeoPoint

      +
    • +
    • +

      com.google.cloud.firestore.Blob

      +
    • +
    +
    +
    +
    +

    16.1.3. Reactive Repository settings

    +
    +

    Spring Data Repositories can be configured via the @EnableReactiveFirestoreRepositories annotation on your main @Configuration class. +With our Spring Boot Starter for Spring Data Cloud Firestore, @EnableReactiveFirestoreRepositories is automatically added. +It is not required to add it to any other class, unless there is a need to override finer grain configuration parameters provided by @EnableReactiveFirestoreRepositories.

    +
    +
    +
    +

    16.1.4. Autoconfiguration

    +
    +

    Our Spring Boot autoconfiguration creates the following beans available in the Spring application context:

    +
    +
    +
      +
    • +

      an instance of FirestoreTemplate

      +
    • +
    • +

      instances of all user defined repositories extending FirestoreReactiveRepository (an extension of ReactiveCrudRepository with additional Cloud Firestore features) when repositories are enabled

      +
    • +
    • +

      an instance of Firestore from the Google Cloud Java Client for Firestore, for convenience and lower level API access

      +
    • +
    +
    +
    +
    +
    +

    16.2. Object Mapping

    +
    +

    Spring Data Cloud Firestore allows you to map domain POJOs to Cloud Firestore collections and documents via annotations:

    +
    +
    +
    +
    import com.google.cloud.firestore.annotation.DocumentId;
    +import com.google.cloud.spring.data.firestore.Document;
    +
    +@Document(collectionName = "usersCollection")
    +public class User {
    +  /** Used to test @PropertyName annotation on a field. */
    +  @PropertyName("drink")
    +  public String favoriteDrink;
    +
    +  @DocumentId private String name;
    +
    +  private Integer age;
    +
    +  public User() {}
    +
    +  public String getName() {
    +    return this.name;
    +  }
    +
    +  public void setName(String name) {
    +    this.name = name;
    +  }
    +
    +  public Integer getAge() {
    +    return this.age;
    +  }
    +
    +  public void setAge(Integer age) {
    +    this.age = age;
    +  }
    +}
    +
    +
    +
    +
    +

    @Document(collectionName = "usersCollection") annotation configures the collection name for the documents of this type. +This annotation is optional, by default the collection name is derived from the class name.

    +
    +
    +

    @DocumentId annotation marks a field to be used as document id. +This annotation is required and the annotated field can only be of String type.

    +
    +
    + + + + + +
    + + +If the property annotated with @DocumentId is null the document id is generated automatically when the entity is saved. +
    +
    +
    + + + + + +
    + + +Internally we use Firestore client library object mapping. See the documentation for supported annotations. +
    +
    +
    +

    16.2.1. Embedded entities and lists

    +
    +

    Spring Data Cloud Firestore supports embedded properties of custom types and lists. +Given a custom POJO definition, you can have properties of this type or lists of this type in your entities. +They are stored as embedded documents (or arrays, correspondingly) in the Cloud Firestore.

    +
    +
    +

    Example:

    +
    +
    +
    +
    @Document(collectionName = "usersCollection")
    +public class User {
    +  /** Used to test @PropertyName annotation on a field. */
    +  @PropertyName("drink")
    +  public String favoriteDrink;
    +
    +  @DocumentId private String name;
    +
    +  private Integer age;
    +
    +  private List<String> pets;
    +
    +  private List<Address> addresses;
    +
    +  private Address homeAddress;
    +
    +  public List<String> getPets() {
    +    return this.pets;
    +  }
    +
    +  public void setPets(List<String> pets) {
    +    this.pets = pets;
    +  }
    +
    +  public List<Address> getAddresses() {
    +    return this.addresses;
    +  }
    +
    +  public void setAddresses(List<Address> addresses) {
    +    this.addresses = addresses;
    +  }
    +
    +  public Timestamp getUpdateTime() {
    +    return updateTime;
    +  }
    +
    +  public void setUpdateTime(Timestamp updateTime) {
    +    this.updateTime = updateTime;
    +  }
    +
    +  @PropertyName("address")
    +  public Address getHomeAddress() {
    +    return this.homeAddress;
    +  }
    +
    +  @PropertyName("address")
    +  public void setHomeAddress(Address homeAddress) {
    +    this.homeAddress = homeAddress;
    +  }
    +  public static class Address {
    +    String streetAddress;
    +    String country;
    +
    +    public Address() {}
    +  }
    +}
    +
    +
    +
    +
    +
    +
    +

    16.3. Reactive Repositories

    +
    +

    Spring Data Repositories is an abstraction that can reduce boilerplate code.

    +
    +
    +

    For example:

    +
    +
    +
    +
    public interface UserRepository extends FirestoreReactiveRepository<User> {
    +  Flux<User> findBy(Pageable pageable);
    +
    +  Flux<User> findByAge(Integer age);
    +
    +  Flux<User> findByAge(Integer age, Sort sort);
    +
    +  Flux<User> findByAgeOrderByNameDesc(Integer age);
    +
    +  Flux<User> findAllByOrderByAge();
    +
    +  Flux<User> findByAgeNot(Integer age);
    +
    +  Flux<User> findByNameAndAge(String name, Integer age);
    +
    +  Flux<User> findByHomeAddressCountry(String country);
    +
    +  Flux<User> findByFavoriteDrink(String drink);
    +
    +  Flux<User> findByAgeGreaterThanAndAgeLessThan(Integer age1, Integer age2);
    +
    +  Flux<User> findByAgeGreaterThan(Integer age);
    +
    +  Flux<User> findByAgeGreaterThan(Integer age, Sort sort);
    +
    +  Flux<User> findByAgeGreaterThan(Integer age, Pageable pageable);
    +
    +  Flux<User> findByAgeIn(List<Integer> ages);
    +
    +  Flux<User> findByAgeNotIn(List<Integer> ages);
    +
    +  Flux<User> findByAgeAndPetsContains(Integer age, List<String> pets);
    +
    +  Flux<User> findByNameAndPetsContains(String name, List<String> pets);
    +
    +  Flux<User> findByPetsContains(List<String> pets);
    +
    +  Flux<User> findByPetsContainsAndAgeIn(String pets, List<Integer> ages);
    +
    +  Mono<Long> countByAgeIsGreaterThan(Integer age);
    +}
    +
    +
    +
    +
    +

    Spring Data generates a working implementation of the specified interface, which can be autowired into an application.

    +
    +
    +

    The User type parameter to FirestoreReactiveRepository refers to the underlying domain type.

    +
    +
    + + + + + +
    + + +You can refer to nested fields using Spring Data JPA Property Expressions +
    +
    +
    +
    +
    public class MyApplication {
    +
    +  @Autowired UserRepository userRepository;
    +
    +  void writeReadDeleteTest() {
    +    List<User.Address> addresses =
    +        Arrays.asList(
    +            new User.Address("123 Alice st", "US"), new User.Address("1 Alice ave", "US"));
    +    User.Address homeAddress = new User.Address("10 Alice blvd", "UK");
    +    User alice = new User("Alice", 29, null, addresses, homeAddress);
    +    User bob = new User("Bob", 60);
    +
    +    this.userRepository.save(alice).block();
    +    this.userRepository.save(bob).block();
    +
    +    assertThat(this.userRepository.count().block()).isEqualTo(2);
    +    assertThat(this.userRepository.findAll().map(User::getName).collectList().block())
    +        .containsExactlyInAnyOrder("Alice", "Bob");
    +
    +    User aliceLoaded = this.userRepository.findById("Alice").block();
    +    assertThat(aliceLoaded.getAddresses()).isEqualTo(addresses);
    +    assertThat(aliceLoaded.getHomeAddress()).isEqualTo(homeAddress);
    +
    +    // cast to SimpleFirestoreReactiveRepository for method be reachable with Spring Boot 2.4
    +    SimpleFirestoreReactiveRepository repository =
    +        AopTestUtils.getTargetObject(this.userRepository);
    +    StepVerifier.create(
    +            repository
    +                .deleteAllById(Arrays.asList("Alice", "Bob"))
    +                .then(this.userRepository.count()))
    +        .expectNext(0L)
    +        .verifyComplete();
    +  }
    +}
    +
    +
    +
    +
    +

    Repositories allow you to define custom Query Methods (detailed in the following sections) for retrieving and counting based on filtering and paging parameters.

    +
    +
    + + + + + +
    + + +Custom queries with @Query annotation are not supported since there is no query language in Cloud Firestore +
    +
    +
    +
    +

    16.4. Firestore Operations & Template

    +
    +

    FirestoreOperations and its implementation, FirestoreTemplate, provides the Template pattern familiar to Spring developers.

    +
    +
    +

    Using the auto-configuration provided by Spring Data Cloud Firestore, your Spring application context will contain a fully configured FirestoreTemplate object that you can autowire in your application:

    +
    +
    +
    +
    @SpringBootApplication
    +public class FirestoreTemplateExample {
    +
    +    @Autowired
    +    FirestoreOperations firestoreOperations;
    +
    +    public Mono<User> createUsers() {
    +        return this.firestoreOperations.save(new User("Alice", 29))
    +            .then(this.firestoreOperations.save(new User("Bob", 60)));
    +    }
    +
    +    public Flux<User> findUsers() {
    +        return this.firestoreOperations.findAll(User.class);
    +    }
    +
    +    public Mono<Long> removeAllUsers() {
    +        return this.firestoreOperations.deleteAll(User.class);
    +    }
    +}
    +
    +
    +
    +
    +

    The Template API provides support for:

    +
    +
    + +
    +
    +
    +

    16.5. Query methods by convention

    +
    +
    +
    public class MyApplication {
    +  void partTreeRepositoryMethodTest() {
    +    User u1 = new User("Cloud", 22, null, null, new Address("1 First st., NYC", "USA"));
    +    u1.favoriteDrink = "tea";
    +    User u2 =
    +        new User(
    +            "Squall",
    +            17,
    +            Arrays.asList("cat", "dog"),
    +            null,
    +            new Address("2 Second st., London", "UK"));
    +    u2.favoriteDrink = "wine";
    +    Flux<User> users = Flux.fromArray(new User[] {u1, u2});
    +
    +    this.userRepository.saveAll(users).blockLast();
    +
    +    assertThat(this.userRepository.count().block()).isEqualTo(2);
    +    assertThat(this.userRepository.findBy(PageRequest.of(0, 10)).collectList().block())
    +        .containsExactly(u1, u2);
    +    assertThat(this.userRepository.findByAge(22).collectList().block()).containsExactly(u1);
    +    assertThat(this.userRepository.findByAgeNot(22).collectList().block()).containsExactly(u2);
    +    assertThat(this.userRepository.findByHomeAddressCountry("USA").collectList().block())
    +        .containsExactly(u1);
    +    assertThat(this.userRepository.findByFavoriteDrink("wine").collectList().block())
    +        .containsExactly(u2);
    +    assertThat(this.userRepository.findByAgeGreaterThanAndAgeLessThan(20, 30).collectList().block())
    +        .containsExactly(u1);
    +    assertThat(this.userRepository.findByAgeGreaterThan(10).collectList().block())
    +        .containsExactlyInAnyOrder(u1, u2);
    +    assertThat(this.userRepository.findByNameAndAge("Cloud", 22).collectList().block())
    +        .containsExactly(u1);
    +    assertThat(
    +            this.userRepository
    +                .findByNameAndPetsContains("Squall", Collections.singletonList("cat"))
    +                .collectList()
    +                .block())
    +        .containsExactly(u2);
    +  }
    +}
    +
    +
    +
    +
    +

    In the example above the query method implementations in UserRepository are generated based on the name of the methods using the Spring Data Query creation naming convention.

    +
    +
    +

    Cloud Firestore only supports filter components joined by AND, and the following operations:

    +
    +
    +
      +
    • +

      equals

      +
    • +
    • +

      is not equal

      +
    • +
    • +

      greater than or equals

      +
    • +
    • +

      greater than

      +
    • +
    • +

      less than or equals

      +
    • +
    • +

      less than

      +
    • +
    • +

      is null

      +
    • +
    • +

      contains (accepts a List with up to 10 elements, or a singular value)

      +
    • +
    • +

      in (accepts a List with up to 10 elements)

      +
    • +
    • +

      not in (accepts a List with up to 10 elements)

      +
    • +
    +
    +
    + + + + + +
    + + +If in operation is used in combination with contains operation, the argument to contains operation has to be a singular value. +
    +
    +
    +

    After writing a custom repository interface specifying just the signatures of these methods, implementations are generated for you and can be used with an auto-wired instance of the repository.

    +
    +
    +
    +

    16.6. Transactions

    +
    +

    Read-only and read-write transactions are provided by TransactionalOperator (see this blog post on reactive transactions for details). +In order to use it, you would need to autowire ReactiveFirestoreTransactionManager like this:

    +
    +
    +
    +
    public class MyApplication {
    +  @Autowired ReactiveFirestoreTransactionManager txManager;
    +}
    +
    +
    +
    +
    +

    After that you will be able to use it to create an instance of TransactionalOperator. +Note that you can switch between read-only and read-write transactions using TransactionDefinition object:

    +
    +
    +
    +
    DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
    +transactionDefinition.setReadOnly(false);
    +TransactionalOperator operator =
    +    TransactionalOperator.create(this.txManager, transactionDefinition);
    +
    +
    +
    +
    +

    When you have an instance of TransactionalOperator, you can invoke a sequence of Firestore operations in a transaction by using operator::transactional:

    +
    +
    +
    +
    User alice = new User("Alice", 29);
    +User bob = new User("Bob", 60);
    +
    +this.userRepository
    +    .save(alice)
    +    .then(this.userRepository.save(bob))
    +    .as(operator::transactional)
    +    .block();
    +
    +this.userRepository
    +    .findAll()
    +    .flatMap(
    +        a -> {
    +          a.setAge(a.getAge() - 1);
    +          return this.userRepository.save(a);
    +        })
    +    .as(operator::transactional)
    +    .collectList()
    +    .block();
    +
    +assertThat(this.userRepository.findAll().map(User::getAge).collectList().block())
    +    .containsExactlyInAnyOrder(28, 59);
    +
    +
    +
    +
    + + + + + +
    + + +Read operations in a transaction can only happen before write operations. +All write operations are applied atomically. +Read documents are locked until the transaction finishes with a commit or a rollback, which are handled by Spring Data. +If an Exception is thrown within a transaction, the rollback operation is performed. +Otherwise, the commit operation is performed. +
    +
    +
    +

    16.6.1. Declarative Transactions with @Transactional Annotation

    +
    +

    This feature requires a bean of SpannerTransactionManager, which is provided when using spring-cloud-gcp-starter-data-firestore.

    +
    +
    +

    FirestoreTemplate and FirestoreReactiveRepository support running methods with the @Transactional annotation as transactions. +If a method annotated with @Transactional calls another method also annotated, then both methods will work within the same transaction.

    +
    +
    +

    One way to use this feature is illustrated here. You would need to do the following:

    +
    +
    +
      +
    1. +

      Annotate your configuration class with the @EnableTransactionManagement annotation.

      +
    2. +
    3. +

      Create a service class that has methods annotated with @Transactional:

      +
    4. +
    +
    +
    +
    +
    class UserService {
    +  @Autowired private UserRepository userRepository;
    +
    +  @Transactional
    +  public Mono<Void> updateUsers() {
    +    return this.userRepository
    +        .findAll()
    +        .flatMap(
    +            a -> {
    +              a.setAge(a.getAge() - 1);
    +              return this.userRepository.save(a);
    +            })
    +        .then();
    +  }
    +}
    +
    +
    +
    +
    +
      +
    1. +

      Make a Spring Bean provider that creates an instance of that class:

      +
    2. +
    +
    +
    +
    +
    @Bean
    +public UserService userService() {
    +  return new UserService();
    +}
    +
    +
    +
    +
    +

    After that, you can autowire your service like so:

    +
    +
    +
    +
    public class MyApplication {
    +  @Autowired UserService userService;
    +}
    +
    +
    +
    +
    +

    Now when you call the methods annotated with @Transactional on your service object, a transaction will be automatically started. +If an error occurs during the execution of a method annotated with @Transactional, the transaction will be rolled back. +If no error occurs, the transaction will be committed.

    +
    +
    +
    +
    +

    16.7. Subcollections

    +
    +

    A subcollection is a collection associated with a specific entity. +Documents in subcollections can contain subcollections as well, allowing you to further nest data. You can nest data up to 100 levels deep.

    +
    +
    + + + + + +
    + + +Deleting a document does not delete its subcollections! +
    +
    +
    +

    To use subcollections you will need to create a FirestoreReactiveOperations object with a parent entity using FirestoreReactiveOperations.withParent call. +You can use this object to save, query and remove entities associated with this parent. +The parent doesn’t have to exist in Firestore, but should have a non-empty id field.

    +
    +
    +

    Autowire FirestoreReactiveOperations:

    +
    +
    +
    +
    @Autowired
    +FirestoreReactiveOperations firestoreTemplate;
    +
    +
    +
    +
    +

    Then you can use this object to create a FirestoreReactiveOperations object with a custom parent:

    +
    +
    +
    +
    FirestoreReactiveOperations bobTemplate =
    +    this.firestoreTemplate.withParent(new User("Bob", 60));
    +
    +PhoneNumber phoneNumber = new PhoneNumber("111-222-333");
    +bobTemplate.save(phoneNumber).block();
    +assertThat(bobTemplate.findAll(PhoneNumber.class).collectList().block())
    +    .containsExactly(phoneNumber);
    +bobTemplate.deleteAll(PhoneNumber.class).block();
    +assertThat(bobTemplate.findAll(PhoneNumber.class).collectList().block()).isEmpty();
    +
    +
    +
    +
    +
    +

    16.8. Update Time and Optimistic Locking

    +
    +

    Firestore stores update time for every document. +If you would like to retrieve it, you can add a field of com.google.cloud.Timestamp type to your entity and annotate it with @UpdateTime annotation.

    +
    +
    +
    +
    @UpdateTime
    +Timestamp updateTime;
    +
    +
    +
    +
    +
    Using update time for optimistic locking
    +
    +

    A field annotated with @UpdateTime can be used for optimistic locking. +To enable that, you need to set version parameter to true:

    +
    +
    +
    +
    @UpdateTime(version = true)
    +Timestamp updateTime;
    +
    +
    +
    +
    +

    When you enable optimistic locking, a precondition will be automatically added to the write request to ensure that the document you are updating was not changed since your last read. +It uses this field’s value as a document version and checks that the version of the document you write is the same as the one you’ve read.

    +
    +
    +

    If the field is empty, a precondition would check that the document with the same id does not exist to ensure you don’t overwrite existing documents unintentionally.

    +
    +
    +
    +
    +

    16.9. Cloud Firestore Spring Boot Starter

    +
    +

    If you prefer using Firestore client only, Spring Framework on Google Cloud provides a convenience starter which automatically configures authentication settings and client objects needed to begin using Google Cloud Firestore in native mode.

    +
    +
    +

    See documentation to learn more about Cloud Firestore.

    +
    +
    +

    To begin using this library, add the spring-cloud-gcp-starter-firestore artifact to your project.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-firestore</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +  implementation("com.google.cloud:spring-cloud-gcp-starter-firestore")
    +}
    +
    +
    +
    +

    16.9.1. Using Cloud Firestore

    +
    +

    The starter automatically configures and registers a Firestore bean in the Spring application context. To start using it, simply use the @Autowired annotation.

    +
    +
    +
    +
    @Autowired
    +Firestore firestore;
    +
    + void writeDocumentFromObject() throws ExecutionException, InterruptedException {
    +   // Add document data with id "joe" using a custom User class
    +   User data =
    +       new User(
    +           "Joe",
    +           Arrays.asList(new Phone(12345, PhoneType.CELL), new Phone(54321, PhoneType.WORK)));
    +
    +   // .get() blocks on response
    +   WriteResult writeResult = this.firestore.document("users/joe").set(data).get();
    +
    +   LOGGER.info("Update time: " + writeResult.getUpdateTime());
    + }
    +
    + User readDocumentToObject() throws ExecutionException, InterruptedException {
    +   ApiFuture<DocumentSnapshot> documentFuture = this.firestore.document("users/joe").get();
    +
    +   User user = documentFuture.get().toObject(User.class);
    +
    +   LOGGER.info("read: " + user);
    +
    +   return user;
    + }
    +
    +
    +
    +
    +
    +
    +

    16.10. Emulator Usage

    +
    +

    The Google Cloud Firebase SDK provides a local, in-memory emulator for Cloud Firestore, which you can use to develop and test your application.

    +
    +
    +

    First follow the Firebase emulator installation steps to install, configure, and run the emulator.

    +
    +
    + + + + + +
    + + +By default, the emulator is configured to run on port 8080; you will need to ensure that the emulator does not run on the same port as your Spring application. +
    +
    +
    +

    Once the Firestore emulator is running, ensure that the following properties are set in your application.properties of your Spring application:

    +
    +
    +
    +
    spring.cloud.gcp.firestore.emulator.enabled=true
    +spring.cloud.gcp.firestore.host-port=${EMULATOR_HOSTPORT}
    +
    +
    +
    +

    From this point onward, your application will connect to your locally running emulator instance instead of the real Firestore service.

    +
    +
    +
    +

    16.11. Samples

    +
    +

    Spring Framework on Google Cloud provides Firestore sample applications to demonstrate API usage:

    +
    + +
    +
    +

    16.12. Test

    +
    +

    Testcontainers provides a gcloud module which offers FirestoreEmulatorContainer. See more at the docs

    +
    +
    +
    +
    +
    +

    17. Cloud Memorystore for Redis

    +
    +
    +

    17.1. Spring Caching

    +
    +

    Cloud Memorystore for Redis provides a fully managed in-memory data store service. +Cloud Memorystore is compatible with the Redis protocol, allowing easy integration with Spring Caching.

    +
    +
    +

    All you have to do is create a Cloud Memorystore instance and use its IP address in application.properties file as spring.redis.host property value. +Everything else is exactly the same as setting up redis-backed Spring caching.

    +
    +
    + + + + + +
    + + +
    +

    Memorystore instances and your application instances have to be located in the same region.

    +
    +
    +
    +
    +

    In short, the following dependencies are needed:

    +
    +
    +
    +
    <dependency>
    +    <groupId>org.springframework.boot</groupId>
    +    <artifactId>spring-boot-starter-cache</artifactId>
    +</dependency>
    +<dependency>
    +    <groupId>org.springframework.boot</groupId>
    +    <artifactId>spring-boot-starter-data-redis</artifactId>
    +</dependency>
    +
    +
    +
    +

    For reactive applications, you can also use spring-boot-starter-data-redis-reactive instead.

    +
    +
    +

    And then you can use org.springframework.cache.annotation.Cacheable annotation for methods you’d like to be cached.

    +
    +
    +
    +
    @Cacheable("cache1")
    +public String hello(@PathVariable String name) {
    +    ....
    +}
    +
    +
    +
    +
    +

    If you are interested in a detailed how-to guide, please check Spring Boot Caching using Cloud Memorystore codelab.

    +
    +
    +

    Cloud Memorystore documentation can be found here.

    +
    +
    +
    +
    +
    +

    18. BigQuery

    +
    +
    +

    Google Cloud BigQuery is a fully managed, petabyte scale, low cost analytics data warehouse.

    +
    +
    +

    Spring Framework on Google Cloud provides:

    +
    +
    +
      +
    • +

      A convenience starter which provides autoconfiguration for the BigQuery client objects with credentials needed to interface with BigQuery.

      +
    • +
    • +

      A Spring Integration message handler for loading data into BigQuery tables in your Spring integration pipelines.

      +
    • +
    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-bigquery</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-bigquery")
    +}
    +
    +
    +
    +

    18.1. Configuration

    +
    +

    The following application properties may be configured with Spring Framework on Google Cloud BigQuery libraries.

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.bigquery.datasetName

    The BigQuery dataset that the BigQueryTemplate and BigQueryFileMessageHandler is scoped to.

    Yes

    spring.cloud.gcp.bigquery.enabled

    Enables or disables Spring Framework on Google Cloud BigQuery autoconfiguration.

    No

    true

    spring.cloud.gcp.bigquery.project-id

    Google Cloud project ID of the project using BigQuery APIs, if different from the one in the Spring Framework on Google Cloud Core Module.

    No

    Project ID is typically inferred from gcloud configuration.

    spring.cloud.gcp.bigquery.credentials.location

    Credentials file location for authenticating with the Google Cloud BigQuery APIs, if different from the ones in the Spring Framework on Google Cloud Core Module

    No

    Inferred from Application Default Credentials, typically set by gcloud.

    spring.cloud.gcp.bigquery.jsonWriterBatchSize

    Batch size which will be used by BigQueryJsonDataWriter while using BigQuery Storage Write API. Note too large or too low values might impact performance.

    No

    1000

    spring.cloud.gcp.bigquery.threadPoolSize

    The size of thread pool of ThreadPoolTaskScheduler which is used by BigQueryTemplate

    No

    4

    +
    +

    18.1.1. BigQuery Client Object

    +
    +

    The GcpBigQueryAutoConfiguration class configures an instance of BigQuery for you by inferring your credentials and Project ID from the machine’s environment.

    +
    +
    +

    Example usage:

    +
    +
    +
    +
    // BigQuery client object provided by our autoconfiguration.
    +@Autowired
    +BigQuery bigquery;
    +
    +public void runQuery() throws InterruptedException {
    +  String query = "SELECT column FROM table;";
    +  QueryJobConfiguration queryConfig =
    +      QueryJobConfiguration.newBuilder(query).build();
    +
    +  // Run the query using the BigQuery object
    +  for (FieldValueList row : bigquery.query(queryConfig).iterateAll()) {
    +    for (FieldValue val : row) {
    +      System.out.println(val);
    +    }
    +  }
    +}
    +
    +
    +
    +
    +

    This object is used to interface with all BigQuery services. +For more information, see the BigQuery Client Library usage examples.

    +
    +
    +
    +

    18.1.2. BigQueryTemplate

    +
    +

    The BigQueryTemplate class is a wrapper over the BigQuery client object and makes it easier to load data into BigQuery tables. +A BigQueryTemplate is scoped to a single dataset. +The autoconfigured BigQueryTemplate instance will use the dataset provided through the property spring.cloud.gcp.bigquery.datasetName.

    +
    +
    +

    Below is a code snippet of how to load a CSV data InputStream to a BigQuery table.

    +
    +
    +
    +
    // BigQuery client object provided by our autoconfiguration.
    +@Autowired
    +BigQueryTemplate bigQueryTemplate;
    +
    +public void loadData(InputStream dataInputStream, String tableName) {
    +  ListenableFuture<Job> bigQueryJobFuture =
    +      bigQueryTemplate.writeDataToTable(
    +          tableName,
    +          dataFile.getInputStream(),
    +          FormatOptions.csv());
    +
    +  // After the future is complete, the data is successfully loaded.
    +  Job job = bigQueryJobFuture.get();
    +}
    +
    +
    +
    +
    +

    Below is a code snippet of how to load a newline-delimited JSON data InputStream to a BigQuery table. This implementation uses the BigQuery Storage Write API. +Here is a sample newline-delimited JSON file which can be used for testing this functionality.

    +
    +
    +
    +
    // BigQuery client object provided by our autoconfiguration.
    +@Autowired
    +BigQueryTemplate bigQueryTemplate;
    +
    +  /**
    +   * This method loads the InputStream of the newline-delimited JSON records to be written in the given table.
    +   * @param tableName name of the table where the data is expected to be written
    +   * @param jsonInputStream InputStream of the newline-delimited JSON records to be written in the given table
    +   */
    +  public void loadJsonStream(String tableName, InputStream jsonInputStream)
    +      throws ExecutionException, InterruptedException {
    +    ListenableFuture<WriteApiResponse> writeApFuture =
    +        bigQueryTemplate.writeJsonStream(tableName, jsonInputStream);
    +    WriteApiResponse apiRes = writeApFuture.get();//get the WriteApiResponse
    +    if (!apiRes.isSuccessful()){
    +      List<StorageError> errors = apiRes.getErrors();
    +      // TODO(developer): process the List of StorageError
    +    }
    +    // else the write process has been successful
    +  }
    +
    +
    +
    +
    +

    Below is a code snippet of how to create table and then load a newline-delimited JSON data InputStream to a BigQuery table. This implementation uses the BigQuery Storage Write API. +Here is a sample newline-delimited JSON file which can be used for testing this functionality.

    +
    +
    +
    +
    // BigQuery client object provided by our autoconfiguration.
    +@Autowired
    +BigQueryTemplate bigQueryTemplate;
    +
    +  /**
    +   * This method created a table with the given name and schema and then loads the InputStream of the newline-delimited JSON records in it.
    +   * @param tableName name of the table where the data is expected to be written
    +   * @param jsonInputStream InputStream of the newline-delimited JSON records to be written in the given table
    +   * @param tableSchema Schema of the table which is required to be created
    +   */
    +  public void createTableAndloadJsonStream(String tableName, InputStream jsonInputStream, Schema tableSchema)
    +      throws ExecutionException, InterruptedException {
    +    ListenableFuture<WriteApiResponse> 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()){
    +      List<StorageError> errors = apiRes.getErrors();
    +      // TODO(developer): process the List of StorageError
    +    }
    +    // else the write process has been successful
    +  }
    +
    +
    +
    +
    +
    +
    +

    18.2. Spring Integration

    +
    +

    Spring Framework on Google Cloud BigQuery also provides a Spring Integration message handler BigQueryFileMessageHandler. +This is useful for incorporating BigQuery data loading operations in a Spring Integration pipeline.

    +
    +
    +

    Below is an example configuring a ServiceActivator bean using the BigQueryFileMessageHandler.

    +
    +
    +
    +
    @Bean
    +public DirectChannel bigQueryWriteDataChannel() {
    +  return new DirectChannel();
    +}
    +
    +@Bean
    +public DirectChannel bigQueryJobReplyChannel() {
    +  return new DirectChannel();
    +}
    +
    +@Bean
    +@ServiceActivator(inputChannel = "bigQueryWriteDataChannel")
    +public MessageHandler messageSender(BigQueryTemplate bigQueryTemplate) {
    +  BigQueryFileMessageHandler messageHandler = new BigQueryFileMessageHandler(bigQueryTemplate);
    +  messageHandler.setFormatOptions(FormatOptions.csv());
    +  messageHandler.setOutputChannel(bigQueryJobReplyChannel());
    +  return messageHandler;
    +}
    +
    +
    +
    +
    +

    18.2.1. BigQuery Message Handling

    +
    +

    The BigQueryFileMessageHandler accepts the following message payload types for loading into BigQuery: java.io.File, byte[], org.springframework.core.io.Resource, and java.io.InputStream. +The message payload will be streamed and written to the BigQuery table you specify.

    +
    +
    +

    By default, the BigQueryFileMessageHandler is configured to read the headers of the messages it receives to determine how to load the data. +The headers are specified by the class BigQuerySpringMessageHeaders and summarized below.

    +
    + ++++ + + + + + + + + + + + + + + +

    Header

    Description

    BigQuerySpringMessageHeaders.TABLE_NAME

    Specifies the BigQuery table within your dataset to write to.

    BigQuerySpringMessageHeaders.FORMAT_OPTIONS

    Describes the data format of your data to load (i.e. CSV, JSON, etc.).

    +
    +

    Alternatively, you may omit these headers and explicitly set the table name or format options by calling setTableName(…​) and setFormatOptions(…​).

    +
    +
    +
    +

    18.2.2. BigQuery Message Reply

    +
    +

    After the BigQueryFileMessageHandler processes a message to load data to your BigQuery table, it will respond with a Job on the reply channel. +The 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<Job> 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.

    +
    +
    + + + + + +
    + + +If you decide to use Spring Integration Gateways and you wish to receive ListenableFuture<Job> 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. +
    +
    +
    +
    +
    +

    18.3. Sample

    +
    +

    A BigQuery sample application is available.

    +
    +
    +
    +
    +
    +

    19. Cloud IAP

    +
    +
    +

    Cloud Identity-Aware Proxy (IAP) provides a security layer over applications deployed to Google Cloud.

    +
    +
    +

    The IAP starter uses Spring Security OAuth 2.0 Resource Server functionality to automatically extract user identity from the proxy-injected x-goog-iap-jwt-assertion HTTP header.

    +
    +
    +

    The following claims are validated automatically:

    +
    +
    +
      +
    • +

      Issue time

      +
    • +
    • +

      Expiration time

      +
    • +
    • +

      Issuer

      +
    • +
    • +

      Audience

      +
    • +
    +
    +
    +

    The audience ("aud" claim) validation string is automatically determined when the application is running on App Engine Standard or App Engine Flexible. +This functionality relies on Cloud Resource Manager API to retrieve project details, so the following setup is needed:

    +
    +
    +
      +
    • +

      Enable Cloud Resource Manager API in Google Cloud Console.

      +
    • +
    • +

      Make sure your application has resourcemanager.projects.get permission.

      +
    • +
    +
    +
    +

    App Engine automatic audience determination can be overridden by using spring.cloud.gcp.security.iap.audience property. It supports multiple allowable audiences by providing a comma-delimited list.

    +
    +
    +

    For Compute Engine or Kubernetes Engine spring.cloud.gcp.security.iap.audience property must be provided, as the audience string depends on the specific Backend Services setup and cannot be inferred automatically. +To determine the audience value, follow directions in IAP Verify the JWT payload guide. +If spring.cloud.gcp.security.iap.audience is not provided, the application will fail to start the following message:

    +
    +
    +
    +
    No qualifying bean of type 'com.google.cloud.spring.security.iap.AudienceProvider' available.
    +
    +
    +
    + + + + + +
    + + +If you create a custom WebSecurityConfigurerAdapter, enable extracting user identity by adding .oauth2ResourceServer().jwt() configuration to the HttpSecurity object. + If no custom WebSecurityConfigurerAdapter is present, nothing needs to be done because Spring Boot will add this customization by default. +
    +
    +
    +

    Starter Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-security-iap</artifactId>
    +</dependency>
    +
    +
    +
    +

    Starter Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-security-iap")
    +}
    +
    +
    +
    +

    19.1. Configuration

    +
    +

    The following properties are available.

    +
    +
    + + + + + +
    + + +Modifying registry, algorithm, and header properties might be useful for testing, but the defaults should not be changed in production. +
    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredDefault

    spring.cloud.gcp.security.iap.registry

    Link to JWK public key registry.

    true

    www.gstatic.com/iap/verify/public_key-jwk

    spring.cloud.gcp.security.iap.algorithm

    Encryption algorithm used to sign the JWK token.

    true

    ES256

    spring.cloud.gcp.security.iap.header

    Header from which to extract the JWK key.

    true

    x-goog-iap-jwt-assertion

    spring.cloud.gcp.security.iap.issuer

    JWK issuer to verify.

    true

    cloud.google.com/iap

    spring.cloud.gcp.security.iap.audience

    Custom JWK audience to verify.

    false on App Engine; true on GCE/GKE

    +
    +
    +

    19.2. Sample

    +
    +

    A sample application is available.

    +
    +
    +
    +
    +
    +

    20. Cloud Vision

    +
    +
    +

    The Google Cloud Vision API allows users to leverage machine learning algorithms for processing images and documents including: image classification, face detection, text extraction, optical character recognition, and others.

    +
    +
    +

    Spring Framework on Google Cloud provides:

    +
    +
    +
      +
    • +

      A convenience starter which automatically configures authentication settings and client objects needed to begin using the Google Cloud Vision API.

      +
    • +
    • +

      CloudVisionTemplate which simplifies interactions with the Cloud Vision API.

      +
      +
        +
      • +

        Allows you to easily send images, PDF, TIFF and GIF documents to the API as Spring Resources.

        +
      • +
      • +

        Offers convenience methods for common operations, such as classifying content of an image.

        +
      • +
      +
      +
    • +
    • +

      DocumentOcrTemplate which offers convenient methods for running optical character recognition (OCR) on PDF and TIFF documents.

      +
    • +
    +
    +
    +

    20.1. Dependency Setup

    +
    +

    To begin using this library, add the spring-cloud-gcp-starter-vision artifact to your project.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +  <groupId>com.google.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-starter-vision</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +  implementation("com.google.cloud:spring-cloud-gcp-starter-vision")
    +}
    +
    +
    +
    +
    +

    20.2. Configuration

    +
    +

    The following options may be configured with Spring Framework on Google Cloud Vision libraries.

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.vision.enabled

    Enables or disables Cloud Vision autoconfiguration

    No

    true

    spring.cloud.gcp.vision.executors-threads-count

    Number of threads used during document OCR processing for waiting on long-running OCR operations

    No

    1

    spring.cloud.gcp.vision.json-output-batch-size

    Number of document pages to include in each OCR output file.

    No

    20

    +
    +

    20.2.1. Cloud Vision OCR Dependencies

    +
    +

    If you are interested in applying optical character recognition (OCR) on documents for your project, you’ll need to add both spring-cloud-gcp-starter-vision and spring-cloud-gcp-starter-storage to your dependencies. +The storage starter is necessary because the Cloud Vision API will process your documents and write OCR output files all within your Google Cloud Storage buckets.

    +
    +
    +

    Maven coordinates using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +  <groupId>com.google.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-starter-vision</artifactId>
    +</dependency>
    +<dependency>
    +  <groupId>com.google.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-starter-storage</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +  implementation("com.google.cloud:spring-cloud-gcp-starter-vision")
    +  implementation("com.google.cloud:spring-cloud-gcp-starter-storage")
    +}
    +
    +
    +
    +
    +
    +

    20.3. Image Analysis

    +
    +

    The CloudVisionTemplate allows you to easily analyze images; it provides the following method for interfacing with Cloud Vision:

    +
    +
    +

    public AnnotateImageResponse analyzeImage(Resource imageResource, Feature.Type…​ featureTypes)

    +
    +
    +

    Parameters:

    +
    +
    +
      +
    • +

      Resource imageResource refers to the Spring Resource of the image object you wish to analyze. +The Google Cloud Vision documentation provides a list of the image types that they support.

      +
    • +
    • +

      Feature.Type…​ featureTypes refers to a var-arg array of Cloud Vision Features to extract from the image. +A feature refers to a kind of image analysis one wishes to perform on an image, such as label detection, OCR recognition, facial detection, etc. +One may specify multiple features to analyze within one request. +A full list of Cloud Vision Features is provided in the Cloud Vision Feature docs.

      +
    • +
    +
    +
    +

    Returns:

    +
    +
    +
      +
    • +

      AnnotateImageResponse contains the results of all the feature analyses that were specified in the request. +For each feature type that you provide in the request, AnnotateImageResponse provides a getter method to get the result of that feature analysis. +For example, if you analyzed an image using the LABEL_DETECTION feature, you would retrieve the results from the response using annotateImageResponse.getLabelAnnotationsList().

      +
      +

      AnnotateImageResponse is provided by the Google Cloud Vision libraries; please consult the RPC reference or Javadoc for more details. +Additionally, you may consult the Cloud Vision docs to familiarize yourself with the concepts and features of the API.

      +
      +
    • +
    +
    +
    +

    20.3.1. Detect Image Labels Example

    +
    +

    Image labeling refers to producing labels that describe the contents of an image. +Below is a code sample of how this is done using the Cloud Vision Spring Template.

    +
    +
    +
    +
    @Autowired
    +private ResourceLoader resourceLoader;
    +
    +@Autowired
    +private CloudVisionTemplate cloudVisionTemplate;
    +
    +public void processImage() {
    +  Resource imageResource = this.resourceLoader.getResource("my_image.jpg");
    +  AnnotateImageResponse response = this.cloudVisionTemplate.analyzeImage(
    +      imageResource, Type.LABEL_DETECTION);
    +  System.out.println("Image Classification results: " + response.getLabelAnnotationsList());
    +}
    +
    +
    +
    +
    +
    +
    +

    20.4. File Analysis

    +
    +

    The CloudVisionTemplate allows you to easily analyze PDF, TIFF and GIF documents; it provides the following method for interfacing with Cloud Vision:

    +
    +
    +

    public AnnotateFileResponse analyzeFile(Resource fileResource, String mimeType, Feature.Type…​ featureTypes)

    +
    +
    +

    Parameters:

    +
    +
    +
      +
    • +

      Resource fileResource refers to the Spring Resource of the PDF, TIFF or GIF object you wish to analyze. +Documents with more than 5 pages are not supported.

      +
    • +
    • +

      String mimeType is the mime type of the fileResource. +Currently, only application/pdf, image/tiff and image/gif are supported.

      +
    • +
    • +

      Feature.Type…​ featureTypes refers to a var-arg array of Cloud Vision Features to extract from the document. +A feature refers to a kind of image analysis one wishes to perform on a document, such as label detection, OCR recognition, facial detection, etc. +One may specify multiple features to analyze within one request. +A full list of Cloud Vision Features is provided in the Cloud Vision Feature docs.

      +
    • +
    +
    +
    +

    Returns:

    +
    +
    +
      +
    • +

      AnnotateFileResponse contains the results of all the feature analyses that were specified in the request. +For each page of the analysed document the response will contain an AnnotateImageResponse object which you can retrieve using annotateFileResponse.getResponsesList(). +For each feature type that you provide in the request, AnnotateImageResponse provides a getter method to get the result of that feature analysis. +For example, if you analysed an PDF using the DOCUMENT_TEXT_DETECTION feature, you would retrieve the results from the response using annotateImageResponse.getFullTextAnnotation().getText().

      +
      +

      AnnotateFileResponse is provided by the Google Cloud Vision libraries; please consult the RPC reference or Javadoc for more details. +Additionally, you may consult the Cloud Vision docs to familiarize yourself with the concepts and features of the API.

      +
      +
    • +
    +
    +
    +

    20.4.1. Running Text Detection Example

    +
    +

    Detect text in files refers to extracting text from small document such as PDF or TIFF. +Below is a code sample of how this is done using the Cloud Vision Spring Template.

    +
    +
    +
    +
    @Autowired
    +private ResourceLoader resourceLoader;
    +
    +@Autowired
    +private CloudVisionTemplate cloudVisionTemplate;
    +
    +public void processPdf() {
    +  Resource imageResource = this.resourceLoader.getResource("my_file.pdf");
    +  AnnotateFileResponse response =
    +    this.cloudVisionTemplate.analyzeFile(
    +        imageResource, "application/pdf", Type.DOCUMENT_TEXT_DETECTION);
    +
    +  response
    +    .getResponsesList()
    +    .forEach(
    +        annotateImageResponse ->
    +            System.out.println(annotateImageResponse.getFullTextAnnotation().getText()));
    +}
    +
    +
    +
    +
    +
    +
    +

    20.5. Document OCR Template

    +
    +

    The DocumentOcrTemplate allows you to easily run optical character recognition (OCR) on your PDF and TIFF documents stored in your Google Storage bucket.

    +
    +
    +

    First, you will need to create a bucket in Google Cloud Storage and upload the documents you wish to process into the bucket.

    +
    +
    +

    20.5.1. Running OCR on a Document

    +
    +

    When OCR is run on a document, the Cloud Vision APIs will output a collection of OCR output files in JSON which describe the text content, bounding rectangles of words and letters, and other information about the document.

    +
    +
    +

    The DocumentOcrTemplate provides the following method for running OCR on a document saved in Google Cloud Storage:

    +
    +
    +

    ListenableFuture<DocumentOcrResultSet> 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.

    +
    +
    + + + + + +
    + + +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. +
    +
    +
    +
    +

    20.5.2. Running OCR Example

    +
    +

    Below is a code snippet of how to run OCR on a document stored in a Google Storage bucket and read the text in the first page of the document.

    +
    +
    +
    +
    @Autowired
    +private DocumentOcrTemplate documentOcrTemplate;
    +
    +public void runOcrOnDocument() {
    +    GoogleStorageLocation document = GoogleStorageLocation.forFile(
    +            "your-bucket", "test.pdf");
    +    GoogleStorageLocation outputLocationPrefix = GoogleStorageLocation.forFolder(
    +            "your-bucket", "output_folder/test.pdf/");
    +
    +    ListenableFuture<DocumentOcrResultSet> result =
    +        this.documentOcrTemplate.runOcrForDocument(
    +            document, outputLocationPrefix);
    +
    +    DocumentOcrResultSet ocrPages = result.get(5, TimeUnit.MINUTES);
    +
    +    String page1Text = ocrPages.getPage(1).getText();
    +    System.out.println(page1Text);
    +}
    +
    +
    +
    +
    +

    20.5.3. Reading OCR Output Files

    +
    +

    In some use-cases, you may need to directly read OCR output files stored in Google Cloud Storage.

    +
    +
    +

    DocumentOcrTemplate offers the following methods for reading and processing OCR output files:

    +
    +
    +
      +
    • +

      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.

      +
    • +
    • +

      readOcrOutputFile(GoogleStorageLocation jsonFile): +Reads a single OCR output file and returns the parsed contents.

      +
    • +
    +
    +
    +
    +

    20.5.4. Reading OCR Output Files Example

    +
    +

    The code snippet below describes how to read the OCR output files of a single document.

    +
    +
    +
    +
    @Autowired
    +private DocumentOcrTemplate documentOcrTemplate;
    +
    +// Parses the OCR output files corresponding to a single document in a directory
    +public void parseOutputFileSet() {
    +  GoogleStorageLocation ocrOutputPrefix = GoogleStorageLocation.forFolder(
    +      "your-bucket", "json_output_set/");
    +
    +  DocumentOcrResultSet result = this.documentOcrTemplate.readOcrOutputFileSet(ocrOutputPrefix);
    +  System.out.println("Page 2 text: " + result.getPage(2).getText());
    +}
    +
    +// Parses a single OCR output file
    +public void parseSingleOutputFile() {
    +  GoogleStorageLocation ocrOutputFile = GoogleStorageLocation.forFile(
    +      "your-bucket", "json_output_set/test_output-2-to-2.json");
    +
    +  DocumentOcrResultSet result = this.documentOcrTemplate.readOcrOutputFile(ocrOutputFile);
    +  System.out.println("Page 2 text: " + result.getPage(2).getText());
    +}
    +
    +
    +
    +
    +
    +

    20.6. Sample

    +
    +

    Samples are provided to show example usages of Spring Framework on Google Cloud with Google Cloud Vision.

    +
    +
    +
      +
    • +

      The Image Labeling Sample shows you how to use image labelling in your Spring application. +The application generates labels describing the content inside the images you specify in the application.

      +
    • +
    • +

      The Document OCR demo shows how you can apply OCR processing on your PDF/TIFF documents in order to extract their text contents.

      +
    • +
    +
    +
    +
    +
    +
    +

    21. Secret Manager

    +
    +
    +

    Google Cloud Secret Manager is a secure and convenient method for storing API keys, passwords, certificates, and other sensitive data. +A detailed summary of its features can be found in the Secret Manager documentation.

    +
    +
    +

    Spring Framework on Google Cloud provides:

    +
    +
    +
      +
    • +

      A property source which allows you to specify and load the secrets of your Google Cloud project into your application context as a Bootstrap Property Source.

      +
    • +
    • +

      A SecretManagerTemplate which allows you to read, write, and update secrets in Secret Manager.

      +
    • +
    +
    +
    +

    21.1. Dependency Setup

    +
    +

    To begin using this library, add the spring-cloud-gcp-starter-secretmanager artifact to your project.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +  <groupId>com.google.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-starter-secretmanager</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +  implementation("com.google.cloud:spring-cloud-gcp-starter-secretmanager")
    +}
    +
    +
    +
    +

    21.1.1. Configuration

    +
    +

    By default, Spring Framework on Google Cloud Secret Manager will authenticate using Application Default Credentials. +This can be overridden using the authentication properties.

    +
    +
    + + + + + +
    + + +All the below settings must be specified in a 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 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 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 Application Default Credentials.

    spring.cloud.gcp.secretmanager.allow-default-secret

    Define the behavior when accessing a non-existent secret string/bytes.
    +If set to true, null will be returned when accessing a non-existent secret; otherwise throwing an exception.

    No

    false

    +
    +
    +
    +

    21.2. Secret Manager Property Source

    +
    +

    The Spring Framework on Google Cloud integration for Google Cloud Secret Manager enables you to use Secret Manager as a bootstrap property source.

    +
    +
    +

    This allows you to specify and load secrets from Google Cloud Secret Manager as properties into the application context during the Bootstrap Phase, which refers to the initial phase when a Spring application is being loaded.

    +
    +
    +

    The Secret Manager property source uses the following syntax to specify secrets:

    +
    +
    +
    +
    # 1. Long form - specify the project ID, secret ID, and version
    +sm://projects/<project-id>/secrets/<secret-id>/versions/<version-id>}
    +
    +# 2.  Long form - specify project ID, secret ID, and use latest version
    +sm://projects/<project-id>/secrets/<secret-id>
    +
    +# 3. Short form - specify project ID, secret ID, and version
    +sm://<project-id>/<secret-id>/<version-id>
    +
    +# 4. Short form - default project; specify secret + version
    +#
    +# The project is inferred from the spring.cloud.gcp.secretmanager.project-id setting
    +# in your bootstrap.properties (see Configuration) or from application-default credentials if
    +# this is not set.
    +sm://<secret-id>/<version>
    +
    +# 5. Shortest form - specify secret ID, use default project and latest version.
    +sm://<secret-id>
    +
    +
    +
    +

    You can use this syntax in the following places:

    +
    +
    +
      +
    1. +

      In your application.properties or bootstrap.properties files:

      +
      +
      +
      # Example of the project-secret long-form syntax.
      +spring.datasource.password=${sm://projects/my-gcp-project/secrets/my-secret}
      +
      +
      +
    2. +
    3. +

      Access the value using the @Value annotation.

      +
      +
      +
      // Example of using shortest form syntax.
      +@Value("${sm://my-secret}")
      +
      +
      +
    4. +
    +
    +
    +
    +

    21.3. Secret Manager Template

    +
    +

    The SecretManagerTemplate class simplifies operations of creating, updating, and reading secrets.

    +
    +
    +

    To begin using this class, you may inject an instance of the class using @Autowired after adding the starter dependency to your project.

    +
    +
    +
    +
    @Autowired
    +private SecretManagerTemplate secretManagerTemplate;
    +
    +
    +
    +
    +

    Please consult SecretManagerOperations for information on what operations are available for the Secret Manager template.

    +
    +
    +
    +

    21.4. Refresh secrets without restarting the application

    +
    +
      +
    1. +

      Before running your application, change the project’s configuration files as follows:

      +
      +

      import the actuator starter dependency to your project,

      +
      +
      +
      +
      <dependency>
      +    <groupId>org.springframework.boot</groupId>
      +    <artifactId>spring-boot-starter-actuator</artifactId>
      +</dependency>
      +
      +
      +
      +

      add the following properties to your project’s application.properties. The latter is used to enable Spring Boot’s Config Data API.

      +
      +
      +
      +
      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.

      +
      +
      +
      +
      spring.cloud.gcp.secretmanager.legacy=false
      +
      +
      +
    2. +
    3. +

      After running the application, update your secret stored in the Secret Manager.

      +
    4. +
    5. +

      To refresh the secret, send the following command to your application sever:

      +
      +
      +
      curl -X POST http://[host]:[port]/actuator/refresh
      +
      +
      +
      +

      Note that only @ConfigurationProperties annotated with @RefreshScope support updating secrets without restarting the application.

      +
      +
    6. +
    +
    +
    +
    +

    21.5. Allow default secret

    +
    +

    By default, when accessing a non-existent secret, the Secret Manager will throw an exception.

    +
    +
    +

    However, if your want to use a default value in such a scenario, you can add the following property to project’s properties.

    +
    +
    +
    +
    `spring.cloud.gcp.secretmanager.allow-default-secret=true`
    +
    +
    +
    +

    Therefore, a variable annotated with @Value("${${sm://application-fake}:DEFAULT}") will be resolved as DEFAULT when there is no application-fake in Secret Manager and application-fake is NOT a valid application property.

    +
    +
    +
    +

    21.6. Sample

    +
    +

    A Secret Manager Sample Application is provided which demonstrates basic property source loading and usage of the template class.

    +
    +
    +
    +
    +
    +

    22. Google Cloud Key Management Service

    +
    +
    +

    The Google Cloud Key Management Service (KMS) allows you to create, import, and manage cryptographic keys and perform cryptographic operations in a single centralized cloud service.

    +
    +
    +

    Spring Framework on Google Cloud offers a utility template class KmsTemplate which allows you to conveniently encrypt and decrypt binary or text data.

    +
    +
    +

    22.1. Dependency Setup

    +
    +

    To begin using this library, add the spring-cloud-gcp-starter-kms artifact to your project.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +  <groupId>com.google.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-starter-kms</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +  implementation("com.google.cloud:spring-cloud-gcp-starter-kms")
    +}
    +
    +
    +
    +
    +

    22.2. Configuration

    +
    +

    The following options may be configured with Spring Framework on Google Cloud KMS libraries.

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.kms.enabled

    Enables or disables Google Cloud KMS autoconfiguration

    No

    true

    spring.cloud.gcp.kms.project-id

    Google Cloud project ID of the project using Cloud KMS APIs, if different from the one in the Spring Framework on Google Cloud Core Module.

    No

    Project ID is typically inferred from gcloud configuration.

    spring.cloud.gcp.kms.credentials.location

    Credentials file location for authenticating with the Cloud KMS APIs, if different from the ones in the Spring Framework on Google Cloud Core Module

    No

    Inferred from Application Default Credentials, typically set by gcloud.

    +
    +
    +

    22.3. Basic Usage

    +
    +

    Once you have added the spring-cloud-gcp-starter-kms to your project, the autoconfiguration class com.google.cloud.spring.autoconfigure.kms.GcpKmsAutoConfiguration will be activated for your project.

    +
    +
    +

    The com.google.cloud.spring.kms.KmsTemplate bean provided by the autoconfiguration is the entrypoint to using Spring Framework on Google Cloud support for Google KMS. This class allows you to specify a Cloud KMS key in your project via a URI string (format described below) and perform encryption/decryption with it.

    +
    +
    +

    The template class automatically validates the CRC32 checksums received responses from Cloud KMS APIs to verify correctness of the response.

    +
    +
    +

    22.3.1. Cloud KMS Key ID format

    +
    +

    Spring Framework on Google Cloud offers the following key syntax to specify Cloud KMS keys in your project:

    +
    +
    +
    +
     1. Shortest form - specify the key by key ring ID, and key ID.
    + The project is inferred from the spring.cloud.gcp.kms.project-id if set, otherwise
    + the default Google Cloud project (such as using application-default-credentials) is used.
    + The location is assumed to be `global`.
    +
    + {key-ring-id}/{key-id}
    +
    + 2. Short form - specify the key by location ID, key ring ID, and key ID.
    + The project is inferred from the spring.cloud.gcp.kms.project-id if set, otherwise
    + the default Google Cloud project (such as using application-default-credentials) is used.
    +
    + {location-id}/{key-ring-id}/{key-id}
    +
    + 3. Full form - specify project ID, location ID, key ring ID, and key ID
    +
    + {project-id}/{location-id}/{key-ring-id}/{key-id}
    +
    + 4. Long form - specify project ID, location ID, key ring ID, and key ID.
    + This format is equivalent to the fully-qualified resource name of a Cloud KMS key.
    +
    + projects/{project-id}/locations/{location-id}/keyRings/{key-ring-id}/cryptoKeys/{key-id}
    +
    +
    +
    +
    +
    +

    22.4. Sample

    +
    +

    A Cloud KMS Sample Application is provided which demonstrates basic encryption and decryption operations.

    +
    +
    +
    +
    +
    +

    23. Cloud Runtime Configuration API

    +
    +
    + + + + + +
    + + +The Google Cloud Runtime Configuration service is in Beta status, and is only available in snapshot and milestone versions of the project. It’s also not available in the Spring Framework on Google Cloud BOM, unlike other modules. +
    +
    +
    +

    Spring Framework on Google Cloud makes it possible to use the Google Runtime Configuration API as a Spring Cloud Config server to remotely store your application configuration data.

    +
    +
    +

    The Spring Framework on Google Cloud Config support is provided via its own Spring Boot starter. +It enables the use of the Google Runtime Configuration API as a source for Spring Boot configuration properties.

    +
    +
    +

    Maven coordinates:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-config</artifactId>
    +    <version>3.8.13</version>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    compile group: 'org.springframework.cloud',
    +    name: 'spring-cloud-gcp-starter-config',
    +    version: '3.8.13'
    +}
    +
    +
    +
    +

    23.1. Configuration

    +
    +

    The following parameters are configurable in Spring Framework on Google Cloud Config:

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.config.enabled

    Enables the Config client

    No

    false

    spring.cloud.gcp.config.name

    Name of your application

    No

    Value of the spring.application.name property. +If none, application

    spring.cloud.gcp.config.profile

    Active profile

    No

    Value of the spring.profiles.active property. +If more than a single profile, last one is chosen

    spring.cloud.gcp.config.timeout-millis

    Timeout in milliseconds for connecting to the Google Runtime Configuration API

    No

    60000

    spring.cloud.gcp.config.project-id

    Google Cloud project ID where the Google Runtime Configuration API is hosted

    No

    spring.cloud.gcp.config.credentials.location

    OAuth2 credentials for authenticating with the Google Runtime Configuration API

    No

    spring.cloud.gcp.config.credentials.encoded-key

    Base64-encoded OAuth2 credentials for authenticating with the Google Runtime Configuration API

    No

    spring.cloud.gcp.config.credentials.scopes

    OAuth2 scope for Spring Framework on Google Cloud Config credentials

    No

    www.googleapis.com/auth/cloudruntimeconfig

    +
    + + + + + +
    + + +These properties should be specified in a bootstrap.yml/bootstrap.properties file, rather than the usual applications.yml/application.properties. +
    +
    +
    + + + + + +
    + + +Core properties, as described in Spring Framework on Google Cloud Core Module, do not apply to Spring Framework on Google Cloud Config. +
    +
    +
    +
    +

    23.2. Quick start

    +
    +
      +
    1. +

      Create a configuration in the Google Runtime Configuration API that is called ${spring.application.name}_${spring.profiles.active}. +In other words, if spring.application.name is myapp and spring.profiles.active is prod, the configuration should be called myapp_prod.

      +
      +

      In order to do that, you should have the Google Cloud SDK installed, own a Google Cloud Project and run the following command:

      +
      +
      +
      +
      gcloud init # if this is your first Google Cloud SDK run.
      +gcloud beta runtime-config configs create myapp_prod
      +gcloud beta runtime-config configs variables set myapp.queue-size 25 --config-name myapp_prod
      +
      +
      +
    2. +
    3. +

      Configure your bootstrap.properties file with your application’s configuration data:

      +
      +
      +
      spring.application.name=myapp
      +spring.profiles.active=prod
      +
      +
      +
    4. +
    5. +

      Add the @ConfigurationProperties annotation to a Spring-managed bean:

      +
      +
      +
      @Component
      +@ConfigurationProperties("myapp")
      +public class SampleConfig {
      +
      +  private int queueSize;
      +
      +  public int getQueueSize() {
      +    return this.queueSize;
      +  }
      +
      +  public void setQueueSize(int queueSize) {
      +    this.queueSize = queueSize;
      +  }
      +}
      +
      +
      +
    6. +
    +
    +
    +

    When your Spring application starts, the queueSize field value will be set to 25 for the above SampleConfig bean.

    +
    +
    +
    +

    23.3. Refreshing the configuration at runtime

    +
    +

    Spring Cloud provides support to have configuration parameters be reloadable with the POST request to /actuator/refresh endpoint.

    +
    +
    +
      +
    1. +

      Add the Spring Boot Actuator dependency:

      +
      +

      Maven coordinates:

      +
      +
      +
      +
      <dependency>
      +    <groupId>org.springframework.boot</groupId>
      +    <artifactId>spring-boot-starter-actuator</artifactId>
      +</dependency>
      +
      +
      +
      +

      Gradle coordinates:

      +
      +
      +
      +
      dependencies {
      +    implementation("org.springframework.boot:spring-boot-starter-actuator")
      +}
      +
      +
      +
    2. +
    3. +

      Add @RefreshScope to your Spring configuration class to have parameters be reloadable at runtime.

      +
    4. +
    5. +

      Add management.endpoints.web.exposure.include=refresh to your application.properties to allow unrestricted access to /actuator/refresh.

      +
    6. +
    7. +

      Update a property with gcloud:

      +
      +
      +
      $ gcloud beta runtime-config configs variables set \
      +  myapp.queue_size 200 \
      +  --config-name myapp_prod
      +
      +
      +
    8. +
    9. +

      Send a POST request to the refresh endpoint:

      +
      +
      +
      $ curl -XPOST https://myapp.host.com/actuator/refresh
      +
      +
      +
    10. +
    +
    +
    +
    +

    23.4. Sample

    +
    +

    A sample application and a codelab are available.

    +
    +
    +
    +
    +
    +

    24. Cloud Foundry

    +
    +
    +

    Spring Framework on Google Cloud provides support for Cloud Foundry’s GCP Service Broker. +Our Pub/Sub, Cloud Spanner, Storage, Cloud Trace and Cloud SQL MySQL and PostgreSQL starters are Cloud Foundry aware and retrieve properties like project ID, credentials, etc., that are used in auto configuration from the Cloud Foundry environment.

    +
    +
    +

    In order to take advantage of the Cloud Foundry support make sure the following dependency is added:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-cloudfoundry</artifactId>
    +</dependency>
    +
    +
    +
    +

    In cases like Pub/Sub’s topic and subscription, or Storage’s bucket name, where those parameters are not used in auto configuration, you can fetch them using the VCAP mapping provided by Spring Boot. +For example, to retrieve the provisioned Pub/Sub topic, you can use the vcap.services.mypubsub.credentials.topic_name property from the application environment.

    +
    +
    + + + + + +
    + + +If the same service is bound to the same application more than once, the auto configuration will not be able to choose among bindings and will not be activated for that service. +This includes both MySQL and PostgreSQL bindings to the same app. +
    +
    +
    + + + + + +
    + + +In order for the Cloud SQL integration to work in Cloud Foundry, auto-reconfiguration must be disabled. +You can do so using the cf set-env <APP> JBP_CONFIG_SPRING_AUTO_RECONFIGURATION '{enabled: false}' command. +Otherwise, Cloud Foundry will produce a DataSource with an invalid JDBC URL (i.e., jdbc:mysql://null/null). +
    +
    +
    +

    24.1. User-Provided Services

    +
    +

    User-provided services enable developers to use services that are not available in the marketplace with their apps running on Cloud Foundry. +For example, you may want to use a user-provided service that points to a shared Google Service (like Cloud Spanner) used across your organization.

    +
    +
    +

    In order for Spring Framework on Google Cloud to detect your user-provided service as a Google Cloud Service, you must add an instance tag indicating the Google Cloud Service it uses. +The tag should simply be the Cloud Foundry name for the Google Service.

    +
    +
    +

    For example, if you create a user-provided service using Cloud Spanner, you might run:

    +
    +
    +
    +
    $ cf create-user-provided-service user-spanner-service -t "google-spanner" ...
    +
    +
    +
    +

    This allows Spring Framework on Google Cloud to retrieve the correct service properties from Cloud Foundry and use them in the auto configuration for your application.

    +
    +
    +

    A mapping of Google service names to Cloud Foundry names are provided below:

    +
    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Google Cloud Service

    Cloud Foundry Name (add this as a tag)

    Google Cloud Pub/Sub

    google-pubsub

    Google Cloud Storage

    google-storage

    Google Cloud Spanner

    google-spanner

    Datastore

    google-datastore

    Firestore

    google-firestore

    BigQuery

    google-bigquery

    Cloud Trace

    google-stackdriver-trace

    Cloud Sql (MySQL)

    google-cloudsql-mysql

    Cloud Sql (PostgreSQL)

    google-cloudsql-postgres

    +
    +
    +
    +
    +

    25. Kotlin Support

    +
    +
    +

    The latest version of the Spring Framework provides first-class support for Kotlin. +For Kotlin users of Spring, the Spring Framework on Google Cloud libraries work out-of-the-box and are fully interoperable with Kotlin applications.

    +
    +
    +

    For more information on building a Spring application in Kotlin, please consult the Spring Kotlin documentation.

    +
    +
    +

    25.1. Prerequisites

    +
    +

    Ensure that your Kotlin application is properly set up. +Based on your build system, you will need to include the correct Kotlin build plugin in your project:

    +
    + +
    +

    Depending on your application’s needs, you may need to augment your build configuration with compiler plugins:

    +
    +
    + +
    +
    +

    Once your Kotlin project is properly configured, the Spring Framework on Google Cloud libraries will work within your application without any additional setup.

    +
    +
    +
    +

    25.2. Sample

    +
    +

    A Kotlin sample application is provided to demonstrate a working Maven setup and various Spring Framework on Google Cloud integrations from within Kotlin.

    +
    +
    +
    +
    +
    +

    26. Configuration properties

    +
    +
    +

    To see the list of all Google Cloud related configuration properties please check the Appendix page.

    +
    +
    +
    +
    +

    27. Migration Guide from Spring Framework on Google Cloud 1.x to 2.x

    +
    +
    +

    27.1. Maven Group ID Change

    +
    +

    Spring Cloud unbundled Spring Framework on Google Cloud and other cloud providers from their release train. +To use the newly unbundled libraries, add the spring-cloud-gcp-dependencies bill of materials (BOM) and change the spring-cloud-gcp group IDs in your pom.xml files:

    +
    +
    +
    Before (pom.xml)
    +
    +
    <dependencyManagement>
    +  <dependencies>
    +    <dependency>
    +      <groupId>org.springframework.cloud</groupId>
    +      <artifactId>spring-cloud-dependencies</artifactId>
    +      <version>${spring-cloud.version}</version>
    +      <type>pom</type>
    +      <scope>import</scope>
    +    </dependency>
    +  </dependencies>
    +</dependencyManagement>
    +
    +<dependencies>
    +  <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter</artifactId>
    +  </dependency>
    +</dependencies>
    +
    +
    +
    +
    After (pom.xml)
    +
    +
    <dependencyManagement>
    +  <dependencies>
    +    <dependency>
    +      <groupId>com.google.cloud</groupId> (2)
    +      <artifactId>spring-cloud-gcp-dependencies</artifactId>
    +      <version>3.4.0</version>
    +      <type>pom</type>
    +      <scope>import</scope>
    +    </dependency>
    +  </dependencies>
    +</dependencyManagement>
    +
    +<dependencies>
    +  <dependency>
    +    <groupId>com.google.cloud</groupId> (3)
    +    <artifactId>spring-cloud-gcp-starter</artifactId>
    +  </dependency>
    +  ... (4)
    +</dependencies>
    +
    +
    +
    + + + + + + + + + + + + + + + + + +
    1Upgrade to Spring Cloud Ilford release
    2Explicitly add spring-cloud-gcp-dependencies to import the BOM
    3Change Group IDs to com.google.cloud
    4Add other starters as desired (i.e., spring-cloud-gcp-starter-pubsub or spring-cloud-gcp-starter-storage)
    +
    +
    +
    +

    27.2. Java Package Name Change

    +
    +

    All code in Spring Framework on Google Cloud has been moved from org.springframework.cloud.gcp over to the com.google.cloud.spring package.

    +
    +
    +
    +

    27.3. Deprecated Items Removed

    +
    +
    +
    Property spring.cloud.gcp.datastore.emulatorHost
    +
    +

    Use spring.cloud.gcp.datastore.host instead

    +
    +
    GcpPubSubHeaders.ACKNOWLEDGEMENT
    +
    +

    Use GcpPubSubHeaders.ORIGINAL_MESSAGE, which is of type BasicAcknowledgeablePubsubMessage

    +
    +
    SpannerQueryOptions.getQueryOptions()
    +
    +

    Use getOptions()

    +
    +
    PubSubTemplate.subscribe(String, MessageReceiver)
    +
    +

    Use subscribe(String, Consumer<BasicAcknowledgeablePubsubMessage>)

    +
    +
    SpannerReadOptions.getReadOptions()
    +
    +

    Use getOptions()

    +
    +
    Cloud Logging
    +
    +
    +
    +
    org.springframework.cloud.gcp.autoconfigure.logging package
    +
    +

    Use com.google.cloud.spring.logging from the spring-cloud-gcp-logging module.

    +
    +
    Cloud Logging Logback Appenders
    +
    +

    Replace org/springframework/cloud/gcp/autoconfigure/logging/logback[-json]-appender.xml with com/google/cloud/spring/logging/logback[-json]-appender.xml from the spring-cloud-gcp-logging module.

    +
    +
    logback-spring.xml
    +
    +
    <configuration>
    +  <include resource="com/google/cloud/spring/logging/logback-appender.xml" />
    +  ...
    +</configuration>
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/js/setup.js b/3.8.13/reference/html/js/setup.js new file mode 100755 index 0000000000..ae5b1b7bd7 --- /dev/null +++ b/3.8.13/reference/html/js/setup.js @@ -0,0 +1,3 @@ +!function(){"use strict";document.getElementsByTagName("html")[0].classList.add("js")}(); +!function(){var t=function(t,n,e){if("function"!=typeof t)throw new TypeError("Expected a function");return setTimeout((function(){t.apply(void 0,e)}),n)};var n=function(t){return t};var e=function(t,n,e){switch(e.length){case 0:return t.call(n);case 1:return t.call(n,e[0]);case 2:return t.call(n,e[0],e[1]);case 3:return t.call(n,e[0],e[1],e[2])}return t.apply(n,e)},r=Math.max;var o=function(t,n,o){return n=r(void 0===n?t.length-1:n,0),function(){for(var c=arguments,u=-1,i=r(c.length-n,0),a=Array(i);++u0){if(++n>=800)return arguments[0]}else n=0;return t.apply(void 0,arguments)}},D=C(R);var G=/\s/;var U=function(t){for(var n=t.length;n--&&G.test(t.charAt(n)););return n},z=/^\s+/;var B=function(t){return t?t.slice(0,U(t)+1).replace(z,""):t};var H=function(t){return null!=t&&"object"==typeof t};var J=function(t){return"symbol"==typeof t||H(t)&&"[object Symbol]"==g(t)},K=/^[-+]0x[0-9a-f]+$/i,Q=/^0b[01]+$/i,V=/^0o[0-7]+$/i,W=parseInt;var X=function(t){if("number"==typeof t)return t;if(J(t))return NaN;if(m(t)){var n="function"==typeof t.valueOf?t.valueOf():t;t=m(n)?n+"":n}if("string"!=typeof t)return 0===t?t:+t;t=B(t);var e=Q.test(t);return e||V.test(t)?W(t.slice(2),e?2:8):K.test(t)?NaN:+t},Y=function(t,e){return D(o(t,e,n),t+"")}((function(n,e,r){return t(n,X(e)||0,r)}));!function(){"use strict";const t=window.localStorage,n=document.documentElement,e=window.matchMedia("(prefers-color-scheme: dark)");function r(){const n=null!==t?t.getItem("theme"):null;return n?"dark"===n:e.matches}function o(){this.checked?(Y((function(){n.classList.add("dark-theme")}),100),c("dark")):(Y((function(){n.classList.remove("dark-theme")}),100),c("light"))}function c(n){t&&t.setItem("theme",n)}r()&&n.classList.add("dark-theme"),window.addEventListener("load",(function(){const t=document.querySelector("#switch-theme-checkbox");t.checked=r(),t.addEventListener("change",o.bind(t))}))}()}(); +//# sourceMappingURL=setup.js.map diff --git a/3.8.13/reference/html/js/setup.js.map b/3.8.13/reference/html/js/setup.js.map new file mode 100755 index 0000000000..2971105ec2 --- /dev/null +++ b/3.8.13/reference/html/js/setup.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["setup/src/main/js/setup/layout.js","setup/node_modules/browser-pack-flat/_prelude","setup/node_modules/lodash/_baseDelay.js","setup/switchtheme.js","setup/node_modules/lodash/identity.js","setup/node_modules/lodash/_apply.js","setup/node_modules/lodash/_overRest.js","setup/node_modules/lodash/constant.js","setup/node_modules/lodash/_freeGlobal.js","setup/node_modules/lodash/_root.js","setup/node_modules/lodash/_Symbol.js","setup/node_modules/lodash/_getRawTag.js","setup/node_modules/lodash/_objectToString.js","setup/node_modules/lodash/_baseGetTag.js","setup/node_modules/lodash/isObject.js","setup/node_modules/lodash/isFunction.js","setup/node_modules/lodash/_isMasked.js","setup/node_modules/lodash/_coreJsData.js","setup/node_modules/lodash/_toSource.js","setup/node_modules/lodash/_baseIsNative.js","setup/node_modules/lodash/_getValue.js","setup/node_modules/lodash/_getNative.js","setup/node_modules/lodash/_defineProperty.js","setup/node_modules/lodash/_baseSetToString.js","setup/node_modules/lodash/_shortOut.js","setup/node_modules/lodash/_setToString.js","setup/node_modules/lodash/_baseRest.js","setup/node_modules/lodash/_trimmedEndIndex.js","setup/node_modules/lodash/_baseTrim.js","setup/node_modules/lodash/isObjectLike.js","setup/node_modules/lodash/isSymbol.js","setup/node_modules/lodash/toNumber.js","setup/node_modules/lodash/delay.js","setup/src/main/js/setup/switchtheme.js","setup/node_modules/browser-pack-flat/_postlude"],"names":["document","getElementsByTagName","classList","add","_$baseDelay_3","func","wait","args","TypeError","setTimeout","apply","undefined","_$identity_25","value","_$apply_2","thisArg","length","call","nativeMax","Math","max","_$overRest_17","start","transform","arguments","index","array","Array","otherArgs","this","_$constant_23","_$freeGlobal_11","global","freeGlobal","Object","self","window","freeSelf","_$root_18","Function","_$Symbol_1","Symbol","objectProto","prototype","hasOwnProperty","nativeObjectToString","toString","symToStringTag","toStringTag","_$getRawTag_13","isOwn","tag","unmasked","e","result","__nativeObjectToString_16","_$objectToString_16","__symToStringTag_4","_$baseGetTag_4","_$isObject_27","type","uid","_$isFunction_26","_$coreJsData_9","maskSrcKey","exec","keys","IE_PROTO","_$isMasked_15","funcToString","_$toSource_21","reIsHostCtor","__funcProto_5","__objectProto_5","__funcToString_5","__hasOwnProperty_5","reIsNative","RegExp","replace","_$baseIsNative_5","test","_$getValue_14","object","key","_$getNative_12","_$defineProperty_10","_$baseSetToString_7","string","configurable","enumerable","writable","nativeNow","Date","now","_$shortOut_20","count","lastCalled","stamp","remaining","_$setToString_19","reWhitespace","_$trimmedEndIndex_22","charAt","reTrimStart","_$baseTrim_8","slice","_$isObjectLike_28","_$isSymbol_29","reIsBadHex","reIsBinary","reIsOctal","freeParseInt","parseInt","_$toNumber_30","other","valueOf","isBinary","_$delay_24","_$baseRest_6","localStorage","htmlElement","documentElement","prefersDarkColorScheme","matchMedia","isInitialThemeDark","theme","getItem","matches","onThemeChange","checked","saveTheme","remove","setItem","addEventListener","toggleCheckboxElement","querySelector","bind"],"mappings":"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","file":"setup.js","sourcesContent":["/*\n * Copyright 2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the 'License');\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an 'AS IS' BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n(function () {\n \"use strict\";\n document.getElementsByTagName(\"html\")[0].classList.add(\"js\");\n})();\n","(function(){\n","/** Error message constants. */\nvar FUNC_ERROR_TEXT = 'Expected a function';\n\n/**\n * The base implementation of `_.delay` and `_.defer` which accepts `args`\n * to provide to `func`.\n *\n * @private\n * @param {Function} func The function to delay.\n * @param {number} wait The number of milliseconds to delay invocation.\n * @param {Array} args The arguments to provide to `func`.\n * @returns {number|Object} Returns the timer id or timeout object.\n */\nfunction baseDelay(func, wait, args) {\n if (typeof func != 'function') {\n throw new TypeError(FUNC_ERROR_TEXT);\n }\n return setTimeout(function() { func.apply(undefined, args); }, wait);\n}\n\nmodule.exports = baseDelay;\n","/*\n * Copyright 2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the 'License');\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an 'AS IS' BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n(function () {\n \"use strict\";\n\n const delay = require(\"lodash/delay\");\n\n const localStorage = window.localStorage;\n const htmlElement = document.documentElement;\n const prefersDarkColorScheme = window.matchMedia(\n \"(prefers-color-scheme: dark)\"\n );\n\n swithInitialTheme();\n window.addEventListener(\"load\", onWindowLoad);\n\n function swithInitialTheme() {\n if (isInitialThemeDark()) {\n htmlElement.classList.add(\"dark-theme\");\n }\n }\n\n function onWindowLoad() {\n const toggleCheckboxElement = document.querySelector(\n \"#switch-theme-checkbox\"\n );\n toggleCheckboxElement.checked = isInitialThemeDark();\n toggleCheckboxElement.addEventListener(\n \"change\",\n onThemeChange.bind(toggleCheckboxElement)\n );\n }\n\n function isInitialThemeDark() {\n const theme = loadTheme();\n return theme ? theme === \"dark\" : prefersDarkColorScheme.matches;\n }\n\n function onThemeChange() {\n if (this.checked) {\n delay(function () {\n htmlElement.classList.add(\"dark-theme\");\n }, 100);\n saveTheme(\"dark\");\n } else {\n delay(function () {\n htmlElement.classList.remove(\"dark-theme\");\n }, 100);\n saveTheme(\"light\");\n }\n }\n\n function saveTheme(theme) {\n if (localStorage) {\n localStorage.setItem(\"theme\", theme);\n }\n }\n\n function loadTheme() {\n return localStorage !== null ? localStorage.getItem(\"theme\") : null;\n }\n})();\n","/**\n * This method returns the first argument it receives.\n *\n * @static\n * @since 0.1.0\n * @memberOf _\n * @category Util\n * @param {*} value Any value.\n * @returns {*} Returns `value`.\n * @example\n *\n * var object = { 'a': 1 };\n *\n * console.log(_.identity(object) === object);\n * // => true\n */\nfunction identity(value) {\n return value;\n}\n\nmodule.exports = identity;\n","/**\n * A faster alternative to `Function#apply`, this function invokes `func`\n * with the `this` binding of `thisArg` and the arguments of `args`.\n *\n * @private\n * @param {Function} func The function to invoke.\n * @param {*} thisArg The `this` binding of `func`.\n * @param {Array} args The arguments to invoke `func` with.\n * @returns {*} Returns the result of `func`.\n */\nfunction apply(func, thisArg, args) {\n switch (args.length) {\n case 0: return func.call(thisArg);\n case 1: return func.call(thisArg, args[0]);\n case 2: return func.call(thisArg, args[0], args[1]);\n case 3: return func.call(thisArg, args[0], args[1], args[2]);\n }\n return func.apply(thisArg, args);\n}\n\nmodule.exports = apply;\n","var apply = require('./_apply');\n\n/* Built-in method references for those with the same name as other `lodash` methods. */\nvar nativeMax = Math.max;\n\n/**\n * A specialized version of `baseRest` which transforms the rest array.\n *\n * @private\n * @param {Function} func The function to apply a rest parameter to.\n * @param {number} [start=func.length-1] The start position of the rest parameter.\n * @param {Function} transform The rest array transform.\n * @returns {Function} Returns the new function.\n */\nfunction overRest(func, start, transform) {\n start = nativeMax(start === undefined ? (func.length - 1) : start, 0);\n return function() {\n var args = arguments,\n index = -1,\n length = nativeMax(args.length - start, 0),\n array = Array(length);\n\n while (++index < length) {\n array[index] = args[start + index];\n }\n index = -1;\n var otherArgs = Array(start + 1);\n while (++index < start) {\n otherArgs[index] = args[index];\n }\n otherArgs[start] = transform(array);\n return apply(func, this, otherArgs);\n };\n}\n\nmodule.exports = overRest;\n","/**\n * Creates a function that returns `value`.\n *\n * @static\n * @memberOf _\n * @since 2.4.0\n * @category Util\n * @param {*} value The value to return from the new function.\n * @returns {Function} Returns the new constant function.\n * @example\n *\n * var objects = _.times(2, _.constant({ 'a': 1 }));\n *\n * console.log(objects);\n * // => [{ 'a': 1 }, { 'a': 1 }]\n *\n * console.log(objects[0] === objects[1]);\n * // => true\n */\nfunction constant(value) {\n return function() {\n return value;\n };\n}\n\nmodule.exports = constant;\n","/** Detect free variable `global` from Node.js. */\nvar freeGlobal = typeof global == 'object' && global && global.Object === Object && global;\n\nmodule.exports = freeGlobal;\n","var freeGlobal = require('./_freeGlobal');\n\n/** Detect free variable `self`. */\nvar freeSelf = typeof self == 'object' && self && self.Object === Object && self;\n\n/** Used as a reference to the global object. */\nvar root = freeGlobal || freeSelf || Function('return this')();\n\nmodule.exports = root;\n","var root = require('./_root');\n\n/** Built-in value references. */\nvar Symbol = root.Symbol;\n\nmodule.exports = Symbol;\n","var Symbol = require('./_Symbol');\n\n/** Used for built-in method references. */\nvar objectProto = Object.prototype;\n\n/** Used to check objects for own properties. */\nvar hasOwnProperty = objectProto.hasOwnProperty;\n\n/**\n * Used to resolve the\n * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)\n * of values.\n */\nvar nativeObjectToString = objectProto.toString;\n\n/** Built-in value references. */\nvar symToStringTag = Symbol ? Symbol.toStringTag : undefined;\n\n/**\n * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.\n *\n * @private\n * @param {*} value The value to query.\n * @returns {string} Returns the raw `toStringTag`.\n */\nfunction getRawTag(value) {\n var isOwn = hasOwnProperty.call(value, symToStringTag),\n tag = value[symToStringTag];\n\n try {\n value[symToStringTag] = undefined;\n var unmasked = true;\n } catch (e) {}\n\n var result = nativeObjectToString.call(value);\n if (unmasked) {\n if (isOwn) {\n value[symToStringTag] = tag;\n } else {\n delete value[symToStringTag];\n }\n }\n return result;\n}\n\nmodule.exports = getRawTag;\n","/** Used for built-in method references. */\nvar objectProto = Object.prototype;\n\n/**\n * Used to resolve the\n * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)\n * of values.\n */\nvar nativeObjectToString = objectProto.toString;\n\n/**\n * Converts `value` to a string using `Object.prototype.toString`.\n *\n * @private\n * @param {*} value The value to convert.\n * @returns {string} Returns the converted string.\n */\nfunction objectToString(value) {\n return nativeObjectToString.call(value);\n}\n\nmodule.exports = objectToString;\n","var Symbol = require('./_Symbol'),\n getRawTag = require('./_getRawTag'),\n objectToString = require('./_objectToString');\n\n/** `Object#toString` result references. */\nvar nullTag = '[object Null]',\n undefinedTag = '[object Undefined]';\n\n/** Built-in value references. */\nvar symToStringTag = Symbol ? Symbol.toStringTag : undefined;\n\n/**\n * The base implementation of `getTag` without fallbacks for buggy environments.\n *\n * @private\n * @param {*} value The value to query.\n * @returns {string} Returns the `toStringTag`.\n */\nfunction baseGetTag(value) {\n if (value == null) {\n return value === undefined ? undefinedTag : nullTag;\n }\n return (symToStringTag && symToStringTag in Object(value))\n ? getRawTag(value)\n : objectToString(value);\n}\n\nmodule.exports = baseGetTag;\n","/**\n * Checks if `value` is the\n * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)\n * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is an object, else `false`.\n * @example\n *\n * _.isObject({});\n * // => true\n *\n * _.isObject([1, 2, 3]);\n * // => true\n *\n * _.isObject(_.noop);\n * // => true\n *\n * _.isObject(null);\n * // => false\n */\nfunction isObject(value) {\n var type = typeof value;\n return value != null && (type == 'object' || type == 'function');\n}\n\nmodule.exports = isObject;\n","var baseGetTag = require('./_baseGetTag'),\n isObject = require('./isObject');\n\n/** `Object#toString` result references. */\nvar asyncTag = '[object AsyncFunction]',\n funcTag = '[object Function]',\n genTag = '[object GeneratorFunction]',\n proxyTag = '[object Proxy]';\n\n/**\n * Checks if `value` is classified as a `Function` object.\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a function, else `false`.\n * @example\n *\n * _.isFunction(_);\n * // => true\n *\n * _.isFunction(/abc/);\n * // => false\n */\nfunction isFunction(value) {\n if (!isObject(value)) {\n return false;\n }\n // The use of `Object#toString` avoids issues with the `typeof` operator\n // in Safari 9 which returns 'object' for typed arrays and other constructors.\n var tag = baseGetTag(value);\n return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag;\n}\n\nmodule.exports = isFunction;\n","var coreJsData = require('./_coreJsData');\n\n/** Used to detect methods masquerading as native. */\nvar maskSrcKey = (function() {\n var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');\n return uid ? ('Symbol(src)_1.' + uid) : '';\n}());\n\n/**\n * Checks if `func` has its source masked.\n *\n * @private\n * @param {Function} func The function to check.\n * @returns {boolean} Returns `true` if `func` is masked, else `false`.\n */\nfunction isMasked(func) {\n return !!maskSrcKey && (maskSrcKey in func);\n}\n\nmodule.exports = isMasked;\n","var root = require('./_root');\n\n/** Used to detect overreaching core-js shims. */\nvar coreJsData = root['__core-js_shared__'];\n\nmodule.exports = coreJsData;\n","/** Used for built-in method references. */\nvar funcProto = Function.prototype;\n\n/** Used to resolve the decompiled source of functions. */\nvar funcToString = funcProto.toString;\n\n/**\n * Converts `func` to its source code.\n *\n * @private\n * @param {Function} func The function to convert.\n * @returns {string} Returns the source code.\n */\nfunction toSource(func) {\n if (func != null) {\n try {\n return funcToString.call(func);\n } catch (e) {}\n try {\n return (func + '');\n } catch (e) {}\n }\n return '';\n}\n\nmodule.exports = toSource;\n","var isFunction = require('./isFunction'),\n isMasked = require('./_isMasked'),\n isObject = require('./isObject'),\n toSource = require('./_toSource');\n\n/**\n * Used to match `RegExp`\n * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).\n */\nvar reRegExpChar = /[\\\\^$.*+?()[\\]{}|]/g;\n\n/** Used to detect host constructors (Safari). */\nvar reIsHostCtor = /^\\[object .+?Constructor\\]$/;\n\n/** Used for built-in method references. */\nvar funcProto = Function.prototype,\n objectProto = Object.prototype;\n\n/** Used to resolve the decompiled source of functions. */\nvar funcToString = funcProto.toString;\n\n/** Used to check objects for own properties. */\nvar hasOwnProperty = objectProto.hasOwnProperty;\n\n/** Used to detect if a method is native. */\nvar reIsNative = RegExp('^' +\n funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\\\$&')\n .replace(/hasOwnProperty|(function).*?(?=\\\\\\()| for .+?(?=\\\\\\])/g, '$1.*?') + '$'\n);\n\n/**\n * The base implementation of `_.isNative` without bad shim checks.\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a native function,\n * else `false`.\n */\nfunction baseIsNative(value) {\n if (!isObject(value) || isMasked(value)) {\n return false;\n }\n var pattern = isFunction(value) ? reIsNative : reIsHostCtor;\n return pattern.test(toSource(value));\n}\n\nmodule.exports = baseIsNative;\n","/**\n * Gets the value at `key` of `object`.\n *\n * @private\n * @param {Object} [object] The object to query.\n * @param {string} key The key of the property to get.\n * @returns {*} Returns the property value.\n */\nfunction getValue(object, key) {\n return object == null ? undefined : object[key];\n}\n\nmodule.exports = getValue;\n","var baseIsNative = require('./_baseIsNative'),\n getValue = require('./_getValue');\n\n/**\n * Gets the native function at `key` of `object`.\n *\n * @private\n * @param {Object} object The object to query.\n * @param {string} key The key of the method to get.\n * @returns {*} Returns the function if it's native, else `undefined`.\n */\nfunction getNative(object, key) {\n var value = getValue(object, key);\n return baseIsNative(value) ? value : undefined;\n}\n\nmodule.exports = getNative;\n","var getNative = require('./_getNative');\n\nvar defineProperty = (function() {\n try {\n var func = getNative(Object, 'defineProperty');\n func({}, '', {});\n return func;\n } catch (e) {}\n}());\n\nmodule.exports = defineProperty;\n","var constant = require('./constant'),\n defineProperty = require('./_defineProperty'),\n identity = require('./identity');\n\n/**\n * The base implementation of `setToString` without support for hot loop shorting.\n *\n * @private\n * @param {Function} func The function to modify.\n * @param {Function} string The `toString` result.\n * @returns {Function} Returns `func`.\n */\nvar baseSetToString = !defineProperty ? identity : function(func, string) {\n return defineProperty(func, 'toString', {\n 'configurable': true,\n 'enumerable': false,\n 'value': constant(string),\n 'writable': true\n });\n};\n\nmodule.exports = baseSetToString;\n","/** Used to detect hot functions by number of calls within a span of milliseconds. */\nvar HOT_COUNT = 800,\n HOT_SPAN = 16;\n\n/* Built-in method references for those with the same name as other `lodash` methods. */\nvar nativeNow = Date.now;\n\n/**\n * Creates a function that'll short out and invoke `identity` instead\n * of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN`\n * milliseconds.\n *\n * @private\n * @param {Function} func The function to restrict.\n * @returns {Function} Returns the new shortable function.\n */\nfunction shortOut(func) {\n var count = 0,\n lastCalled = 0;\n\n return function() {\n var stamp = nativeNow(),\n remaining = HOT_SPAN - (stamp - lastCalled);\n\n lastCalled = stamp;\n if (remaining > 0) {\n if (++count >= HOT_COUNT) {\n return arguments[0];\n }\n } else {\n count = 0;\n }\n return func.apply(undefined, arguments);\n };\n}\n\nmodule.exports = shortOut;\n","var baseSetToString = require('./_baseSetToString'),\n shortOut = require('./_shortOut');\n\n/**\n * Sets the `toString` method of `func` to return `string`.\n *\n * @private\n * @param {Function} func The function to modify.\n * @param {Function} string The `toString` result.\n * @returns {Function} Returns `func`.\n */\nvar setToString = shortOut(baseSetToString);\n\nmodule.exports = setToString;\n","var identity = require('./identity'),\n overRest = require('./_overRest'),\n setToString = require('./_setToString');\n\n/**\n * The base implementation of `_.rest` which doesn't validate or coerce arguments.\n *\n * @private\n * @param {Function} func The function to apply a rest parameter to.\n * @param {number} [start=func.length-1] The start position of the rest parameter.\n * @returns {Function} Returns the new function.\n */\nfunction baseRest(func, start) {\n return setToString(overRest(func, start, identity), func + '');\n}\n\nmodule.exports = baseRest;\n","/** Used to match a single whitespace character. */\nvar reWhitespace = /\\s/;\n\n/**\n * Used by `_.trim` and `_.trimEnd` to get the index of the last non-whitespace\n * character of `string`.\n *\n * @private\n * @param {string} string The string to inspect.\n * @returns {number} Returns the index of the last non-whitespace character.\n */\nfunction trimmedEndIndex(string) {\n var index = string.length;\n\n while (index-- && reWhitespace.test(string.charAt(index))) {}\n return index;\n}\n\nmodule.exports = trimmedEndIndex;\n","var trimmedEndIndex = require('./_trimmedEndIndex');\n\n/** Used to match leading whitespace. */\nvar reTrimStart = /^\\s+/;\n\n/**\n * The base implementation of `_.trim`.\n *\n * @private\n * @param {string} string The string to trim.\n * @returns {string} Returns the trimmed string.\n */\nfunction baseTrim(string) {\n return string\n ? string.slice(0, trimmedEndIndex(string) + 1).replace(reTrimStart, '')\n : string;\n}\n\nmodule.exports = baseTrim;\n","/**\n * Checks if `value` is object-like. A value is object-like if it's not `null`\n * and has a `typeof` result of \"object\".\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is object-like, else `false`.\n * @example\n *\n * _.isObjectLike({});\n * // => true\n *\n * _.isObjectLike([1, 2, 3]);\n * // => true\n *\n * _.isObjectLike(_.noop);\n * // => false\n *\n * _.isObjectLike(null);\n * // => false\n */\nfunction isObjectLike(value) {\n return value != null && typeof value == 'object';\n}\n\nmodule.exports = isObjectLike;\n","var baseGetTag = require('./_baseGetTag'),\n isObjectLike = require('./isObjectLike');\n\n/** `Object#toString` result references. */\nvar symbolTag = '[object Symbol]';\n\n/**\n * Checks if `value` is classified as a `Symbol` primitive or object.\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a symbol, else `false`.\n * @example\n *\n * _.isSymbol(Symbol.iterator);\n * // => true\n *\n * _.isSymbol('abc');\n * // => false\n */\nfunction isSymbol(value) {\n return typeof value == 'symbol' ||\n (isObjectLike(value) && baseGetTag(value) == symbolTag);\n}\n\nmodule.exports = isSymbol;\n","var baseTrim = require('./_baseTrim'),\n isObject = require('./isObject'),\n isSymbol = require('./isSymbol');\n\n/** Used as references for various `Number` constants. */\nvar NAN = 0 / 0;\n\n/** Used to detect bad signed hexadecimal string values. */\nvar reIsBadHex = /^[-+]0x[0-9a-f]+$/i;\n\n/** Used to detect binary string values. */\nvar reIsBinary = /^0b[01]+$/i;\n\n/** Used to detect octal string values. */\nvar reIsOctal = /^0o[0-7]+$/i;\n\n/** Built-in method references without a dependency on `root`. */\nvar freeParseInt = parseInt;\n\n/**\n * Converts `value` to a number.\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to process.\n * @returns {number} Returns the number.\n * @example\n *\n * _.toNumber(3.2);\n * // => 3.2\n *\n * _.toNumber(Number.MIN_VALUE);\n * // => 5e-324\n *\n * _.toNumber(Infinity);\n * // => Infinity\n *\n * _.toNumber('3.2');\n * // => 3.2\n */\nfunction toNumber(value) {\n if (typeof value == 'number') {\n return value;\n }\n if (isSymbol(value)) {\n return NAN;\n }\n if (isObject(value)) {\n var other = typeof value.valueOf == 'function' ? value.valueOf() : value;\n value = isObject(other) ? (other + '') : other;\n }\n if (typeof value != 'string') {\n return value === 0 ? value : +value;\n }\n value = baseTrim(value);\n var isBinary = reIsBinary.test(value);\n return (isBinary || reIsOctal.test(value))\n ? freeParseInt(value.slice(2), isBinary ? 2 : 8)\n : (reIsBadHex.test(value) ? NAN : +value);\n}\n\nmodule.exports = toNumber;\n","var baseDelay = require('./_baseDelay'),\n baseRest = require('./_baseRest'),\n toNumber = require('./toNumber');\n\n/**\n * Invokes `func` after `wait` milliseconds. Any additional arguments are\n * provided to `func` when it's invoked.\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Function\n * @param {Function} func The function to delay.\n * @param {number} wait The number of milliseconds to delay invocation.\n * @param {...*} [args] The arguments to invoke `func` with.\n * @returns {number} Returns the timer id.\n * @example\n *\n * _.delay(function(text) {\n * console.log(text);\n * }, 1000, 'later');\n * // => Logs 'later' after one second.\n */\nvar delay = baseRest(function(func, wait, args) {\n return baseDelay(func, toNumber(wait) || 0, args);\n});\n\nmodule.exports = delay;\n","/*\n * Copyright 2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the 'License');\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an 'AS IS' BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n(function () {\n \"use strict\";\n\n const delay = require(\"lodash/delay\");\n\n const localStorage = window.localStorage;\n const htmlElement = document.documentElement;\n const prefersDarkColorScheme = window.matchMedia(\n \"(prefers-color-scheme: dark)\"\n );\n\n swithInitialTheme();\n window.addEventListener(\"load\", onWindowLoad);\n\n function swithInitialTheme() {\n if (isInitialThemeDark()) {\n htmlElement.classList.add(\"dark-theme\");\n }\n }\n\n function onWindowLoad() {\n const toggleCheckboxElement = document.querySelector(\n \"#switch-theme-checkbox\"\n );\n toggleCheckboxElement.checked = isInitialThemeDark();\n toggleCheckboxElement.addEventListener(\n \"change\",\n onThemeChange.bind(toggleCheckboxElement)\n );\n }\n\n function isInitialThemeDark() {\n const theme = loadTheme();\n return theme ? theme === \"dark\" : prefersDarkColorScheme.matches;\n }\n\n function onThemeChange() {\n if (this.checked) {\n delay(function () {\n htmlElement.classList.add(\"dark-theme\");\n }, 100);\n saveTheme(\"dark\");\n } else {\n delay(function () {\n htmlElement.classList.remove(\"dark-theme\");\n }, 100);\n saveTheme(\"light\");\n }\n }\n\n function saveTheme(theme) {\n if (localStorage) {\n localStorage.setItem(\"theme\", theme);\n }\n }\n\n function loadTheme() {\n return localStorage !== null ? localStorage.getItem(\"theme\") : null;\n }\n})();\n","\n}());"]} \ No newline at end of file diff --git a/3.8.13/reference/html/js/site.js b/3.8.13/reference/html/js/site.js new file mode 100755 index 0000000000..910209e554 --- /dev/null +++ b/3.8.13/reference/html/js/site.js @@ -0,0 +1,7 @@ +!function(){"use strict";function n(){const n=document.getElementById("anchor-rewrite"),o=window.location.hash.substr(1);n&&o&&function(n,o){const e=[n];for(console.debug(n);o[n];){if(n=o[n],e.includes(n))return void console.error("Skipping circular anchor update");e.push(n)}window.location.hash=n}(o,JSON.parse(n.innerHTML))}window.addEventListener("load",n),window.addEventListener("hashchange",n)}(); +!function(){"use strict";!function(){let t=document.getElementById("author"),n=t;for(;t;)t.classList.contains("author")&&(n=t),t=t.nextElementSibling;n&&n.classList.add("last-author")}()}(); +!function(){var t=function(t,n,e){if("function"!=typeof t)throw new TypeError("Expected a function");return setTimeout((function(){t.apply(void 0,e)}),n)};var n=function(t){return t};var e=function(t,n,e){switch(e.length){case 0:return t.call(n);case 1:return t.call(n,e[0]);case 2:return t.call(n,e[0],e[1]);case 3:return t.call(n,e[0],e[1],e[2])}return t.apply(n,e)},r=Math.max;var o=function(t,n,o){return n=r(void 0===n?t.length-1:n,0),function(){for(var c=arguments,i=-1,u=r(c.length-n,0),a=Array(u);++i0){if(++n>=800)return arguments[0]}else n=0;return t.apply(void 0,arguments)}},G=D(I);var M=/\s/;var U=function(t){for(var n=t.length;n--&&M.test(t.charAt(n)););return n},z=/^\s+/;var B=function(t){return t?t.slice(0,U(t)+1).replace(z,""):t};var H=function(t){return null!=t&&"object"==typeof t};var J=function(t){return"symbol"==typeof t||H(t)&&"[object Symbol]"==g(t)},K=/^[-+]0x[0-9a-f]+$/i,Q=/^0b[01]+$/i,V=/^0o[0-7]+$/i,W=parseInt;var X=function(t){if("number"==typeof t)return t;if(J(t))return NaN;if(m(t)){var n="function"==typeof t.valueOf?t.valueOf():t;t=m(n)?n+"":n}if("string"!=typeof t)return 0===t?t:+t;t=B(t);var e=Q.test(t);return e||V.test(t)?W(t.slice(2),e?2:8):K.test(t)?NaN:+t},Y=function(t,e){return G(o(t,e,n),t+"")}((function(n,e,r){return t(n,X(e)||0,r)}));!function(){"use strict";function t(t){const e=t.querySelector("code").cloneNode(!0);for(const t of e.querySelectorAll(".hide-when-unfolded"))t.parentNode.removeChild(t);const r=e.innerText;r&&window.navigator.clipboard.writeText(r+"\n").then(n.bind(this))}function n(){this.classList.add("clicked")}function e(){this.classList.remove("clicked")}function r(t){const n=t.querySelector("code"),e=!n.classList.contains("unfolded");n.classList.remove(e?"folding":"unfolding"),n.classList.add(e?"unfolding":"folding"),Y((function(){n.classList.remove(e?"unfolding":"folding"),n.classList.toggle("unfolded")}),1100),o(this,!e)}function o(t,n){const e=n?"Expanded folded text":"Collapse foldable text";t.classList.remove(n?"fold-button":"unfold-button"),t.classList.add(n?"unfold-button":"fold-button"),t.querySelector("span.label").innerText=e,t.title=e}!function(){for(const t of document.querySelectorAll(".doc pre.highlight")){const e=document.createElement("div");e.className="codetools",n(t,e)&&t.appendChild(e)}function n(n,i){let u=0;return function(t){return!!t.querySelector("span.hide-when-folded")}(n)&&(!function(t,n){const e=c();o(e,!0),e.addEventListener("click",r.bind(e,t)),n.appendChild(e)}(n,i),u++),window.navigator.clipboard&&(!function(n,r){const o=c("Copy to clipboard","copy-button");o.addEventListener("click",t.bind(o,n)),o.addEventListener("mouseleave",e.bind(o)),o.addEventListener("blur",e.bind(o));const i=document.createElement("span");o.appendChild(i),i.className="copied",r.appendChild(o)}(n,i),u++),u>0}function c(t,n){const e=document.createElement("button");e.className=n,e.title=t,e.type="button";const r=document.createElement("span");return r.appendChild(document.createTextNode(t)),r.className="label",e.appendChild(r),e}}()}()}(); +!function(){function e(n){return n instanceof Map?n.clear=n.delete=n.set=function(){throw new Error("map is read-only")}:n instanceof Set&&(n.add=n.clear=n.delete=function(){throw new Error("set is read-only")}),Object.freeze(n),Object.getOwnPropertyNames(n).forEach((function(t){var a=n[t];"object"!=typeof a||Object.isFrozen(a)||e(a)})),n}var n=e,t=e;n.default=t;class a{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1}ignoreMatch(){this.isMatchIgnored=!0}}function i(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function r(e,...n){const t=Object.create(null);for(const n in e)t[n]=e[n];return n.forEach((function(e){for(const n in e)t[n]=e[n]})),t}const s=e=>!!e.kind;class o{constructor(e,n){this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){this.buffer+=i(e)}openNode(e){if(!s(e))return;let n=e.kind;e.sublanguage||(n=`${this.classPrefix}${n}`),this.span(n)}closeNode(e){s(e)&&(this.buffer+="")}value(){return this.buffer}span(e){this.buffer+=``}}class l{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const n={kind:e,children:[]};this.add(n),this.stack.push(n)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n),n.children.forEach((n=>this._walk(e,n))),e.closeNode(n)),e}static _collapse(e){"string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{l._collapse(e)})))}}class c extends l{constructor(e){super(),this.options=e}addKeyword(e,n){""!==e&&(this.openNode(n),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,n){const t=e.root;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){return new o(this,this.options).value()}finalize(){return!0}}function g(e){return e?"string"==typeof e?e:e.source:null}const d=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;const u="[a-zA-Z]\\w*",b="[a-zA-Z_]\\w*",m="\\b\\d+(\\.\\d+)?",h="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",f="\\b(0b[01]+)",p={begin:"\\\\[\\s\\S]",relevance:0},_={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[p]},E={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[p]},v={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},N=function(e,n,t={}){const a=r({className:"comment",begin:e,end:n,contains:[]},t);return a.contains.push(v),a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),a},y=N("//","$"),w=N("/\\*","\\*/"),x=N("#","$"),O={className:"number",begin:m,relevance:0},M={className:"number",begin:h,relevance:0},k={className:"number",begin:f,relevance:0},R={className:"number",begin:m+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},A={begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[p,{begin:/\[/,end:/\]/,relevance:0,contains:[p]}]}]},S={className:"title",begin:u,relevance:0},T={className:"title",begin:b,relevance:0},C={begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0};var D=Object.freeze({__proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:u,UNDERSCORE_IDENT_RE:b,NUMBER_RE:m,C_NUMBER_RE:h,BINARY_NUMBER_RE:f,RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(e={})=>{const n=/^#![ ]*\//;return e.binary&&(e.begin=function(...e){return e.map((e=>g(e))).join("")}(n,/.*\b/,e.binary,/\b.*/)),r({className:"meta",begin:n,end:/$/,relevance:0,"on:begin":(e,n)=>{0!==e.index&&n.ignoreMatch()}},e)},BACKSLASH_ESCAPE:p,APOS_STRING_MODE:_,QUOTE_STRING_MODE:E,PHRASAL_WORDS_MODE:v,COMMENT:N,C_LINE_COMMENT_MODE:y,C_BLOCK_COMMENT_MODE:w,HASH_COMMENT_MODE:x,NUMBER_MODE:O,C_NUMBER_MODE:M,BINARY_NUMBER_MODE:k,CSS_NUMBER_MODE:R,REGEXP_MODE:A,TITLE_MODE:S,UNDERSCORE_TITLE_MODE:T,METHOD_GUARD:C,END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,n)=>{n.data._beginMatch=e[1]},"on:end":(e,n)=>{n.data._beginMatch!==e[1]&&n.ignoreMatch()}})}});function L(e,n){"."===e.input[e.index-1]&&n.ignoreMatch()}function B(e,n){n&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",e.__beforeBegin=L,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords,void 0===e.relevance&&(e.relevance=0))}function $(e,n){Array.isArray(e.illegal)&&(e.illegal=function(...e){return"("+e.map((e=>g(e))).join("|")+")"}(...e.illegal))}function I(e,n){if(e.match){if(e.begin||e.end)throw new Error("begin & end are not supported with match");e.begin=e.match,delete e.match}}function j(e,n){void 0===e.relevance&&(e.relevance=1)}const z=["of","and","for","in","not","or","if","then","parent","list","value"];function P(e,n,t="keyword"){const a={};return"string"==typeof e?i(t,e.split(" ")):Array.isArray(e)?i(t,e):Object.keys(e).forEach((function(t){Object.assign(a,P(e[t],n,t))})),a;function i(e,t){n&&(t=t.map((e=>e.toLowerCase()))),t.forEach((function(n){const t=n.split("|");a[t[0]]=[e,U(t[0],t[1])]}))}}function U(e,n){return n?Number(n):function(e){return z.includes(e.toLowerCase())}(e)?0:1}function K(e,{plugins:n}){function t(n,t){return new RegExp(g(n),"m"+(e.case_insensitive?"i":"")+(t?"g":""))}class a{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,n){n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]),this.matchAt+=function(e){return new RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const e=this.regexes.map((e=>e[1]));this.matcherRe=t(function(e,n="|"){let t=0;return e.map((e=>{t+=1;const n=t;let a=g(e),i="";for(;a.length>0;){const e=d.exec(a);if(!e){i+=a;break}i+=a.substring(0,e.index),a=a.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?i+="\\"+String(Number(e[1])+n):(i+=e[0],"("===e[0]&&t++)}return i})).map((e=>`(${e})`)).join(n)}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const n=this.matcherRe.exec(e);if(!n)return null;const t=n.findIndex(((e,n)=>n>0&&void 0!==e)),a=this.matchIndexes[t];return n.splice(0,t),Object.assign(n,a)}}class i{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];const n=new a;return this.rules.slice(e).forEach((([e,t])=>n.addRule(e,t))),n.compile(),this.multiRegexes[e]=n,n}resumingScanAtSamePosition(){return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,n){this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){const n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex;let t=n.exec(e);if(this.resumingScanAtSamePosition())if(t&&t.index===this.lastIndex);else{const n=this.getMatcher(0);n.lastIndex=this.lastIndex+1,t=n.exec(e)}return t&&(this.regexIndex+=t.position+1,this.regexIndex===this.count&&this.considerAll()),t}}if(e.compilerExtensions||(e.compilerExtensions=[]),e.contains&&e.contains.includes("self"))throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return e.classNameAliases=r(e.classNameAliases||{}),function n(a,s){const o=a;if(a.isCompiled)return o;[I].forEach((e=>e(a,s))),e.compilerExtensions.forEach((e=>e(a,s))),a.__beforeBegin=null,[B,$,j].forEach((e=>e(a,s))),a.isCompiled=!0;let l=null;if("object"==typeof a.keywords&&(l=a.keywords.$pattern,delete a.keywords.$pattern),a.keywords&&(a.keywords=P(a.keywords,e.case_insensitive)),a.lexemes&&l)throw new Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return l=l||a.lexemes||/\w+/,o.keywordPatternRe=t(l,!0),s&&(a.begin||(a.begin=/\B|\b/),o.beginRe=t(a.begin),a.endSameAsBegin&&(a.end=a.begin),a.end||a.endsWithParent||(a.end=/\B|\b/),a.end&&(o.endRe=t(a.end)),o.terminatorEnd=g(a.end)||"",a.endsWithParent&&s.terminatorEnd&&(o.terminatorEnd+=(a.end?"|":"")+s.terminatorEnd)),a.illegal&&(o.illegalRe=t(a.illegal)),a.contains||(a.contains=[]),a.contains=[].concat(...a.contains.map((function(e){return function(e){e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((function(n){return r(e,{variants:null},n)})));if(e.cachedVariants)return e.cachedVariants;if(H(e))return r(e,{starts:e.starts?r(e.starts):null});if(Object.isFrozen(e))return r(e);return e}("self"===e?a:e)}))),a.contains.forEach((function(e){n(e,o)})),a.starts&&n(a.starts,s),o.matcher=function(e){const n=new i;return e.contains.forEach((e=>n.addRule(e.begin,{rule:e,type:"begin"}))),e.terminatorEnd&&n.addRule(e.terminatorEnd,{type:"end"}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n}(o),o}(e)}function H(e){return!!e&&(e.endsWithParent||H(e.starts))}function Z(e){const n={props:["language","code","autodetect"],data:function(){return{detectedLanguage:"",unknownLanguage:!1}},computed:{className(){return this.unknownLanguage?"":"hljs "+this.detectedLanguage},highlighted(){if(!this.autoDetect&&!e.getLanguage(this.language))return console.warn(`The language "${this.language}" you specified could not be found.`),this.unknownLanguage=!0,i(this.code);let n={};return this.autoDetect?(n=e.highlightAuto(this.code),this.detectedLanguage=n.language):(n=e.highlight(this.language,this.code,this.ignoreIllegals),this.detectedLanguage=this.language),n.value},autoDetect(){return!this.language||(e=this.autodetect,Boolean(e||""===e));var e},ignoreIllegals:()=>!0},render(e){return e("pre",{},[e("code",{class:this.className,domProps:{innerHTML:this.highlighted}})])}};return{Component:n,VuePlugin:{install(e){e.component("highlightjs",n)}}}}const G={"after:highlightElement":({el:e,result:n,text:t})=>{const a=q(e);if(!a.length)return;const r=document.createElement("div");r.innerHTML=n.value,n.value=function(e,n,t){let a=0,r="";const s=[];function o(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset"}function c(e){r+=""}function g(e){("start"===e.event?l:c)(e.node)}for(;e.length||n.length;){let n=o();if(r+=i(t.substring(a,n[0].offset)),a=n[0].offset,n===e){s.reverse().forEach(c);do{g(n.splice(0,1)[0]),n=o()}while(n===e&&n.length&&n[0].offset===a);s.reverse().forEach(l)}else"start"===n[0].event?s.push(n[0].node):s.pop(),g(n.splice(0,1)[0])}return r+i(t.substr(a))}(a,q(r),t)}};function F(e){return e.nodeName.toLowerCase()}function q(e){const n=[];return function e(t,a){for(let i=t.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=e(i,a),F(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}const W={},Q=e=>{console.error(e)},X=(e,...n)=>{console.log(`WARN: ${e}`,...n)},V=(e,n)=>{W[`${e}/${n}`]||(console.log(`Deprecated as of ${e}. ${n}`),W[`${e}/${n}`]=!0)},J=i,Y=r,ee=Symbol("nomatch");var ne=function(e){const t=Object.create(null),i=Object.create(null),r=[];let s=!0;const o=/(^(<[^>]+>|\t|)+|\n)/gm,l="Could not find the language '{}', did you forget to load/include a language module?",g={disableAutodetect:!0,name:"Plain text",contains:[]};let d={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:c};function u(e){return d.noHighlightRe.test(e)}function b(e,n,t,a){let i="",r="";"object"==typeof n?(i=e,t=n.ignoreIllegals,r=n.language,a=void 0):(V("10.7.0","highlight(lang, code, ...args) has been deprecated."),V("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"),r=e,i=n);const s={code:i,language:r};M("before:highlight",s);const o=s.result?s.result:m(s.language,s.code,t,a);return o.code=s.code,M("after:highlight",o),o}function m(e,n,i,o){function c(e,n){const t=N.case_insensitive?n[0].toLowerCase():n[0];return Object.prototype.hasOwnProperty.call(e.keywords,t)&&e.keywords[t]}function g(){null!=O.subLanguage?function(){if(""===R)return;let e=null;if("string"==typeof O.subLanguage){if(!t[O.subLanguage])return void k.addText(R);e=m(O.subLanguage,R,!0,M[O.subLanguage]),M[O.subLanguage]=e.top}else e=h(R,O.subLanguage.length?O.subLanguage:null);O.relevance>0&&(A+=e.relevance),k.addSublanguage(e.emitter,e.language)}():function(){if(!O.keywords)return void k.addText(R);let e=0;O.keywordPatternRe.lastIndex=0;let n=O.keywordPatternRe.exec(R),t="";for(;n;){t+=R.substring(e,n.index);const a=c(O,n);if(a){const[e,i]=a;if(k.addText(t),t="",A+=i,e.startsWith("_"))t+=n[0];else{const t=N.classNameAliases[e]||e;k.addKeyword(n[0],t)}}else t+=n[0];e=O.keywordPatternRe.lastIndex,n=O.keywordPatternRe.exec(R)}t+=R.substr(e),k.addText(t)}(),R=""}function u(e){return e.className&&k.openNode(N.classNameAliases[e.className]||e.className),O=Object.create(e,{parent:{value:O}}),O}function b(e,n,t){let i=function(e,n){const t=e&&e.exec(n);return t&&0===t.index}(e.endRe,t);if(i){if(e["on:end"]){const t=new a(e);e["on:end"](n,t),t.isMatchIgnored&&(i=!1)}if(i){for(;e.endsParent&&e.parent;)e=e.parent;return e}}if(e.endsWithParent)return b(e.parent,n,t)}function f(e){return 0===O.matcher.regexIndex?(R+=e[0],1):(C=!0,0)}function p(e){const n=e[0],t=e.rule,i=new a(t),r=[t.__beforeBegin,t["on:begin"]];for(const t of r)if(t&&(t(e,i),i.isMatchIgnored))return f(n);return t&&t.endSameAsBegin&&(t.endRe=new RegExp(n.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),t.skip?R+=n:(t.excludeBegin&&(R+=n),g(),t.returnBegin||t.excludeBegin||(R=n)),u(t),t.returnBegin?0:n.length}function _(e){const t=e[0],a=n.substr(e.index),i=b(O,e,a);if(!i)return ee;const r=O;r.skip?R+=t:(r.returnEnd||r.excludeEnd||(R+=t),g(),r.excludeEnd&&(R=t));do{O.className&&k.closeNode(),O.skip||O.subLanguage||(A+=O.relevance),O=O.parent}while(O!==i.parent);return i.starts&&(i.endSameAsBegin&&(i.starts.endRe=i.endRe),u(i.starts)),r.returnEnd?0:t.length}let E={};function v(t,a){const r=a&&a[0];if(R+=t,null==r)return g(),0;if("begin"===E.type&&"end"===a.type&&E.index===a.index&&""===r){if(R+=n.slice(a.index,a.index+1),!s){const n=new Error("0 width match regex");throw n.languageName=e,n.badRule=E.rule,n}return 1}if(E=a,"begin"===a.type)return p(a);if("illegal"===a.type&&!i){const e=new Error('Illegal lexeme "'+r+'" for mode "'+(O.className||"")+'"');throw e.mode=O,e}if("end"===a.type){const e=_(a);if(e!==ee)return e}if("illegal"===a.type&&""===r)return 1;if(T>1e5&&T>3*a.index){throw new Error("potential infinite loop, way more iterations than matches")}return R+=r,r.length}const N=w(e);if(!N)throw Q(l.replace("{}",e)),new Error('Unknown language: "'+e+'"');const y=K(N,{plugins:r});let x="",O=o||y;const M={},k=new d.__emitter(d);!function(){const e=[];for(let n=O;n!==N;n=n.parent)n.className&&e.unshift(n.className);e.forEach((e=>k.openNode(e)))}();let R="",A=0,S=0,T=0,C=!1;try{for(O.matcher.considerAll();;){T++,C?C=!1:O.matcher.considerAll(),O.matcher.lastIndex=S;const e=O.matcher.exec(n);if(!e)break;const t=v(n.substring(S,e.index),e);S=e.index+t}return v(n.substr(S)),k.closeAllNodes(),k.finalize(),x=k.toHTML(),{relevance:Math.floor(A),value:x,language:e,illegal:!1,emitter:k,top:O}}catch(t){if(t.message&&t.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:t.message,context:n.slice(S-100,S+100),mode:t.mode},sofar:x,relevance:0,value:J(n),emitter:k};if(s)return{illegal:!1,relevance:0,value:J(n),emitter:k,language:e,top:O,errorRaised:t};throw t}}function h(e,n){n=n||d.languages||Object.keys(t);const a=function(e){const n={relevance:0,emitter:new d.__emitter(d),value:J(e),illegal:!1,top:g};return n.emitter.addText(e),n}(e),i=n.filter(w).filter(O).map((n=>m(n,e,!1)));i.unshift(a);const r=i.sort(((e,n)=>{if(e.relevance!==n.relevance)return n.relevance-e.relevance;if(e.language&&n.language){if(w(e.language).supersetOf===n.language)return 1;if(w(n.language).supersetOf===e.language)return-1}return 0})),[s,o]=r,l=s;return l.second_best=o,l}const f={"before:highlightElement":({el:e})=>{d.useBR&&(e.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n"))},"after:highlightElement":({result:e})=>{d.useBR&&(e.value=e.value.replace(/\n/g,"
    "))}},p=/^(<[^>]+>|\t)+/gm,_={"after:highlightElement":({result:e})=>{d.tabReplace&&(e.value=e.value.replace(p,(e=>e.replace(/\t/g,d.tabReplace))))}};function E(e){let n=null;const t=function(e){let n=e.className+" ";n+=e.parentNode?e.parentNode.className:"";const t=d.languageDetectRe.exec(n);if(t){const n=w(t[1]);return n||(X(l.replace("{}",t[1])),X("Falling back to no-highlight mode for this block.",e)),n?t[1]:"no-highlight"}return n.split(/\s+/).find((e=>u(e)||w(e)))}(e);if(u(t))return;M("before:highlightElement",{el:e,language:t}),n=e;const a=n.textContent,r=t?b(a,{language:t,ignoreIllegals:!0}):h(a);M("after:highlightElement",{el:e,result:r,text:a}),e.innerHTML=r.value,function(e,n,t){const a=n?i[n]:t;e.classList.add("hljs"),a&&e.classList.add(a)}(e,t,r.language),e.result={language:r.language,re:r.relevance,relavance:r.relevance},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.relevance,relavance:r.second_best.relevance})}const v=()=>{if(v.called)return;v.called=!0,V("10.6.0","initHighlighting() is deprecated. Use highlightAll() instead.");document.querySelectorAll("pre code").forEach(E)};let N=!1;function y(){if("loading"===document.readyState)return void(N=!0);document.querySelectorAll("pre code").forEach(E)}function w(e){return e=(e||"").toLowerCase(),t[e]||t[i[e]]}function x(e,{languageName:n}){"string"==typeof e&&(e=[e]),e.forEach((e=>{i[e.toLowerCase()]=n}))}function O(e){const n=w(e);return n&&!n.disableAutodetect}function M(e,n){const t=e;r.forEach((function(e){e[t]&&e[t](n)}))}"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(function(){N&&y()}),!1),Object.assign(e,{highlight:b,highlightAuto:h,highlightAll:y,fixMarkup:function(e){return V("10.2.0","fixMarkup will be removed entirely in v11.0"),V("10.2.0","Please see https://github.com/highlightjs/highlight.js/issues/2534"),n=e,d.tabReplace||d.useBR?n.replace(o,(e=>"\n"===e?d.useBR?"
    ":e:d.tabReplace?e.replace(/\t/g,d.tabReplace):e)):n;var n},highlightElement:E,highlightBlock:function(e){return V("10.7.0","highlightBlock will be removed entirely in v12.0"),V("10.7.0","Please use highlightElement now."),E(e)},configure:function(e){e.useBR&&(V("10.3.0","'useBR' will be removed entirely in v11.0"),V("10.3.0","Please see https://github.com/highlightjs/highlight.js/issues/2559")),d=Y(d,e)},initHighlighting:v,initHighlightingOnLoad:function(){V("10.6.0","initHighlightingOnLoad() is deprecated. Use highlightAll() instead."),N=!0},registerLanguage:function(n,a){let i=null;try{i=a(e)}catch(e){if(Q("Language definition for '{}' could not be registered.".replace("{}",n)),!s)throw e;Q(e),i=g}i.name||(i.name=n),t[n]=i,i.rawDefinition=a.bind(null,e),i.aliases&&x(i.aliases,{languageName:n})},unregisterLanguage:function(e){delete t[e];for(const n of Object.keys(i))i[n]===e&&delete i[n]},listLanguages:function(){return Object.keys(t)},getLanguage:w,registerAliases:x,requireLanguage:function(e){V("10.4.0","requireLanguage will be removed entirely in v11."),V("10.4.0","Please see https://github.com/highlightjs/highlight.js/pull/2844");const n=w(e);if(n)return n;throw new Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:O,inherit:Y,addPlugin:function(e){!function(e){e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=n=>{e["before:highlightBlock"](Object.assign({block:n.el},n))}),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=n=>{e["after:highlightBlock"](Object.assign({block:n.el},n))})}(e),r.push(e)},vuePlugin:Z(e).VuePlugin}),e.debugMode=function(){s=!1},e.safeMode=function(){s=!0},e.versionString="10.7.3";for(const e in D)"object"==typeof D[e]&&n(D[e]);return Object.assign(e,D),e.addPlugin(f),e.addPlugin(G),e.addPlugin(_),e}({});function te(...e){return e.map((e=>{return(n=e)?"string"==typeof n?n:n.source:null;var n})).join("")}var ae=function(e){const n=[{className:"strong",begin:/\*{2}([^\n]+?)\*{2}/},{className:"strong",begin:te(/\*\*/,/((\*(?!\*)|\\[^\n]|[^*\n\\])+\n)+/,/(\*(?!\*)|\\[^\n]|[^*\n\\])*/,/\*\*/),relevance:0},{className:"strong",begin:/\B\*(\S|\S[^\n]*?\S)\*(?!\w)/},{className:"strong",begin:/\*[^\s]([^\n]+\n)+([^\n]+)\*/}],t=[{className:"emphasis",begin:/_{2}([^\n]+?)_{2}/},{className:"emphasis",begin:te(/__/,/((_(?!_)|\\[^\n]|[^_\n\\])+\n)+/,/(_(?!_)|\\[^\n]|[^_\n\\])*/,/__/),relevance:0},{className:"emphasis",begin:/\b_(\S|\S[^\n]*?\S)_(?!\w)/},{className:"emphasis",begin:/_[^\s]([^\n]+\n)+([^\n]+)_/},{className:"emphasis",begin:"\\B'(?!['\\s])",end:"(\\n{2}|')",contains:[{begin:"\\\\'\\w",relevance:0}],relevance:0}];return{name:"AsciiDoc",aliases:["adoc"],contains:[e.COMMENT("^/{4,}\\n","\\n/{4,}$",{relevance:10}),e.COMMENT("^//","$",{relevance:0}),{className:"title",begin:"^\\.\\w.*$"},{begin:"^[=\\*]{4,}\\n",end:"\\n^[=\\*]{4,}$",relevance:10},{className:"section",relevance:10,variants:[{begin:"^(={1,6})[ \t].+?([ \t]\\1)?$"},{begin:"^[^\\[\\]\\n]+?\\n[=\\-~\\^\\+]{2,}$"}]},{className:"meta",begin:"^:.+?:",end:"\\s",excludeEnd:!0,relevance:10},{className:"meta",begin:"^\\[.+?\\]$",relevance:0},{className:"quote",begin:"^_{4,}\\n",end:"\\n_{4,}$",relevance:10},{className:"code",begin:"^[\\-\\.]{4,}\\n",end:"\\n[\\-\\.]{4,}$",relevance:10},{begin:"^\\+{4,}\\n",end:"\\n\\+{4,}$",contains:[{begin:"<",end:">",subLanguage:"xml",relevance:0}],relevance:10},{className:"bullet",begin:"^(\\*+|-+|\\.+|[^\\n]+?::)\\s+"},{className:"symbol",begin:"^(NOTE|TIP|IMPORTANT|WARNING|CAUTION):\\s+",relevance:10},{begin:/\\[*_`]/},{begin:/\\\\\*{2}[^\n]*?\*{2}/},{begin:/\\\\_{2}[^\n]*_{2}/},{begin:/\\\\`{2}[^\n]*`{2}/},{begin:/[:;}][*_`](?![*_`])/},...n,...t,{className:"string",variants:[{begin:"``.+?''"},{begin:"`.+?'"}]},{className:"code",begin:/`{2}/,end:/(\n{2}|`{2})/},{className:"code",begin:"(`.+?`|\\+.+?\\+)",relevance:0},{className:"code",begin:"^[ \\t]",end:"$",relevance:0},{begin:"^'{3,}[ \\t]*$",relevance:10},{begin:"(link:)?(http|https|ftp|file|irc|image:?):\\S+?\\[[^[]*?\\]",returnBegin:!0,contains:[{begin:"(link|image:?):",relevance:0},{className:"link",begin:"\\w",end:"[^\\[]+",relevance:0},{className:"string",begin:"\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0,relevance:0}],relevance:10}]}};function ie(...e){return e.map((e=>{return(n=e)?"string"==typeof n?n:n.source:null;var n})).join("")}var re=function(e){const n={},t={begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/,contains:[n]}]};Object.assign(n,{className:"variable",variants:[{begin:ie(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},t]});const a={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},i={begin:/<<-?\s*(?=\w+)/,starts:{contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,className:"string"})]}},r={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,n,a]};a.contains.push(r);const s={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,n]},o=e.SHEBANG({binary:`(${["fish","bash","zsh","sh","csh","ksh","tcsh","dash","scsh"].join("|")})`,relevance:10}),l={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b[a-z._-]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp"},contains:[o,e.SHEBANG(),l,s,e.HASH_COMMENT_MODE,i,r,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},n]}};const se=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],oe=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],le=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],ce=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],ge=["align-content","align-items","align-self","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","auto","backface-visibility","background","background-attachment","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","border","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","clear","clip","clip-path","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","content","counter-increment","counter-reset","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-smoothing","font-stretch","font-style","font-variant","font-variant-ligatures","font-variation-settings","font-weight","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inherit","initial","justify-content","left","letter-spacing","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-bottom","margin-left","margin-right","margin-top","marks","mask","max-height","max-width","min-height","min-width","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-bottom","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","perspective","perspective-origin","pointer-events","position","quotes","resize","right","src","tab-size","table-layout","text-align","text-align-last","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-indent","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","white-space","widows","width","word-break","word-spacing","word-wrap","z-index"].reverse();function de(e){return function(...e){return e.map((e=>function(e){return e?"string"==typeof e?e:e.source:null}(e))).join("")}("(?=",e,")")}var ue=function(e){const n=(e=>({IMPORTANT:{className:"meta",begin:"!important"},HEXCOLOR:{className:"number",begin:"#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})"},ATTRIBUTE_SELECTOR_MODE:{className:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]}}))(e),t=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE];return{name:"CSS",case_insensitive:!0,illegal:/[=|'\$]/,keywords:{keyframePosition:"from to"},classNameAliases:{keyframePosition:"selector-tag"},contains:[e.C_BLOCK_COMMENT_MODE,{begin:/-(webkit|moz|ms|o)-(?=[a-z])/},e.CSS_NUMBER_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/,relevance:0},{className:"selector-class",begin:"\\.[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},n.ATTRIBUTE_SELECTOR_MODE,{className:"selector-pseudo",variants:[{begin:":("+le.join("|")+")"},{begin:"::("+ce.join("|")+")"}]},{className:"attribute",begin:"\\b("+ge.join("|")+")\\b"},{begin:":",end:"[;}]",contains:[n.HEXCOLOR,n.IMPORTANT,e.CSS_NUMBER_MODE,...t,{begin:/(url|data-uri)\(/,end:/\)/,relevance:0,keywords:{built_in:"url data-uri"},contains:[{className:"string",begin:/[^)]/,endsWithParent:!0,excludeEnd:!0}]},{className:"built_in",begin:/[\w-]+(?=\()/}]},{begin:de(/@/),end:"[{;]",relevance:0,illegal:/:/,contains:[{className:"keyword",begin:/@-?\w[\w]*(-\w+)*/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:{$pattern:/[a-z-]+/,keyword:"and or not only",attribute:oe.join(" ")},contains:[{begin:/[a-z-]+(?=:)/,className:"attribute"},...t,e.CSS_NUMBER_MODE]}]},{className:"selector-tag",begin:"\\b("+se.join("|")+")\\b"}]}};var be=function(e){return{name:"Diff",aliases:["patch"],contains:[{className:"meta",relevance:10,variants:[{begin:/^@@ +-\d+,\d+ +\+\d+,\d+ +@@/},{begin:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{begin:/^--- +\d+,\d+ +----$/}]},{className:"comment",variants:[{begin:/Index: /,end:/$/},{begin:/^index/,end:/$/},{begin:/={3,}/,end:/$/},{begin:/^-{3}/,end:/$/},{begin:/^\*{3} /,end:/$/},{begin:/^\+{3}/,end:/$/},{begin:/^\*{15}$/},{begin:/^diff --git/,end:/$/}]},{className:"addition",begin:/^\+/,end:/$/},{className:"deletion",begin:/^-/,end:/$/},{className:"addition",begin:/^!/,end:/$/}]}};var me=function(e){return{name:"Dockerfile",aliases:["docker"],case_insensitive:!0,keywords:"from maintainer expose env arg user onbuild stopsignal",contains:[e.HASH_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.NUMBER_MODE,{beginKeywords:"run cmd entrypoint volume add copy workdir label healthcheck shell",starts:{end:/[^\\]$/,subLanguage:"bash"}}],illegal:"function(e){return e?"string"==typeof e?e:e.source:null}(e))).join("")}("(?=",e,")")}function pe(e,n={}){return n.variants=e,n}var _e=function(e){const n="[A-Za-z0-9_$]+",t=pe([e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]})]),a={className:"regexp",begin:/~?\/[^\/\n]+\//,contains:[e.BACKSLASH_ESCAPE]},i=pe([e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE]),r=pe([{begin:/"""/,end:/"""/},{begin:/'''/,end:/'''/},{begin:"\\$/",end:"/\\$",relevance:10},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE],{className:"string"});return{name:"Groovy",keywords:{built_in:"this super",literal:"true false null",keyword:"byte short char int long boolean float double void def as in assert trait abstract static volatile transient public private protected synchronized final class interface enum if else for while switch case break default continue throw throws try catch finally implements extends new import package return instanceof"},contains:[e.SHEBANG({binary:"groovy",relevance:10}),t,r,a,i,{className:"class",beginKeywords:"class interface trait enum",end:/\{/,illegal:":",contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{className:"meta",begin:"@[A-Za-z]+",relevance:0},{className:"attr",begin:n+"[ \t]*:",relevance:0},{begin:/\?/,end:/:/,relevance:0,contains:[t,r,a,i,"self"]},{className:"symbol",begin:"^[ \t]*"+fe(n+":"),excludeBegin:!0,end:n+":",relevance:0}],illegal:/#|<\//}};function Ee(...e){return e.map((e=>{return(n=e)?"string"==typeof n?n:n.source:null;var n})).join("")}var ve=function(e){const n="HTTP/(2|1\\.[01])",t={className:"attribute",begin:Ee("^",/[A-Za-z][A-Za-z0-9-]*/,"(?=\\:\\s)"),starts:{contains:[{className:"punctuation",begin:/: /,relevance:0,starts:{end:"$",relevance:0}}]}},a=[t,{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}];return{name:"HTTP",aliases:["https"],illegal:/\S/,contains:[{begin:"^(?="+n+" \\d{3})",end:/$/,contains:[{className:"meta",begin:n},{className:"number",begin:"\\b\\d{3}\\b"}],starts:{end:/\b\B/,illegal:/\S/,contains:a}},{begin:"(?=^[A-Z]+ (.*?) "+n+"$)",end:/$/,contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{className:"meta",begin:n},{className:"keyword",begin:"[A-Z]+"}],starts:{end:/\b\B/,illegal:/\S/,contains:a}},e.inherit(t,{relevance:0})]}},Ne="\\.([0-9](_*[0-9])*)",ye="[0-9a-fA-F](_*[0-9a-fA-F])*",we={className:"number",variants:[{begin:`(\\b([0-9](_*[0-9])*)((${Ne})|\\.)?|(${Ne}))[eE][+-]?([0-9](_*[0-9])*)[fFdD]?\\b`},{begin:`\\b([0-9](_*[0-9])*)((${Ne})[fFdD]?\\b|\\.([fFdD]\\b)?)`},{begin:`(${Ne})[fFdD]?\\b`},{begin:"\\b([0-9](_*[0-9])*)[fFdD]\\b"},{begin:`\\b0[xX]((${ye})\\.?|(${ye})?\\.(${ye}))[pP][+-]?([0-9](_*[0-9])*)[fFdD]?\\b`},{begin:"\\b(0|[1-9](_*[0-9])*)[lL]?\\b"},{begin:`\\b0[xX](${ye})[lL]?\\b`},{begin:"\\b0(_*[0-7])*[lL]?\\b"},{begin:"\\b0[bB][01](_*[01])*[lL]?\\b"}],relevance:0};var xe=function(e){var n="[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",t="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",a={className:"meta",begin:"@"+n,contains:[{begin:/\(/,end:/\)/,contains:["self"]}]};const i=we;return{name:"Java",aliases:["jsp"],keywords:t,illegal:/<\/|#/,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),{begin:/import java\.[a-z]+\./,keywords:"import",relevance:2},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"class",beginKeywords:"class interface enum",end:/[{;=]/,excludeEnd:!0,relevance:1,keywords:"class interface enum",illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"new throw return else",relevance:0},{className:"class",begin:"record\\s+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,excludeEnd:!0,end:/[{;=]/,keywords:t,contains:[{beginKeywords:"record"},{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,keywords:t,relevance:0,contains:[e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"function",begin:"([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:t,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,keywords:t,relevance:0,contains:[a,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,i,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},i,a]}};const Oe="[A-Za-z$_][0-9A-Za-z$_]*",Me=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],ke=["true","false","null","undefined","NaN","Infinity"],Re=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer","BigInt64Array","BigUint64Array","BigInt"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function Ae(e){return Se("(?=",e,")")}function Se(...e){return e.map((e=>{return(n=e)?"string"==typeof n?n:n.source:null;var n})).join("")}var Te=function(e){const n=Oe,t="<>",a="",i={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{const t=e[0].length+e.index,a=e.input[t];"<"!==a?">"===a&&(((e,{after:n})=>{const t="",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:e.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:r,contains:f}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:t,end:a},{begin:i.begin,"on:begin":i.isTrulyOpeningTag,end:i.end}],subLanguage:"xml",contains:[{begin:i.begin,end:i.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[{;]/,excludeEnd:!0,keywords:r,contains:["self",e.inherit(e.TITLE_MODE,{begin:n}),p],illegal:/%/},{beginKeywords:"while if switch catch for"},{className:"function",begin:e.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",returnBegin:!0,contains:[p,e.inherit(e.TITLE_MODE,{begin:n})]},{variants:[{begin:"\\."+n},{begin:"\\$"+n}],relevance:0},{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"[\]]/,contains:[{beginKeywords:"extends"},e.UNDERSCORE_TITLE_MODE]},{begin:/\b(?=constructor)/,end:/[{;]/,excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:n}),"self",p]},{begin:"(get|set)\\s+(?="+n+"\\()",end:/\{/,keywords:"get set",contains:[e.inherit(e.TITLE_MODE,{begin:n}),{begin:/\(\)/},p]},{begin:/\$[(.]/}]}};var Ce=function(e){const n={literal:"true false null"},t=[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE],a=[e.QUOTE_STRING_MODE,e.C_NUMBER_MODE],i={end:",",endsWithParent:!0,excludeEnd:!0,contains:a,keywords:n},r={begin:/\{/,end:/\}/,contains:[{className:"attr",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE],illegal:"\\n"},e.inherit(i,{begin:/:/})].concat(t),illegal:"\\S"},s={begin:"\\[",end:"\\]",contains:[e.inherit(i)],illegal:"\\S"};return a.push(r,s),t.forEach((function(e){a.push(e)})),{name:"JSON",contains:a,keywords:n,illegal:"\\S"}},De="\\.([0-9](_*[0-9])*)",Le="[0-9a-fA-F](_*[0-9a-fA-F])*",Be={className:"number",variants:[{begin:`(\\b([0-9](_*[0-9])*)((${De})|\\.)?|(${De}))[eE][+-]?([0-9](_*[0-9])*)[fFdD]?\\b`},{begin:`\\b([0-9](_*[0-9])*)((${De})[fFdD]?\\b|\\.([fFdD]\\b)?)`},{begin:`(${De})[fFdD]?\\b`},{begin:"\\b([0-9](_*[0-9])*)[fFdD]\\b"},{begin:`\\b0[xX]((${Le})\\.?|(${Le})?\\.(${Le}))[pP][+-]?([0-9](_*[0-9])*)[fFdD]?\\b`},{begin:"\\b(0|[1-9](_*[0-9])*)[lL]?\\b"},{begin:`\\b0[xX](${Le})[lL]?\\b`},{begin:"\\b0(_*[0-7])*[lL]?\\b"},{begin:"\\b0[bB][01](_*[01])*[lL]?\\b"}],relevance:0};var $e=function(e){const n={keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual",built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",literal:"true false null"},t={className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"@"},a={className:"subst",begin:/\$\{/,end:/\}/,contains:[e.C_NUMBER_MODE]},i={className:"variable",begin:"\\$"+e.UNDERSCORE_IDENT_RE},r={className:"string",variants:[{begin:'"""',end:'"""(?=[^"])',contains:[i,a]},{begin:"'",end:"'",illegal:/\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/,contains:[e.BACKSLASH_ESCAPE,i,a]}]};a.contains.push(r);const s={className:"meta",begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UNDERSCORE_IDENT_RE+")?"},o={className:"meta",begin:"@"+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/,end:/\)/,contains:[e.inherit(r,{className:"meta-string"})]}]},l=Be,c=e.COMMENT("/\\*","\\*/",{contains:[e.C_BLOCK_COMMENT_MODE]}),g={variants:[{className:"type",begin:e.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/,contains:[]}]},d=g;return d.variants[1].contains=[g],g.variants[1].contains=[d],{name:"Kotlin",aliases:["kt","kts"],keywords:n,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,c,{className:"keyword",begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol",begin:/@\w+/}]}},t,s,o,{className:"function",beginKeywords:"fun",end:"[(]|$",returnBegin:!0,excludeEnd:!0,keywords:n,relevance:5,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"type",begin://,keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:n,relevance:0,contains:[{begin:/:/,end:/[=,\/]/,endsWithParent:!0,contains:[g,e.C_LINE_COMMENT_MODE,c],relevance:0},e.C_LINE_COMMENT_MODE,c,s,o,r,e.C_NUMBER_MODE]},c]},{className:"class",beginKeywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0,illegal:"extends implements",contains:[{beginKeywords:"public protected internal private constructor"},e.UNDERSCORE_TITLE_MODE,{className:"type",begin://,excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,]|$/,excludeBegin:!0,returnEnd:!0},s,o]},r,{className:"meta",begin:"^#!/usr/bin/env",end:"$",illegal:"\n"},l]}};function Ie(...e){return e.map((e=>{return(n=e)?"string"==typeof n?n:n.source:null;var n})).join("")}var je=function(e){const n={begin:/<\/?[A-Za-z_]/,end:">",subLanguage:"xml",relevance:0},t={variants:[{begin:/\[.+?\]\[.*?\]/,relevance:0},{begin:/\[.+?\]\(((data|javascript|mailto):|(?:http|ftp)s?:\/\/).*?\)/,relevance:2},{begin:Ie(/\[.+?\]\(/,/[A-Za-z][A-Za-z0-9+.-]*/,/:\/\/.*?\)/),relevance:2},{begin:/\[.+?\]\([./?&#].*?\)/,relevance:1},{begin:/\[.+?\]\(.*?\)/,relevance:0}],returnBegin:!0,contains:[{className:"string",relevance:0,begin:"\\[",end:"\\]",excludeBegin:!0,returnEnd:!0},{className:"link",relevance:0,begin:"\\]\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0},{className:"symbol",relevance:0,begin:"\\]\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0}]},a={className:"strong",contains:[],variants:[{begin:/_{2}/,end:/_{2}/},{begin:/\*{2}/,end:/\*{2}/}]},i={className:"emphasis",contains:[],variants:[{begin:/\*(?!\*)/,end:/\*/},{begin:/_(?!_)/,end:/_/,relevance:0}]};a.contains.push(i),i.contains.push(a);let r=[n,t];return a.contains=a.contains.concat(r),i.contains=i.contains.concat(r),r=r.concat(a,i),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:r},{begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n",contains:r}]}]},n,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)",end:"\\s+",excludeEnd:!0},a,i,{className:"quote",begin:"^>\\s+",contains:r,end:"$"},{className:"code",variants:[{begin:"(`{3,})[^`](.|\\n)*?\\1`*[ ]*"},{begin:"(~{3,})[^~](.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))",contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{begin:"^[-\\*]{3,}",end:"$"},t,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}};var ze=function(e){const n={keyword:"rec with let in inherit assert if else then",literal:"true false or and null",built_in:"import abort baseNameOf dirOf isNull builtins map removeAttrs throw toString derivation"},t={className:"subst",begin:/\$\{/,end:/\}/,keywords:n},a={className:"string",contains:[t],variants:[{begin:"''",end:"''"},{begin:'"',end:'"'}]},i=[e.NUMBER_MODE,e.HASH_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,{begin:/[a-zA-Z0-9-_]+(\s*=)/,returnBegin:!0,relevance:0,contains:[{className:"attr",begin:/\S+/}]}];return t.contains=i,{name:"Nix",aliases:["nixos"],keywords:n,contains:i}};var Pe=function(e){var n="[ \\t\\f]*",t=n+"[:=]"+n,a="[ \\t\\f]+",i="("+t+"|"+"[ \\t\\f]+)",r="([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",s="([^\\\\:= \\t\\f\\n]|\\\\.)+",o={end:i,relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{begin:"\\\\\\\\"},{begin:"\\\\\\n"}]}};return{name:".properties",case_insensitive:!0,illegal:/\S/,contains:[e.COMMENT("^\\s*[!#]","$"),{returnBegin:!0,variants:[{begin:r+t,relevance:1},{begin:r+a,relevance:0}],contains:[{className:"attr",begin:r,endsParent:!0,relevance:0}],starts:o},{begin:s+i,returnBegin:!0,relevance:0,contains:[{className:"meta",begin:s,endsParent:!0,relevance:0}],starts:o},{className:"attr",relevance:0,begin:s+n+"$"}]}};function Ue(...e){return e.map((e=>{return(n=e)?"string"==typeof n?n:n.source:null;var n})).join("")}var Ke=function(e){const n="([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)",t={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor __FILE__",built_in:"proc lambda",literal:"true false nil"},a={className:"doctag",begin:"@[A-Za-z]+"},i={begin:"#<",end:">"},r=[e.COMMENT("#","$",{contains:[a]}),e.COMMENT("^=begin","^=end",{contains:[a],relevance:10}),e.COMMENT("^__END__","\\n$")],s={className:"subst",begin:/#\{/,end:/\}/,keywords:t},o={className:"string",contains:[e.BACKSLASH_ESCAPE,s],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:/%[qQwWx]?\(/,end:/\)/},{begin:/%[qQwWx]?\[/,end:/\]/},{begin:/%[qQwWx]?\{/,end:/\}/},{begin:/%[qQwWx]?/},{begin:/%[qQwWx]?\//,end:/\//},{begin:/%[qQwWx]?%/,end:/%/},{begin:/%[qQwWx]?-/,end:/-/},{begin:/%[qQwWx]?\|/,end:/\|/},{begin:/\B\?(\\\d{1,3})/},{begin:/\B\?(\\x[A-Fa-f0-9]{1,2})/},{begin:/\B\?(\\u\{?[A-Fa-f0-9]{1,6}\}?)/},{begin:/\B\?(\\M-\\C-|\\M-\\c|\\c\\M-|\\M-|\\C-\\M-)[\x20-\x7e]/},{begin:/\B\?\\(c|C-)[\x20-\x7e]/},{begin:/\B\?\\?\S/},{begin:/<<[-~]?'?(\w+)\n(?:[^\n]*\n)*?\s*\1\b/,returnBegin:!0,contains:[{begin:/<<[-~]?'?/},e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,contains:[e.BACKSLASH_ESCAPE,s]})]}]},l="[0-9](_?[0-9])*",c={className:"number",relevance:0,variants:[{begin:`\\b([1-9](_?[0-9])*|0)(\\.(${l}))?([eE][+-]?(${l})|r)?i?\\b`},{begin:"\\b0[dD][0-9](_?[0-9])*r?i?\\b"},{begin:"\\b0[bB][0-1](_?[0-1])*r?i?\\b"},{begin:"\\b0[oO][0-7](_?[0-7])*r?i?\\b"},{begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\b"},{begin:"\\b0(_?[0-7])+r?i?\\b"}]},g={className:"params",begin:"\\(",end:"\\)",endsParent:!0,keywords:t},d=[o,{className:"class",beginKeywords:"class module",end:"$|;",illegal:/=/,contains:[e.inherit(e.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|!)?"}),{begin:"<\\s*",contains:[{begin:"("+e.IDENT_RE+"::)?"+e.IDENT_RE,relevance:0}]}].concat(r)},{className:"function",begin:Ue(/def\s+/,(u=n+"\\s*(\\(|;|$)",Ue("(?=",u,")"))),relevance:0,keywords:"def",end:"$|;",contains:[e.inherit(e.TITLE_MODE,{begin:n}),g].concat(r)},{begin:e.IDENT_RE+"::"},{className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"(!|\\?)?:",relevance:0},{className:"symbol",begin:":(?!\\s)",contains:[o,{begin:n}],relevance:0},c,{className:"variable",begin:"(\\$\\W)|((\\$|@@?)(\\w+))(?=[^@$?])(?![A-Za-z])(?![@$?'])"},{className:"params",begin:/\|/,end:/\|/,relevance:0,keywords:t},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[{className:"regexp",contains:[e.BACKSLASH_ESCAPE,s],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:/%r\{/,end:/\}[a-z]*/},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(i,r),relevance:0}].concat(i,r);var u;s.contains=d,g.contains=d;const b=[{begin:/^\s*=>/,starts:{end:"$",contains:d}},{className:"meta",begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d+(p\\d+)?[^\\d][^>]+>)(?=[ ])",starts:{end:"$",contains:d}}];return r.unshift(i),{name:"Ruby",aliases:["rb","gemspec","podspec","thor","irb"],keywords:t,illegal:/\/\*/,contains:[e.SHEBANG({binary:"ruby"})].concat(b).concat(r).concat(d)}};var He=function(e){const n={className:"subst",variants:[{begin:"\\$[A-Za-z0-9_]+"},{begin:/\$\{/,end:/\}/}]},t={className:"string",variants:[{begin:'"""',end:'"""'},{begin:'"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:'[a-z]+"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE,n]},{className:"string",begin:'[a-z]+"""',end:'"""',contains:[n],relevance:10}]},a={className:"type",begin:"\\b[A-Z][A-Za-z0-9_]*",relevance:0},i={className:"title",begin:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,relevance:0},r={className:"class",beginKeywords:"class object trait type",end:/[:={\[\n;]/,excludeEnd:!0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{beginKeywords:"extends with",relevance:10},{begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[a]},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[a]},i]},s={className:"function",beginKeywords:"def",end:/[:={\[(\n;]/,excludeEnd:!0,contains:[i]};return{name:"Scala",keywords:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t,{className:"symbol",begin:"'\\w[\\w\\d_]*(?!')"},a,s,r,e.C_NUMBER_MODE,{className:"meta",begin:"@[A-Za-z]+"}]}};var Ze=function(e){return{name:"Shell Session",aliases:["console"],contains:[{className:"meta",begin:/^\s{0,3}[/~\w\d[\]()@-]*[>%$#]/,starts:{end:/[^\\](?=\s*$)/,subLanguage:"bash"}}]}};function Ge(e){return e?"string"==typeof e?e:e.source:null}function Fe(...e){return e.map((e=>Ge(e))).join("")}function qe(...e){return"("+e.map((e=>Ge(e))).join("|")+")"}var We=function(e){const n=e.COMMENT("--","$"),t=["true","false","unknown"],a=["bigint","binary","blob","boolean","char","character","clob","date","dec","decfloat","decimal","float","int","integer","interval","nchar","nclob","national","numeric","real","row","smallint","time","timestamp","varchar","varying","varbinary"],i=["abs","acos","array_agg","asin","atan","avg","cast","ceil","ceiling","coalesce","corr","cos","cosh","count","covar_pop","covar_samp","cume_dist","dense_rank","deref","element","exp","extract","first_value","floor","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","last_value","lead","listagg","ln","log","log10","lower","max","min","mod","nth_value","ntile","nullif","percent_rank","percentile_cont","percentile_disc","position","position_regex","power","rank","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","row_number","sin","sinh","sqrt","stddev_pop","stddev_samp","substring","substring_regex","sum","tan","tanh","translate","translate_regex","treat","trim","trim_array","unnest","upper","value_of","var_pop","var_samp","width_bucket"],r=["create table","insert into","primary key","foreign key","not null","alter table","add constraint","grouping sets","on overflow","character set","respect nulls","ignore nulls","nulls first","nulls last","depth first","breadth first"],s=i,o=["abs","acos","all","allocate","alter","and","any","are","array","array_agg","array_max_cardinality","as","asensitive","asin","asymmetric","at","atan","atomic","authorization","avg","begin","begin_frame","begin_partition","between","bigint","binary","blob","boolean","both","by","call","called","cardinality","cascaded","case","cast","ceil","ceiling","char","char_length","character","character_length","check","classifier","clob","close","coalesce","collate","collect","column","commit","condition","connect","constraint","contains","convert","copy","corr","corresponding","cos","cosh","count","covar_pop","covar_samp","create","cross","cube","cume_dist","current","current_catalog","current_date","current_default_transform_group","current_path","current_role","current_row","current_schema","current_time","current_timestamp","current_path","current_role","current_transform_group_for_type","current_user","cursor","cycle","date","day","deallocate","dec","decimal","decfloat","declare","default","define","delete","dense_rank","deref","describe","deterministic","disconnect","distinct","double","drop","dynamic","each","element","else","empty","end","end_frame","end_partition","end-exec","equals","escape","every","except","exec","execute","exists","exp","external","extract","false","fetch","filter","first_value","float","floor","for","foreign","frame_row","free","from","full","function","fusion","get","global","grant","group","grouping","groups","having","hold","hour","identity","in","indicator","initial","inner","inout","insensitive","insert","int","integer","intersect","intersection","interval","into","is","join","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","language","large","last_value","lateral","lead","leading","left","like","like_regex","listagg","ln","local","localtime","localtimestamp","log","log10","lower","match","match_number","match_recognize","matches","max","member","merge","method","min","minute","mod","modifies","module","month","multiset","national","natural","nchar","nclob","new","no","none","normalize","not","nth_value","ntile","null","nullif","numeric","octet_length","occurrences_regex","of","offset","old","omit","on","one","only","open","or","order","out","outer","over","overlaps","overlay","parameter","partition","pattern","per","percent","percent_rank","percentile_cont","percentile_disc","period","portion","position","position_regex","power","precedes","precision","prepare","primary","procedure","ptf","range","rank","reads","real","recursive","ref","references","referencing","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","release","result","return","returns","revoke","right","rollback","rollup","row","row_number","rows","running","savepoint","scope","scroll","search","second","seek","select","sensitive","session_user","set","show","similar","sin","sinh","skip","smallint","some","specific","specifictype","sql","sqlexception","sqlstate","sqlwarning","sqrt","start","static","stddev_pop","stddev_samp","submultiset","subset","substring","substring_regex","succeeds","sum","symmetric","system","system_time","system_user","table","tablesample","tan","tanh","then","time","timestamp","timezone_hour","timezone_minute","to","trailing","translate","translate_regex","translation","treat","trigger","trim","trim_array","true","truncate","uescape","union","unique","unknown","unnest","update ","upper","user","using","value","values","value_of","var_pop","var_samp","varbinary","varchar","varying","versioning","when","whenever","where","width_bucket","window","with","within","without","year","add","asc","collation","desc","final","first","last","view"].filter((e=>!i.includes(e))),l={begin:Fe(/\b/,qe(...s),/\s*\(/),keywords:{built_in:s}};return{name:"SQL",case_insensitive:!0,illegal:/[{}]|<\//,keywords:{$pattern:/\b[\w\.]+/,keyword:function(e,{exceptions:n,when:t}={}){const a=t;return n=n||[],e.map((e=>e.match(/\|\d+$/)||n.includes(e)?e:a(e)?`${e}|0`:e))}(o,{when:e=>e.length<3}),literal:t,type:a,built_in:["current_catalog","current_date","current_default_transform_group","current_path","current_role","current_schema","current_transform_group_for_type","current_user","session_user","system_time","system_user","current_time","localtime","current_timestamp","localtimestamp"]},contains:[{begin:qe(...r),keywords:{$pattern:/[\w\.]+/,keyword:o.concat(r),literal:t,type:a}},{className:"type",begin:qe("double precision","large object","with timezone","without timezone")},l,{className:"variable",begin:/@[a-z0-9]+/},{className:"string",variants:[{begin:/'/,end:/'/,contains:[{begin:/''/}]}]},{begin:/"/,end:/"/,contains:[{begin:/""/}]},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,n,{className:"operator",begin:/[-+*/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?/,relevance:0}]}};function Qe(e){return e?"string"==typeof e?e:e.source:null}function Xe(e){return Ve("(?=",e,")")}function Ve(...e){return e.map((e=>Qe(e))).join("")}function Je(...e){return"("+e.map((e=>Qe(e))).join("|")+")"}var Ye=function(e){const n=Ve(/[A-Z_]/,Ve("(",/[A-Z0-9_.-]*:/,")?"),/[A-Z0-9_.-]*/),t={className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},a={begin:/\s/,contains:[{className:"meta-keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]},i=e.inherit(a,{begin:/\(/,end:/\)/}),r=e.inherit(e.APOS_STRING_MODE,{className:"meta-string"}),s=e.inherit(e.QUOTE_STRING_MODE,{className:"meta-string"}),o={endsWithParent:!0,illegal:/`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin://,relevance:10,contains:[a,s,r,i,{begin:/\[/,end:/\]/,contains:[{className:"meta",begin://,contains:[a,i,s,r]}]}]},e.COMMENT(//,{relevance:10}),{begin://,relevance:10},t,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:/)/,end:/>/,keywords:{name:"style"},contains:[o],starts:{end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:/)/,end:/>/,keywords:{name:"script"},contains:[o],starts:{end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:/<>|<\/>/},{className:"tag",begin:Ve(//,/>/,/\s/)))),end:/\/?>/,contains:[{className:"name",begin:n,relevance:0,starts:o}]},{className:"tag",begin:Ve(/<\//,Xe(Ve(n,/>/))),contains:[{className:"name",begin:n,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}};var en=function(e){var n="true false yes no null",t="[\\w#;/?:@&=+$,.~*'()[\\]]+",a={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},i=e.inherit(a,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),r={className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},s={end:",",endsWithParent:!0,excludeEnd:!0,keywords:n,relevance:0},o={begin:/\{/,end:/\}/,contains:[s],illegal:"\\n",relevance:0},l={begin:"\\[",end:"\\]",contains:[s],illegal:"\\n",relevance:0},c=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$",relevance:10},{className:"string",begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+t},{className:"type",begin:"!<"+t+">"},{className:"type",begin:"!"+t},{className:"type",begin:"!!"+t},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)",relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},r,{className:"number",begin:e.C_NUMBER_RE+"\\b",relevance:0},o,l,a],g=[...c];return g.pop(),g.push(i),s.contains=g,{name:"YAML",case_insensitive:!0,aliases:["yml"],contains:c}};!function(){"use strict";ne.registerLanguage("asciidoc",ae),ne.registerLanguage("bash",re),ne.registerLanguage("css",ue),ne.registerLanguage("diff",be),ne.registerLanguage("dockerfile",me),ne.registerLanguage("gradle",he),ne.registerLanguage("groovy",_e),ne.registerLanguage("http",ve),ne.registerLanguage("java",xe),ne.registerLanguage("javascript",Te),ne.registerLanguage("json",Ce),ne.registerLanguage("kotlin",$e),ne.registerLanguage("markdown",je),ne.registerLanguage("nix",ze),ne.registerLanguage("properties",Pe),ne.registerLanguage("ruby",Ke),ne.registerLanguage("scala",He),ne.registerLanguage("shell",Ze),ne.registerLanguage("bash",Ze),ne.registerLanguage("sql",We),ne.registerLanguage("xml",Ye),ne.registerLanguage("yaml",en),ne.configure({ignoreUnescapedHTML:!0});for(const e of document.querySelectorAll("pre.highlight > code"))ne.highlightBlock(e)}()}(); +!function(){"use strict";function t(t){const e=n('
    ');return t.prepend(e),e}function e(t,e){const o=t.querySelector(".title").textContent,c=t.querySelectorAll(".content").item(0),s=function(t,e,n){let o=t.nextElementSibling;for(;o;){if(o.matches(e))return o;if(o.matches(n))return;o=o.nextElementSibling}}(t,".colist",".listingblock");s&&c.append(s);const i=n('
    '+o+"
    ");return i.dataset.blockName=o,c.dataset.blockName=o,e.append(i),{tabElement:i,content:c}}function n(t){const e=document.createElement("template");return e.innerHTML=t,e.content.firstChild}function o(t){let e=t.previousElementSibling;for(;e&&!e.classList.contains("primary");)e=e.previousElementSibling;return e}function c(t){const e=this.textContent;window.localStorage.setItem(t,e);for(const n of document.querySelectorAll(".tab"))i(n)===t&&n.textContent===e&&s(n)}function s(t){for(const e of t.parentNode.children)e.classList.remove("selected");t.classList.add("selected");for(const e of t.parentNode.parentNode.children)e.classList.contains("content")&&(t.dataset.blockName===e.dataset.blockName?e.classList.remove("hidden"):e.classList.add("hidden"))}function i(t){const e=[];for(t of t.parentNode.querySelectorAll(".tab"))e.push(t.textContent.toLowerCase());return e.sort().join("-")}window.addEventListener("load",(function(){(function(){for(const n of document.querySelectorAll(".primary")){if(n.querySelector("div.switch"))return void console.debug("Skipping tabs due to existing blockswitches");e(n,t(n)).tabElement.classList.add("selected"),n.querySelector(".title").remove(),n.classList.add("tabs-content")}for(const t of document.querySelectorAll(".secondary")){const n=o(t);if(n){const o=e(t,n.querySelector(".tabs"));o.content.classList.add("hidden"),n.append(o.content),t.remove()}else console.error("Found secondary block with no primary sibling")}})(),function(){for(const t of document.querySelectorAll(".tab")){const e=i(t);t.addEventListener("click",c.bind(t,e)),t.textContent===window.localStorage.getItem(e)&&s(t)}}()}))}(); +!function(){var t=function(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)},e={};(function(t){(function(){var n="object"==typeof t&&t&&t.Object===Object&&t;e=n}).call(this)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{});var n="object"==typeof self&&self&&self.Object===Object&&self,o=e||n||Function("return this")(),r=function(){return o.Date.now()},i=/\s/;var c=function(t){for(var e=t.length;e--&&i.test(t.charAt(e)););return e},u=/^\s+/;var a=function(t){return t?t.slice(0,c(t)+1).replace(u,""):t},l=o.Symbol,f=Object.prototype,d=f.hasOwnProperty,s=f.toString,m=l?l.toStringTag:void 0;var v=function(t){var e=d.call(t,m),n=t[m];try{t[m]=void 0;var o=!0}catch(t){}var r=s.call(t);return o&&(e?t[m]=n:delete t[m]),r},p=Object.prototype.toString;var g=function(t){return p.call(t)},h=l?l.toStringTag:void 0;var y=function(t){return null==t?void 0===t?"[object Undefined]":"[object Null]":h&&h in Object(t)?v(t):g(t)};var w=function(t){return null!=t&&"object"==typeof t};var E=function(t){return"symbol"==typeof t||w(t)&&"[object Symbol]"==y(t)},b=/^[-+]0x[0-9a-f]+$/i,L=/^0b[01]+$/i,j=/^0o[0-7]+$/i,x=parseInt;var S=function(e){if("number"==typeof e)return e;if(E(e))return NaN;if(t(e)){var n="function"==typeof e.valueOf?e.valueOf():e;e=t(n)?n+"":n}if("string"!=typeof e)return 0===e?e:+e;e=a(e);var o=L.test(e);return o||j.test(e)?x(e.slice(2),o?2:8):b.test(e)?NaN:+e},T=Math.max,O=Math.min;var N=function(e,n,o){var i,c,u,a,l,f,d=0,s=!1,m=!1,v=!0;if("function"!=typeof e)throw new TypeError("Expected a function");function p(t){var n=i,o=c;return i=c=void 0,d=t,a=e.apply(o,n)}function g(t){return d=t,l=setTimeout(y,n),s?p(t):a}function h(t){var e=t-f;return void 0===f||e>=n||e<0||m&&t-d>=u}function y(){var t=r();if(h(t))return w(t);l=setTimeout(y,function(t){var e=n-(t-f);return m?O(e,u-(t-d)):e}(t))}function w(t){return l=void 0,v&&i?p(t):(i=c=void 0,a)}function E(){var t=r(),e=h(t);if(i=arguments,c=this,f=t,e){if(void 0===l)return g(f);if(m)return clearTimeout(l),l=setTimeout(y,n),p(f)}return void 0===l&&(l=setTimeout(y,n)),a}return n=S(n)||0,t(o)&&(s=!!o.leading,u=(m="maxWait"in o)?T(S(o.maxWait)||0,n):u,v="trailing"in o?!!o.trailing:v),E.cancel=function(){void 0!==l&&clearTimeout(l),d=0,i=f=c=l=void 0},E.flush=function(){return void 0===l?a:w(r())},E};var q=function(e,n,o){var r=!0,i=!0;if("function"!=typeof e)throw new TypeError("Expected a function");return t(o)&&(r="leading"in o?!!o.leading:r,i="trailing"in o?!!o.trailing:i),N(e,n,{leading:r,maxWait:n,trailing:i})};!function(){"use strict";let t,e,n,o,r,i,c=null,u=!1;function a(){v();const t=r.get(window.location.hash),e=g(window.location.hash);t&&E(e)&&(u=!0,b("activating window location hash"),y(t.parentElement)),f()}window.addEventListener("load",(function(){if(t=document.querySelector("#toc"),e=document.querySelector("#toggle-toc"),n=document.querySelector("#content"),!t||!n)return;o=function(){const e=r(),o=[];for(let t=0;t<=e;t++)o.push("h"+(t+1)+"[id]");return n.querySelectorAll(o);function r(){let e=1;for(const n of t.querySelectorAll("ul","ol"))e=Math.max(e,i(n));return e}function i(e){let n=0;for(;e&&e!==t;)n+="UL"===e.nodeName||"OL"===e.nodeName?1:0,e=e.parentElement;return e?n:-1}}(),r=function(){const e=new Map;for(const n of t.querySelectorAll("li > a")){const t=n.getAttribute("href");t&&e.set(t,n)}return e}(),i=function(){const t=new Map;for(const e of o){const n=h(e);if(n){const o=r.get(n);if(o){const n=o.parentElement;t.set(e,n)}}}return t}(),a(),window.addEventListener("hashchange",a),window.addEventListener("scroll",l),window.addEventListener("scroll",f),window.addEventListener("resize",d),t.addEventListener("click",s),e.addEventListener("click",m)}));const l=q((function(){v(),u||p()}),50,{leading:!0}),f=N((function(){if(b("scrolling ended"),v(),u=!1,c){E(g(h(c)))||p()}else p()}),50),d=q((function(){v()}),50,{leading:!0});function s(t){if("A"===t.target.nodeName){const e=t.target.parentElement;if(e&&"back-to-index"===e.id)return;u=!0,b("activating clicked toc element"),y(t.target.parentElement)}}function m(t){t.stopPropagation();document.body.classList.toggle("show-toc")?document.documentElement.addEventListener("click",m):document.documentElement.removeEventListener("click",m)}function v(){const t=window.getComputedStyle(document.documentElement),e=parseInt(t.getPropertyValue("--layout-banner-height"),10);w()>=e?document.body.classList.add("fixed-toc"):document.body.classList.remove("fixed-toc")}function p(){b("activating top header element");const t=function(){const t=w()+45;for(let e=0;et)return o[e-1>=0?e-1:0];return o[o.length-1]}();y(i.get(t))}function g(t){for(let e=0;e=0&&e.bottom<=(window.innerHeight||document.documentElement.clientHeight)}function b(t){false}}()}(); +//# sourceMappingURL=site.js.map diff --git a/3.8.13/reference/html/js/site.js.map b/3.8.13/reference/html/js/site.js.map new file mode 100755 index 0000000000..1994be0021 --- /dev/null +++ b/3.8.13/reference/html/js/site.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["site/src/main/js/site/anchorrewrite.js","site/anchorrewrite.js","site/src/main/js/site/author.js","site/author.js","site/node_modules/browser-pack-flat/_prelude","site/node_modules/lodash/_baseDelay.js","site/codetools.js","site/node_modules/lodash/identity.js","site/node_modules/lodash/_apply.js","site/node_modules/lodash/_overRest.js","site/node_modules/lodash/constant.js","site/node_modules/lodash/_freeGlobal.js","site/node_modules/lodash/_root.js","site/node_modules/lodash/_Symbol.js","site/node_modules/lodash/_getRawTag.js","site/node_modules/lodash/_objectToString.js","site/node_modules/lodash/_baseGetTag.js","site/node_modules/lodash/isObject.js","site/node_modules/lodash/isFunction.js","site/node_modules/lodash/_isMasked.js","site/node_modules/lodash/_coreJsData.js","site/node_modules/lodash/_toSource.js","site/node_modules/lodash/_baseIsNative.js","site/node_modules/lodash/_getValue.js","site/node_modules/lodash/_getNative.js","site/node_modules/lodash/_defineProperty.js","site/node_modules/lodash/_baseSetToString.js","site/node_modules/lodash/_shortOut.js","site/node_modules/lodash/_setToString.js","site/node_modules/lodash/_baseRest.js","site/node_modules/lodash/_trimmedEndIndex.js","site/node_modules/lodash/_baseTrim.js","site/node_modules/lodash/isObjectLike.js","site/node_modules/lodash/isSymbol.js","site/node_modules/lodash/toNumber.js","site/node_modules/lodash/delay.js","site/src/main/js/site/codetools.js","site/node_modules/browser-pack-flat/_postlude","site/node_modules/highlight.js/lib/core.js","site/highlight.js","site/node_modules/highlight.js/lib/languages/asciidoc.js","site/node_modules/highlight.js/lib/languages/bash.js","site/node_modules/highlight.js/lib/languages/css.js","site/node_modules/highlight.js/lib/languages/diff.js","site/node_modules/highlight.js/lib/languages/dockerfile.js","site/node_modules/highlight.js/lib/languages/gradle.js","site/node_modules/highlight.js/lib/languages/groovy.js","site/node_modules/highlight.js/lib/languages/http.js","site/node_modules/highlight.js/lib/languages/java.js","site/node_modules/highlight.js/lib/languages/javascript.js","site/node_modules/highlight.js/lib/languages/json.js","site/node_modules/highlight.js/lib/languages/kotlin.js","site/node_modules/highlight.js/lib/languages/markdown.js","site/node_modules/highlight.js/lib/languages/nix.js","site/node_modules/highlight.js/lib/languages/properties.js","site/node_modules/highlight.js/lib/languages/ruby.js","site/node_modules/highlight.js/lib/languages/scala.js","site/node_modules/highlight.js/lib/languages/shell.js","site/node_modules/highlight.js/lib/languages/sql.js","site/node_modules/highlight.js/lib/languages/xml.js","site/node_modules/highlight.js/lib/languages/yaml.js","site/src/main/js/site/highlight.js","site/src/main/js/site/tabs.js","site/tabs.js","site/toc.js","site/node_modules/lodash/now.js","site/node_modules/lodash/debounce.js","site/node_modules/lodash/throttle.js","site/src/main/js/site/toc.js"],"names":["onChange","element","document","getElementById","anchor","window","location","hash","substr","rewrites","seen","console","debug","includes","error","push","updateAnchor","JSON","parse","innerHTML","addEventListener","candidate","lastAuthorElement","classList","contains","nextElementSibling","add","markLastAuthor","_$baseDelay_3","func","wait","args","TypeError","setTimeout","apply","undefined","_$identity_25","value","_$apply_2","thisArg","length","call","nativeMax","Math","max","_$overRest_17","start","transform","arguments","index","array","Array","otherArgs","this","_$constant_23","_$freeGlobal_11","global","freeGlobal","Object","self","freeSelf","_$root_18","Function","_$Symbol_1","Symbol","objectProto","prototype","hasOwnProperty","nativeObjectToString","toString","symToStringTag","toStringTag","_$getRawTag_13","isOwn","tag","unmasked","e","result","__nativeObjectToString_16","_$objectToString_16","__symToStringTag_4","_$baseGetTag_4","_$isObject_27","type","uid","_$isFunction_26","_$coreJsData_9","maskSrcKey","exec","keys","IE_PROTO","_$isMasked_15","funcToString","_$toSource_21","reIsHostCtor","__funcProto_5","__objectProto_5","__funcToString_5","__hasOwnProperty_5","reIsNative","RegExp","replace","_$baseIsNative_5","test","_$getValue_14","object","key","_$getNative_12","_$defineProperty_10","_$baseSetToString_7","string","configurable","enumerable","writable","nativeNow","Date","now","_$shortOut_20","count","lastCalled","stamp","remaining","_$setToString_19","reWhitespace","_$trimmedEndIndex_22","charAt","reTrimStart","_$baseTrim_8","slice","_$isObjectLike_28","_$isSymbol_29","reIsBadHex","reIsBinary","reIsOctal","freeParseInt","parseInt","_$toNumber_30","other","valueOf","isBinary","_$delay_24","_$baseRest_6","onCopyButtonClick","preElement","copy","querySelector","cloneNode","hideWhenFoldedElement","querySelectorAll","parentNode","removeChild","text","innerText","navigator","clipboard","writeText","then","markClicked","bind","clearClicked","remove","onFoldUnfoldButtonClick","codeElement","unfolding","toggle","updateFoldUnfoldButton","button","unfold","label","title","codeToolsElement","createElement","className","addButtons","appendChild","numberOfButtons","hasHideWhenFoldedSpans","foldUnfoldButton","createButton","addFoldUnfoldButton","copyButton","copiedPopup","addCopyButton","buttonElement","labelElement","createTextNode","addCodeToolElements","deepFreeze","obj","Map","clear","delete","set","Error","Set","freeze","getOwnPropertyNames","forEach","name","prop","isFrozen","deepFreezeEs6","_default","default","Response","constructor","mode","data","isMatchIgnored","ignoreMatch","escapeHTML","inherit","original","objects","create","emitsWrappingTags","node","kind","HTMLRenderer","parseTree","options","buffer","classPrefix","walk","addText","openNode","sublanguage","span","closeNode","TokenTree","rootNode","children","stack","top","root","pop","closeAllNodes","toJSON","stringify","builder","_walk","static","child","every","el","join","_collapse","TokenTreeEmitter","super","addKeyword","addSublanguage","emitter","toHTML","finalize","source","re","BACKREF_RE","IDENT_RE","UNDERSCORE_IDENT_RE","NUMBER_RE","C_NUMBER_RE","BINARY_NUMBER_RE","BACKSLASH_ESCAPE","begin","relevance","APOS_STRING_MODE","end","illegal","QUOTE_STRING_MODE","PHRASAL_WORDS_MODE","COMMENT","modeOptions","C_LINE_COMMENT_MODE","C_BLOCK_COMMENT_MODE","HASH_COMMENT_MODE","NUMBER_MODE","C_NUMBER_MODE","BINARY_NUMBER_MODE","CSS_NUMBER_MODE","REGEXP_MODE","TITLE_MODE","UNDERSCORE_TITLE_MODE","METHOD_GUARD","MODES","__proto__","MATCH_NOTHING_RE","RE_STARTERS_RE","SHEBANG","opts","beginShebang","binary","map","x","concat","m","resp","END_SAME_AS_BEGIN","assign","_beginMatch","skipIfhasPrecedingDot","match","response","input","beginKeywords","parent","split","__beforeBegin","keywords","compileIllegal","_parent","isArray","either","compileMatch","compileRelevance","COMMON_KEYWORDS","compileKeywords","rawKeywords","caseInsensitive","compiledKeywords","compileList","keywordList","toLowerCase","keyword","pair","scoreForKeyword","providedScore","Number","commonKeyword","compileLanguage","language","plugins","langRe","case_insensitive","MultiRegex","matchIndexes","regexes","matchAt","position","addRule","countMatchGroups","compile","terminators","matcherRe","regexps","separator","numCaptures","regex","offset","out","substring","String","lastIndex","s","i","findIndex","matchData","splice","ResumableMultiRegex","rules","multiRegexes","regexIndex","getMatcher","matcher","resumingScanAtSamePosition","considerAll","m2","compilerExtensions","classNameAliases","compileMode","cmode","isCompiled","ext","keywordPattern","$pattern","lexemes","keywordPatternRe","beginRe","endSameAsBegin","endsWithParent","endRe","terminatorEnd","illegalRe","c","variants","cachedVariants","variant","dependencyOnParent","starts","expandOrCloneMode","mm","term","rule","buildModeRegex","BuildVuePlugin","hljs","Component","props","detectedLanguage","unknownLanguage","computed","highlighted","autoDetect","getLanguage","warn","code","highlightAuto","highlight","ignoreIllegals","autodetect","Boolean","render","class","domProps","VuePlugin","install","Vue","component","mergeHTMLPlugin","originalStream","nodeStream","resultNode","processed","nodeStack","selectStream","event","open","attributeString","attr","nodeName","attributes","close","stream","reverse","mergeStreams","_nodeStream","firstChild","nextSibling","nodeType","nodeValue","seenDeprecations","message","log","deprecated","version","escape$1","inherit$1","NO_MATCH","_$highlight_1","languages","aliases","SAFE_MODE","fixMarkupRe","LANGUAGE_NOT_FOUND","PLAINTEXT_LANGUAGE","disableAutodetect","noHighlightRe","languageDetectRe","tabReplace","useBR","__emitter","shouldNotHighlight","languageName","codeOrlanguageName","optionsOrCode","continuation","context","fire","_highlight","codeToHighlight","keywordData","matchText","processBuffer","subLanguage","modeBuffer","continuations","processSubLanguage","buf","keywordRelevance","startsWith","cssClass","processKeywords","startNewMode","endOfMode","matchPlusRemainder","matched","lexeme","endsParent","doIgnore","resumeScanAtSamePosition","doBeginMatch","newMode","beforeCallbacks","cb","skip","excludeBegin","returnBegin","doEndMatch","endMode","origin","returnEnd","excludeEnd","lastMatch","processLexeme","textBeforeMatch","err","badRule","iterations","md","list","current","unshift","item","processContinuations","processedCount","floor","illegalBy","msg","sofar","errorRaised","languageSubset","plaintext","justTextHighlightResult","results","filter","autoDetection","sorted","sort","a","b","supersetOf","best","secondBest","second_best","brPlugin","TAB_REPLACE_RE","tabReplacePlugin","highlightElement","block","classes","find","_class","blockLanguage","textContent","currentLang","resultLang","updateClassName","relavance","initHighlighting","called","wantsHighlight","highlightAll","readyState","registerAliases","aliasList","alias","lang","plugin","fixMarkup","arg","html","highlightBlock","configure","userOptions","initHighlightingOnLoad","registerLanguage","languageDefinition","error$1","rawDefinition","unregisterLanguage","listLanguages","requireLanguage","addPlugin","upgradePluginAPI","vuePlugin","debugMode","safeMode","versionString","HLJS","__concat_2","__source_2","_$asciidoc_2","STRONG","EMPHASIS","__concat_3","__source_3","_$bash_3","VAR","BRACED_VAR","SUBST","HERE_DOC","QUOTE_STRING","ARITHMETIC","KNOWN_SHEBANG","FUNCTION","literal","built_in","TAGS","MEDIA_FEATURES","PSEUDO_CLASSES","PSEUDO_ELEMENTS","ATTRIBUTES","lookahead","__source_4","__concat_4","_$css_4","modes","IMPORTANT","HEXCOLOR","ATTRIBUTE_SELECTOR_MODE","__MODES_4","STRINGS","keyframePosition","attribute","_$diff_5","_$dockerfile_6","_$gradle_7","__lookahead_8","__source_8","__concat_8","_$groovy_8","REGEXP","NUMBER","STRING","__concat_9","__source_9","_$http_9","VERSION","HEADER","HEADERS_AND_BODY","frac","hexDigits","NUMERIC","_$java_10","JAVA_IDENT_RE","KEYWORDS","ANNOTATION","__IDENT_RE_11","LITERALS","BUILT_INS","__lookahead_11","__concat_11","__source_11","_$javascript_11","IDENT_RE$1","FRAGMENT","XML_TAG","isTrulyOpeningTag","afterMatchIndex","nextChar","after","indexOf","hasClosingTag","KEYWORDS$1","decimalInteger","HTML_TEMPLATE","CSS_TEMPLATE","TEMPLATE_STRING","SUBST_INTERNALS","SUBST_AND_COMMENTS","PARAMS_CONTAINS","PARAMS","exports","_$json_12","ALLOWED_COMMENTS","TYPES","VALUE_CONTAINER","OBJECT","ARRAY","__frac_13","__hexDigits_13","__NUMERIC_13","_$kotlin_13","LABEL","VARIABLE","ANNOTATION_USE_SITE","KOTLIN_NUMBER_MODE","KOTLIN_NESTED_COMMENT","KOTLIN_PAREN_TYPE","KOTLIN_PAREN_TYPE2","__concat_14","__source_14","_$markdown_14","INLINE_HTML","LINK","BOLD","ITALIC","CONTAINABLE","_$nix_15","NIX_KEYWORDS","ANTIQUOTE","EXPRESSIONS","_$properties_16","WS0","EQUAL_DELIM","WS_DELIM","DELIM","KEY_ALPHANUM","KEY_OTHER","DELIM_AND_VALUE","__concat_17","__source_17","_$ruby_17","RUBY_METHOD_RE","RUBY_KEYWORDS","YARDOCTAG","IRB_OBJECT","COMMENT_MODES","digits","RUBY_DEFAULT_CONTAINS","IRB_DEFAULT","_$scala_18","TYPE","NAME","CLASS","METHOD","_$shell_19","__source_20","__concat_20","__either_20","_$sql_20","COMMENT_MODE","RESERVED_FUNCTIONS","COMBOS","FUNCTIONS","FUNCTION_CALL","exceptions","when","qualifyFn","reduceRelevancy","__source_21","__lookahead_21","__concat_21","__either_21","_$xml_21","TAG_NAME_RE","XML_ENTITIES","XML_META_KEYWORDS","XML_META_PAR_KEYWORDS","APOS_META_STRING_MODE","QUOTE_META_STRING_MODE","TAG_INTERNALS","_$yaml_22","URI_CHARACTERS","CONTAINER_STRING","TIMESTAMP","VALUE_MODES","ignoreUnescapedHTML","createTabsElement","primaryElement","tabsElement","createElementFromHtml","prepend","createTab","blockElement","content","colist","selector","stopSelector","sibling","matches","append","tabElement","dataset","blockName","template","findPrimaryElement","secondaryElement","previousElementSibling","onTabClick","tabId","localStorage","setItem","getTabId","select","id","tab","addTabs","getItem","configureTabs","_$isObject_10","_$freeGlobal_4","_$root_7","_$now_13","_$trimmedEndIndex_8","_$baseTrim_3","_$getRawTag_5","__nativeObjectToString_6","_$objectToString_6","__symToStringTag_2","_$baseGetTag_2","_$isObjectLike_11","_$isSymbol_12","_$toNumber_15","nativeMin","min","_$debounce_9","lastArgs","lastThis","maxWait","timerId","lastCallTime","lastInvokeTime","leading","maxing","trailing","invokeFunc","time","leadingEdge","timerExpired","shouldInvoke","timeSinceLastCall","trailingEdge","timeWaiting","remainingWait","debounced","isInvoking","clearTimeout","cancel","flush","_$throttle_14","tocElement","toggleTocElement","contentElement","headingElements","hrefToTocAnchorElement","headingElementToTocElement","lastActiveTocElement","disableOnScroll","onLocationHashChange","updateFixedPositionClass","tocAnchorElement","get","headingElement","getHeadingElementByHref","isInViewport","activateTocElement","parentElement","onEndScroll","maxHeadingLevel","getMaxHeadingLevel","headingSelectors","headingLevel","listElement","getHeadingLevel","findHeadingElements","href","getAttribute","buildHrefToTocAnchorElement","getChildAnchorHref","buildHeadingElementToTocElement","onScroll","onResize","onTocElementClick","onToggleTocClick","activateTopHeadingTocElement","target","stopPropagation","body","documentElement","removeEventListener","computedStyle","getComputedStyle","bannerHeight","getPropertyValue","getTop","topHeadingElement","topHeadingPos","offsetTop","getTopHeading","achorElement","deactivateTocElement","scrollTop","rect","getBoundingClientRect","bottom","innerHeight","clientHeight"],"mappings":"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CL9F,UAAW,cAKbka,EAAe,CACnBla,UAAW,SACX2F,MAAO,IAAKG,IAAK,IACjBlP,SAAU,CACRkX,EAAKpI,iBACLoU,EACAE,IAGJA,EAAMpjB,SAAST,KAAK+jB,GACpB,MASMC,EAAa,CACjBxU,MAAO,SACPG,IAAK,OACLlP,SAAU,CACR,CAAE+O,MAAO,gBAAiB3F,UAAW,UACrC8N,EAAKvH,YACLuT,IAcEM,EAAgBtM,EAAK3G,QAAQ,CACjCG,OAAQ,IAZa,CACrB,OACA,OACA,MACA,KACA,MACA,MACA,OACA,OACA,QAG2B7C,KAAK,QAChCmB,UAAW,KAEPyU,EAAW,CACfra,UAAW,WACX2F,MAAO,4BACPwO,aAAa,EACbvd,SAAU,CAACkX,EAAKvL,QAAQuL,EAAKlH,WAAY,CAACjB,MAAO,gBACjDC,UAAW,GAGb,MAAO,CACLlE,KAAM,OACN2P,QAAS,CAAC,KAAM,OAChB9I,SAAU,CACRkE,SAAU,gBACVnD,QACE,+DACFgR,QACE,aACFC,SAGE,6uBAeJ3jB,SAAU,CACRwjB,EACAtM,EAAK3G,UACLkT,EACAF,EACArM,EAAKxH,kBACL2T,EACAC,EA3EkB,CACpBla,UAAW,GACX2F,MAAO,OAGW,CAClB3F,UAAW,SACX2F,MAAO,IAAKG,IAAK,KAuEfgU,GF0wFN,EG76FA,MAuBMU,GAAO,CACX,IACA,OACA,UACA,UACA,QACA,QACA,IACA,aACA,OACA,SACA,SACA,UACA,OACA,OACA,KACA,MACA,UACA,MACA,MACA,KACA,KACA,KACA,WACA,aACA,SACA,SACA,OACA,KACA,KACA,KACA,KACA,KACA,KACA,SACA,SACA,OACA,IACA,SACA,MACA,QACA,MACA,MACA,QACA,SACA,KACA,OACA,OACA,OACA,MACA,SACA,KACA,IACA,IACA,QACA,OACA,UACA,OACA,SACA,UACA,MACA,QACA,QACA,KACA,WACA,QACA,KACA,QACA,OACA,KACA,KACA,MACA,SAGIC,GAAiB,CACrB,YACA,cACA,eACA,QACA,cACA,cACA,sBACA,gBACA,eACA,eACA,gBACA,OACA,SACA,QACA,kBACA,aACA,cACA,iBACA,kBACA,UACA,uBACA,mBACA,yBACA,+BACA,aACA,OACA,YACA,SACA,QAEA,YACA,YACA,aACA,cAIIC,GAAiB,CACrB,SACA,WACA,QACA,UACA,UACA,UACA,UACA,MACA,WACA,OACA,QACA,UACA,QACA,cACA,gBACA,aACA,SACA,QACA,gBACA,eACA,MACA,OACA,eACA,QACA,gBACA,WACA,UACA,KACA,OACA,aACA,eACA,OACA,OACA,aACA,MACA,YACA,UACA,iBACA,eACA,mBACA,cACA,aACA,eACA,WACA,eACA,OACA,oBACA,YACA,aACA,WACA,QACA,OACA,QACA,SACA,gBACA,eACA,QACA,UACA,SAIIC,GAAkB,CACtB,QACA,WACA,SACA,MACA,aACA,eACA,aACA,gBACA,SACA,OACA,cACA,YACA,UACA,kBAGIC,GAAa,CACjB,gBACA,cACA,aACA,YACA,kBACA,sBACA,qBACA,sBACA,4BACA,iBACA,uBACA,4BACA,OACA,sBACA,aACA,wBACA,kBACA,mBACA,mBACA,oBACA,sBACA,oBACA,kBACA,SACA,gBACA,sBACA,4BACA,6BACA,sBACA,sBACA,kBACA,eACA,eACA,sBACA,sBACA,qBACA,sBACA,qBACA,cACA,oBACA,oBACA,oBACA,gBACA,eACA,qBACA,qBACA,qBACA,iBACA,eACA,aACA,mBACA,yBACA,0BACA,mBACA,mBACA,eACA,SACA,uBACA,aACA,aACA,cACA,eACA,eACA,eACA,QACA,OACA,YACA,QACA,eACA,cACA,aACA,cACA,oBACA,oBACA,oBACA,cACA,eACA,UACA,UACA,oBACA,gBACA,SACA,YACA,UACA,cACA,SACA,OACA,aACA,iBACA,YACA,YACA,cACA,YACA,QACA,OACA,eACA,cACA,wBACA,eACA,yBACA,YACA,mBACA,iBACA,eACA,aACA,eACA,yBACA,0BACA,cACA,SACA,UACA,OACA,oBACA,kBACA,mBACA,WACA,UACA,UACA,kBACA,OACA,iBACA,cACA,aACA,mBACA,sBACA,kBACA,SACA,gBACA,cACA,eACA,aACA,QACA,OACA,aACA,YACA,aACA,YACA,WACA,YACA,WACA,YACA,SACA,OACA,SACA,aACA,kBACA,UACA,QACA,UACA,UACA,gBACA,iBACA,gBACA,gBACA,WACA,gBACA,aACA,aACA,UACA,iBACA,eACA,gBACA,cACA,mBACA,oBACA,oBACA,cACA,qBACA,iBACA,WACA,SACA,SACA,QACA,MACA,WACA,eACA,aACA,kBACA,kBACA,wBACA,uBACA,wBACA,cACA,gBACA,iBACA,cACA,iBACA,0BACA,MACA,YACA,mBACA,kBACA,aACA,mBACA,sBACA,sBACA,6BACA,eACA,iBACA,aACA,cACA,SACA,QACA,aACA,eACA,YACA,WAGAxK,UAsBF,SAASyK,GAAU1V,GACjB,OAOF,YAAmBhO,GAEjB,OADeA,EAAKoQ,KAAKC,GApB3B,SAAgBrC,GACd,OAAKA,EACa,iBAAPA,EAAwBA,EAE5BA,EAAGD,OAHM,IHq7FlB,CGl6FiC4V,CAAOtT,KAAI/C,KAAK,GHm7FjD,CG37FSsW,CAAO,MAAO5V,EAAI,IHk7F3B,CGpxFA,IAAA6V,GA3IA,SAAalN,GACX,MAAMmN,EArdM,CAACnN,IACN,CACLoN,UAAW,CACTlb,UAAW,OACX2F,MAAO,cAETwV,SAAU,CACRnb,UAAW,SACX2F,MAAO,oCAETyV,wBAAyB,CACvBpb,UAAW,gBACX2F,MAAO,KACPG,IAAK,KACLC,QAAS,IACTnP,SAAU,CACRkX,EAAKjI,iBACLiI,EAAK9H,sBAocGqV,CAAMvN,GAWdwN,EAAU,CACdxN,EAAKjI,iBACLiI,EAAK9H,mBAGP,MAAO,CACLtE,KAAM,MACNsI,kBAAkB,EAClBjE,QAAS,UACTwC,SAAU,CACRgT,iBAAkB,WAEpBpP,iBAAkB,CAGhBoP,iBAAkB,gBAEpB3kB,SAAU,CACRkX,EAAKzH,qBAxBa,CACpBV,MAAO,gCA2BLmI,EAAKpH,gBACL,CACE1G,UAAW,cACX2F,MAAO,kBACPC,UAAW,GAEb,CACE5F,UAAW,iBACX2F,MAAO,6BACPC,UAAW,GAEbqV,EAAMG,wBACN,CACEpb,UAAW,kBACXmN,SAAU,CACR,CACExH,MAAO,KAAO+U,GAAejW,KAAK,KAAO,KAE3C,CACEkB,MAAO,MAAQgV,GAAgBlW,KAAK,KAAO,OAUjD,CACEzE,UAAW,YACX2F,MAAO,OAASiV,GAAWnW,KAAK,KAAO,QAGzC,CACEkB,MAAO,IACPG,IAAK,OACLlP,SAAU,CACRqkB,EAAME,SACNF,EAAMC,UACNpN,EAAKpH,mBACF4U,EAIH,CACE3V,MAAO,mBACPG,IAAK,KACLF,UAAW,EACX2C,SAAU,CACRgS,SAAU,gBAEZ3jB,SAAU,CACR,CACEoJ,UAAW,SAGX2F,MAAO,OACPmH,gBAAgB,EAChB0H,YAAY,KA3FA,CACxBxU,UAAW,WACX2F,MAAO,kBAgGL,CACEA,MAAOkV,GAAU,KACjB/U,IAAK,OACLF,UAAW,EACXG,QAAS,IACTnP,SAAU,CACR,CACEoJ,UAAW,UACX2F,MAlGa,qBAoGf,CACEA,MAAO,KACPmH,gBAAgB,EAChB0H,YAAY,EACZ5O,UAAW,EACX2C,SAAU,CACRkE,SAAU,UACVnD,QA5GS,kBA6GTkS,UAAWf,GAAehW,KAAK,MAEjC7N,SAAU,CACR,CACE+O,MAAO,eACP3F,UAAW,gBAEVsb,EACHxN,EAAKpH,oBAKb,CACE1G,UAAW,eACX2F,MAAO,OAAS6U,GAAK/V,KAAK,KAAO,SHq7FzC,EI17GA,IAAAgX,GA3EA,SAAc3N,GACZ,MAAO,CACLpM,KAAM,OACN2P,QAAS,CAAC,SACVza,SAAU,CACR,CACEoJ,UAAW,OACX4F,UAAW,GACXuH,SAAU,CACR,CACExH,MAAO,gCAET,CACEA,MAAO,+BAET,CACEA,MAAO,0BAIb,CACE3F,UAAW,UACXmN,SAAU,CACR,CACExH,MAAO,UACPG,IAAK,KAEP,CACEH,MAAO,SACPG,IAAK,KAEP,CACEH,MAAO,QACPG,IAAK,KAEP,CACEH,MAAO,QACPG,IAAK,KAEP,CACEH,MAAO,UACPG,IAAK,KAEP,CACEH,MAAO,SACPG,IAAK,KAEP,CACEH,MAAO,YAET,CACEA,MAAO,cACPG,IAAK,OAIX,CACE9F,UAAW,WACX2F,MAAO,MACPG,IAAK,KAEP,CACE9F,UAAW,WACX2F,MAAO,KACPG,IAAK,KAEP,CACE9F,UAAW,WACX2F,MAAO,KACPG,IAAK,MJshHb,EKnkHA,IAAA4V,GAvBA,SAAoB5N,GAClB,MAAO,CACLpM,KAAM,aACN2P,QAAS,CAAC,UACVrH,kBAAkB,EAClBzB,SAAU,yDACV3R,SAAU,CACRkX,EAAKxH,kBACLwH,EAAKjI,iBACLiI,EAAK9H,kBACL8H,EAAKvH,YACL,CACE4B,cAAe,qEACfoF,OAAQ,CACNzH,IAAK,SACL8M,YAAa,UAInB7M,QAAS,KL0mHb,EM5lHA,IAAA4V,GApCA,SAAgB7N,GACd,MAAO,CACLpM,KAAM,SACNsI,kBAAkB,EAClBzB,SAAU,CACRe,QACE,mxCAkBJ1S,SAAU,CACRkX,EAAK1H,oBACL0H,EAAKzH,qBACLyH,EAAKjI,iBACLiI,EAAK9H,kBACL8H,EAAKvH,YACLuH,EAAKnH,aN+oHX,EOhqHA,SAASiV,GAAUzW,GACjB,OAOF,YAAmBhO,GAEjB,OADeA,EAAKoQ,KAAKC,GApB3B,SAAgBrC,GACd,OAAKA,EACa,iBAAPA,EAAwBA,EAE5BA,EAAGD,OAHM,IP4rHlB,COzqHiC2W,CAAOrU,KAAI/C,KAAK,GP0rHjD,COlsHSqX,CAAO,MAAO3W,EAAI,IPyrH3B,COtqHA,SAASgI,GAASA,EAAUnM,EAAM,CAAA,GAEhC,OADAA,EAAImM,SAAWA,EACRnM,CPyrHT,COtjHA,IAAA+a,GAhIA,SAAgBjO,GACd,MAAMzI,EAAW,iBACXa,EAAUiH,GAAS,CACvBW,EAAK1H,oBACL0H,EAAKzH,qBACLyH,EAAK5H,QACH,UACA,OACA,CACEN,UAAW,EACXhP,SAAU,CACR,CAEE+O,MAAO,OACPC,UAAW,GAEb,CACE5F,UAAW,SACX2F,MAAO,mBAMXqW,EAAS,CACbhc,UAAW,SACX2F,MAAO,iBACP/O,SAAU,CAAEkX,EAAKpI,mBAEbuW,EAAS9O,GAAS,CACtBW,EAAKrH,mBACLqH,EAAKtH,gBAED0V,EAAS/O,GAAS,CACtB,CACExH,MAAO,MACPG,IAAK,OAEP,CACEH,MAAO,MACPG,IAAK,OAEP,CACEH,MAAO,OACPG,IAAK,OACLF,UAAW,IAEbkI,EAAKjI,iBACLiI,EAAK9H,mBAEP,CACEhG,UAAW,WAIb,MAAO,CACL0B,KAAM,SACN6G,SAAU,CACRgS,SAAU,aACVD,QAAS,kBACThR,QACM,6TAQR1S,SAAU,CACRkX,EAAK3G,QAAQ,CACXG,OAAQ,SACR1B,UAAW,KAEbM,EACAgW,EACAF,EACAC,EACA,CACEjc,UAAW,QACXmI,cAAe,6BACfrC,IAAK,KACLC,QAAS,IACTnP,SAAU,CACR,CACEuR,cAAe,sBAEjB2F,EAAKjH,wBAGT,CACE7G,UAAW,OACX2F,MAAO,aACPC,UAAW,GAEb,CAEE5F,UAAW,OACX2F,MAAON,EAAW,UAClBO,UAAW,GAEb,CAGED,MAAO,KACPG,IAAK,IACLF,UAAW,EACXhP,SAAU,CACRsP,EACAgW,EACAF,EACAC,EACA,SAGJ,CAEEjc,UAAW,SACX2F,MAAO,UAAYiW,GAAUvW,EAAW,KACxC6O,cAAc,EACdpO,IAAKT,EAAW,IAChBO,UAAW,IAGfG,QAAS,QP0rHb,EQ/0HA,SAASoW,MAAUhlB,GAEjB,OADeA,EAAKoQ,KAAKC,IAAM4U,OAZjBjX,EAYwBqC,GAVpB,iBAAPrC,EAAwBA,EAE5BA,EAAGD,OAHM,KADlB,IAAgBC,CAY0B,IAAEV,KAAK,GRy2HjD,CQtwHA,IAAA4X,GAvFA,SAAcvO,GACZ,MAAMwO,EAAU,oBAEVC,EAAS,CACbvc,UAAW,YACX2F,MAAOwW,GAAO,IAHI,wBAGc,cAChC5O,OAAQ,CACN3W,SAAU,CACR,CACEoJ,UAAW,cACX2F,MAAO,KACPC,UAAW,EACX2H,OAAQ,CACNzH,IAAK,IACLF,UAAW,OAMf4W,EAAmB,CACvBD,EACA,CACE5W,MAAO,SACP4H,OAAQ,CAAEqF,YAAa,GAAI9F,gBAAgB,KAI/C,MAAO,CACLpL,KAAM,OACN2P,QAAS,CAAC,SACVtL,QAAS,KACTnP,SAAU,CAER,CACE+O,MAAO,OAAS2W,EAAU,WAC1BxW,IAAK,IACLlP,SAAU,CACR,CACEoJ,UAAW,OACX2F,MAAO2W,GAET,CACEtc,UAAW,SAAU2F,MAAO,iBAGhC4H,OAAQ,CACNzH,IAAK,OACLC,QAAS,KACTnP,SAAU4lB,IAId,CACE7W,MAAO,oBAAsB2W,EAAU,KACvCxW,IAAK,IACLlP,SAAU,CACR,CACEoJ,UAAW,SACX2F,MAAO,IACPG,IAAK,IACLoO,cAAc,EACdM,YAAY,GAEd,CACExU,UAAW,OACX2F,MAAO2W,GAET,CACEtc,UAAW,UACX2F,MAAO,WAGX4H,OAAQ,CACNzH,IAAK,OACLC,QAAS,KACTnP,SAAU4lB,IAId1O,EAAKvL,QAAQga,EAAQ,CACnB3W,UAAW,KR22HnB,ES39HI6W,GAAO,uBACPC,GAAY,8BACZC,GAAU,CACZ3c,UAAW,SACXmN,SAAU,CAGR,CAAExH,MAAO,0BAA2B8W,cAAgBA,4CAGpD,CAAE9W,MAAO,yBAA0B8W,kCACnC,CAAE9W,MAAO,IAAI8W,iBACb,CAAE9W,MAAO,iCAGT,CAAEA,MAAO,aAAa+W,YAAmBA,WAAkBA,4CAI3D,CAAE/W,MAAO,kCAGT,CAAEA,MAAO,YAAY+W,eAGrB,CAAE/W,MAAO,0BAGT,CAAEA,MAAO,kCAEXC,UAAW,GAoJb,IAAAgX,GA1IA,SAAc9O,GACZ,IAAI+O,EAAgB,iCAEhBC,EAAW,iWAMXC,EAAa,CACf/c,UAAW,OACX2F,MAAO,IAAMkX,EACbjmB,SAAU,CACR,CACE+O,MAAO,KACPG,IAAK,KACLlP,SAAU,CAAC,WAIjB,MAAMqlB,EAASU,GAEf,MAAO,CACLjb,KAAM,OACN2P,QAAS,CAAC,OACV9I,SAAUuU,EACV/W,QAAS,QACTnP,SAAU,CACRkX,EAAK5H,QACH,UACA,OACA,CACEN,UAAW,EACXhP,SAAU,CACR,CAEE+O,MAAO,OAAQC,UAAW,GAE5B,CACE5F,UAAW,SACX2F,MAAO,iBAMf,CACEA,MAAO,wBACP4C,SAAU,SACV3C,UAAW,GAEbkI,EAAK1H,oBACL0H,EAAKzH,qBACLyH,EAAKjI,iBACLiI,EAAK9H,kBACL,CACEhG,UAAW,QACXmI,cAAe,uBAAwBrC,IAAK,QAAS0O,YAAY,EAKjE5O,UAAW,EACX2C,SAAU,uBACVxC,QAAS,WACTnP,SAAU,CACR,CAAEuR,cAAe,sBACjB2F,EAAKjH,wBAGT,CAGEsB,cAAe,wBACfvC,UAAW,GAEb,CACE5F,UAAW,QACX2F,MAAO,aAAemI,EAAKxI,oBAAsB,UACjD6O,aAAa,EACbK,YAAY,EACZ1O,IAAK,QACLyC,SAAUuU,EACVlmB,SAAU,CACR,CAAEuR,cAAe,UACjB,CACExC,MAAOmI,EAAKxI,oBAAsB,UAClC6O,aAAa,EACbvO,UAAW,EACXhP,SAAU,CAACkX,EAAKjH,wBAElB,CACE7G,UAAW,SACX2F,MAAO,KAAMG,IAAK,KAClByC,SAAUuU,EACVlX,UAAW,EACXhP,SAAU,CACRkX,EAAKzH,uBAGTyH,EAAK1H,oBACL0H,EAAKzH,uBAGT,CACErG,UAAW,WACX2F,MAAO,qHAAoCmI,EAAKxI,oBAAsB,UAAW6O,aAAa,EAAMrO,IAAK,QACzG0O,YAAY,EACZjM,SAAUuU,EACVlmB,SAAU,CACR,CACE+O,MAAOmI,EAAKxI,oBAAsB,UAAW6O,aAAa,EAC1DvO,UAAW,EACXhP,SAAU,CAACkX,EAAKjH,wBAElB,CACE7G,UAAW,SACX2F,MAAO,KAAMG,IAAK,KAClByC,SAAUuU,EACVlX,UAAW,EACXhP,SAAU,CACRmmB,EACAjP,EAAKjI,iBACLiI,EAAK9H,kBACLiW,EACAnO,EAAKzH,uBAGTyH,EAAK1H,oBACL0H,EAAKzH,uBAGT4V,EACAc,GTo+HN,EUnpIA,MAAMC,GAAW,2BACXF,GAAW,CACf,KACA,KACA,KACA,KACA,MACA,QACA,UACA,MACA,MACA,WACA,KACA,SACA,OACA,OACA,QACA,QACA,aACA,OACA,QACA,OACA,UACA,MACA,SACA,WACA,SACA,SACA,MACA,QACA,QACA,QAIA,WACA,QACA,QACA,SACA,SACA,OACA,SACA,WAEIG,GAAW,CACf,OACA,QACA,OACA,YACA,MACA,YAoFIC,GAAY,GAAGzV,OAlCI,CACvB,cACA,aACA,gBACA,eAEA,UACA,UAEA,OACA,WACA,QACA,aACA,WACA,YACA,qBACA,YACA,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AAS,CAAE,WACXza,SAAU,CACR,CACEoJ,UAAW,OAIX2F,MAAO,iCACP4H,OAAQ,CACNzH,IAAK,gBACL8M,YAAa,UlBkgMvB,EmB/gMA,SAASoP,GAAO7c,GACd,OAAKA,EACa,iBAAPA,EAAwBA,EAE5BA,EAAGD,OAHM,InBgiMlB,CmBthMA,SAAS+c,MAAU9qB,GAEjB,OADeA,EAAKoQ,KAAKC,GAAMwa,GAAOxa,KAAI/C,KAAK,GnB8hMjD,CmBnhMA,SAASyd,MAAU/qB,GAEjB,MADe,IAAMA,EAAKoQ,KAAKC,GAAMwa,GAAOxa,KAAI/C,KAAK,KAAO,GnB8hM9D,CmBr4KA,IAAA0d,GA/oBA,SAAarU,GACX,MAAMsU,EAAetU,EAAK5H,QAAQ,KAAM,KAmBlC+W,EAAW,CACf,OACA,QAGA,WAUI2B,EAAQ,CACZ,SACA,SACA,OACA,UACA,OACA,YACA,OACA,OACA,MACA,WACA,UACA,QACA,MACA,UACA,WACA,QACA,QACA,WACA,UACA,OACA,MACA,WACA,OACA,YACA,UACA,UACA,aAmYIyD,EAAqB,CACzB,MACA,OACA,YACA,OACA,OACA,MACA,OACA,OACA,UACA,WACA,OACA,MACA,OACA,QACA,YACA,aACA,YACA,aACA,QACA,UACA,MACA,UACA,cACA,QACA,aACA,gBACA,cACA,cACA,iBACA,aACA,aACA,uBACA,aACA,MACA,aACA,OACA,UACA,KACA,MACA,QACA,QACA,MACA,MACA,MACA,YACA,QACA,SACA,eACA,kBACA,kBACA,WACA,iBACA,QACA,OACA,YACA,YACA,aACA,iBACA,UACA,aACA,WACA,WACA,WACA,aACA,MACA,OACA,OACA,aACA,cACA,YACA,kBACA,MACA,MACA,OACA,YACA,kBACA,QACA,OACA,aACA,SACA,QACA,WACA,UACA,WACA,gBAwBIC,EAAS,CACb,eACA,cACA,cACA,cACA,WACA,cACA,iBACA,gBACA,cACA,gBACA,gBACA,eACA,cACA,aACA,cACA,iBAGIC,EAAYF,EAEZvF,EAAW,CArff,MACA,OACA,MACA,WACA,QACA,MACA,MACA,MACA,QACA,YACA,wBACA,KACA,aACA,OACA,aACA,KACA,OACA,SACA,gBACA,MACA,QACA,cACA,kBACA,UACA,SACA,SACA,OACA,UACA,OACA,KACA,OACA,SACA,cACA,WACA,OACA,OACA,OACA,UACA,OACA,cACA,YACA,mBACA,QACA,aACA,OACA,QACA,WACA,UACA,UACA,SACA,SACA,YACA,UACA,aACA,WACA,UACA,OACA,OACA,gBACA,MACA,OACA,QACA,YACA,aACA,SACA,QACA,OACA,YACA,UACA,kBACA,eACA,kCACA,eACA,eACA,cACA,iBACA,eACA,oBACA,eACA,eACA,mCACA,eACA,SACA,QACA,OACA,MACA,aACA,MACA,UACA,WACA,UACA,UACA,SACA,SACA,aACA,QACA,WACA,gBACA,aACA,WACA,SACA,OACA,UACA,OACA,UACA,OACA,QACA,MACA,YACA,gBACA,WACA,SACA,SACA,QACA,SACA,OACA,UACA,SACA,MACA,WACA,UACA,QACA,QACA,SACA,cACA,QACA,QACA,MACA,UACA,YACA,OACA,OACA,OACA,WACA,SACA,MACA,SACA,QACA,QACA,WACA,SACA,SACA,OACA,OACA,WACA,KACA,YACA,UACA,QACA,QACA,cACA,SACA,MACA,UACA,YACA,eACA,WACA,OACA,KACA,OACA,aACA,gBACA,cACA,cACA,iBACA,aACA,aACA,uBACA,aACA,MACA,WACA,QACA,aACA,UACA,OACA,UACA,OACA,OACA,aACA,UACA,KACA,QACA,YACA,iBACA,MACA,QACA,QACA,QACA,eACA,kBACA,UACA,MACA,SACA,QACA,SACA,MACA,SACA,MACA,WACA,SACA,QACA,WACA,WACA,UACA,QACA,QACA,MACA,KACA,OACA,YACA,MACA,YACA,QACA,OACA,SACA,UACA,eACA,oBACA,KACA,SACA,MACA,OACA,KACA,MACA,OACA,OACA,KACA,QACA,MACA,QACA,OACA,WACA,UACA,YACA,YACA,UACA,MACA,UACA,eACA,kBACA,kBACA,SACA,UACA,WACA,iBACA,QACA,WACA,YACA,UACA,UACA,YACA,MACA,QACA,OACA,QACA,OACA,YACA,MACA,aACA,cACA,YACA,YACA,aACA,iBACA,UACA,aACA,WACA,WACA,WACA,UACA,SACA,SACA,UACA,SACA,QACA,WACA,SACA,MACA,aACA,OACA,UACA,YACA,QACA,SACA,SACA,SACA,OACA,SACA,YACA,eACA,MACA,OACA,UACA,MACA,OACA,OACA,WACA,OACA,WACA,eACA,MACA,eACA,WACA,aACA,OACA,QACA,SACA,aACA,cACA,cACA,SACA,YACA,kBACA,WACA,MACA,YACA,SACA,cACA,cACA,QACA,cACA,MACA,OACA,OACA,OACA,YACA,gBACA,kBACA,KACA,WACA,YACA,kBACA,cACA,QACA,UACA,OACA,aACA,OACA,WACA,UACA,QACA,SACA,UACA,SACA,YACA,QACA,OACA,QACA,QACA,SACA,WACA,UACA,WACA,YACA,UACA,UACA,aACA,OACA,WACA,QACA,eACA,SACA,OACA,SACA,UACA,OAzXA,MACA,MACA,YACA,OACA,QACA,QACA,OACA,QA0f0D/G,QAAQzM,IAC1D+Y,EAAmBpsB,SAASqT,KAchCkZ,EAAgB,CACpB7c,MAAOsc,GAAO,KAAMC,MAAUK,GAAY,SAC1Cha,SAAU,CACRgS,SAAUgI,IAmBd,MAAO,CACL7gB,KAAM,MACNsI,kBAAkB,EAElBjE,QAAS,WACTwC,SAAU,CACRkE,SAAU,YACVnD,QArBJ,SAAyB0L,GAAMyN,WAACA,EAAUC,KAAEA,GAAQ,CAAA,GAClD,MAAMC,EAAYD,EAElB,OADAD,EAAaA,GAAc,GACpBzN,EAAKzN,KAAK4N,GACXA,EAAKnN,MAAM,WAAaya,EAAWxsB,SAASkf,GACvCA,EACEwN,EAAUxN,GACZ,GAAGA,MAEHA,GnB+hMb,CmBlhMMyN,CAAgB9F,EAAU,CAAE4F,KAAOlb,GAAMA,EAAE5P,OAAS,IACtD0iB,QAAS2C,EACT1iB,KAAMqkB,EACNrE,SAzF4B,CAC9B,kBACA,eACA,kCACA,eACA,eACA,iBACA,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","file":"site.js","sourcesContent":["/*\n * Copyright 2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the 'License');\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an 'AS IS' BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n(function () {\n \"use strict\";\n\n window.addEventListener(\"load\", onChange);\n window.addEventListener(\"hashchange\", onChange);\n\n function onChange() {\n const element = document.getElementById(\"anchor-rewrite\");\n const anchor = window.location.hash.substr(1);\n if (element && anchor) {\n const rewites = JSON.parse(element.innerHTML);\n updateAnchor(anchor, rewites);\n }\n }\n\n function updateAnchor(anchor, rewrites) {\n const seen = [anchor];\n console.debug(anchor);\n while (rewrites[anchor]) {\n anchor = rewrites[anchor];\n if (seen.includes(anchor)) {\n console.error(\"Skipping circular anchor update\");\n return;\n }\n seen.push(anchor);\n }\n window.location.hash = anchor;\n }\n})();\n","/*\n * Copyright 2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the 'License');\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an 'AS IS' BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n(function () {\n \"use strict\";\n\n window.addEventListener(\"load\", onChange);\n window.addEventListener(\"hashchange\", onChange);\n\n function onChange() {\n const element = document.getElementById(\"anchor-rewrite\");\n const anchor = window.location.hash.substr(1);\n if (element && anchor) {\n const rewites = JSON.parse(element.innerHTML);\n updateAnchor(anchor, rewites);\n }\n }\n\n function updateAnchor(anchor, rewrites) {\n const seen = [anchor];\n console.debug(anchor);\n while (rewrites[anchor]) {\n anchor = rewrites[anchor];\n if (seen.includes(anchor)) {\n console.error(\"Skipping circular anchor update\");\n return;\n }\n seen.push(anchor);\n }\n window.location.hash = anchor;\n }\n})();\n","/*\n * Copyright 2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the 'License');\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an 'AS IS' BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n(function () {\n \"use strict\";\n\n markLastAuthor();\n\n function markLastAuthor() {\n let candidate = document.getElementById(\"author\");\n let lastAuthorElement = candidate;\n while (candidate) {\n if (candidate.classList.contains(\"author\")) {\n lastAuthorElement = candidate;\n }\n candidate = candidate.nextElementSibling;\n }\n if (lastAuthorElement) {\n lastAuthorElement.classList.add(\"last-author\");\n }\n }\n})();\n","/*\n * Copyright 2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the 'License');\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an 'AS IS' BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n(function () {\n \"use strict\";\n\n markLastAuthor();\n\n function markLastAuthor() {\n let candidate = document.getElementById(\"author\");\n let lastAuthorElement = candidate;\n while (candidate) {\n if (candidate.classList.contains(\"author\")) {\n lastAuthorElement = candidate;\n }\n candidate = candidate.nextElementSibling;\n }\n if (lastAuthorElement) {\n lastAuthorElement.classList.add(\"last-author\");\n }\n }\n})();\n","(function(){\n","/** Error message constants. */\nvar FUNC_ERROR_TEXT = 'Expected a function';\n\n/**\n * The base implementation of `_.delay` and `_.defer` which accepts `args`\n * to provide to `func`.\n *\n * @private\n * @param {Function} func The function to delay.\n * @param {number} wait The number of milliseconds to delay invocation.\n * @param {Array} args The arguments to provide to `func`.\n * @returns {number|Object} Returns the timer id or timeout object.\n */\nfunction baseDelay(func, wait, args) {\n if (typeof func != 'function') {\n throw new TypeError(FUNC_ERROR_TEXT);\n }\n return setTimeout(function() { func.apply(undefined, args); }, wait);\n}\n\nmodule.exports = baseDelay;\n","/*\n * Copyright 2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the 'License');\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an 'AS IS' BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n(function () {\n \"use strict\";\n\n const delay = require(\"lodash/delay\");\n\n addCodeToolElements();\n\n function addCodeToolElements() {\n for (const preElement of document.querySelectorAll(\".doc pre.highlight\")) {\n const codeToolsElement = document.createElement(\"div\");\n codeToolsElement.className = \"codetools\";\n if (addButtons(preElement, codeToolsElement)) {\n preElement.appendChild(codeToolsElement);\n }\n }\n\n function addButtons(preElement, codeToolsElement) {\n let numberOfButtons = 0;\n if (hasHideWhenFoldedSpans(preElement)) {\n addFoldUnfoldButton(preElement, codeToolsElement);\n numberOfButtons++;\n }\n if (window.navigator.clipboard) {\n addCopyButton(preElement, codeToolsElement);\n numberOfButtons++;\n }\n return numberOfButtons > 0;\n }\n\n function hasHideWhenFoldedSpans(preElement) {\n return !!preElement.querySelector(\"span.hide-when-folded\");\n }\n\n function addFoldUnfoldButton(preElement, codeToolsElement) {\n const foldUnfoldButton = createButton();\n updateFoldUnfoldButton(foldUnfoldButton, true);\n foldUnfoldButton.addEventListener(\n \"click\",\n onFoldUnfoldButtonClick.bind(foldUnfoldButton, preElement)\n );\n codeToolsElement.appendChild(foldUnfoldButton);\n }\n\n function addCopyButton(preElement, codeToolsElement) {\n const copyButton = createButton(\"Copy to clipboard\", \"copy-button\");\n copyButton.addEventListener(\n \"click\",\n onCopyButtonClick.bind(copyButton, preElement)\n );\n copyButton.addEventListener(\"mouseleave\", clearClicked.bind(copyButton));\n copyButton.addEventListener(\"blur\", clearClicked.bind(copyButton));\n const copiedPopup = document.createElement(\"span\");\n copyButton.appendChild(copiedPopup);\n copiedPopup.className = \"copied\";\n codeToolsElement.appendChild(copyButton);\n }\n\n function createButton(label, className) {\n const buttonElement = document.createElement(\"button\");\n buttonElement.className = className;\n buttonElement.title = label;\n buttonElement.type = \"button\";\n const labelElement = document.createElement(\"span\");\n labelElement.appendChild(document.createTextNode(label));\n labelElement.className = \"label\";\n buttonElement.appendChild(labelElement);\n return buttonElement;\n }\n }\n\n function onCopyButtonClick(preElement) {\n const codeElement = preElement.querySelector(\"code\");\n const copy = codeElement.cloneNode(true);\n for (const hideWhenFoldedElement of copy.querySelectorAll(\n \".hide-when-unfolded\"\n )) {\n hideWhenFoldedElement.parentNode.removeChild(hideWhenFoldedElement);\n }\n const text = copy.innerText;\n if (text) {\n window.navigator.clipboard\n .writeText(text + \"\\n\")\n .then(markClicked.bind(this));\n }\n }\n\n function markClicked() {\n this.classList.add(\"clicked\");\n }\n\n function clearClicked() {\n this.classList.remove(\"clicked\");\n }\n\n function onFoldUnfoldButtonClick(preElement) {\n const codeElement = preElement.querySelector(\"code\");\n const unfolding = !codeElement.classList.contains(\"unfolded\");\n codeElement.classList.remove(unfolding ? \"folding\" : \"unfolding\");\n codeElement.classList.add(unfolding ? \"unfolding\" : \"folding\");\n delay(function () {\n codeElement.classList.remove(unfolding ? \"unfolding\" : \"folding\");\n codeElement.classList.toggle(\"unfolded\");\n }, 1100);\n updateFoldUnfoldButton(this, !unfolding);\n }\n\n function updateFoldUnfoldButton(button, unfold) {\n const label = unfold ? \"Expanded folded text\" : \"Collapse foldable text\";\n button.classList.remove(unfold ? \"fold-button\" : \"unfold-button\");\n button.classList.add(unfold ? \"unfold-button\" : \"fold-button\");\n button.querySelector(\"span.label\").innerText = label;\n button.title = label;\n }\n})();\n","/**\n * This method returns the first argument it receives.\n *\n * @static\n * @since 0.1.0\n * @memberOf _\n * @category Util\n * @param {*} value Any value.\n * @returns {*} Returns `value`.\n * @example\n *\n * var object = { 'a': 1 };\n *\n * console.log(_.identity(object) === object);\n * // => true\n */\nfunction identity(value) {\n return value;\n}\n\nmodule.exports = identity;\n","/**\n * A faster alternative to `Function#apply`, this function invokes `func`\n * with the `this` binding of `thisArg` and the arguments of `args`.\n *\n * @private\n * @param {Function} func The function to invoke.\n * @param {*} thisArg The `this` binding of `func`.\n * @param {Array} args The arguments to invoke `func` with.\n * @returns {*} Returns the result of `func`.\n */\nfunction apply(func, thisArg, args) {\n switch (args.length) {\n case 0: return func.call(thisArg);\n case 1: return func.call(thisArg, args[0]);\n case 2: return func.call(thisArg, args[0], args[1]);\n case 3: return func.call(thisArg, args[0], args[1], args[2]);\n }\n return func.apply(thisArg, args);\n}\n\nmodule.exports = apply;\n","var apply = require('./_apply');\n\n/* Built-in method references for those with the same name as other `lodash` methods. */\nvar nativeMax = Math.max;\n\n/**\n * A specialized version of `baseRest` which transforms the rest array.\n *\n * @private\n * @param {Function} func The function to apply a rest parameter to.\n * @param {number} [start=func.length-1] The start position of the rest parameter.\n * @param {Function} transform The rest array transform.\n * @returns {Function} Returns the new function.\n */\nfunction overRest(func, start, transform) {\n start = nativeMax(start === undefined ? (func.length - 1) : start, 0);\n return function() {\n var args = arguments,\n index = -1,\n length = nativeMax(args.length - start, 0),\n array = Array(length);\n\n while (++index < length) {\n array[index] = args[start + index];\n }\n index = -1;\n var otherArgs = Array(start + 1);\n while (++index < start) {\n otherArgs[index] = args[index];\n }\n otherArgs[start] = transform(array);\n return apply(func, this, otherArgs);\n };\n}\n\nmodule.exports = overRest;\n","/**\n * Creates a function that returns `value`.\n *\n * @static\n * @memberOf _\n * @since 2.4.0\n * @category Util\n * @param {*} value The value to return from the new function.\n * @returns {Function} Returns the new constant function.\n * @example\n *\n * var objects = _.times(2, _.constant({ 'a': 1 }));\n *\n * console.log(objects);\n * // => [{ 'a': 1 }, { 'a': 1 }]\n *\n * console.log(objects[0] === objects[1]);\n * // => true\n */\nfunction constant(value) {\n return function() {\n return value;\n };\n}\n\nmodule.exports = constant;\n","/** Detect free variable `global` from Node.js. */\nvar freeGlobal = typeof global == 'object' && global && global.Object === Object && global;\n\nmodule.exports = freeGlobal;\n","var freeGlobal = require('./_freeGlobal');\n\n/** Detect free variable `self`. */\nvar freeSelf = typeof self == 'object' && self && self.Object === Object && self;\n\n/** Used as a reference to the global object. */\nvar root = freeGlobal || freeSelf || Function('return this')();\n\nmodule.exports = root;\n","var root = require('./_root');\n\n/** Built-in value references. */\nvar Symbol = root.Symbol;\n\nmodule.exports = Symbol;\n","var Symbol = require('./_Symbol');\n\n/** Used for built-in method references. */\nvar objectProto = Object.prototype;\n\n/** Used to check objects for own properties. */\nvar hasOwnProperty = objectProto.hasOwnProperty;\n\n/**\n * Used to resolve the\n * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)\n * of values.\n */\nvar nativeObjectToString = objectProto.toString;\n\n/** Built-in value references. */\nvar symToStringTag = Symbol ? Symbol.toStringTag : undefined;\n\n/**\n * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.\n *\n * @private\n * @param {*} value The value to query.\n * @returns {string} Returns the raw `toStringTag`.\n */\nfunction getRawTag(value) {\n var isOwn = hasOwnProperty.call(value, symToStringTag),\n tag = value[symToStringTag];\n\n try {\n value[symToStringTag] = undefined;\n var unmasked = true;\n } catch (e) {}\n\n var result = nativeObjectToString.call(value);\n if (unmasked) {\n if (isOwn) {\n value[symToStringTag] = tag;\n } else {\n delete value[symToStringTag];\n }\n }\n return result;\n}\n\nmodule.exports = getRawTag;\n","/** Used for built-in method references. */\nvar objectProto = Object.prototype;\n\n/**\n * Used to resolve the\n * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)\n * of values.\n */\nvar nativeObjectToString = objectProto.toString;\n\n/**\n * Converts `value` to a string using `Object.prototype.toString`.\n *\n * @private\n * @param {*} value The value to convert.\n * @returns {string} Returns the converted string.\n */\nfunction objectToString(value) {\n return nativeObjectToString.call(value);\n}\n\nmodule.exports = objectToString;\n","var Symbol = require('./_Symbol'),\n getRawTag = require('./_getRawTag'),\n objectToString = require('./_objectToString');\n\n/** `Object#toString` result references. */\nvar nullTag = '[object Null]',\n undefinedTag = '[object Undefined]';\n\n/** Built-in value references. */\nvar symToStringTag = Symbol ? Symbol.toStringTag : undefined;\n\n/**\n * The base implementation of `getTag` without fallbacks for buggy environments.\n *\n * @private\n * @param {*} value The value to query.\n * @returns {string} Returns the `toStringTag`.\n */\nfunction baseGetTag(value) {\n if (value == null) {\n return value === undefined ? undefinedTag : nullTag;\n }\n return (symToStringTag && symToStringTag in Object(value))\n ? getRawTag(value)\n : objectToString(value);\n}\n\nmodule.exports = baseGetTag;\n","/**\n * Checks if `value` is the\n * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)\n * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is an object, else `false`.\n * @example\n *\n * _.isObject({});\n * // => true\n *\n * _.isObject([1, 2, 3]);\n * // => true\n *\n * _.isObject(_.noop);\n * // => true\n *\n * _.isObject(null);\n * // => false\n */\nfunction isObject(value) {\n var type = typeof value;\n return value != null && (type == 'object' || type == 'function');\n}\n\nmodule.exports = isObject;\n","var baseGetTag = require('./_baseGetTag'),\n isObject = require('./isObject');\n\n/** `Object#toString` result references. */\nvar asyncTag = '[object AsyncFunction]',\n funcTag = '[object Function]',\n genTag = '[object GeneratorFunction]',\n proxyTag = '[object Proxy]';\n\n/**\n * Checks if `value` is classified as a `Function` object.\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a function, else `false`.\n * @example\n *\n * _.isFunction(_);\n * // => true\n *\n * _.isFunction(/abc/);\n * // => false\n */\nfunction isFunction(value) {\n if (!isObject(value)) {\n return false;\n }\n // The use of `Object#toString` avoids issues with the `typeof` operator\n // in Safari 9 which returns 'object' for typed arrays and other constructors.\n var tag = baseGetTag(value);\n return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag;\n}\n\nmodule.exports = isFunction;\n","var coreJsData = require('./_coreJsData');\n\n/** Used to detect methods masquerading as native. */\nvar maskSrcKey = (function() {\n var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');\n return uid ? ('Symbol(src)_1.' + uid) : '';\n}());\n\n/**\n * Checks if `func` has its source masked.\n *\n * @private\n * @param {Function} func The function to check.\n * @returns {boolean} Returns `true` if `func` is masked, else `false`.\n */\nfunction isMasked(func) {\n return !!maskSrcKey && (maskSrcKey in func);\n}\n\nmodule.exports = isMasked;\n","var root = require('./_root');\n\n/** Used to detect overreaching core-js shims. */\nvar coreJsData = root['__core-js_shared__'];\n\nmodule.exports = coreJsData;\n","/** Used for built-in method references. */\nvar funcProto = Function.prototype;\n\n/** Used to resolve the decompiled source of functions. */\nvar funcToString = funcProto.toString;\n\n/**\n * Converts `func` to its source code.\n *\n * @private\n * @param {Function} func The function to convert.\n * @returns {string} Returns the source code.\n */\nfunction toSource(func) {\n if (func != null) {\n try {\n return funcToString.call(func);\n } catch (e) {}\n try {\n return (func + '');\n } catch (e) {}\n }\n return '';\n}\n\nmodule.exports = toSource;\n","var isFunction = require('./isFunction'),\n isMasked = require('./_isMasked'),\n isObject = require('./isObject'),\n toSource = require('./_toSource');\n\n/**\n * Used to match `RegExp`\n * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).\n */\nvar reRegExpChar = /[\\\\^$.*+?()[\\]{}|]/g;\n\n/** Used to detect host constructors (Safari). */\nvar reIsHostCtor = /^\\[object .+?Constructor\\]$/;\n\n/** Used for built-in method references. */\nvar funcProto = Function.prototype,\n objectProto = Object.prototype;\n\n/** Used to resolve the decompiled source of functions. */\nvar funcToString = funcProto.toString;\n\n/** Used to check objects for own properties. */\nvar hasOwnProperty = objectProto.hasOwnProperty;\n\n/** Used to detect if a method is native. */\nvar reIsNative = RegExp('^' +\n funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\\\$&')\n .replace(/hasOwnProperty|(function).*?(?=\\\\\\()| for .+?(?=\\\\\\])/g, '$1.*?') + '$'\n);\n\n/**\n * The base implementation of `_.isNative` without bad shim checks.\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a native function,\n * else `false`.\n */\nfunction baseIsNative(value) {\n if (!isObject(value) || isMasked(value)) {\n return false;\n }\n var pattern = isFunction(value) ? reIsNative : reIsHostCtor;\n return pattern.test(toSource(value));\n}\n\nmodule.exports = baseIsNative;\n","/**\n * Gets the value at `key` of `object`.\n *\n * @private\n * @param {Object} [object] The object to query.\n * @param {string} key The key of the property to get.\n * @returns {*} Returns the property value.\n */\nfunction getValue(object, key) {\n return object == null ? undefined : object[key];\n}\n\nmodule.exports = getValue;\n","var baseIsNative = require('./_baseIsNative'),\n getValue = require('./_getValue');\n\n/**\n * Gets the native function at `key` of `object`.\n *\n * @private\n * @param {Object} object The object to query.\n * @param {string} key The key of the method to get.\n * @returns {*} Returns the function if it's native, else `undefined`.\n */\nfunction getNative(object, key) {\n var value = getValue(object, key);\n return baseIsNative(value) ? value : undefined;\n}\n\nmodule.exports = getNative;\n","var getNative = require('./_getNative');\n\nvar defineProperty = (function() {\n try {\n var func = getNative(Object, 'defineProperty');\n func({}, '', {});\n return func;\n } catch (e) {}\n}());\n\nmodule.exports = defineProperty;\n","var constant = require('./constant'),\n defineProperty = require('./_defineProperty'),\n identity = require('./identity');\n\n/**\n * The base implementation of `setToString` without support for hot loop shorting.\n *\n * @private\n * @param {Function} func The function to modify.\n * @param {Function} string The `toString` result.\n * @returns {Function} Returns `func`.\n */\nvar baseSetToString = !defineProperty ? identity : function(func, string) {\n return defineProperty(func, 'toString', {\n 'configurable': true,\n 'enumerable': false,\n 'value': constant(string),\n 'writable': true\n });\n};\n\nmodule.exports = baseSetToString;\n","/** Used to detect hot functions by number of calls within a span of milliseconds. */\nvar HOT_COUNT = 800,\n HOT_SPAN = 16;\n\n/* Built-in method references for those with the same name as other `lodash` methods. */\nvar nativeNow = Date.now;\n\n/**\n * Creates a function that'll short out and invoke `identity` instead\n * of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN`\n * milliseconds.\n *\n * @private\n * @param {Function} func The function to restrict.\n * @returns {Function} Returns the new shortable function.\n */\nfunction shortOut(func) {\n var count = 0,\n lastCalled = 0;\n\n return function() {\n var stamp = nativeNow(),\n remaining = HOT_SPAN - (stamp - lastCalled);\n\n lastCalled = stamp;\n if (remaining > 0) {\n if (++count >= HOT_COUNT) {\n return arguments[0];\n }\n } else {\n count = 0;\n }\n return func.apply(undefined, arguments);\n };\n}\n\nmodule.exports = shortOut;\n","var baseSetToString = require('./_baseSetToString'),\n shortOut = require('./_shortOut');\n\n/**\n * Sets the `toString` method of `func` to return `string`.\n *\n * @private\n * @param {Function} func The function to modify.\n * @param {Function} string The `toString` result.\n * @returns {Function} Returns `func`.\n */\nvar setToString = shortOut(baseSetToString);\n\nmodule.exports = setToString;\n","var identity = require('./identity'),\n overRest = require('./_overRest'),\n setToString = require('./_setToString');\n\n/**\n * The base implementation of `_.rest` which doesn't validate or coerce arguments.\n *\n * @private\n * @param {Function} func The function to apply a rest parameter to.\n * @param {number} [start=func.length-1] The start position of the rest parameter.\n * @returns {Function} Returns the new function.\n */\nfunction baseRest(func, start) {\n return setToString(overRest(func, start, identity), func + '');\n}\n\nmodule.exports = baseRest;\n","/** Used to match a single whitespace character. */\nvar reWhitespace = /\\s/;\n\n/**\n * Used by `_.trim` and `_.trimEnd` to get the index of the last non-whitespace\n * character of `string`.\n *\n * @private\n * @param {string} string The string to inspect.\n * @returns {number} Returns the index of the last non-whitespace character.\n */\nfunction trimmedEndIndex(string) {\n var index = string.length;\n\n while (index-- && reWhitespace.test(string.charAt(index))) {}\n return index;\n}\n\nmodule.exports = trimmedEndIndex;\n","var trimmedEndIndex = require('./_trimmedEndIndex');\n\n/** Used to match leading whitespace. */\nvar reTrimStart = /^\\s+/;\n\n/**\n * The base implementation of `_.trim`.\n *\n * @private\n * @param {string} string The string to trim.\n * @returns {string} Returns the trimmed string.\n */\nfunction baseTrim(string) {\n return string\n ? string.slice(0, trimmedEndIndex(string) + 1).replace(reTrimStart, '')\n : string;\n}\n\nmodule.exports = baseTrim;\n","/**\n * Checks if `value` is object-like. A value is object-like if it's not `null`\n * and has a `typeof` result of \"object\".\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is object-like, else `false`.\n * @example\n *\n * _.isObjectLike({});\n * // => true\n *\n * _.isObjectLike([1, 2, 3]);\n * // => true\n *\n * _.isObjectLike(_.noop);\n * // => false\n *\n * _.isObjectLike(null);\n * // => false\n */\nfunction isObjectLike(value) {\n return value != null && typeof value == 'object';\n}\n\nmodule.exports = isObjectLike;\n","var baseGetTag = require('./_baseGetTag'),\n isObjectLike = require('./isObjectLike');\n\n/** `Object#toString` result references. */\nvar symbolTag = '[object Symbol]';\n\n/**\n * Checks if `value` is classified as a `Symbol` primitive or object.\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a symbol, else `false`.\n * @example\n *\n * _.isSymbol(Symbol.iterator);\n * // => true\n *\n * _.isSymbol('abc');\n * // => false\n */\nfunction isSymbol(value) {\n return typeof value == 'symbol' ||\n (isObjectLike(value) && baseGetTag(value) == symbolTag);\n}\n\nmodule.exports = isSymbol;\n","var baseTrim = require('./_baseTrim'),\n isObject = require('./isObject'),\n isSymbol = require('./isSymbol');\n\n/** Used as references for various `Number` constants. */\nvar NAN = 0 / 0;\n\n/** Used to detect bad signed hexadecimal string values. */\nvar reIsBadHex = /^[-+]0x[0-9a-f]+$/i;\n\n/** Used to detect binary string values. */\nvar reIsBinary = /^0b[01]+$/i;\n\n/** Used to detect octal string values. */\nvar reIsOctal = /^0o[0-7]+$/i;\n\n/** Built-in method references without a dependency on `root`. */\nvar freeParseInt = parseInt;\n\n/**\n * Converts `value` to a number.\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to process.\n * @returns {number} Returns the number.\n * @example\n *\n * _.toNumber(3.2);\n * // => 3.2\n *\n * _.toNumber(Number.MIN_VALUE);\n * // => 5e-324\n *\n * _.toNumber(Infinity);\n * // => Infinity\n *\n * _.toNumber('3.2');\n * // => 3.2\n */\nfunction toNumber(value) {\n if (typeof value == 'number') {\n return value;\n }\n if (isSymbol(value)) {\n return NAN;\n }\n if (isObject(value)) {\n var other = typeof value.valueOf == 'function' ? value.valueOf() : value;\n value = isObject(other) ? (other + '') : other;\n }\n if (typeof value != 'string') {\n return value === 0 ? value : +value;\n }\n value = baseTrim(value);\n var isBinary = reIsBinary.test(value);\n return (isBinary || reIsOctal.test(value))\n ? freeParseInt(value.slice(2), isBinary ? 2 : 8)\n : (reIsBadHex.test(value) ? NAN : +value);\n}\n\nmodule.exports = toNumber;\n","var baseDelay = require('./_baseDelay'),\n baseRest = require('./_baseRest'),\n toNumber = require('./toNumber');\n\n/**\n * Invokes `func` after `wait` milliseconds. Any additional arguments are\n * provided to `func` when it's invoked.\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Function\n * @param {Function} func The function to delay.\n * @param {number} wait The number of milliseconds to delay invocation.\n * @param {...*} [args] The arguments to invoke `func` with.\n * @returns {number} Returns the timer id.\n * @example\n *\n * _.delay(function(text) {\n * console.log(text);\n * }, 1000, 'later');\n * // => Logs 'later' after one second.\n */\nvar delay = baseRest(function(func, wait, args) {\n return baseDelay(func, toNumber(wait) || 0, args);\n});\n\nmodule.exports = delay;\n","/*\n * Copyright 2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the 'License');\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an 'AS IS' BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n(function () {\n \"use strict\";\n\n const delay = require(\"lodash/delay\");\n\n addCodeToolElements();\n\n function addCodeToolElements() {\n for (const preElement of document.querySelectorAll(\".doc pre.highlight\")) {\n const codeToolsElement = document.createElement(\"div\");\n codeToolsElement.className = \"codetools\";\n if (addButtons(preElement, codeToolsElement)) {\n preElement.appendChild(codeToolsElement);\n }\n }\n\n function addButtons(preElement, codeToolsElement) {\n let numberOfButtons = 0;\n if (hasHideWhenFoldedSpans(preElement)) {\n addFoldUnfoldButton(preElement, codeToolsElement);\n numberOfButtons++;\n }\n if (window.navigator.clipboard) {\n addCopyButton(preElement, codeToolsElement);\n numberOfButtons++;\n }\n return numberOfButtons > 0;\n }\n\n function hasHideWhenFoldedSpans(preElement) {\n return !!preElement.querySelector(\"span.hide-when-folded\");\n }\n\n function addFoldUnfoldButton(preElement, codeToolsElement) {\n const foldUnfoldButton = createButton();\n updateFoldUnfoldButton(foldUnfoldButton, true);\n foldUnfoldButton.addEventListener(\n \"click\",\n onFoldUnfoldButtonClick.bind(foldUnfoldButton, preElement)\n );\n codeToolsElement.appendChild(foldUnfoldButton);\n }\n\n function addCopyButton(preElement, codeToolsElement) {\n const copyButton = createButton(\"Copy to clipboard\", \"copy-button\");\n copyButton.addEventListener(\n \"click\",\n onCopyButtonClick.bind(copyButton, preElement)\n );\n copyButton.addEventListener(\"mouseleave\", clearClicked.bind(copyButton));\n copyButton.addEventListener(\"blur\", clearClicked.bind(copyButton));\n const copiedPopup = document.createElement(\"span\");\n copyButton.appendChild(copiedPopup);\n copiedPopup.className = \"copied\";\n codeToolsElement.appendChild(copyButton);\n }\n\n function createButton(label, className) {\n const buttonElement = document.createElement(\"button\");\n buttonElement.className = className;\n buttonElement.title = label;\n buttonElement.type = \"button\";\n const labelElement = document.createElement(\"span\");\n labelElement.appendChild(document.createTextNode(label));\n labelElement.className = \"label\";\n buttonElement.appendChild(labelElement);\n return buttonElement;\n }\n }\n\n function onCopyButtonClick(preElement) {\n const codeElement = preElement.querySelector(\"code\");\n const copy = codeElement.cloneNode(true);\n for (const hideWhenFoldedElement of copy.querySelectorAll(\n \".hide-when-unfolded\"\n )) {\n hideWhenFoldedElement.parentNode.removeChild(hideWhenFoldedElement);\n }\n const text = copy.innerText;\n if (text) {\n window.navigator.clipboard\n .writeText(text + \"\\n\")\n .then(markClicked.bind(this));\n }\n }\n\n function markClicked() {\n this.classList.add(\"clicked\");\n }\n\n function clearClicked() {\n this.classList.remove(\"clicked\");\n }\n\n function onFoldUnfoldButtonClick(preElement) {\n const codeElement = preElement.querySelector(\"code\");\n const unfolding = !codeElement.classList.contains(\"unfolded\");\n codeElement.classList.remove(unfolding ? \"folding\" : \"unfolding\");\n codeElement.classList.add(unfolding ? \"unfolding\" : \"folding\");\n delay(function () {\n codeElement.classList.remove(unfolding ? \"unfolding\" : \"folding\");\n codeElement.classList.toggle(\"unfolded\");\n }, 1100);\n updateFoldUnfoldButton(this, !unfolding);\n }\n\n function updateFoldUnfoldButton(button, unfold) {\n const label = unfold ? \"Expanded folded text\" : \"Collapse foldable text\";\n button.classList.remove(unfold ? \"fold-button\" : \"unfold-button\");\n button.classList.add(unfold ? \"unfold-button\" : \"fold-button\");\n button.querySelector(\"span.label\").innerText = label;\n button.title = label;\n }\n})();\n","\n}());","function deepFreeze(obj) {\n if (obj instanceof Map) {\n obj.clear = obj.delete = obj.set = function () {\n throw new Error('map is read-only');\n };\n } else if (obj instanceof Set) {\n obj.add = obj.clear = obj.delete = function () {\n throw new Error('set is read-only');\n };\n }\n\n // Freeze self\n Object.freeze(obj);\n\n Object.getOwnPropertyNames(obj).forEach(function (name) {\n var prop = obj[name];\n\n // Freeze prop if it is an object\n if (typeof prop == 'object' && !Object.isFrozen(prop)) {\n deepFreeze(prop);\n }\n });\n\n return obj;\n}\n\nvar deepFreezeEs6 = deepFreeze;\nvar _default = deepFreeze;\ndeepFreezeEs6.default = _default;\n\n/** @implements CallbackResponse */\nclass Response {\n /**\n * @param {CompiledMode} mode\n */\n constructor(mode) {\n // eslint-disable-next-line no-undefined\n if (mode.data === undefined) mode.data = {};\n\n this.data = mode.data;\n this.isMatchIgnored = false;\n }\n\n ignoreMatch() {\n this.isMatchIgnored = true;\n }\n}\n\n/**\n * @param {string} value\n * @returns {string}\n */\nfunction escapeHTML(value) {\n return value\n .replace(/&/g, '&')\n .replace(//g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * performs a shallow merge of multiple objects into one\n *\n * @template T\n * @param {T} original\n * @param {Record[]} objects\n * @returns {T} a single new object\n */\nfunction inherit(original, ...objects) {\n /** @type Record */\n const result = Object.create(null);\n\n for (const key in original) {\n result[key] = original[key];\n }\n objects.forEach(function(obj) {\n for (const key in obj) {\n result[key] = obj[key];\n }\n });\n return /** @type {T} */ (result);\n}\n\n/**\n * @typedef {object} Renderer\n * @property {(text: string) => void} addText\n * @property {(node: Node) => void} openNode\n * @property {(node: Node) => void} closeNode\n * @property {() => string} value\n */\n\n/** @typedef {{kind?: string, sublanguage?: boolean}} Node */\n/** @typedef {{walk: (r: Renderer) => void}} Tree */\n/** */\n\nconst SPAN_CLOSE = '
    ';\n\n/**\n * Determines if a node needs to be wrapped in \n *\n * @param {Node} node */\nconst emitsWrappingTags = (node) => {\n return !!node.kind;\n};\n\n/** @type {Renderer} */\nclass HTMLRenderer {\n /**\n * Creates a new HTMLRenderer\n *\n * @param {Tree} parseTree - the parse tree (must support `walk` API)\n * @param {{classPrefix: string}} options\n */\n constructor(parseTree, options) {\n this.buffer = \"\";\n this.classPrefix = options.classPrefix;\n parseTree.walk(this);\n }\n\n /**\n * Adds texts to the output stream\n *\n * @param {string} text */\n addText(text) {\n this.buffer += escapeHTML(text);\n }\n\n /**\n * Adds a node open to the output stream (if needed)\n *\n * @param {Node} node */\n openNode(node) {\n if (!emitsWrappingTags(node)) return;\n\n let className = node.kind;\n if (!node.sublanguage) {\n className = `${this.classPrefix}${className}`;\n }\n this.span(className);\n }\n\n /**\n * Adds a node close to the output stream (if needed)\n *\n * @param {Node} node */\n closeNode(node) {\n if (!emitsWrappingTags(node)) return;\n\n this.buffer += SPAN_CLOSE;\n }\n\n /**\n * returns the accumulated buffer\n */\n value() {\n return this.buffer;\n }\n\n // helpers\n\n /**\n * Builds a span element\n *\n * @param {string} className */\n span(className) {\n this.buffer += ``;\n }\n}\n\n/** @typedef {{kind?: string, sublanguage?: boolean, children: Node[]} | string} Node */\n/** @typedef {{kind?: string, sublanguage?: boolean, children: Node[]} } DataNode */\n/** */\n\nclass TokenTree {\n constructor() {\n /** @type DataNode */\n this.rootNode = { children: [] };\n this.stack = [this.rootNode];\n }\n\n get top() {\n return this.stack[this.stack.length - 1];\n }\n\n get root() { return this.rootNode; }\n\n /** @param {Node} node */\n add(node) {\n this.top.children.push(node);\n }\n\n /** @param {string} kind */\n openNode(kind) {\n /** @type Node */\n const node = { kind, children: [] };\n this.add(node);\n this.stack.push(node);\n }\n\n closeNode() {\n if (this.stack.length > 1) {\n return this.stack.pop();\n }\n // eslint-disable-next-line no-undefined\n return undefined;\n }\n\n closeAllNodes() {\n while (this.closeNode());\n }\n\n toJSON() {\n return JSON.stringify(this.rootNode, null, 4);\n }\n\n /**\n * @typedef { import(\"./html_renderer\").Renderer } Renderer\n * @param {Renderer} builder\n */\n walk(builder) {\n // this does not\n return this.constructor._walk(builder, this.rootNode);\n // this works\n // return TokenTree._walk(builder, this.rootNode);\n }\n\n /**\n * @param {Renderer} builder\n * @param {Node} node\n */\n static _walk(builder, node) {\n if (typeof node === \"string\") {\n builder.addText(node);\n } else if (node.children) {\n builder.openNode(node);\n node.children.forEach((child) => this._walk(builder, child));\n builder.closeNode(node);\n }\n return builder;\n }\n\n /**\n * @param {Node} node\n */\n static _collapse(node) {\n if (typeof node === \"string\") return;\n if (!node.children) return;\n\n if (node.children.every(el => typeof el === \"string\")) {\n // node.text = node.children.join(\"\");\n // delete node.children;\n node.children = [node.children.join(\"\")];\n } else {\n node.children.forEach((child) => {\n TokenTree._collapse(child);\n });\n }\n }\n}\n\n/**\n Currently this is all private API, but this is the minimal API necessary\n that an Emitter must implement to fully support the parser.\n\n Minimal interface:\n\n - addKeyword(text, kind)\n - addText(text)\n - addSublanguage(emitter, subLanguageName)\n - finalize()\n - openNode(kind)\n - closeNode()\n - closeAllNodes()\n - toHTML()\n\n*/\n\n/**\n * @implements {Emitter}\n */\nclass TokenTreeEmitter extends TokenTree {\n /**\n * @param {*} options\n */\n constructor(options) {\n super();\n this.options = options;\n }\n\n /**\n * @param {string} text\n * @param {string} kind\n */\n addKeyword(text, kind) {\n if (text === \"\") { return; }\n\n this.openNode(kind);\n this.addText(text);\n this.closeNode();\n }\n\n /**\n * @param {string} text\n */\n addText(text) {\n if (text === \"\") { return; }\n\n this.add(text);\n }\n\n /**\n * @param {Emitter & {root: DataNode}} emitter\n * @param {string} name\n */\n addSublanguage(emitter, name) {\n /** @type DataNode */\n const node = emitter.root;\n node.kind = name;\n node.sublanguage = true;\n this.add(node);\n }\n\n toHTML() {\n const renderer = new HTMLRenderer(this, this.options);\n return renderer.value();\n }\n\n finalize() {\n return true;\n }\n}\n\n/**\n * @param {string} value\n * @returns {RegExp}\n * */\nfunction escape(value) {\n return new RegExp(value.replace(/[-/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&'), 'm');\n}\n\n/**\n * @param {RegExp | string } re\n * @returns {string}\n */\nfunction source(re) {\n if (!re) return null;\n if (typeof re === \"string\") return re;\n\n return re.source;\n}\n\n/**\n * @param {...(RegExp | string) } args\n * @returns {string}\n */\nfunction concat(...args) {\n const joined = args.map((x) => source(x)).join(\"\");\n return joined;\n}\n\n/**\n * Any of the passed expresssions may match\n *\n * Creates a huge this | this | that | that match\n * @param {(RegExp | string)[] } args\n * @returns {string}\n */\nfunction either(...args) {\n const joined = '(' + args.map((x) => source(x)).join(\"|\") + \")\";\n return joined;\n}\n\n/**\n * @param {RegExp} re\n * @returns {number}\n */\nfunction countMatchGroups(re) {\n return (new RegExp(re.toString() + '|')).exec('').length - 1;\n}\n\n/**\n * Does lexeme start with a regular expression match at the beginning\n * @param {RegExp} re\n * @param {string} lexeme\n */\nfunction startsWith(re, lexeme) {\n const match = re && re.exec(lexeme);\n return match && match.index === 0;\n}\n\n// BACKREF_RE matches an open parenthesis or backreference. To avoid\n// an incorrect parse, it additionally matches the following:\n// - [...] elements, where the meaning of parentheses and escapes change\n// - other escape sequences, so we do not misparse escape sequences as\n// interesting elements\n// - non-matching or lookahead parentheses, which do not capture. These\n// follow the '(' with a '?'.\nconst BACKREF_RE = /\\[(?:[^\\\\\\]]|\\\\.)*\\]|\\(\\??|\\\\([1-9][0-9]*)|\\\\./;\n\n// join logically computes regexps.join(separator), but fixes the\n// backreferences so they continue to match.\n// it also places each individual regular expression into it's own\n// match group, keeping track of the sequencing of those match groups\n// is currently an exercise for the caller. :-)\n/**\n * @param {(string | RegExp)[]} regexps\n * @param {string} separator\n * @returns {string}\n */\nfunction join(regexps, separator = \"|\") {\n let numCaptures = 0;\n\n return regexps.map((regex) => {\n numCaptures += 1;\n const offset = numCaptures;\n let re = source(regex);\n let out = '';\n\n while (re.length > 0) {\n const match = BACKREF_RE.exec(re);\n if (!match) {\n out += re;\n break;\n }\n out += re.substring(0, match.index);\n re = re.substring(match.index + match[0].length);\n if (match[0][0] === '\\\\' && match[1]) {\n // Adjust the backreference.\n out += '\\\\' + String(Number(match[1]) + offset);\n } else {\n out += match[0];\n if (match[0] === '(') {\n numCaptures++;\n }\n }\n }\n return out;\n }).map(re => `(${re})`).join(separator);\n}\n\n// Common regexps\nconst MATCH_NOTHING_RE = /\\b\\B/;\nconst IDENT_RE = '[a-zA-Z]\\\\w*';\nconst UNDERSCORE_IDENT_RE = '[a-zA-Z_]\\\\w*';\nconst NUMBER_RE = '\\\\b\\\\d+(\\\\.\\\\d+)?';\nconst C_NUMBER_RE = '(-?)(\\\\b0[xX][a-fA-F0-9]+|(\\\\b\\\\d+(\\\\.\\\\d*)?|\\\\.\\\\d+)([eE][-+]?\\\\d+)?)'; // 0x..., 0..., decimal, float\nconst BINARY_NUMBER_RE = '\\\\b(0b[01]+)'; // 0b...\nconst RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\\\*|\\\\*=|\\\\+|\\\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\\\?|\\\\[|\\\\{|\\\\(|\\\\^|\\\\^=|\\\\||\\\\|=|\\\\|\\\\||~';\n\n/**\n* @param { Partial & {binary?: string | RegExp} } opts\n*/\nconst SHEBANG = (opts = {}) => {\n const beginShebang = /^#![ ]*\\//;\n if (opts.binary) {\n opts.begin = concat(\n beginShebang,\n /.*\\b/,\n opts.binary,\n /\\b.*/);\n }\n return inherit({\n className: 'meta',\n begin: beginShebang,\n end: /$/,\n relevance: 0,\n /** @type {ModeCallback} */\n \"on:begin\": (m, resp) => {\n if (m.index !== 0) resp.ignoreMatch();\n }\n }, opts);\n};\n\n// Common modes\nconst BACKSLASH_ESCAPE = {\n begin: '\\\\\\\\[\\\\s\\\\S]', relevance: 0\n};\nconst APOS_STRING_MODE = {\n className: 'string',\n begin: '\\'',\n end: '\\'',\n illegal: '\\\\n',\n contains: [BACKSLASH_ESCAPE]\n};\nconst QUOTE_STRING_MODE = {\n className: 'string',\n begin: '\"',\n end: '\"',\n illegal: '\\\\n',\n contains: [BACKSLASH_ESCAPE]\n};\nconst PHRASAL_WORDS_MODE = {\n begin: /\\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\\b/\n};\n/**\n * Creates a comment mode\n *\n * @param {string | RegExp} begin\n * @param {string | RegExp} end\n * @param {Mode | {}} [modeOptions]\n * @returns {Partial}\n */\nconst COMMENT = function(begin, end, modeOptions = {}) {\n const mode = inherit(\n {\n className: 'comment',\n begin,\n end,\n contains: []\n },\n modeOptions\n );\n mode.contains.push(PHRASAL_WORDS_MODE);\n mode.contains.push({\n className: 'doctag',\n begin: '(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):',\n relevance: 0\n });\n return mode;\n};\nconst C_LINE_COMMENT_MODE = COMMENT('//', '$');\nconst C_BLOCK_COMMENT_MODE = COMMENT('/\\\\*', '\\\\*/');\nconst HASH_COMMENT_MODE = COMMENT('#', '$');\nconst NUMBER_MODE = {\n className: 'number',\n begin: NUMBER_RE,\n relevance: 0\n};\nconst C_NUMBER_MODE = {\n className: 'number',\n begin: C_NUMBER_RE,\n relevance: 0\n};\nconst BINARY_NUMBER_MODE = {\n className: 'number',\n begin: BINARY_NUMBER_RE,\n relevance: 0\n};\nconst CSS_NUMBER_MODE = {\n className: 'number',\n begin: NUMBER_RE + '(' +\n '%|em|ex|ch|rem' +\n '|vw|vh|vmin|vmax' +\n '|cm|mm|in|pt|pc|px' +\n '|deg|grad|rad|turn' +\n '|s|ms' +\n '|Hz|kHz' +\n '|dpi|dpcm|dppx' +\n ')?',\n relevance: 0\n};\nconst REGEXP_MODE = {\n // this outer rule makes sure we actually have a WHOLE regex and not simply\n // an expression such as:\n //\n // 3 / something\n //\n // (which will then blow up when regex's `illegal` sees the newline)\n begin: /(?=\\/[^/\\n]*\\/)/,\n contains: [{\n className: 'regexp',\n begin: /\\//,\n end: /\\/[gimuy]*/,\n illegal: /\\n/,\n contains: [\n BACKSLASH_ESCAPE,\n {\n begin: /\\[/,\n end: /\\]/,\n relevance: 0,\n contains: [BACKSLASH_ESCAPE]\n }\n ]\n }]\n};\nconst TITLE_MODE = {\n className: 'title',\n begin: IDENT_RE,\n relevance: 0\n};\nconst UNDERSCORE_TITLE_MODE = {\n className: 'title',\n begin: UNDERSCORE_IDENT_RE,\n relevance: 0\n};\nconst METHOD_GUARD = {\n // excludes method names from keyword processing\n begin: '\\\\.\\\\s*' + UNDERSCORE_IDENT_RE,\n relevance: 0\n};\n\n/**\n * Adds end same as begin mechanics to a mode\n *\n * Your mode must include at least a single () match group as that first match\n * group is what is used for comparison\n * @param {Partial} mode\n */\nconst END_SAME_AS_BEGIN = function(mode) {\n return Object.assign(mode,\n {\n /** @type {ModeCallback} */\n 'on:begin': (m, resp) => { resp.data._beginMatch = m[1]; },\n /** @type {ModeCallback} */\n 'on:end': (m, resp) => { if (resp.data._beginMatch !== m[1]) resp.ignoreMatch(); }\n });\n};\n\nvar MODES = /*#__PURE__*/Object.freeze({\n __proto__: null,\n MATCH_NOTHING_RE: MATCH_NOTHING_RE,\n IDENT_RE: IDENT_RE,\n UNDERSCORE_IDENT_RE: UNDERSCORE_IDENT_RE,\n NUMBER_RE: NUMBER_RE,\n C_NUMBER_RE: C_NUMBER_RE,\n BINARY_NUMBER_RE: BINARY_NUMBER_RE,\n RE_STARTERS_RE: RE_STARTERS_RE,\n SHEBANG: SHEBANG,\n BACKSLASH_ESCAPE: BACKSLASH_ESCAPE,\n APOS_STRING_MODE: APOS_STRING_MODE,\n QUOTE_STRING_MODE: QUOTE_STRING_MODE,\n PHRASAL_WORDS_MODE: PHRASAL_WORDS_MODE,\n COMMENT: COMMENT,\n C_LINE_COMMENT_MODE: C_LINE_COMMENT_MODE,\n C_BLOCK_COMMENT_MODE: C_BLOCK_COMMENT_MODE,\n HASH_COMMENT_MODE: HASH_COMMENT_MODE,\n NUMBER_MODE: NUMBER_MODE,\n C_NUMBER_MODE: C_NUMBER_MODE,\n BINARY_NUMBER_MODE: BINARY_NUMBER_MODE,\n CSS_NUMBER_MODE: CSS_NUMBER_MODE,\n REGEXP_MODE: REGEXP_MODE,\n TITLE_MODE: TITLE_MODE,\n UNDERSCORE_TITLE_MODE: UNDERSCORE_TITLE_MODE,\n METHOD_GUARD: METHOD_GUARD,\n END_SAME_AS_BEGIN: END_SAME_AS_BEGIN\n});\n\n// Grammar extensions / plugins\n// See: https://github.com/highlightjs/highlight.js/issues/2833\n\n// Grammar extensions allow \"syntactic sugar\" to be added to the grammar modes\n// without requiring any underlying changes to the compiler internals.\n\n// `compileMatch` being the perfect small example of now allowing a grammar\n// author to write `match` when they desire to match a single expression rather\n// than being forced to use `begin`. The extension then just moves `match` into\n// `begin` when it runs. Ie, no features have been added, but we've just made\n// the experience of writing (and reading grammars) a little bit nicer.\n\n// ------\n\n// TODO: We need negative look-behind support to do this properly\n/**\n * Skip a match if it has a preceding dot\n *\n * This is used for `beginKeywords` to prevent matching expressions such as\n * `bob.keyword.do()`. The mode compiler automatically wires this up as a\n * special _internal_ 'on:begin' callback for modes with `beginKeywords`\n * @param {RegExpMatchArray} match\n * @param {CallbackResponse} response\n */\nfunction skipIfhasPrecedingDot(match, response) {\n const before = match.input[match.index - 1];\n if (before === \".\") {\n response.ignoreMatch();\n }\n}\n\n\n/**\n * `beginKeywords` syntactic sugar\n * @type {CompilerExt}\n */\nfunction beginKeywords(mode, parent) {\n if (!parent) return;\n if (!mode.beginKeywords) return;\n\n // for languages with keywords that include non-word characters checking for\n // a word boundary is not sufficient, so instead we check for a word boundary\n // or whitespace - this does no harm in any case since our keyword engine\n // doesn't allow spaces in keywords anyways and we still check for the boundary\n // first\n mode.begin = '\\\\b(' + mode.beginKeywords.split(' ').join('|') + ')(?!\\\\.)(?=\\\\b|\\\\s)';\n mode.__beforeBegin = skipIfhasPrecedingDot;\n mode.keywords = mode.keywords || mode.beginKeywords;\n delete mode.beginKeywords;\n\n // prevents double relevance, the keywords themselves provide\n // relevance, the mode doesn't need to double it\n // eslint-disable-next-line no-undefined\n if (mode.relevance === undefined) mode.relevance = 0;\n}\n\n/**\n * Allow `illegal` to contain an array of illegal values\n * @type {CompilerExt}\n */\nfunction compileIllegal(mode, _parent) {\n if (!Array.isArray(mode.illegal)) return;\n\n mode.illegal = either(...mode.illegal);\n}\n\n/**\n * `match` to match a single expression for readability\n * @type {CompilerExt}\n */\nfunction compileMatch(mode, _parent) {\n if (!mode.match) return;\n if (mode.begin || mode.end) throw new Error(\"begin & end are not supported with match\");\n\n mode.begin = mode.match;\n delete mode.match;\n}\n\n/**\n * provides the default 1 relevance to all modes\n * @type {CompilerExt}\n */\nfunction compileRelevance(mode, _parent) {\n // eslint-disable-next-line no-undefined\n if (mode.relevance === undefined) mode.relevance = 1;\n}\n\n// keywords that should have no default relevance value\nconst COMMON_KEYWORDS = [\n 'of',\n 'and',\n 'for',\n 'in',\n 'not',\n 'or',\n 'if',\n 'then',\n 'parent', // common variable name\n 'list', // common variable name\n 'value' // common variable name\n];\n\nconst DEFAULT_KEYWORD_CLASSNAME = \"keyword\";\n\n/**\n * Given raw keywords from a language definition, compile them.\n *\n * @param {string | Record | Array} rawKeywords\n * @param {boolean} caseInsensitive\n */\nfunction compileKeywords(rawKeywords, caseInsensitive, className = DEFAULT_KEYWORD_CLASSNAME) {\n /** @type KeywordDict */\n const compiledKeywords = {};\n\n // input can be a string of keywords, an array of keywords, or a object with\n // named keys representing className (which can then point to a string or array)\n if (typeof rawKeywords === 'string') {\n compileList(className, rawKeywords.split(\" \"));\n } else if (Array.isArray(rawKeywords)) {\n compileList(className, rawKeywords);\n } else {\n Object.keys(rawKeywords).forEach(function(className) {\n // collapse all our objects back into the parent object\n Object.assign(\n compiledKeywords,\n compileKeywords(rawKeywords[className], caseInsensitive, className)\n );\n });\n }\n return compiledKeywords;\n\n // ---\n\n /**\n * Compiles an individual list of keywords\n *\n * Ex: \"for if when while|5\"\n *\n * @param {string} className\n * @param {Array} keywordList\n */\n function compileList(className, keywordList) {\n if (caseInsensitive) {\n keywordList = keywordList.map(x => x.toLowerCase());\n }\n keywordList.forEach(function(keyword) {\n const pair = keyword.split('|');\n compiledKeywords[pair[0]] = [className, scoreForKeyword(pair[0], pair[1])];\n });\n }\n}\n\n/**\n * Returns the proper score for a given keyword\n *\n * Also takes into account comment keywords, which will be scored 0 UNLESS\n * another score has been manually assigned.\n * @param {string} keyword\n * @param {string} [providedScore]\n */\nfunction scoreForKeyword(keyword, providedScore) {\n // manual scores always win over common keywords\n // so you can force a score of 1 if you really insist\n if (providedScore) {\n return Number(providedScore);\n }\n\n return commonKeyword(keyword) ? 0 : 1;\n}\n\n/**\n * Determines if a given keyword is common or not\n *\n * @param {string} keyword */\nfunction commonKeyword(keyword) {\n return COMMON_KEYWORDS.includes(keyword.toLowerCase());\n}\n\n// compilation\n\n/**\n * Compiles a language definition result\n *\n * Given the raw result of a language definition (Language), compiles this so\n * that it is ready for highlighting code.\n * @param {Language} language\n * @param {{plugins: HLJSPlugin[]}} opts\n * @returns {CompiledLanguage}\n */\nfunction compileLanguage(language, { plugins }) {\n /**\n * Builds a regex with the case sensativility of the current language\n *\n * @param {RegExp | string} value\n * @param {boolean} [global]\n */\n function langRe(value, global) {\n return new RegExp(\n source(value),\n 'm' + (language.case_insensitive ? 'i' : '') + (global ? 'g' : '')\n );\n }\n\n /**\n Stores multiple regular expressions and allows you to quickly search for\n them all in a string simultaneously - returning the first match. It does\n this by creating a huge (a|b|c) regex - each individual item wrapped with ()\n and joined by `|` - using match groups to track position. When a match is\n found checking which position in the array has content allows us to figure\n out which of the original regexes / match groups triggered the match.\n\n The match object itself (the result of `Regex.exec`) is returned but also\n enhanced by merging in any meta-data that was registered with the regex.\n This is how we keep track of which mode matched, and what type of rule\n (`illegal`, `begin`, end, etc).\n */\n class MultiRegex {\n constructor() {\n this.matchIndexes = {};\n // @ts-ignore\n this.regexes = [];\n this.matchAt = 1;\n this.position = 0;\n }\n\n // @ts-ignore\n addRule(re, opts) {\n opts.position = this.position++;\n // @ts-ignore\n this.matchIndexes[this.matchAt] = opts;\n this.regexes.push([opts, re]);\n this.matchAt += countMatchGroups(re) + 1;\n }\n\n compile() {\n if (this.regexes.length === 0) {\n // avoids the need to check length every time exec is called\n // @ts-ignore\n this.exec = () => null;\n }\n const terminators = this.regexes.map(el => el[1]);\n this.matcherRe = langRe(join(terminators), true);\n this.lastIndex = 0;\n }\n\n /** @param {string} s */\n exec(s) {\n this.matcherRe.lastIndex = this.lastIndex;\n const match = this.matcherRe.exec(s);\n if (!match) { return null; }\n\n // eslint-disable-next-line no-undefined\n const i = match.findIndex((el, i) => i > 0 && el !== undefined);\n // @ts-ignore\n const matchData = this.matchIndexes[i];\n // trim off any earlier non-relevant match groups (ie, the other regex\n // match groups that make up the multi-matcher)\n match.splice(0, i);\n\n return Object.assign(match, matchData);\n }\n }\n\n /*\n Created to solve the key deficiently with MultiRegex - there is no way to\n test for multiple matches at a single location. Why would we need to do\n that? In the future a more dynamic engine will allow certain matches to be\n ignored. An example: if we matched say the 3rd regex in a large group but\n decided to ignore it - we'd need to started testing again at the 4th\n regex... but MultiRegex itself gives us no real way to do that.\n\n So what this class creates MultiRegexs on the fly for whatever search\n position they are needed.\n\n NOTE: These additional MultiRegex objects are created dynamically. For most\n grammars most of the time we will never actually need anything more than the\n first MultiRegex - so this shouldn't have too much overhead.\n\n Say this is our search group, and we match regex3, but wish to ignore it.\n\n regex1 | regex2 | regex3 | regex4 | regex5 ' ie, startAt = 0\n\n What we need is a new MultiRegex that only includes the remaining\n possibilities:\n\n regex4 | regex5 ' ie, startAt = 3\n\n This class wraps all that complexity up in a simple API... `startAt` decides\n where in the array of expressions to start doing the matching. It\n auto-increments, so if a match is found at position 2, then startAt will be\n set to 3. If the end is reached startAt will return to 0.\n\n MOST of the time the parser will be setting startAt manually to 0.\n */\n class ResumableMultiRegex {\n constructor() {\n // @ts-ignore\n this.rules = [];\n // @ts-ignore\n this.multiRegexes = [];\n this.count = 0;\n\n this.lastIndex = 0;\n this.regexIndex = 0;\n }\n\n // @ts-ignore\n getMatcher(index) {\n if (this.multiRegexes[index]) return this.multiRegexes[index];\n\n const matcher = new MultiRegex();\n this.rules.slice(index).forEach(([re, opts]) => matcher.addRule(re, opts));\n matcher.compile();\n this.multiRegexes[index] = matcher;\n return matcher;\n }\n\n resumingScanAtSamePosition() {\n return this.regexIndex !== 0;\n }\n\n considerAll() {\n this.regexIndex = 0;\n }\n\n // @ts-ignore\n addRule(re, opts) {\n this.rules.push([re, opts]);\n if (opts.type === \"begin\") this.count++;\n }\n\n /** @param {string} s */\n exec(s) {\n const m = this.getMatcher(this.regexIndex);\n m.lastIndex = this.lastIndex;\n let result = m.exec(s);\n\n // The following is because we have no easy way to say \"resume scanning at the\n // existing position but also skip the current rule ONLY\". What happens is\n // all prior rules are also skipped which can result in matching the wrong\n // thing. Example of matching \"booger\":\n\n // our matcher is [string, \"booger\", number]\n //\n // ....booger....\n\n // if \"booger\" is ignored then we'd really need a regex to scan from the\n // SAME position for only: [string, number] but ignoring \"booger\" (if it\n // was the first match), a simple resume would scan ahead who knows how\n // far looking only for \"number\", ignoring potential string matches (or\n // future \"booger\" matches that might be valid.)\n\n // So what we do: We execute two matchers, one resuming at the same\n // position, but the second full matcher starting at the position after:\n\n // /--- resume first regex match here (for [number])\n // |/---- full match here for [string, \"booger\", number]\n // vv\n // ....booger....\n\n // Which ever results in a match first is then used. So this 3-4 step\n // process essentially allows us to say \"match at this position, excluding\n // a prior rule that was ignored\".\n //\n // 1. Match \"booger\" first, ignore. Also proves that [string] does non match.\n // 2. Resume matching for [number]\n // 3. Match at index + 1 for [string, \"booger\", number]\n // 4. If #2 and #3 result in matches, which came first?\n if (this.resumingScanAtSamePosition()) {\n if (result && result.index === this.lastIndex) ; else { // use the second matcher result\n const m2 = this.getMatcher(0);\n m2.lastIndex = this.lastIndex + 1;\n result = m2.exec(s);\n }\n }\n\n if (result) {\n this.regexIndex += result.position + 1;\n if (this.regexIndex === this.count) {\n // wrap-around to considering all matches again\n this.considerAll();\n }\n }\n\n return result;\n }\n }\n\n /**\n * Given a mode, builds a huge ResumableMultiRegex that can be used to walk\n * the content and find matches.\n *\n * @param {CompiledMode} mode\n * @returns {ResumableMultiRegex}\n */\n function buildModeRegex(mode) {\n const mm = new ResumableMultiRegex();\n\n mode.contains.forEach(term => mm.addRule(term.begin, { rule: term, type: \"begin\" }));\n\n if (mode.terminatorEnd) {\n mm.addRule(mode.terminatorEnd, { type: \"end\" });\n }\n if (mode.illegal) {\n mm.addRule(mode.illegal, { type: \"illegal\" });\n }\n\n return mm;\n }\n\n /** skip vs abort vs ignore\n *\n * @skip - The mode is still entered and exited normally (and contains rules apply),\n * but all content is held and added to the parent buffer rather than being\n * output when the mode ends. Mostly used with `sublanguage` to build up\n * a single large buffer than can be parsed by sublanguage.\n *\n * - The mode begin ands ends normally.\n * - Content matched is added to the parent mode buffer.\n * - The parser cursor is moved forward normally.\n *\n * @abort - A hack placeholder until we have ignore. Aborts the mode (as if it\n * never matched) but DOES NOT continue to match subsequent `contains`\n * modes. Abort is bad/suboptimal because it can result in modes\n * farther down not getting applied because an earlier rule eats the\n * content but then aborts.\n *\n * - The mode does not begin.\n * - Content matched by `begin` is added to the mode buffer.\n * - The parser cursor is moved forward accordingly.\n *\n * @ignore - Ignores the mode (as if it never matched) and continues to match any\n * subsequent `contains` modes. Ignore isn't technically possible with\n * the current parser implementation.\n *\n * - The mode does not begin.\n * - Content matched by `begin` is ignored.\n * - The parser cursor is not moved forward.\n */\n\n /**\n * Compiles an individual mode\n *\n * This can raise an error if the mode contains certain detectable known logic\n * issues.\n * @param {Mode} mode\n * @param {CompiledMode | null} [parent]\n * @returns {CompiledMode | never}\n */\n function compileMode(mode, parent) {\n const cmode = /** @type CompiledMode */ (mode);\n if (mode.isCompiled) return cmode;\n\n [\n // do this early so compiler extensions generally don't have to worry about\n // the distinction between match/begin\n compileMatch\n ].forEach(ext => ext(mode, parent));\n\n language.compilerExtensions.forEach(ext => ext(mode, parent));\n\n // __beforeBegin is considered private API, internal use only\n mode.__beforeBegin = null;\n\n [\n beginKeywords,\n // do this later so compiler extensions that come earlier have access to the\n // raw array if they wanted to perhaps manipulate it, etc.\n compileIllegal,\n // default to 1 relevance if not specified\n compileRelevance\n ].forEach(ext => ext(mode, parent));\n\n mode.isCompiled = true;\n\n let keywordPattern = null;\n if (typeof mode.keywords === \"object\") {\n keywordPattern = mode.keywords.$pattern;\n delete mode.keywords.$pattern;\n }\n\n if (mode.keywords) {\n mode.keywords = compileKeywords(mode.keywords, language.case_insensitive);\n }\n\n // both are not allowed\n if (mode.lexemes && keywordPattern) {\n throw new Error(\"ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) \");\n }\n\n // `mode.lexemes` was the old standard before we added and now recommend\n // using `keywords.$pattern` to pass the keyword pattern\n keywordPattern = keywordPattern || mode.lexemes || /\\w+/;\n cmode.keywordPatternRe = langRe(keywordPattern, true);\n\n if (parent) {\n if (!mode.begin) mode.begin = /\\B|\\b/;\n cmode.beginRe = langRe(mode.begin);\n if (mode.endSameAsBegin) mode.end = mode.begin;\n if (!mode.end && !mode.endsWithParent) mode.end = /\\B|\\b/;\n if (mode.end) cmode.endRe = langRe(mode.end);\n cmode.terminatorEnd = source(mode.end) || '';\n if (mode.endsWithParent && parent.terminatorEnd) {\n cmode.terminatorEnd += (mode.end ? '|' : '') + parent.terminatorEnd;\n }\n }\n if (mode.illegal) cmode.illegalRe = langRe(/** @type {RegExp | string} */ (mode.illegal));\n if (!mode.contains) mode.contains = [];\n\n mode.contains = [].concat(...mode.contains.map(function(c) {\n return expandOrCloneMode(c === 'self' ? mode : c);\n }));\n mode.contains.forEach(function(c) { compileMode(/** @type Mode */ (c), cmode); });\n\n if (mode.starts) {\n compileMode(mode.starts, parent);\n }\n\n cmode.matcher = buildModeRegex(cmode);\n return cmode;\n }\n\n if (!language.compilerExtensions) language.compilerExtensions = [];\n\n // self is not valid at the top-level\n if (language.contains && language.contains.includes('self')) {\n throw new Error(\"ERR: contains `self` is not supported at the top-level of a language. See documentation.\");\n }\n\n // we need a null object, which inherit will guarantee\n language.classNameAliases = inherit(language.classNameAliases || {});\n\n return compileMode(/** @type Mode */ (language));\n}\n\n/**\n * Determines if a mode has a dependency on it's parent or not\n *\n * If a mode does have a parent dependency then often we need to clone it if\n * it's used in multiple places so that each copy points to the correct parent,\n * where-as modes without a parent can often safely be re-used at the bottom of\n * a mode chain.\n *\n * @param {Mode | null} mode\n * @returns {boolean} - is there a dependency on the parent?\n * */\nfunction dependencyOnParent(mode) {\n if (!mode) return false;\n\n return mode.endsWithParent || dependencyOnParent(mode.starts);\n}\n\n/**\n * Expands a mode or clones it if necessary\n *\n * This is necessary for modes with parental dependenceis (see notes on\n * `dependencyOnParent`) and for nodes that have `variants` - which must then be\n * exploded into their own individual modes at compile time.\n *\n * @param {Mode} mode\n * @returns {Mode | Mode[]}\n * */\nfunction expandOrCloneMode(mode) {\n if (mode.variants && !mode.cachedVariants) {\n mode.cachedVariants = mode.variants.map(function(variant) {\n return inherit(mode, { variants: null }, variant);\n });\n }\n\n // EXPAND\n // if we have variants then essentially \"replace\" the mode with the variants\n // this happens in compileMode, where this function is called from\n if (mode.cachedVariants) {\n return mode.cachedVariants;\n }\n\n // CLONE\n // if we have dependencies on parents then we need a unique\n // instance of ourselves, so we can be reused with many\n // different parents without issue\n if (dependencyOnParent(mode)) {\n return inherit(mode, { starts: mode.starts ? inherit(mode.starts) : null });\n }\n\n if (Object.isFrozen(mode)) {\n return inherit(mode);\n }\n\n // no special dependency issues, just return ourselves\n return mode;\n}\n\nvar version = \"10.7.3\";\n\n// @ts-nocheck\n\nfunction hasValueOrEmptyAttribute(value) {\n return Boolean(value || value === \"\");\n}\n\nfunction BuildVuePlugin(hljs) {\n const Component = {\n props: [\"language\", \"code\", \"autodetect\"],\n data: function() {\n return {\n detectedLanguage: \"\",\n unknownLanguage: false\n };\n },\n computed: {\n className() {\n if (this.unknownLanguage) return \"\";\n\n return \"hljs \" + this.detectedLanguage;\n },\n highlighted() {\n // no idea what language to use, return raw code\n if (!this.autoDetect && !hljs.getLanguage(this.language)) {\n console.warn(`The language \"${this.language}\" you specified could not be found.`);\n this.unknownLanguage = true;\n return escapeHTML(this.code);\n }\n\n let result = {};\n if (this.autoDetect) {\n result = hljs.highlightAuto(this.code);\n this.detectedLanguage = result.language;\n } else {\n result = hljs.highlight(this.language, this.code, this.ignoreIllegals);\n this.detectedLanguage = this.language;\n }\n return result.value;\n },\n autoDetect() {\n return !this.language || hasValueOrEmptyAttribute(this.autodetect);\n },\n ignoreIllegals() {\n return true;\n }\n },\n // this avoids needing to use a whole Vue compilation pipeline just\n // to build Highlight.js\n render(createElement) {\n return createElement(\"pre\", {}, [\n createElement(\"code\", {\n class: this.className,\n domProps: { innerHTML: this.highlighted }\n })\n ]);\n }\n // template: `
    `\n };\n\n const VuePlugin = {\n install(Vue) {\n Vue.component('highlightjs', Component);\n }\n };\n\n return { Component, VuePlugin };\n}\n\n/* plugin itself */\n\n/** @type {HLJSPlugin} */\nconst mergeHTMLPlugin = {\n \"after:highlightElement\": ({ el, result, text }) => {\n const originalStream = nodeStream(el);\n if (!originalStream.length) return;\n\n const resultNode = document.createElement('div');\n resultNode.innerHTML = result.value;\n result.value = mergeStreams(originalStream, nodeStream(resultNode), text);\n }\n};\n\n/* Stream merging support functions */\n\n/**\n * @typedef Event\n * @property {'start'|'stop'} event\n * @property {number} offset\n * @property {Node} node\n */\n\n/**\n * @param {Node} node\n */\nfunction tag(node) {\n return node.nodeName.toLowerCase();\n}\n\n/**\n * @param {Node} node\n */\nfunction nodeStream(node) {\n /** @type Event[] */\n const result = [];\n (function _nodeStream(node, offset) {\n for (let child = node.firstChild; child; child = child.nextSibling) {\n if (child.nodeType === 3) {\n offset += child.nodeValue.length;\n } else if (child.nodeType === 1) {\n result.push({\n event: 'start',\n offset: offset,\n node: child\n });\n offset = _nodeStream(child, offset);\n // Prevent void elements from having an end tag that would actually\n // double them in the output. There are more void elements in HTML\n // but we list only those realistically expected in code display.\n if (!tag(child).match(/br|hr|img|input/)) {\n result.push({\n event: 'stop',\n offset: offset,\n node: child\n });\n }\n }\n }\n return offset;\n })(node, 0);\n return result;\n}\n\n/**\n * @param {any} original - the original stream\n * @param {any} highlighted - stream of the highlighted source\n * @param {string} value - the original source itself\n */\nfunction mergeStreams(original, highlighted, value) {\n let processed = 0;\n let result = '';\n const nodeStack = [];\n\n function selectStream() {\n if (!original.length || !highlighted.length) {\n return original.length ? original : highlighted;\n }\n if (original[0].offset !== highlighted[0].offset) {\n return (original[0].offset < highlighted[0].offset) ? original : highlighted;\n }\n\n /*\n To avoid starting the stream just before it should stop the order is\n ensured that original always starts first and closes last:\n\n if (event1 == 'start' && event2 == 'start')\n return original;\n if (event1 == 'start' && event2 == 'stop')\n return highlighted;\n if (event1 == 'stop' && event2 == 'start')\n return original;\n if (event1 == 'stop' && event2 == 'stop')\n return highlighted;\n\n ... which is collapsed to:\n */\n return highlighted[0].event === 'start' ? original : highlighted;\n }\n\n /**\n * @param {Node} node\n */\n function open(node) {\n /** @param {Attr} attr */\n function attributeString(attr) {\n return ' ' + attr.nodeName + '=\"' + escapeHTML(attr.value) + '\"';\n }\n // @ts-ignore\n result += '<' + tag(node) + [].map.call(node.attributes, attributeString).join('') + '>';\n }\n\n /**\n * @param {Node} node\n */\n function close(node) {\n result += '';\n }\n\n /**\n * @param {Event} event\n */\n function render(event) {\n (event.event === 'start' ? open : close)(event.node);\n }\n\n while (original.length || highlighted.length) {\n let stream = selectStream();\n result += escapeHTML(value.substring(processed, stream[0].offset));\n processed = stream[0].offset;\n if (stream === original) {\n /*\n On any opening or closing tag of the original markup we first close\n the entire highlighted node stack, then render the original tag along\n with all the following original tags at the same offset and then\n reopen all the tags on the highlighted stack.\n */\n nodeStack.reverse().forEach(close);\n do {\n render(stream.splice(0, 1)[0]);\n stream = selectStream();\n } while (stream === original && stream.length && stream[0].offset === processed);\n nodeStack.reverse().forEach(open);\n } else {\n if (stream[0].event === 'start') {\n nodeStack.push(stream[0].node);\n } else {\n nodeStack.pop();\n }\n render(stream.splice(0, 1)[0]);\n }\n }\n return result + escapeHTML(value.substr(processed));\n}\n\n/*\n\nFor the reasoning behind this please see:\nhttps://github.com/highlightjs/highlight.js/issues/2880#issuecomment-747275419\n\n*/\n\n/**\n * @type {Record}\n */\nconst seenDeprecations = {};\n\n/**\n * @param {string} message\n */\nconst error = (message) => {\n console.error(message);\n};\n\n/**\n * @param {string} message\n * @param {any} args\n */\nconst warn = (message, ...args) => {\n console.log(`WARN: ${message}`, ...args);\n};\n\n/**\n * @param {string} version\n * @param {string} message\n */\nconst deprecated = (version, message) => {\n if (seenDeprecations[`${version}/${message}`]) return;\n\n console.log(`Deprecated as of ${version}. ${message}`);\n seenDeprecations[`${version}/${message}`] = true;\n};\n\n/*\nSyntax highlighting with language autodetection.\nhttps://highlightjs.org/\n*/\n\nconst escape$1 = escapeHTML;\nconst inherit$1 = inherit;\nconst NO_MATCH = Symbol(\"nomatch\");\n\n/**\n * @param {any} hljs - object that is extended (legacy)\n * @returns {HLJSApi}\n */\nconst HLJS = function(hljs) {\n // Global internal variables used within the highlight.js library.\n /** @type {Record} */\n const languages = Object.create(null);\n /** @type {Record} */\n const aliases = Object.create(null);\n /** @type {HLJSPlugin[]} */\n const plugins = [];\n\n // safe/production mode - swallows more errors, tries to keep running\n // even if a single syntax or parse hits a fatal error\n let SAFE_MODE = true;\n const fixMarkupRe = /(^(<[^>]+>|\\t|)+|\\n)/gm;\n const LANGUAGE_NOT_FOUND = \"Could not find the language '{}', did you forget to load/include a language module?\";\n /** @type {Language} */\n const PLAINTEXT_LANGUAGE = { disableAutodetect: true, name: 'Plain text', contains: [] };\n\n // Global options used when within external APIs. This is modified when\n // calling the `hljs.configure` function.\n /** @type HLJSOptions */\n let options = {\n noHighlightRe: /^(no-?highlight)$/i,\n languageDetectRe: /\\blang(?:uage)?-([\\w-]+)\\b/i,\n classPrefix: 'hljs-',\n tabReplace: null,\n useBR: false,\n languages: null,\n // beta configuration options, subject to change, welcome to discuss\n // https://github.com/highlightjs/highlight.js/issues/1086\n __emitter: TokenTreeEmitter\n };\n\n /* Utility functions */\n\n /**\n * Tests a language name to see if highlighting should be skipped\n * @param {string} languageName\n */\n function shouldNotHighlight(languageName) {\n return options.noHighlightRe.test(languageName);\n }\n\n /**\n * @param {HighlightedHTMLElement} block - the HTML element to determine language for\n */\n function blockLanguage(block) {\n let classes = block.className + ' ';\n\n classes += block.parentNode ? block.parentNode.className : '';\n\n // language-* takes precedence over non-prefixed class names.\n const match = options.languageDetectRe.exec(classes);\n if (match) {\n const language = getLanguage(match[1]);\n if (!language) {\n warn(LANGUAGE_NOT_FOUND.replace(\"{}\", match[1]));\n warn(\"Falling back to no-highlight mode for this block.\", block);\n }\n return language ? match[1] : 'no-highlight';\n }\n\n return classes\n .split(/\\s+/)\n .find((_class) => shouldNotHighlight(_class) || getLanguage(_class));\n }\n\n /**\n * Core highlighting function.\n *\n * OLD API\n * highlight(lang, code, ignoreIllegals, continuation)\n *\n * NEW API\n * highlight(code, {lang, ignoreIllegals})\n *\n * @param {string} codeOrlanguageName - the language to use for highlighting\n * @param {string | HighlightOptions} optionsOrCode - the code to highlight\n * @param {boolean} [ignoreIllegals] - whether to ignore illegal matches, default is to bail\n * @param {CompiledMode} [continuation] - current continuation mode, if any\n *\n * @returns {HighlightResult} Result - an object that represents the result\n * @property {string} language - the language name\n * @property {number} relevance - the relevance score\n * @property {string} value - the highlighted HTML code\n * @property {string} code - the original raw code\n * @property {CompiledMode} top - top of the current mode stack\n * @property {boolean} illegal - indicates whether any illegal matches were found\n */\n function highlight(codeOrlanguageName, optionsOrCode, ignoreIllegals, continuation) {\n let code = \"\";\n let languageName = \"\";\n if (typeof optionsOrCode === \"object\") {\n code = codeOrlanguageName;\n ignoreIllegals = optionsOrCode.ignoreIllegals;\n languageName = optionsOrCode.language;\n // continuation not supported at all via the new API\n // eslint-disable-next-line no-undefined\n continuation = undefined;\n } else {\n // old API\n deprecated(\"10.7.0\", \"highlight(lang, code, ...args) has been deprecated.\");\n deprecated(\"10.7.0\", \"Please use highlight(code, options) instead.\\nhttps://github.com/highlightjs/highlight.js/issues/2277\");\n languageName = codeOrlanguageName;\n code = optionsOrCode;\n }\n\n /** @type {BeforeHighlightContext} */\n const context = {\n code,\n language: languageName\n };\n // the plugin can change the desired language or the code to be highlighted\n // just be changing the object it was passed\n fire(\"before:highlight\", context);\n\n // a before plugin can usurp the result completely by providing it's own\n // in which case we don't even need to call highlight\n const result = context.result\n ? context.result\n : _highlight(context.language, context.code, ignoreIllegals, continuation);\n\n result.code = context.code;\n // the plugin can change anything in result to suite it\n fire(\"after:highlight\", result);\n\n return result;\n }\n\n /**\n * private highlight that's used internally and does not fire callbacks\n *\n * @param {string} languageName - the language to use for highlighting\n * @param {string} codeToHighlight - the code to highlight\n * @param {boolean?} [ignoreIllegals] - whether to ignore illegal matches, default is to bail\n * @param {CompiledMode?} [continuation] - current continuation mode, if any\n * @returns {HighlightResult} - result of the highlight operation\n */\n function _highlight(languageName, codeToHighlight, ignoreIllegals, continuation) {\n /**\n * Return keyword data if a match is a keyword\n * @param {CompiledMode} mode - current mode\n * @param {RegExpMatchArray} match - regexp match data\n * @returns {KeywordData | false}\n */\n function keywordData(mode, match) {\n const matchText = language.case_insensitive ? match[0].toLowerCase() : match[0];\n return Object.prototype.hasOwnProperty.call(mode.keywords, matchText) && mode.keywords[matchText];\n }\n\n function processKeywords() {\n if (!top.keywords) {\n emitter.addText(modeBuffer);\n return;\n }\n\n let lastIndex = 0;\n top.keywordPatternRe.lastIndex = 0;\n let match = top.keywordPatternRe.exec(modeBuffer);\n let buf = \"\";\n\n while (match) {\n buf += modeBuffer.substring(lastIndex, match.index);\n const data = keywordData(top, match);\n if (data) {\n const [kind, keywordRelevance] = data;\n emitter.addText(buf);\n buf = \"\";\n\n relevance += keywordRelevance;\n if (kind.startsWith(\"_\")) {\n // _ implied for relevance only, do not highlight\n // by applying a class name\n buf += match[0];\n } else {\n const cssClass = language.classNameAliases[kind] || kind;\n emitter.addKeyword(match[0], cssClass);\n }\n } else {\n buf += match[0];\n }\n lastIndex = top.keywordPatternRe.lastIndex;\n match = top.keywordPatternRe.exec(modeBuffer);\n }\n buf += modeBuffer.substr(lastIndex);\n emitter.addText(buf);\n }\n\n function processSubLanguage() {\n if (modeBuffer === \"\") return;\n /** @type HighlightResult */\n let result = null;\n\n if (typeof top.subLanguage === 'string') {\n if (!languages[top.subLanguage]) {\n emitter.addText(modeBuffer);\n return;\n }\n result = _highlight(top.subLanguage, modeBuffer, true, continuations[top.subLanguage]);\n continuations[top.subLanguage] = /** @type {CompiledMode} */ (result.top);\n } else {\n result = highlightAuto(modeBuffer, top.subLanguage.length ? top.subLanguage : null);\n }\n\n // Counting embedded language score towards the host language may be disabled\n // with zeroing the containing mode relevance. Use case in point is Markdown that\n // allows XML everywhere and makes every XML snippet to have a much larger Markdown\n // score.\n if (top.relevance > 0) {\n relevance += result.relevance;\n }\n emitter.addSublanguage(result.emitter, result.language);\n }\n\n function processBuffer() {\n if (top.subLanguage != null) {\n processSubLanguage();\n } else {\n processKeywords();\n }\n modeBuffer = '';\n }\n\n /**\n * @param {Mode} mode - new mode to start\n */\n function startNewMode(mode) {\n if (mode.className) {\n emitter.openNode(language.classNameAliases[mode.className] || mode.className);\n }\n top = Object.create(mode, { parent: { value: top } });\n return top;\n }\n\n /**\n * @param {CompiledMode } mode - the mode to potentially end\n * @param {RegExpMatchArray} match - the latest match\n * @param {string} matchPlusRemainder - match plus remainder of content\n * @returns {CompiledMode | void} - the next mode, or if void continue on in current mode\n */\n function endOfMode(mode, match, matchPlusRemainder) {\n let matched = startsWith(mode.endRe, matchPlusRemainder);\n\n if (matched) {\n if (mode[\"on:end\"]) {\n const resp = new Response(mode);\n mode[\"on:end\"](match, resp);\n if (resp.isMatchIgnored) matched = false;\n }\n\n if (matched) {\n while (mode.endsParent && mode.parent) {\n mode = mode.parent;\n }\n return mode;\n }\n }\n // even if on:end fires an `ignore` it's still possible\n // that we might trigger the end node because of a parent mode\n if (mode.endsWithParent) {\n return endOfMode(mode.parent, match, matchPlusRemainder);\n }\n }\n\n /**\n * Handle matching but then ignoring a sequence of text\n *\n * @param {string} lexeme - string containing full match text\n */\n function doIgnore(lexeme) {\n if (top.matcher.regexIndex === 0) {\n // no more regexs to potentially match here, so we move the cursor forward one\n // space\n modeBuffer += lexeme[0];\n return 1;\n } else {\n // no need to move the cursor, we still have additional regexes to try and\n // match at this very spot\n resumeScanAtSamePosition = true;\n return 0;\n }\n }\n\n /**\n * Handle the start of a new potential mode match\n *\n * @param {EnhancedMatch} match - the current match\n * @returns {number} how far to advance the parse cursor\n */\n function doBeginMatch(match) {\n const lexeme = match[0];\n const newMode = match.rule;\n\n const resp = new Response(newMode);\n // first internal before callbacks, then the public ones\n const beforeCallbacks = [newMode.__beforeBegin, newMode[\"on:begin\"]];\n for (const cb of beforeCallbacks) {\n if (!cb) continue;\n cb(match, resp);\n if (resp.isMatchIgnored) return doIgnore(lexeme);\n }\n\n if (newMode && newMode.endSameAsBegin) {\n newMode.endRe = escape(lexeme);\n }\n\n if (newMode.skip) {\n modeBuffer += lexeme;\n } else {\n if (newMode.excludeBegin) {\n modeBuffer += lexeme;\n }\n processBuffer();\n if (!newMode.returnBegin && !newMode.excludeBegin) {\n modeBuffer = lexeme;\n }\n }\n startNewMode(newMode);\n // if (mode[\"after:begin\"]) {\n // let resp = new Response(mode);\n // mode[\"after:begin\"](match, resp);\n // }\n return newMode.returnBegin ? 0 : lexeme.length;\n }\n\n /**\n * Handle the potential end of mode\n *\n * @param {RegExpMatchArray} match - the current match\n */\n function doEndMatch(match) {\n const lexeme = match[0];\n const matchPlusRemainder = codeToHighlight.substr(match.index);\n\n const endMode = endOfMode(top, match, matchPlusRemainder);\n if (!endMode) { return NO_MATCH; }\n\n const origin = top;\n if (origin.skip) {\n modeBuffer += lexeme;\n } else {\n if (!(origin.returnEnd || origin.excludeEnd)) {\n modeBuffer += lexeme;\n }\n processBuffer();\n if (origin.excludeEnd) {\n modeBuffer = lexeme;\n }\n }\n do {\n if (top.className) {\n emitter.closeNode();\n }\n if (!top.skip && !top.subLanguage) {\n relevance += top.relevance;\n }\n top = top.parent;\n } while (top !== endMode.parent);\n if (endMode.starts) {\n if (endMode.endSameAsBegin) {\n endMode.starts.endRe = endMode.endRe;\n }\n startNewMode(endMode.starts);\n }\n return origin.returnEnd ? 0 : lexeme.length;\n }\n\n function processContinuations() {\n const list = [];\n for (let current = top; current !== language; current = current.parent) {\n if (current.className) {\n list.unshift(current.className);\n }\n }\n list.forEach(item => emitter.openNode(item));\n }\n\n /** @type {{type?: MatchType, index?: number, rule?: Mode}}} */\n let lastMatch = {};\n\n /**\n * Process an individual match\n *\n * @param {string} textBeforeMatch - text preceeding the match (since the last match)\n * @param {EnhancedMatch} [match] - the match itself\n */\n function processLexeme(textBeforeMatch, match) {\n const lexeme = match && match[0];\n\n // add non-matched text to the current mode buffer\n modeBuffer += textBeforeMatch;\n\n if (lexeme == null) {\n processBuffer();\n return 0;\n }\n\n // we've found a 0 width match and we're stuck, so we need to advance\n // this happens when we have badly behaved rules that have optional matchers to the degree that\n // sometimes they can end up matching nothing at all\n // Ref: https://github.com/highlightjs/highlight.js/issues/2140\n if (lastMatch.type === \"begin\" && match.type === \"end\" && lastMatch.index === match.index && lexeme === \"\") {\n // spit the \"skipped\" character that our regex choked on back into the output sequence\n modeBuffer += codeToHighlight.slice(match.index, match.index + 1);\n if (!SAFE_MODE) {\n /** @type {AnnotatedError} */\n const err = new Error('0 width match regex');\n err.languageName = languageName;\n err.badRule = lastMatch.rule;\n throw err;\n }\n return 1;\n }\n lastMatch = match;\n\n if (match.type === \"begin\") {\n return doBeginMatch(match);\n } else if (match.type === \"illegal\" && !ignoreIllegals) {\n // illegal match, we do not continue processing\n /** @type {AnnotatedError} */\n const err = new Error('Illegal lexeme \"' + lexeme + '\" for mode \"' + (top.className || '') + '\"');\n err.mode = top;\n throw err;\n } else if (match.type === \"end\") {\n const processed = doEndMatch(match);\n if (processed !== NO_MATCH) {\n return processed;\n }\n }\n\n // edge case for when illegal matches $ (end of line) which is technically\n // a 0 width match but not a begin/end match so it's not caught by the\n // first handler (when ignoreIllegals is true)\n if (match.type === \"illegal\" && lexeme === \"\") {\n // advance so we aren't stuck in an infinite loop\n return 1;\n }\n\n // infinite loops are BAD, this is a last ditch catch all. if we have a\n // decent number of iterations yet our index (cursor position in our\n // parsing) still 3x behind our index then something is very wrong\n // so we bail\n if (iterations > 100000 && iterations > match.index * 3) {\n const err = new Error('potential infinite loop, way more iterations than matches');\n throw err;\n }\n\n /*\n Why might be find ourselves here? Only one occasion now. An end match that was\n triggered but could not be completed. When might this happen? When an `endSameasBegin`\n rule sets the end rule to a specific match. Since the overall mode termination rule that's\n being used to scan the text isn't recompiled that means that any match that LOOKS like\n the end (but is not, because it is not an exact match to the beginning) will\n end up here. A definite end match, but when `doEndMatch` tries to \"reapply\"\n the end rule and fails to match, we wind up here, and just silently ignore the end.\n\n This causes no real harm other than stopping a few times too many.\n */\n\n modeBuffer += lexeme;\n return lexeme.length;\n }\n\n const language = getLanguage(languageName);\n if (!language) {\n error(LANGUAGE_NOT_FOUND.replace(\"{}\", languageName));\n throw new Error('Unknown language: \"' + languageName + '\"');\n }\n\n const md = compileLanguage(language, { plugins });\n let result = '';\n /** @type {CompiledMode} */\n let top = continuation || md;\n /** @type Record */\n const continuations = {}; // keep continuations for sub-languages\n const emitter = new options.__emitter(options);\n processContinuations();\n let modeBuffer = '';\n let relevance = 0;\n let index = 0;\n let iterations = 0;\n let resumeScanAtSamePosition = false;\n\n try {\n top.matcher.considerAll();\n\n for (;;) {\n iterations++;\n if (resumeScanAtSamePosition) {\n // only regexes not matched previously will now be\n // considered for a potential match\n resumeScanAtSamePosition = false;\n } else {\n top.matcher.considerAll();\n }\n top.matcher.lastIndex = index;\n\n const match = top.matcher.exec(codeToHighlight);\n // console.log(\"match\", match[0], match.rule && match.rule.begin)\n\n if (!match) break;\n\n const beforeMatch = codeToHighlight.substring(index, match.index);\n const processedCount = processLexeme(beforeMatch, match);\n index = match.index + processedCount;\n }\n processLexeme(codeToHighlight.substr(index));\n emitter.closeAllNodes();\n emitter.finalize();\n result = emitter.toHTML();\n\n return {\n // avoid possible breakage with v10 clients expecting\n // this to always be an integer\n relevance: Math.floor(relevance),\n value: result,\n language: languageName,\n illegal: false,\n emitter: emitter,\n top: top\n };\n } catch (err) {\n if (err.message && err.message.includes('Illegal')) {\n return {\n illegal: true,\n illegalBy: {\n msg: err.message,\n context: codeToHighlight.slice(index - 100, index + 100),\n mode: err.mode\n },\n sofar: result,\n relevance: 0,\n value: escape$1(codeToHighlight),\n emitter: emitter\n };\n } else if (SAFE_MODE) {\n return {\n illegal: false,\n relevance: 0,\n value: escape$1(codeToHighlight),\n emitter: emitter,\n language: languageName,\n top: top,\n errorRaised: err\n };\n } else {\n throw err;\n }\n }\n }\n\n /**\n * returns a valid highlight result, without actually doing any actual work,\n * auto highlight starts with this and it's possible for small snippets that\n * auto-detection may not find a better match\n * @param {string} code\n * @returns {HighlightResult}\n */\n function justTextHighlightResult(code) {\n const result = {\n relevance: 0,\n emitter: new options.__emitter(options),\n value: escape$1(code),\n illegal: false,\n top: PLAINTEXT_LANGUAGE\n };\n result.emitter.addText(code);\n return result;\n }\n\n /**\n Highlighting with language detection. Accepts a string with the code to\n highlight. Returns an object with the following properties:\n\n - language (detected language)\n - relevance (int)\n - value (an HTML string with highlighting markup)\n - second_best (object with the same structure for second-best heuristically\n detected language, may be absent)\n\n @param {string} code\n @param {Array} [languageSubset]\n @returns {AutoHighlightResult}\n */\n function highlightAuto(code, languageSubset) {\n languageSubset = languageSubset || options.languages || Object.keys(languages);\n const plaintext = justTextHighlightResult(code);\n\n const results = languageSubset.filter(getLanguage).filter(autoDetection).map(name =>\n _highlight(name, code, false)\n );\n results.unshift(plaintext); // plaintext is always an option\n\n const sorted = results.sort((a, b) => {\n // sort base on relevance\n if (a.relevance !== b.relevance) return b.relevance - a.relevance;\n\n // always award the tie to the base language\n // ie if C++ and Arduino are tied, it's more likely to be C++\n if (a.language && b.language) {\n if (getLanguage(a.language).supersetOf === b.language) {\n return 1;\n } else if (getLanguage(b.language).supersetOf === a.language) {\n return -1;\n }\n }\n\n // otherwise say they are equal, which has the effect of sorting on\n // relevance while preserving the original ordering - which is how ties\n // have historically been settled, ie the language that comes first always\n // wins in the case of a tie\n return 0;\n });\n\n const [best, secondBest] = sorted;\n\n /** @type {AutoHighlightResult} */\n const result = best;\n result.second_best = secondBest;\n\n return result;\n }\n\n /**\n Post-processing of the highlighted markup:\n\n - replace TABs with something more useful\n - replace real line-breaks with '
    ' for non-pre containers\n\n @param {string} html\n @returns {string}\n */\n function fixMarkup(html) {\n if (!(options.tabReplace || options.useBR)) {\n return html;\n }\n\n return html.replace(fixMarkupRe, match => {\n if (match === '\\n') {\n return options.useBR ? '
    ' : match;\n } else if (options.tabReplace) {\n return match.replace(/\\t/g, options.tabReplace);\n }\n return match;\n });\n }\n\n /**\n * Builds new class name for block given the language name\n *\n * @param {HTMLElement} element\n * @param {string} [currentLang]\n * @param {string} [resultLang]\n */\n function updateClassName(element, currentLang, resultLang) {\n const language = currentLang ? aliases[currentLang] : resultLang;\n\n element.classList.add(\"hljs\");\n if (language) element.classList.add(language);\n }\n\n /** @type {HLJSPlugin} */\n const brPlugin = {\n \"before:highlightElement\": ({ el }) => {\n if (options.useBR) {\n el.innerHTML = el.innerHTML.replace(/\\n/g, '').replace(//g, '\\n');\n }\n },\n \"after:highlightElement\": ({ result }) => {\n if (options.useBR) {\n result.value = result.value.replace(/\\n/g, \"
    \");\n }\n }\n };\n\n const TAB_REPLACE_RE = /^(<[^>]+>|\\t)+/gm;\n /** @type {HLJSPlugin} */\n const tabReplacePlugin = {\n \"after:highlightElement\": ({ result }) => {\n if (options.tabReplace) {\n result.value = result.value.replace(TAB_REPLACE_RE, (m) =>\n m.replace(/\\t/g, options.tabReplace)\n );\n }\n }\n };\n\n /**\n * Applies highlighting to a DOM node containing code. Accepts a DOM node and\n * two optional parameters for fixMarkup.\n *\n * @param {HighlightedHTMLElement} element - the HTML element to highlight\n */\n function highlightElement(element) {\n /** @type HTMLElement */\n let node = null;\n const language = blockLanguage(element);\n\n if (shouldNotHighlight(language)) return;\n\n // support for v10 API\n fire(\"before:highlightElement\",\n { el: element, language: language });\n\n node = element;\n const text = node.textContent;\n const result = language ? highlight(text, { language, ignoreIllegals: true }) : highlightAuto(text);\n\n // support for v10 API\n fire(\"after:highlightElement\", { el: element, result, text });\n\n element.innerHTML = result.value;\n updateClassName(element, language, result.language);\n element.result = {\n language: result.language,\n // TODO: remove with version 11.0\n re: result.relevance,\n relavance: result.relevance\n };\n if (result.second_best) {\n element.second_best = {\n language: result.second_best.language,\n // TODO: remove with version 11.0\n re: result.second_best.relevance,\n relavance: result.second_best.relevance\n };\n }\n }\n\n /**\n * Updates highlight.js global options with the passed options\n *\n * @param {Partial} userOptions\n */\n function configure(userOptions) {\n if (userOptions.useBR) {\n deprecated(\"10.3.0\", \"'useBR' will be removed entirely in v11.0\");\n deprecated(\"10.3.0\", \"Please see https://github.com/highlightjs/highlight.js/issues/2559\");\n }\n options = inherit$1(options, userOptions);\n }\n\n /**\n * Highlights to all
     blocks on a page\n   *\n   * @type {Function & {called?: boolean}}\n   */\n  // TODO: remove v12, deprecated\n  const initHighlighting = () => {\n    if (initHighlighting.called) return;\n    initHighlighting.called = true;\n\n    deprecated(\"10.6.0\", \"initHighlighting() is deprecated.  Use highlightAll() instead.\");\n\n    const blocks = document.querySelectorAll('pre code');\n    blocks.forEach(highlightElement);\n  };\n\n  // Higlights all when DOMContentLoaded fires\n  // TODO: remove v12, deprecated\n  function initHighlightingOnLoad() {\n    deprecated(\"10.6.0\", \"initHighlightingOnLoad() is deprecated.  Use highlightAll() instead.\");\n    wantsHighlight = true;\n  }\n\n  let wantsHighlight = false;\n\n  /**\n   * auto-highlights all pre>code elements on the page\n   */\n  function highlightAll() {\n    // if we are called too early in the loading process\n    if (document.readyState === \"loading\") {\n      wantsHighlight = true;\n      return;\n    }\n\n    const blocks = document.querySelectorAll('pre code');\n    blocks.forEach(highlightElement);\n  }\n\n  function boot() {\n    // if a highlight was requested before DOM was loaded, do now\n    if (wantsHighlight) highlightAll();\n  }\n\n  // make sure we are in the browser environment\n  if (typeof window !== 'undefined' && window.addEventListener) {\n    window.addEventListener('DOMContentLoaded', boot, false);\n  }\n\n  /**\n   * Register a language grammar module\n   *\n   * @param {string} languageName\n   * @param {LanguageFn} languageDefinition\n   */\n  function registerLanguage(languageName, languageDefinition) {\n    let lang = null;\n    try {\n      lang = languageDefinition(hljs);\n    } catch (error$1) {\n      error(\"Language definition for '{}' could not be registered.\".replace(\"{}\", languageName));\n      // hard or soft error\n      if (!SAFE_MODE) { throw error$1; } else { error(error$1); }\n      // languages that have serious errors are replaced with essentially a\n      // \"plaintext\" stand-in so that the code blocks will still get normal\n      // css classes applied to them - and one bad language won't break the\n      // entire highlighter\n      lang = PLAINTEXT_LANGUAGE;\n    }\n    // give it a temporary name if it doesn't have one in the meta-data\n    if (!lang.name) lang.name = languageName;\n    languages[languageName] = lang;\n    lang.rawDefinition = languageDefinition.bind(null, hljs);\n\n    if (lang.aliases) {\n      registerAliases(lang.aliases, { languageName });\n    }\n  }\n\n  /**\n   * Remove a language grammar module\n   *\n   * @param {string} languageName\n   */\n  function unregisterLanguage(languageName) {\n    delete languages[languageName];\n    for (const alias of Object.keys(aliases)) {\n      if (aliases[alias] === languageName) {\n        delete aliases[alias];\n      }\n    }\n  }\n\n  /**\n   * @returns {string[]} List of language internal names\n   */\n  function listLanguages() {\n    return Object.keys(languages);\n  }\n\n  /**\n    intended usage: When one language truly requires another\n\n    Unlike `getLanguage`, this will throw when the requested language\n    is not available.\n\n    @param {string} name - name of the language to fetch/require\n    @returns {Language | never}\n  */\n  function requireLanguage(name) {\n    deprecated(\"10.4.0\", \"requireLanguage will be removed entirely in v11.\");\n    deprecated(\"10.4.0\", \"Please see https://github.com/highlightjs/highlight.js/pull/2844\");\n\n    const lang = getLanguage(name);\n    if (lang) { return lang; }\n\n    const err = new Error('The \\'{}\\' language is required, but not loaded.'.replace('{}', name));\n    throw err;\n  }\n\n  /**\n   * @param {string} name - name of the language to retrieve\n   * @returns {Language | undefined}\n   */\n  function getLanguage(name) {\n    name = (name || '').toLowerCase();\n    return languages[name] || languages[aliases[name]];\n  }\n\n  /**\n   *\n   * @param {string|string[]} aliasList - single alias or list of aliases\n   * @param {{languageName: string}} opts\n   */\n  function registerAliases(aliasList, { languageName }) {\n    if (typeof aliasList === 'string') {\n      aliasList = [aliasList];\n    }\n    aliasList.forEach(alias => { aliases[alias.toLowerCase()] = languageName; });\n  }\n\n  /**\n   * Determines if a given language has auto-detection enabled\n   * @param {string} name - name of the language\n   */\n  function autoDetection(name) {\n    const lang = getLanguage(name);\n    return lang && !lang.disableAutodetect;\n  }\n\n  /**\n   * Upgrades the old highlightBlock plugins to the new\n   * highlightElement API\n   * @param {HLJSPlugin} plugin\n   */\n  function upgradePluginAPI(plugin) {\n    // TODO: remove with v12\n    if (plugin[\"before:highlightBlock\"] && !plugin[\"before:highlightElement\"]) {\n      plugin[\"before:highlightElement\"] = (data) => {\n        plugin[\"before:highlightBlock\"](\n          Object.assign({ block: data.el }, data)\n        );\n      };\n    }\n    if (plugin[\"after:highlightBlock\"] && !plugin[\"after:highlightElement\"]) {\n      plugin[\"after:highlightElement\"] = (data) => {\n        plugin[\"after:highlightBlock\"](\n          Object.assign({ block: data.el }, data)\n        );\n      };\n    }\n  }\n\n  /**\n   * @param {HLJSPlugin} plugin\n   */\n  function addPlugin(plugin) {\n    upgradePluginAPI(plugin);\n    plugins.push(plugin);\n  }\n\n  /**\n   *\n   * @param {PluginEvent} event\n   * @param {any} args\n   */\n  function fire(event, args) {\n    const cb = event;\n    plugins.forEach(function(plugin) {\n      if (plugin[cb]) {\n        plugin[cb](args);\n      }\n    });\n  }\n\n  /**\n  Note: fixMarkup is deprecated and will be removed entirely in v11\n\n  @param {string} arg\n  @returns {string}\n  */\n  function deprecateFixMarkup(arg) {\n    deprecated(\"10.2.0\", \"fixMarkup will be removed entirely in v11.0\");\n    deprecated(\"10.2.0\", \"Please see https://github.com/highlightjs/highlight.js/issues/2534\");\n\n    return fixMarkup(arg);\n  }\n\n  /**\n   *\n   * @param {HighlightedHTMLElement} el\n   */\n  function deprecateHighlightBlock(el) {\n    deprecated(\"10.7.0\", \"highlightBlock will be removed entirely in v12.0\");\n    deprecated(\"10.7.0\", \"Please use highlightElement now.\");\n\n    return highlightElement(el);\n  }\n\n  /* Interface definition */\n  Object.assign(hljs, {\n    highlight,\n    highlightAuto,\n    highlightAll,\n    fixMarkup: deprecateFixMarkup,\n    highlightElement,\n    // TODO: Remove with v12 API\n    highlightBlock: deprecateHighlightBlock,\n    configure,\n    initHighlighting,\n    initHighlightingOnLoad,\n    registerLanguage,\n    unregisterLanguage,\n    listLanguages,\n    getLanguage,\n    registerAliases,\n    requireLanguage,\n    autoDetection,\n    inherit: inherit$1,\n    addPlugin,\n    // plugins for frameworks\n    vuePlugin: BuildVuePlugin(hljs).VuePlugin\n  });\n\n  hljs.debugMode = function() { SAFE_MODE = false; };\n  hljs.safeMode = function() { SAFE_MODE = true; };\n  hljs.versionString = version;\n\n  for (const key in MODES) {\n    // @ts-ignore\n    if (typeof MODES[key] === \"object\") {\n      // @ts-ignore\n      deepFreezeEs6(MODES[key]);\n    }\n  }\n\n  // merge all the modes/regexs into our main object\n  Object.assign(hljs, MODES);\n\n  // built-in plugins, likely to be moved out of core in the future\n  hljs.addPlugin(brPlugin); // slated to be removed in v11\n  hljs.addPlugin(mergeHTMLPlugin);\n  hljs.addPlugin(tabReplacePlugin);\n  return hljs;\n};\n\n// export an \"instance\" of the highlighter\nvar highlight = HLJS({});\n\nmodule.exports = highlight;\n","/*\n * Copyright 2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the 'License');\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an 'AS IS' BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n(function () {\n  'use strict';\n\n  const highlightJs = require('highlight.js/lib/core');\n\n  highlightJs.registerLanguage('asciidoc', require('highlight.js/lib/languages/asciidoc'));\n  highlightJs.registerLanguage('bash', require('highlight.js/lib/languages/bash'));\n  highlightJs.registerLanguage('css', require('highlight.js/lib/languages/css'));\n  highlightJs.registerLanguage('diff', require('highlight.js/lib/languages/diff'));\n  highlightJs.registerLanguage('dockerfile', require('highlight.js/lib/languages/dockerfile'));\n  highlightJs.registerLanguage('gradle', require('highlight.js/lib/languages/gradle'));\n  highlightJs.registerLanguage('groovy', require('highlight.js/lib/languages/groovy'));\n  highlightJs.registerLanguage('http', require('highlight.js/lib/languages/http'));\n  highlightJs.registerLanguage('java', require('highlight.js/lib/languages/java'));\n  highlightJs.registerLanguage('javascript',require('highlight.js/lib/languages/javascript'));\n  highlightJs.registerLanguage('json', require('highlight.js/lib/languages/json'));\n  highlightJs.registerLanguage('kotlin', require('highlight.js/lib/languages/kotlin'));\n  highlightJs.registerLanguage('markdown', require('highlight.js/lib/languages/markdown'));\n  highlightJs.registerLanguage('nix', require('highlight.js/lib/languages/nix'));\n  highlightJs.registerLanguage('properties', require('highlight.js/lib/languages/properties'));\n  highlightJs.registerLanguage('ruby', require('highlight.js/lib/languages/ruby'));\n  highlightJs.registerLanguage('scala', require('highlight.js/lib/languages/scala'));\n  highlightJs.registerLanguage('shell', require('highlight.js/lib/languages/shell'));\n  highlightJs.registerLanguage('bash', require('highlight.js/lib/languages/shell'));\n  highlightJs.registerLanguage('sql', require('highlight.js/lib/languages/sql'));\n  highlightJs.registerLanguage('xml', require('highlight.js/lib/languages/xml'));\n  highlightJs.registerLanguage('yaml', require('highlight.js/lib/languages/yaml'));\n\n  highlightJs.configure({ignoreUnescapedHTML: true});\n\n  for(const codeElement of document.querySelectorAll('pre.highlight > code')) {\n    highlightJs.highlightBlock(codeElement);\n  }\n\n})();\n","/**\n * @param {string} value\n * @returns {RegExp}\n * */\n\n/**\n * @param {RegExp | string } re\n * @returns {string}\n */\nfunction source(re) {\n  if (!re) return null;\n  if (typeof re === \"string\") return re;\n\n  return re.source;\n}\n\n/**\n * @param {...(RegExp | string) } args\n * @returns {string}\n */\nfunction concat(...args) {\n  const joined = args.map((x) => source(x)).join(\"\");\n  return joined;\n}\n\n/*\nLanguage: AsciiDoc\nRequires: xml.js\nAuthor: Dan Allen \nWebsite: http://asciidoc.org\nDescription: A semantic, text-based document format that can be exported to HTML, DocBook and other backends.\nCategory: markup\n*/\n\n/** @type LanguageFn */\nfunction asciidoc(hljs) {\n  const HORIZONTAL_RULE = {\n    begin: '^\\'{3,}[ \\\\t]*$',\n    relevance: 10\n  };\n  const ESCAPED_FORMATTING = [\n    // escaped constrained formatting marks (i.e., \\* \\_ or \\`)\n    {\n      begin: /\\\\[*_`]/\n    },\n    // escaped unconstrained formatting marks (i.e., \\\\** \\\\__ or \\\\``)\n    // must ignore until the next formatting marks\n    // this rule might not be 100% compliant with Asciidoctor 2.0 but we are entering undefined behavior territory...\n    {\n      begin: /\\\\\\\\\\*{2}[^\\n]*?\\*{2}/\n    },\n    {\n      begin: /\\\\\\\\_{2}[^\\n]*_{2}/\n    },\n    {\n      begin: /\\\\\\\\`{2}[^\\n]*`{2}/\n    },\n    // guard: constrained formatting mark may not be preceded by \":\", \";\" or\n    // \"}\". match these so the constrained rule doesn't see them\n    {\n      begin: /[:;}][*_`](?![*_`])/\n    }\n  ];\n  const STRONG = [\n    // inline unconstrained strong (single line)\n    {\n      className: 'strong',\n      begin: /\\*{2}([^\\n]+?)\\*{2}/\n    },\n    // inline unconstrained strong (multi-line)\n    {\n      className: 'strong',\n      begin: concat(\n        /\\*\\*/,\n        /((\\*(?!\\*)|\\\\[^\\n]|[^*\\n\\\\])+\\n)+/,\n        /(\\*(?!\\*)|\\\\[^\\n]|[^*\\n\\\\])*/,\n        /\\*\\*/\n      ),\n      relevance: 0\n    },\n    // inline constrained strong (single line)\n    {\n      className: 'strong',\n      // must not precede or follow a word character\n      begin: /\\B\\*(\\S|\\S[^\\n]*?\\S)\\*(?!\\w)/\n    },\n    // inline constrained strong (multi-line)\n    {\n      className: 'strong',\n      // must not precede or follow a word character\n      begin: /\\*[^\\s]([^\\n]+\\n)+([^\\n]+)\\*/\n    }\n  ];\n  const EMPHASIS = [\n    // inline unconstrained emphasis (single line)\n    {\n      className: 'emphasis',\n      begin: /_{2}([^\\n]+?)_{2}/\n    },\n    // inline unconstrained emphasis (multi-line)\n    {\n      className: 'emphasis',\n      begin: concat(\n        /__/,\n        /((_(?!_)|\\\\[^\\n]|[^_\\n\\\\])+\\n)+/,\n        /(_(?!_)|\\\\[^\\n]|[^_\\n\\\\])*/,\n        /__/\n      ),\n      relevance: 0\n    },\n    // inline constrained emphasis (single line)\n    {\n      className: 'emphasis',\n      // must not precede or follow a word character\n      begin: /\\b_(\\S|\\S[^\\n]*?\\S)_(?!\\w)/\n    },\n    // inline constrained emphasis (multi-line)\n    {\n      className: 'emphasis',\n      // must not precede or follow a word character\n      begin: /_[^\\s]([^\\n]+\\n)+([^\\n]+)_/\n    },\n    // inline constrained emphasis using single quote (legacy)\n    {\n      className: 'emphasis',\n      // must not follow a word character or be followed by a single quote or space\n      begin: '\\\\B\\'(?![\\'\\\\s])',\n      end: '(\\\\n{2}|\\')',\n      // allow escaped single quote followed by word char\n      contains: [{\n        begin: '\\\\\\\\\\'\\\\w',\n        relevance: 0\n      }],\n      relevance: 0\n    }\n  ];\n  const ADMONITION = {\n    className: 'symbol',\n    begin: '^(NOTE|TIP|IMPORTANT|WARNING|CAUTION):\\\\s+',\n    relevance: 10\n  };\n  const BULLET_LIST = {\n    className: 'bullet',\n    begin: '^(\\\\*+|-+|\\\\.+|[^\\\\n]+?::)\\\\s+'\n  };\n\n  return {\n    name: 'AsciiDoc',\n    aliases: ['adoc'],\n    contains: [\n      // block comment\n      hljs.COMMENT(\n        '^/{4,}\\\\n',\n        '\\\\n/{4,}$',\n        // can also be done as...\n        // '^/{4,}$',\n        // '^/{4,}$',\n        {\n          relevance: 10\n        }\n      ),\n      // line comment\n      hljs.COMMENT(\n        '^//',\n        '$',\n        {\n          relevance: 0\n        }\n      ),\n      // title\n      {\n        className: 'title',\n        begin: '^\\\\.\\\\w.*$'\n      },\n      // example, admonition & sidebar blocks\n      {\n        begin: '^[=\\\\*]{4,}\\\\n',\n        end: '\\\\n^[=\\\\*]{4,}$',\n        relevance: 10\n      },\n      // headings\n      {\n        className: 'section',\n        relevance: 10,\n        variants: [\n          {\n            begin: '^(={1,6})[ \\t].+?([ \\t]\\\\1)?$'\n          },\n          {\n            begin: '^[^\\\\[\\\\]\\\\n]+?\\\\n[=\\\\-~\\\\^\\\\+]{2,}$'\n          }\n        ]\n      },\n      // document attributes\n      {\n        className: 'meta',\n        begin: '^:.+?:',\n        end: '\\\\s',\n        excludeEnd: true,\n        relevance: 10\n      },\n      // block attributes\n      {\n        className: 'meta',\n        begin: '^\\\\[.+?\\\\]$',\n        relevance: 0\n      },\n      // quoteblocks\n      {\n        className: 'quote',\n        begin: '^_{4,}\\\\n',\n        end: '\\\\n_{4,}$',\n        relevance: 10\n      },\n      // listing and literal blocks\n      {\n        className: 'code',\n        begin: '^[\\\\-\\\\.]{4,}\\\\n',\n        end: '\\\\n[\\\\-\\\\.]{4,}$',\n        relevance: 10\n      },\n      // passthrough blocks\n      {\n        begin: '^\\\\+{4,}\\\\n',\n        end: '\\\\n\\\\+{4,}$',\n        contains: [{\n          begin: '<',\n          end: '>',\n          subLanguage: 'xml',\n          relevance: 0\n        }],\n        relevance: 10\n      },\n\n      BULLET_LIST,\n      ADMONITION,\n      ...ESCAPED_FORMATTING,\n      ...STRONG,\n      ...EMPHASIS,\n\n      // inline smart quotes\n      {\n        className: 'string',\n        variants: [\n          {\n            begin: \"``.+?''\"\n          },\n          {\n            begin: \"`.+?'\"\n          }\n        ]\n      },\n      // inline unconstrained emphasis\n      {\n        className: 'code',\n        begin: /`{2}/,\n        end: /(\\n{2}|`{2})/\n      },\n      // inline code snippets (TODO should get same treatment as strong and emphasis)\n      {\n        className: 'code',\n        begin: '(`.+?`|\\\\+.+?\\\\+)',\n        relevance: 0\n      },\n      // indented literal block\n      {\n        className: 'code',\n        begin: '^[ \\\\t]',\n        end: '$',\n        relevance: 0\n      },\n      HORIZONTAL_RULE,\n      // images and links\n      {\n        begin: '(link:)?(http|https|ftp|file|irc|image:?):\\\\S+?\\\\[[^[]*?\\\\]',\n        returnBegin: true,\n        contains: [\n          {\n            begin: '(link|image:?):',\n            relevance: 0\n          },\n          {\n            className: 'link',\n            begin: '\\\\w',\n            end: '[^\\\\[]+',\n            relevance: 0\n          },\n          {\n            className: 'string',\n            begin: '\\\\[',\n            end: '\\\\]',\n            excludeBegin: true,\n            excludeEnd: true,\n            relevance: 0\n          }\n        ],\n        relevance: 10\n      }\n    ]\n  };\n}\n\nmodule.exports = asciidoc;\n","/**\n * @param {string} value\n * @returns {RegExp}\n * */\n\n/**\n * @param {RegExp | string } re\n * @returns {string}\n */\nfunction source(re) {\n  if (!re) return null;\n  if (typeof re === \"string\") return re;\n\n  return re.source;\n}\n\n/**\n * @param {...(RegExp | string) } args\n * @returns {string}\n */\nfunction concat(...args) {\n  const joined = args.map((x) => source(x)).join(\"\");\n  return joined;\n}\n\n/*\nLanguage: Bash\nAuthor: vah \nContributrors: Benjamin Pannell \nWebsite: https://www.gnu.org/software/bash/\nCategory: common\n*/\n\n/** @type LanguageFn */\nfunction bash(hljs) {\n  const VAR = {};\n  const BRACED_VAR = {\n    begin: /\\$\\{/,\n    end:/\\}/,\n    contains: [\n      \"self\",\n      {\n        begin: /:-/,\n        contains: [ VAR ]\n      } // default values\n    ]\n  };\n  Object.assign(VAR,{\n    className: 'variable',\n    variants: [\n      {begin: concat(/\\$[\\w\\d#@][\\w\\d_]*/,\n        // negative look-ahead tries to avoid matching patterns that are not\n        // Perl at all like $ident$, @ident@, etc.\n        `(?![\\\\w\\\\d])(?![$])`) },\n      BRACED_VAR\n    ]\n  });\n\n  const SUBST = {\n    className: 'subst',\n    begin: /\\$\\(/, end: /\\)/,\n    contains: [hljs.BACKSLASH_ESCAPE]\n  };\n  const HERE_DOC = {\n    begin: /<<-?\\s*(?=\\w+)/,\n    starts: {\n      contains: [\n        hljs.END_SAME_AS_BEGIN({\n          begin: /(\\w+)/,\n          end: /(\\w+)/,\n          className: 'string'\n        })\n      ]\n    }\n  };\n  const QUOTE_STRING = {\n    className: 'string',\n    begin: /\"/, end: /\"/,\n    contains: [\n      hljs.BACKSLASH_ESCAPE,\n      VAR,\n      SUBST\n    ]\n  };\n  SUBST.contains.push(QUOTE_STRING);\n  const ESCAPED_QUOTE = {\n    className: '',\n    begin: /\\\\\"/\n\n  };\n  const APOS_STRING = {\n    className: 'string',\n    begin: /'/, end: /'/\n  };\n  const ARITHMETIC = {\n    begin: /\\$\\(\\(/,\n    end: /\\)\\)/,\n    contains: [\n      { begin: /\\d+#[0-9a-f]+/, className: \"number\" },\n      hljs.NUMBER_MODE,\n      VAR\n    ]\n  };\n  const SH_LIKE_SHELLS = [\n    \"fish\",\n    \"bash\",\n    \"zsh\",\n    \"sh\",\n    \"csh\",\n    \"ksh\",\n    \"tcsh\",\n    \"dash\",\n    \"scsh\",\n  ];\n  const KNOWN_SHEBANG = hljs.SHEBANG({\n    binary: `(${SH_LIKE_SHELLS.join(\"|\")})`,\n    relevance: 10\n  });\n  const FUNCTION = {\n    className: 'function',\n    begin: /\\w[\\w\\d_]*\\s*\\(\\s*\\)\\s*\\{/,\n    returnBegin: true,\n    contains: [hljs.inherit(hljs.TITLE_MODE, {begin: /\\w[\\w\\d_]*/})],\n    relevance: 0\n  };\n\n  return {\n    name: 'Bash',\n    aliases: ['sh', 'zsh'],\n    keywords: {\n      $pattern: /\\b[a-z._-]+\\b/,\n      keyword:\n        'if then else elif fi for while in do done case esac function',\n      literal:\n        'true false',\n      built_in:\n        // Shell built-ins\n        // http://www.gnu.org/software/bash/manual/html_node/Shell-Builtin-Commands.html\n        'break cd continue eval exec exit export getopts hash pwd readonly return shift test times ' +\n        'trap umask unset ' +\n        // Bash built-ins\n        'alias bind builtin caller command declare echo enable help let local logout mapfile printf ' +\n        'read readarray source type typeset ulimit unalias ' +\n        // Shell modifiers\n        'set shopt ' +\n        // Zsh built-ins\n        'autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles ' +\n        'compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate ' +\n        'fc fg float functions getcap getln history integer jobs kill limit log noglob popd print ' +\n        'pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit ' +\n        'unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof ' +\n        'zpty zregexparse zsocket zstyle ztcp'\n    },\n    contains: [\n      KNOWN_SHEBANG, // to catch known shells and boost relevancy\n      hljs.SHEBANG(), // to catch unknown shells but still highlight the shebang\n      FUNCTION,\n      ARITHMETIC,\n      hljs.HASH_COMMENT_MODE,\n      HERE_DOC,\n      QUOTE_STRING,\n      ESCAPED_QUOTE,\n      APOS_STRING,\n      VAR\n    ]\n  };\n}\n\nmodule.exports = bash;\n","const MODES = (hljs) => {\n  return {\n    IMPORTANT: {\n      className: 'meta',\n      begin: '!important'\n    },\n    HEXCOLOR: {\n      className: 'number',\n      begin: '#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})'\n    },\n    ATTRIBUTE_SELECTOR_MODE: {\n      className: 'selector-attr',\n      begin: /\\[/,\n      end: /\\]/,\n      illegal: '$',\n      contains: [\n        hljs.APOS_STRING_MODE,\n        hljs.QUOTE_STRING_MODE\n      ]\n    }\n  };\n};\n\nconst TAGS = [\n  'a',\n  'abbr',\n  'address',\n  'article',\n  'aside',\n  'audio',\n  'b',\n  'blockquote',\n  'body',\n  'button',\n  'canvas',\n  'caption',\n  'cite',\n  'code',\n  'dd',\n  'del',\n  'details',\n  'dfn',\n  'div',\n  'dl',\n  'dt',\n  'em',\n  'fieldset',\n  'figcaption',\n  'figure',\n  'footer',\n  'form',\n  'h1',\n  'h2',\n  'h3',\n  'h4',\n  'h5',\n  'h6',\n  'header',\n  'hgroup',\n  'html',\n  'i',\n  'iframe',\n  'img',\n  'input',\n  'ins',\n  'kbd',\n  'label',\n  'legend',\n  'li',\n  'main',\n  'mark',\n  'menu',\n  'nav',\n  'object',\n  'ol',\n  'p',\n  'q',\n  'quote',\n  'samp',\n  'section',\n  'span',\n  'strong',\n  'summary',\n  'sup',\n  'table',\n  'tbody',\n  'td',\n  'textarea',\n  'tfoot',\n  'th',\n  'thead',\n  'time',\n  'tr',\n  'ul',\n  'var',\n  'video'\n];\n\nconst MEDIA_FEATURES = [\n  'any-hover',\n  'any-pointer',\n  'aspect-ratio',\n  'color',\n  'color-gamut',\n  'color-index',\n  'device-aspect-ratio',\n  'device-height',\n  'device-width',\n  'display-mode',\n  'forced-colors',\n  'grid',\n  'height',\n  'hover',\n  'inverted-colors',\n  'monochrome',\n  'orientation',\n  'overflow-block',\n  'overflow-inline',\n  'pointer',\n  'prefers-color-scheme',\n  'prefers-contrast',\n  'prefers-reduced-motion',\n  'prefers-reduced-transparency',\n  'resolution',\n  'scan',\n  'scripting',\n  'update',\n  'width',\n  // TODO: find a better solution?\n  'min-width',\n  'max-width',\n  'min-height',\n  'max-height'\n];\n\n// https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes\nconst PSEUDO_CLASSES = [\n  'active',\n  'any-link',\n  'blank',\n  'checked',\n  'current',\n  'default',\n  'defined',\n  'dir', // dir()\n  'disabled',\n  'drop',\n  'empty',\n  'enabled',\n  'first',\n  'first-child',\n  'first-of-type',\n  'fullscreen',\n  'future',\n  'focus',\n  'focus-visible',\n  'focus-within',\n  'has', // has()\n  'host', // host or host()\n  'host-context', // host-context()\n  'hover',\n  'indeterminate',\n  'in-range',\n  'invalid',\n  'is', // is()\n  'lang', // lang()\n  'last-child',\n  'last-of-type',\n  'left',\n  'link',\n  'local-link',\n  'not', // not()\n  'nth-child', // nth-child()\n  'nth-col', // nth-col()\n  'nth-last-child', // nth-last-child()\n  'nth-last-col', // nth-last-col()\n  'nth-last-of-type', //nth-last-of-type()\n  'nth-of-type', //nth-of-type()\n  'only-child',\n  'only-of-type',\n  'optional',\n  'out-of-range',\n  'past',\n  'placeholder-shown',\n  'read-only',\n  'read-write',\n  'required',\n  'right',\n  'root',\n  'scope',\n  'target',\n  'target-within',\n  'user-invalid',\n  'valid',\n  'visited',\n  'where' // where()\n];\n\n// https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements\nconst PSEUDO_ELEMENTS = [\n  'after',\n  'backdrop',\n  'before',\n  'cue',\n  'cue-region',\n  'first-letter',\n  'first-line',\n  'grammar-error',\n  'marker',\n  'part',\n  'placeholder',\n  'selection',\n  'slotted',\n  'spelling-error'\n];\n\nconst ATTRIBUTES = [\n  'align-content',\n  'align-items',\n  'align-self',\n  'animation',\n  'animation-delay',\n  'animation-direction',\n  'animation-duration',\n  'animation-fill-mode',\n  'animation-iteration-count',\n  'animation-name',\n  'animation-play-state',\n  'animation-timing-function',\n  'auto',\n  'backface-visibility',\n  'background',\n  'background-attachment',\n  'background-clip',\n  'background-color',\n  'background-image',\n  'background-origin',\n  'background-position',\n  'background-repeat',\n  'background-size',\n  'border',\n  'border-bottom',\n  'border-bottom-color',\n  'border-bottom-left-radius',\n  'border-bottom-right-radius',\n  'border-bottom-style',\n  'border-bottom-width',\n  'border-collapse',\n  'border-color',\n  'border-image',\n  'border-image-outset',\n  'border-image-repeat',\n  'border-image-slice',\n  'border-image-source',\n  'border-image-width',\n  'border-left',\n  'border-left-color',\n  'border-left-style',\n  'border-left-width',\n  'border-radius',\n  'border-right',\n  'border-right-color',\n  'border-right-style',\n  'border-right-width',\n  'border-spacing',\n  'border-style',\n  'border-top',\n  'border-top-color',\n  'border-top-left-radius',\n  'border-top-right-radius',\n  'border-top-style',\n  'border-top-width',\n  'border-width',\n  'bottom',\n  'box-decoration-break',\n  'box-shadow',\n  'box-sizing',\n  'break-after',\n  'break-before',\n  'break-inside',\n  'caption-side',\n  'clear',\n  'clip',\n  'clip-path',\n  'color',\n  'column-count',\n  'column-fill',\n  'column-gap',\n  'column-rule',\n  'column-rule-color',\n  'column-rule-style',\n  'column-rule-width',\n  'column-span',\n  'column-width',\n  'columns',\n  'content',\n  'counter-increment',\n  'counter-reset',\n  'cursor',\n  'direction',\n  'display',\n  'empty-cells',\n  'filter',\n  'flex',\n  'flex-basis',\n  'flex-direction',\n  'flex-flow',\n  'flex-grow',\n  'flex-shrink',\n  'flex-wrap',\n  'float',\n  'font',\n  'font-display',\n  'font-family',\n  'font-feature-settings',\n  'font-kerning',\n  'font-language-override',\n  'font-size',\n  'font-size-adjust',\n  'font-smoothing',\n  'font-stretch',\n  'font-style',\n  'font-variant',\n  'font-variant-ligatures',\n  'font-variation-settings',\n  'font-weight',\n  'height',\n  'hyphens',\n  'icon',\n  'image-orientation',\n  'image-rendering',\n  'image-resolution',\n  'ime-mode',\n  'inherit',\n  'initial',\n  'justify-content',\n  'left',\n  'letter-spacing',\n  'line-height',\n  'list-style',\n  'list-style-image',\n  'list-style-position',\n  'list-style-type',\n  'margin',\n  'margin-bottom',\n  'margin-left',\n  'margin-right',\n  'margin-top',\n  'marks',\n  'mask',\n  'max-height',\n  'max-width',\n  'min-height',\n  'min-width',\n  'nav-down',\n  'nav-index',\n  'nav-left',\n  'nav-right',\n  'nav-up',\n  'none',\n  'normal',\n  'object-fit',\n  'object-position',\n  'opacity',\n  'order',\n  'orphans',\n  'outline',\n  'outline-color',\n  'outline-offset',\n  'outline-style',\n  'outline-width',\n  'overflow',\n  'overflow-wrap',\n  'overflow-x',\n  'overflow-y',\n  'padding',\n  'padding-bottom',\n  'padding-left',\n  'padding-right',\n  'padding-top',\n  'page-break-after',\n  'page-break-before',\n  'page-break-inside',\n  'perspective',\n  'perspective-origin',\n  'pointer-events',\n  'position',\n  'quotes',\n  'resize',\n  'right',\n  'src', // @font-face\n  'tab-size',\n  'table-layout',\n  'text-align',\n  'text-align-last',\n  'text-decoration',\n  'text-decoration-color',\n  'text-decoration-line',\n  'text-decoration-style',\n  'text-indent',\n  'text-overflow',\n  'text-rendering',\n  'text-shadow',\n  'text-transform',\n  'text-underline-position',\n  'top',\n  'transform',\n  'transform-origin',\n  'transform-style',\n  'transition',\n  'transition-delay',\n  'transition-duration',\n  'transition-property',\n  'transition-timing-function',\n  'unicode-bidi',\n  'vertical-align',\n  'visibility',\n  'white-space',\n  'widows',\n  'width',\n  'word-break',\n  'word-spacing',\n  'word-wrap',\n  'z-index'\n  // reverse makes sure longer attributes `font-weight` are matched fully\n  // instead of getting false positives on say `font`\n].reverse();\n\n/**\n * @param {string} value\n * @returns {RegExp}\n * */\n\n/**\n * @param {RegExp | string } re\n * @returns {string}\n */\nfunction source(re) {\n  if (!re) return null;\n  if (typeof re === \"string\") return re;\n\n  return re.source;\n}\n\n/**\n * @param {RegExp | string } re\n * @returns {string}\n */\nfunction lookahead(re) {\n  return concat('(?=', re, ')');\n}\n\n/**\n * @param {...(RegExp | string) } args\n * @returns {string}\n */\nfunction concat(...args) {\n  const joined = args.map((x) => source(x)).join(\"\");\n  return joined;\n}\n\n/*\nLanguage: CSS\nCategory: common, css\nWebsite: https://developer.mozilla.org/en-US/docs/Web/CSS\n*/\n\n/** @type LanguageFn */\nfunction css(hljs) {\n  const modes = MODES(hljs);\n  const FUNCTION_DISPATCH = {\n    className: \"built_in\",\n    begin: /[\\w-]+(?=\\()/\n  };\n  const VENDOR_PREFIX = {\n    begin: /-(webkit|moz|ms|o)-(?=[a-z])/\n  };\n  const AT_MODIFIERS = \"and or not only\";\n  const AT_PROPERTY_RE = /@-?\\w[\\w]*(-\\w+)*/; // @-webkit-keyframes\n  const IDENT_RE = '[a-zA-Z-][a-zA-Z0-9_-]*';\n  const STRINGS = [\n    hljs.APOS_STRING_MODE,\n    hljs.QUOTE_STRING_MODE\n  ];\n\n  return {\n    name: 'CSS',\n    case_insensitive: true,\n    illegal: /[=|'\\$]/,\n    keywords: {\n      keyframePosition: \"from to\"\n    },\n    classNameAliases: {\n      // for visual continuity with `tag {}` and because we\n      // don't have a great class for this?\n      keyframePosition: \"selector-tag\"\n    },\n    contains: [\n      hljs.C_BLOCK_COMMENT_MODE,\n      VENDOR_PREFIX,\n      // to recognize keyframe 40% etc which are outside the scope of our\n      // attribute value mode\n      hljs.CSS_NUMBER_MODE,\n      {\n        className: 'selector-id',\n        begin: /#[A-Za-z0-9_-]+/,\n        relevance: 0\n      },\n      {\n        className: 'selector-class',\n        begin: '\\\\.' + IDENT_RE,\n        relevance: 0\n      },\n      modes.ATTRIBUTE_SELECTOR_MODE,\n      {\n        className: 'selector-pseudo',\n        variants: [\n          {\n            begin: ':(' + PSEUDO_CLASSES.join('|') + ')'\n          },\n          {\n            begin: '::(' + PSEUDO_ELEMENTS.join('|') + ')'\n          }\n        ]\n      },\n      // we may actually need this (12/2020)\n      // { // pseudo-selector params\n      //   begin: /\\(/,\n      //   end: /\\)/,\n      //   contains: [ hljs.CSS_NUMBER_MODE ]\n      // },\n      {\n        className: 'attribute',\n        begin: '\\\\b(' + ATTRIBUTES.join('|') + ')\\\\b'\n      },\n      // attribute values\n      {\n        begin: ':',\n        end: '[;}]',\n        contains: [\n          modes.HEXCOLOR,\n          modes.IMPORTANT,\n          hljs.CSS_NUMBER_MODE,\n          ...STRINGS,\n          // needed to highlight these as strings and to avoid issues with\n          // illegal characters that might be inside urls that would tigger the\n          // languages illegal stack\n          {\n            begin: /(url|data-uri)\\(/,\n            end: /\\)/,\n            relevance: 0, // from keywords\n            keywords: {\n              built_in: \"url data-uri\"\n            },\n            contains: [\n              {\n                className: \"string\",\n                // any character other than `)` as in `url()` will be the start\n                // of a string, which ends with `)` (from the parent mode)\n                begin: /[^)]/,\n                endsWithParent: true,\n                excludeEnd: true\n              }\n            ]\n          },\n          FUNCTION_DISPATCH\n        ]\n      },\n      {\n        begin: lookahead(/@/),\n        end: '[{;]',\n        relevance: 0,\n        illegal: /:/, // break on Less variables @var: ...\n        contains: [\n          {\n            className: 'keyword',\n            begin: AT_PROPERTY_RE\n          },\n          {\n            begin: /\\s/,\n            endsWithParent: true,\n            excludeEnd: true,\n            relevance: 0,\n            keywords: {\n              $pattern: /[a-z-]+/,\n              keyword: AT_MODIFIERS,\n              attribute: MEDIA_FEATURES.join(\" \")\n            },\n            contains: [\n              {\n                begin: /[a-z-]+(?=:)/,\n                className: \"attribute\"\n              },\n              ...STRINGS,\n              hljs.CSS_NUMBER_MODE\n            ]\n          }\n        ]\n      },\n      {\n        className: 'selector-tag',\n        begin: '\\\\b(' + TAGS.join('|') + ')\\\\b'\n      }\n    ]\n  };\n}\n\nmodule.exports = css;\n","/*\nLanguage: Diff\nDescription: Unified and context diff\nAuthor: Vasily Polovnyov \nWebsite: https://www.gnu.org/software/diffutils/\nCategory: common\n*/\n\n/** @type LanguageFn */\nfunction diff(hljs) {\n  return {\n    name: 'Diff',\n    aliases: ['patch'],\n    contains: [\n      {\n        className: 'meta',\n        relevance: 10,\n        variants: [\n          {\n            begin: /^@@ +-\\d+,\\d+ +\\+\\d+,\\d+ +@@/\n          },\n          {\n            begin: /^\\*\\*\\* +\\d+,\\d+ +\\*\\*\\*\\*$/\n          },\n          {\n            begin: /^--- +\\d+,\\d+ +----$/\n          }\n        ]\n      },\n      {\n        className: 'comment',\n        variants: [\n          {\n            begin: /Index: /,\n            end: /$/\n          },\n          {\n            begin: /^index/,\n            end: /$/\n          },\n          {\n            begin: /={3,}/,\n            end: /$/\n          },\n          {\n            begin: /^-{3}/,\n            end: /$/\n          },\n          {\n            begin: /^\\*{3} /,\n            end: /$/\n          },\n          {\n            begin: /^\\+{3}/,\n            end: /$/\n          },\n          {\n            begin: /^\\*{15}$/\n          },\n          {\n            begin: /^diff --git/,\n            end: /$/\n          }\n        ]\n      },\n      {\n        className: 'addition',\n        begin: /^\\+/,\n        end: /$/\n      },\n      {\n        className: 'deletion',\n        begin: /^-/,\n        end: /$/\n      },\n      {\n        className: 'addition',\n        begin: /^!/,\n        end: /$/\n      }\n    ]\n  };\n}\n\nmodule.exports = diff;\n","/*\nLanguage: Dockerfile\nRequires: bash.js\nAuthor: Alexis Hénaut \nDescription: language definition for Dockerfile files\nWebsite: https://docs.docker.com/engine/reference/builder/\nCategory: config\n*/\n\n/** @type LanguageFn */\nfunction dockerfile(hljs) {\n  return {\n    name: 'Dockerfile',\n    aliases: ['docker'],\n    case_insensitive: true,\n    keywords: 'from maintainer expose env arg user onbuild stopsignal',\n    contains: [\n      hljs.HASH_COMMENT_MODE,\n      hljs.APOS_STRING_MODE,\n      hljs.QUOTE_STRING_MODE,\n      hljs.NUMBER_MODE,\n      {\n        beginKeywords: 'run cmd entrypoint volume add copy workdir label healthcheck shell',\n        starts: {\n          end: /[^\\\\]$/,\n          subLanguage: 'bash'\n        }\n      }\n    ],\n    illegal: '\n*/\n\nfunction gradle(hljs) {\n  return {\n    name: 'Gradle',\n    case_insensitive: true,\n    keywords: {\n      keyword:\n        'task project allprojects subprojects artifacts buildscript configurations ' +\n        'dependencies repositories sourceSets description delete from into include ' +\n        'exclude source classpath destinationDir includes options sourceCompatibility ' +\n        'targetCompatibility group flatDir doLast doFirst flatten todir fromdir ant ' +\n        'def abstract break case catch continue default do else extends final finally ' +\n        'for if implements instanceof native new private protected public return static ' +\n        'switch synchronized throw throws transient try volatile while strictfp package ' +\n        'import false null super this true antlrtask checkstyle codenarc copy boolean ' +\n        'byte char class double float int interface long short void compile runTime ' +\n        'file fileTree abs any append asList asWritable call collect compareTo count ' +\n        'div dump each eachByte eachFile eachLine every find findAll flatten getAt ' +\n        'getErr getIn getOut getText grep immutable inject inspect intersect invokeMethods ' +\n        'isCase join leftShift minus multiply newInputStream newOutputStream newPrintWriter ' +\n        'newReader newWriter next plus pop power previous print println push putAt read ' +\n        'readBytes readLines reverse reverseEach round size sort splitEachLine step subMap ' +\n        'times toInteger toList tokenize upto waitForOrKill withPrintWriter withReader ' +\n        'withStream withWriter withWriterAppend write writeLine'\n    },\n    contains: [\n      hljs.C_LINE_COMMENT_MODE,\n      hljs.C_BLOCK_COMMENT_MODE,\n      hljs.APOS_STRING_MODE,\n      hljs.QUOTE_STRING_MODE,\n      hljs.NUMBER_MODE,\n      hljs.REGEXP_MODE\n\n    ]\n  };\n}\n\nmodule.exports = gradle;\n","/**\n * @param {string} value\n * @returns {RegExp}\n * */\n\n/**\n * @param {RegExp | string } re\n * @returns {string}\n */\nfunction source(re) {\n  if (!re) return null;\n  if (typeof re === \"string\") return re;\n\n  return re.source;\n}\n\n/**\n * @param {RegExp | string } re\n * @returns {string}\n */\nfunction lookahead(re) {\n  return concat('(?=', re, ')');\n}\n\n/**\n * @param {...(RegExp | string) } args\n * @returns {string}\n */\nfunction concat(...args) {\n  const joined = args.map((x) => source(x)).join(\"\");\n  return joined;\n}\n\n/*\n Language: Groovy\n Author: Guillaume Laforge \n Description: Groovy programming language implementation inspired from Vsevolod's Java mode\n Website: https://groovy-lang.org\n */\n\nfunction variants(variants, obj = {}) {\n  obj.variants = variants;\n  return obj;\n}\n\nfunction groovy(hljs) {\n  const IDENT_RE = '[A-Za-z0-9_$]+';\n  const COMMENT = variants([\n    hljs.C_LINE_COMMENT_MODE,\n    hljs.C_BLOCK_COMMENT_MODE,\n    hljs.COMMENT(\n      '/\\\\*\\\\*',\n      '\\\\*/',\n      {\n        relevance: 0,\n        contains: [\n          {\n            // eat up @'s in emails to prevent them to be recognized as doctags\n            begin: /\\w+@/,\n            relevance: 0\n          },\n          {\n            className: 'doctag',\n            begin: '@[A-Za-z]+'\n          }\n        ]\n      }\n    )\n  ]);\n  const REGEXP = {\n    className: 'regexp',\n    begin: /~?\\/[^\\/\\n]+\\//,\n    contains: [ hljs.BACKSLASH_ESCAPE ]\n  };\n  const NUMBER = variants([\n    hljs.BINARY_NUMBER_MODE,\n    hljs.C_NUMBER_MODE\n  ]);\n  const STRING = variants([\n    {\n      begin: /\"\"\"/,\n      end: /\"\"\"/\n    },\n    {\n      begin: /'''/,\n      end: /'''/\n    },\n    {\n      begin: \"\\\\$/\",\n      end: \"/\\\\$\",\n      relevance: 10\n    },\n    hljs.APOS_STRING_MODE,\n    hljs.QUOTE_STRING_MODE\n  ],\n  {\n    className: \"string\"\n  }\n  );\n\n  return {\n    name: 'Groovy',\n    keywords: {\n      built_in: 'this super',\n      literal: 'true false null',\n      keyword:\n            'byte short char int long boolean float double void ' +\n            // groovy specific keywords\n            'def as in assert trait ' +\n            // common keywords with Java\n            'abstract static volatile transient public private protected synchronized final ' +\n            'class interface enum if else for while switch case break default continue ' +\n            'throw throws try catch finally implements extends new import package return instanceof'\n    },\n    contains: [\n      hljs.SHEBANG({\n        binary: \"groovy\",\n        relevance: 10\n      }),\n      COMMENT,\n      STRING,\n      REGEXP,\n      NUMBER,\n      {\n        className: 'class',\n        beginKeywords: 'class interface trait enum',\n        end: /\\{/,\n        illegal: ':',\n        contains: [\n          {\n            beginKeywords: 'extends implements'\n          },\n          hljs.UNDERSCORE_TITLE_MODE\n        ]\n      },\n      {\n        className: 'meta',\n        begin: '@[A-Za-z]+',\n        relevance: 0\n      },\n      {\n        // highlight map keys and named parameters as attrs\n        className: 'attr',\n        begin: IDENT_RE + '[ \\t]*:',\n        relevance: 0\n      },\n      {\n        // catch middle element of the ternary operator\n        // to avoid highlight it as a label, named parameter, or map key\n        begin: /\\?/,\n        end: /:/,\n        relevance: 0,\n        contains: [\n          COMMENT,\n          STRING,\n          REGEXP,\n          NUMBER,\n          'self'\n        ]\n      },\n      {\n        // highlight labeled statements\n        className: 'symbol',\n        begin: '^[ \\t]*' + lookahead(IDENT_RE + ':'),\n        excludeBegin: true,\n        end: IDENT_RE + ':',\n        relevance: 0\n      }\n    ],\n    illegal: /#|<\\//\n  };\n}\n\nmodule.exports = groovy;\n","/**\n * @param {string} value\n * @returns {RegExp}\n * */\n\n/**\n * @param {RegExp | string } re\n * @returns {string}\n */\nfunction source(re) {\n  if (!re) return null;\n  if (typeof re === \"string\") return re;\n\n  return re.source;\n}\n\n/**\n * @param {...(RegExp | string) } args\n * @returns {string}\n */\nfunction concat(...args) {\n  const joined = args.map((x) => source(x)).join(\"\");\n  return joined;\n}\n\n/*\nLanguage: HTTP\nDescription: HTTP request and response headers with automatic body highlighting\nAuthor: Ivan Sagalaev \nCategory: common, protocols\nWebsite: https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview\n*/\n\nfunction http(hljs) {\n  const VERSION = 'HTTP/(2|1\\\\.[01])';\n  const HEADER_NAME = /[A-Za-z][A-Za-z0-9-]*/;\n  const HEADER = {\n    className: 'attribute',\n    begin: concat('^', HEADER_NAME, '(?=\\\\:\\\\s)'),\n    starts: {\n      contains: [\n        {\n          className: \"punctuation\",\n          begin: /: /,\n          relevance: 0,\n          starts: {\n            end: '$',\n            relevance: 0\n          }\n        }\n      ]\n    }\n  };\n  const HEADERS_AND_BODY = [\n    HEADER,\n    {\n      begin: '\\\\n\\\\n',\n      starts: { subLanguage: [], endsWithParent: true }\n    }\n  ];\n\n  return {\n    name: 'HTTP',\n    aliases: ['https'],\n    illegal: /\\S/,\n    contains: [\n      // response\n      {\n        begin: '^(?=' + VERSION + \" \\\\d{3})\",\n        end: /$/,\n        contains: [\n          {\n            className: \"meta\",\n            begin: VERSION\n          },\n          {\n            className: 'number', begin: '\\\\b\\\\d{3}\\\\b'\n          }\n        ],\n        starts: {\n          end: /\\b\\B/,\n          illegal: /\\S/,\n          contains: HEADERS_AND_BODY\n        }\n      },\n      // request\n      {\n        begin: '(?=^[A-Z]+ (.*?) ' + VERSION + '$)',\n        end: /$/,\n        contains: [\n          {\n            className: 'string',\n            begin: ' ',\n            end: ' ',\n            excludeBegin: true,\n            excludeEnd: true\n          },\n          {\n            className: \"meta\",\n            begin: VERSION\n          },\n          {\n            className: 'keyword',\n            begin: '[A-Z]+'\n          }\n        ],\n        starts: {\n          end: /\\b\\B/,\n          illegal: /\\S/,\n          contains: HEADERS_AND_BODY\n        }\n      },\n      // to allow headers to work even without a preamble\n      hljs.inherit(HEADER, {\n        relevance: 0\n      })\n    ]\n  };\n}\n\nmodule.exports = http;\n","// https://docs.oracle.com/javase/specs/jls/se15/html/jls-3.html#jls-3.10\nvar decimalDigits = '[0-9](_*[0-9])*';\nvar frac = `\\\\.(${decimalDigits})`;\nvar hexDigits = '[0-9a-fA-F](_*[0-9a-fA-F])*';\nvar NUMERIC = {\n  className: 'number',\n  variants: [\n    // DecimalFloatingPointLiteral\n    // including ExponentPart\n    { begin: `(\\\\b(${decimalDigits})((${frac})|\\\\.)?|(${frac}))` +\n      `[eE][+-]?(${decimalDigits})[fFdD]?\\\\b` },\n    // excluding ExponentPart\n    { begin: `\\\\b(${decimalDigits})((${frac})[fFdD]?\\\\b|\\\\.([fFdD]\\\\b)?)` },\n    { begin: `(${frac})[fFdD]?\\\\b` },\n    { begin: `\\\\b(${decimalDigits})[fFdD]\\\\b` },\n\n    // HexadecimalFloatingPointLiteral\n    { begin: `\\\\b0[xX]((${hexDigits})\\\\.?|(${hexDigits})?\\\\.(${hexDigits}))` +\n      `[pP][+-]?(${decimalDigits})[fFdD]?\\\\b` },\n\n    // DecimalIntegerLiteral\n    { begin: '\\\\b(0|[1-9](_*[0-9])*)[lL]?\\\\b' },\n\n    // HexIntegerLiteral\n    { begin: `\\\\b0[xX](${hexDigits})[lL]?\\\\b` },\n\n    // OctalIntegerLiteral\n    { begin: '\\\\b0(_*[0-7])*[lL]?\\\\b' },\n\n    // BinaryIntegerLiteral\n    { begin: '\\\\b0[bB][01](_*[01])*[lL]?\\\\b' },\n  ],\n  relevance: 0\n};\n\n/*\nLanguage: Java\nAuthor: Vsevolod Solovyov \nCategory: common, enterprise\nWebsite: https://www.java.com/\n*/\n\nfunction java(hljs) {\n  var JAVA_IDENT_RE = '[\\u00C0-\\u02B8a-zA-Z_$][\\u00C0-\\u02B8a-zA-Z_$0-9]*';\n  var GENERIC_IDENT_RE = JAVA_IDENT_RE + '(<' + JAVA_IDENT_RE + '(\\\\s*,\\\\s*' + JAVA_IDENT_RE + ')*>)?';\n  var KEYWORDS = 'false synchronized int abstract float private char boolean var static null if const ' +\n    'for true while long strictfp finally protected import native final void ' +\n    'enum else break transient catch instanceof byte super volatile case assert short ' +\n    'package default double public try this switch continue throws protected public private ' +\n    'module requires exports do';\n\n  var ANNOTATION = {\n    className: 'meta',\n    begin: '@' + JAVA_IDENT_RE,\n    contains: [\n      {\n        begin: /\\(/,\n        end: /\\)/,\n        contains: [\"self\"] // allow nested () inside our annotation\n      },\n    ]\n  };\n  const NUMBER = NUMERIC;\n\n  return {\n    name: 'Java',\n    aliases: ['jsp'],\n    keywords: KEYWORDS,\n    illegal: /<\\/|#/,\n    contains: [\n      hljs.COMMENT(\n        '/\\\\*\\\\*',\n        '\\\\*/',\n        {\n          relevance: 0,\n          contains: [\n            {\n              // eat up @'s in emails to prevent them to be recognized as doctags\n              begin: /\\w+@/, relevance: 0\n            },\n            {\n              className: 'doctag',\n              begin: '@[A-Za-z]+'\n            }\n          ]\n        }\n      ),\n      // relevance boost\n      {\n        begin: /import java\\.[a-z]+\\./,\n        keywords: \"import\",\n        relevance: 2\n      },\n      hljs.C_LINE_COMMENT_MODE,\n      hljs.C_BLOCK_COMMENT_MODE,\n      hljs.APOS_STRING_MODE,\n      hljs.QUOTE_STRING_MODE,\n      {\n        className: 'class',\n        beginKeywords: 'class interface enum', end: /[{;=]/, excludeEnd: true,\n        // TODO: can this be removed somehow?\n        // an extra boost because Java is more popular than other languages with\n        // this same syntax feature (this is just to preserve our tests passing\n        // for now)\n        relevance: 1,\n        keywords: 'class interface enum',\n        illegal: /[:\"\\[\\]]/,\n        contains: [\n          { beginKeywords: 'extends implements' },\n          hljs.UNDERSCORE_TITLE_MODE\n        ]\n      },\n      {\n        // Expression keywords prevent 'keyword Name(...)' from being\n        // recognized as a function definition\n        beginKeywords: 'new throw return else',\n        relevance: 0\n      },\n      {\n        className: 'class',\n        begin: 'record\\\\s+' + hljs.UNDERSCORE_IDENT_RE + '\\\\s*\\\\(',\n        returnBegin: true,\n        excludeEnd: true,\n        end: /[{;=]/,\n        keywords: KEYWORDS,\n        contains: [\n          { beginKeywords: \"record\" },\n          {\n            begin: hljs.UNDERSCORE_IDENT_RE + '\\\\s*\\\\(',\n            returnBegin: true,\n            relevance: 0,\n            contains: [hljs.UNDERSCORE_TITLE_MODE]\n          },\n          {\n            className: 'params',\n            begin: /\\(/, end: /\\)/,\n            keywords: KEYWORDS,\n            relevance: 0,\n            contains: [\n              hljs.C_BLOCK_COMMENT_MODE\n            ]\n          },\n          hljs.C_LINE_COMMENT_MODE,\n          hljs.C_BLOCK_COMMENT_MODE\n        ]\n      },\n      {\n        className: 'function',\n        begin: '(' + GENERIC_IDENT_RE + '\\\\s+)+' + hljs.UNDERSCORE_IDENT_RE + '\\\\s*\\\\(', returnBegin: true, end: /[{;=]/,\n        excludeEnd: true,\n        keywords: KEYWORDS,\n        contains: [\n          {\n            begin: hljs.UNDERSCORE_IDENT_RE + '\\\\s*\\\\(', returnBegin: true,\n            relevance: 0,\n            contains: [hljs.UNDERSCORE_TITLE_MODE]\n          },\n          {\n            className: 'params',\n            begin: /\\(/, end: /\\)/,\n            keywords: KEYWORDS,\n            relevance: 0,\n            contains: [\n              ANNOTATION,\n              hljs.APOS_STRING_MODE,\n              hljs.QUOTE_STRING_MODE,\n              NUMBER,\n              hljs.C_BLOCK_COMMENT_MODE\n            ]\n          },\n          hljs.C_LINE_COMMENT_MODE,\n          hljs.C_BLOCK_COMMENT_MODE\n        ]\n      },\n      NUMBER,\n      ANNOTATION\n    ]\n  };\n}\n\nmodule.exports = java;\n","const IDENT_RE = '[A-Za-z$_][0-9A-Za-z$_]*';\nconst KEYWORDS = [\n  \"as\", // for exports\n  \"in\",\n  \"of\",\n  \"if\",\n  \"for\",\n  \"while\",\n  \"finally\",\n  \"var\",\n  \"new\",\n  \"function\",\n  \"do\",\n  \"return\",\n  \"void\",\n  \"else\",\n  \"break\",\n  \"catch\",\n  \"instanceof\",\n  \"with\",\n  \"throw\",\n  \"case\",\n  \"default\",\n  \"try\",\n  \"switch\",\n  \"continue\",\n  \"typeof\",\n  \"delete\",\n  \"let\",\n  \"yield\",\n  \"const\",\n  \"class\",\n  // JS handles these with a special rule\n  // \"get\",\n  // \"set\",\n  \"debugger\",\n  \"async\",\n  \"await\",\n  \"static\",\n  \"import\",\n  \"from\",\n  \"export\",\n  \"extends\"\n];\nconst LITERALS = [\n  \"true\",\n  \"false\",\n  \"null\",\n  \"undefined\",\n  \"NaN\",\n  \"Infinity\"\n];\n\nconst TYPES = [\n  \"Intl\",\n  \"DataView\",\n  \"Number\",\n  \"Math\",\n  \"Date\",\n  \"String\",\n  \"RegExp\",\n  \"Object\",\n  \"Function\",\n  \"Boolean\",\n  \"Error\",\n  \"Symbol\",\n  \"Set\",\n  \"Map\",\n  \"WeakSet\",\n  \"WeakMap\",\n  \"Proxy\",\n  \"Reflect\",\n  \"JSON\",\n  \"Promise\",\n  \"Float64Array\",\n  \"Int16Array\",\n  \"Int32Array\",\n  \"Int8Array\",\n  \"Uint16Array\",\n  \"Uint32Array\",\n  \"Float32Array\",\n  \"Array\",\n  \"Uint8Array\",\n  \"Uint8ClampedArray\",\n  \"ArrayBuffer\",\n  \"BigInt64Array\",\n  \"BigUint64Array\",\n  \"BigInt\"\n];\n\nconst ERROR_TYPES = [\n  \"EvalError\",\n  \"InternalError\",\n  \"RangeError\",\n  \"ReferenceError\",\n  \"SyntaxError\",\n  \"TypeError\",\n  \"URIError\"\n];\n\nconst BUILT_IN_GLOBALS = [\n  \"setInterval\",\n  \"setTimeout\",\n  \"clearInterval\",\n  \"clearTimeout\",\n\n  \"require\",\n  \"exports\",\n\n  \"eval\",\n  \"isFinite\",\n  \"isNaN\",\n  \"parseFloat\",\n  \"parseInt\",\n  \"decodeURI\",\n  \"decodeURIComponent\",\n  \"encodeURI\",\n  \"encodeURIComponent\",\n  \"escape\",\n  \"unescape\"\n];\n\nconst BUILT_IN_VARIABLES = [\n  \"arguments\",\n  \"this\",\n  \"super\",\n  \"console\",\n  \"window\",\n  \"document\",\n  \"localStorage\",\n  \"module\",\n  \"global\" // Node.js\n];\n\nconst BUILT_INS = [].concat(\n  BUILT_IN_GLOBALS,\n  BUILT_IN_VARIABLES,\n  TYPES,\n  ERROR_TYPES\n);\n\n/**\n * @param {string} value\n * @returns {RegExp}\n * */\n\n/**\n * @param {RegExp | string } re\n * @returns {string}\n */\nfunction source(re) {\n  if (!re) return null;\n  if (typeof re === \"string\") return re;\n\n  return re.source;\n}\n\n/**\n * @param {RegExp | string } re\n * @returns {string}\n */\nfunction lookahead(re) {\n  return concat('(?=', re, ')');\n}\n\n/**\n * @param {...(RegExp | string) } args\n * @returns {string}\n */\nfunction concat(...args) {\n  const joined = args.map((x) => source(x)).join(\"\");\n  return joined;\n}\n\n/*\nLanguage: JavaScript\nDescription: JavaScript (JS) is a lightweight, interpreted, or just-in-time compiled programming language with first-class functions.\nCategory: common, scripting\nWebsite: https://developer.mozilla.org/en-US/docs/Web/JavaScript\n*/\n\n/** @type LanguageFn */\nfunction javascript(hljs) {\n  /**\n   * Takes a string like \" {\n    const tag = \"',\n    end: ''\n  };\n  const XML_TAG = {\n    begin: /<[A-Za-z0-9\\\\._:-]+/,\n    end: /\\/[A-Za-z0-9\\\\._:-]+>|\\/>/,\n    /**\n     * @param {RegExpMatchArray} match\n     * @param {CallbackResponse} response\n     */\n    isTrulyOpeningTag: (match, response) => {\n      const afterMatchIndex = match[0].length + match.index;\n      const nextChar = match.input[afterMatchIndex];\n      // nested type?\n      // HTML should not include another raw `<` inside a tag\n      // But a type might: `>`, etc.\n      if (nextChar === \"<\") {\n        response.ignoreMatch();\n        return;\n      }\n      // \n      // This is now either a tag or a type.\n      if (nextChar === \">\") {\n        // if we cannot find a matching closing tag, then we\n        // will ignore it\n        if (!hasClosingTag(match, { after: afterMatchIndex })) {\n          response.ignoreMatch();\n        }\n      }\n    }\n  };\n  const KEYWORDS$1 = {\n    $pattern: IDENT_RE,\n    keyword: KEYWORDS,\n    literal: LITERALS,\n    built_in: BUILT_INS\n  };\n\n  // https://tc39.es/ecma262/#sec-literals-numeric-literals\n  const decimalDigits = '[0-9](_?[0-9])*';\n  const frac = `\\\\.(${decimalDigits})`;\n  // DecimalIntegerLiteral, including Annex B NonOctalDecimalIntegerLiteral\n  // https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals\n  const decimalInteger = `0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*`;\n  const NUMBER = {\n    className: 'number',\n    variants: [\n      // DecimalLiteral\n      { begin: `(\\\\b(${decimalInteger})((${frac})|\\\\.)?|(${frac}))` +\n        `[eE][+-]?(${decimalDigits})\\\\b` },\n      { begin: `\\\\b(${decimalInteger})\\\\b((${frac})\\\\b|\\\\.)?|(${frac})\\\\b` },\n\n      // DecimalBigIntegerLiteral\n      { begin: `\\\\b(0|[1-9](_?[0-9])*)n\\\\b` },\n\n      // NonDecimalIntegerLiteral\n      { begin: \"\\\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\\\b\" },\n      { begin: \"\\\\b0[bB][0-1](_?[0-1])*n?\\\\b\" },\n      { begin: \"\\\\b0[oO][0-7](_?[0-7])*n?\\\\b\" },\n\n      // LegacyOctalIntegerLiteral (does not include underscore separators)\n      // https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals\n      { begin: \"\\\\b0[0-7]+n?\\\\b\" },\n    ],\n    relevance: 0\n  };\n\n  const SUBST = {\n    className: 'subst',\n    begin: '\\\\$\\\\{',\n    end: '\\\\}',\n    keywords: KEYWORDS$1,\n    contains: [] // defined later\n  };\n  const HTML_TEMPLATE = {\n    begin: 'html`',\n    end: '',\n    starts: {\n      end: '`',\n      returnEnd: false,\n      contains: [\n        hljs.BACKSLASH_ESCAPE,\n        SUBST\n      ],\n      subLanguage: 'xml'\n    }\n  };\n  const CSS_TEMPLATE = {\n    begin: 'css`',\n    end: '',\n    starts: {\n      end: '`',\n      returnEnd: false,\n      contains: [\n        hljs.BACKSLASH_ESCAPE,\n        SUBST\n      ],\n      subLanguage: 'css'\n    }\n  };\n  const TEMPLATE_STRING = {\n    className: 'string',\n    begin: '`',\n    end: '`',\n    contains: [\n      hljs.BACKSLASH_ESCAPE,\n      SUBST\n    ]\n  };\n  const JSDOC_COMMENT = hljs.COMMENT(\n    /\\/\\*\\*(?!\\/)/,\n    '\\\\*/',\n    {\n      relevance: 0,\n      contains: [\n        {\n          className: 'doctag',\n          begin: '@[A-Za-z]+',\n          contains: [\n            {\n              className: 'type',\n              begin: '\\\\{',\n              end: '\\\\}',\n              relevance: 0\n            },\n            {\n              className: 'variable',\n              begin: IDENT_RE$1 + '(?=\\\\s*(-)|$)',\n              endsParent: true,\n              relevance: 0\n            },\n            // eat spaces (not newlines) so we can find\n            // types or variables\n            {\n              begin: /(?=[^\\n])\\s/,\n              relevance: 0\n            }\n          ]\n        }\n      ]\n    }\n  );\n  const COMMENT = {\n    className: \"comment\",\n    variants: [\n      JSDOC_COMMENT,\n      hljs.C_BLOCK_COMMENT_MODE,\n      hljs.C_LINE_COMMENT_MODE\n    ]\n  };\n  const SUBST_INTERNALS = [\n    hljs.APOS_STRING_MODE,\n    hljs.QUOTE_STRING_MODE,\n    HTML_TEMPLATE,\n    CSS_TEMPLATE,\n    TEMPLATE_STRING,\n    NUMBER,\n    hljs.REGEXP_MODE\n  ];\n  SUBST.contains = SUBST_INTERNALS\n    .concat({\n      // we need to pair up {} inside our subst to prevent\n      // it from ending too early by matching another }\n      begin: /\\{/,\n      end: /\\}/,\n      keywords: KEYWORDS$1,\n      contains: [\n        \"self\"\n      ].concat(SUBST_INTERNALS)\n    });\n  const SUBST_AND_COMMENTS = [].concat(COMMENT, SUBST.contains);\n  const PARAMS_CONTAINS = SUBST_AND_COMMENTS.concat([\n    // eat recursive parens in sub expressions\n    {\n      begin: /\\(/,\n      end: /\\)/,\n      keywords: KEYWORDS$1,\n      contains: [\"self\"].concat(SUBST_AND_COMMENTS)\n    }\n  ]);\n  const PARAMS = {\n    className: 'params',\n    begin: /\\(/,\n    end: /\\)/,\n    excludeBegin: true,\n    excludeEnd: true,\n    keywords: KEYWORDS$1,\n    contains: PARAMS_CONTAINS\n  };\n\n  return {\n    name: 'Javascript',\n    aliases: ['js', 'jsx', 'mjs', 'cjs'],\n    keywords: KEYWORDS$1,\n    // this will be extended by TypeScript\n    exports: { PARAMS_CONTAINS },\n    illegal: /#(?![$_A-z])/,\n    contains: [\n      hljs.SHEBANG({\n        label: \"shebang\",\n        binary: \"node\",\n        relevance: 5\n      }),\n      {\n        label: \"use_strict\",\n        className: 'meta',\n        relevance: 10,\n        begin: /^\\s*['\"]use (strict|asm)['\"]/\n      },\n      hljs.APOS_STRING_MODE,\n      hljs.QUOTE_STRING_MODE,\n      HTML_TEMPLATE,\n      CSS_TEMPLATE,\n      TEMPLATE_STRING,\n      COMMENT,\n      NUMBER,\n      { // object attr container\n        begin: concat(/[{,\\n]\\s*/,\n          // we need to look ahead to make sure that we actually have an\n          // attribute coming up so we don't steal a comma from a potential\n          // \"value\" container\n          //\n          // NOTE: this might not work how you think.  We don't actually always\n          // enter this mode and stay.  Instead it might merely match `,\n          // ` and then immediately end after the , because it\n          // fails to find any actual attrs. But this still does the job because\n          // it prevents the value contain rule from grabbing this instead and\n          // prevening this rule from firing when we actually DO have keys.\n          lookahead(concat(\n            // we also need to allow for multiple possible comments inbetween\n            // the first key:value pairing\n            /(((\\/\\/.*$)|(\\/\\*(\\*[^/]|[^*])*\\*\\/))\\s*)*/,\n            IDENT_RE$1 + '\\\\s*:'))),\n        relevance: 0,\n        contains: [\n          {\n            className: 'attr',\n            begin: IDENT_RE$1 + lookahead('\\\\s*:'),\n            relevance: 0\n          }\n        ]\n      },\n      { // \"value\" container\n        begin: '(' + hljs.RE_STARTERS_RE + '|\\\\b(case|return|throw)\\\\b)\\\\s*',\n        keywords: 'return throw case',\n        contains: [\n          COMMENT,\n          hljs.REGEXP_MODE,\n          {\n            className: 'function',\n            // we have to count the parens to make sure we actually have the\n            // correct bounding ( ) before the =>.  There could be any number of\n            // sub-expressions inside also surrounded by parens.\n            begin: '(\\\\(' +\n            '[^()]*(\\\\(' +\n            '[^()]*(\\\\(' +\n            '[^()]*' +\n            '\\\\)[^()]*)*' +\n            '\\\\)[^()]*)*' +\n            '\\\\)|' + hljs.UNDERSCORE_IDENT_RE + ')\\\\s*=>',\n            returnBegin: true,\n            end: '\\\\s*=>',\n            contains: [\n              {\n                className: 'params',\n                variants: [\n                  {\n                    begin: hljs.UNDERSCORE_IDENT_RE,\n                    relevance: 0\n                  },\n                  {\n                    className: null,\n                    begin: /\\(\\s*\\)/,\n                    skip: true\n                  },\n                  {\n                    begin: /\\(/,\n                    end: /\\)/,\n                    excludeBegin: true,\n                    excludeEnd: true,\n                    keywords: KEYWORDS$1,\n                    contains: PARAMS_CONTAINS\n                  }\n                ]\n              }\n            ]\n          },\n          { // could be a comma delimited list of params to a function call\n            begin: /,/, relevance: 0\n          },\n          {\n            className: '',\n            begin: /\\s/,\n            end: /\\s*/,\n            skip: true\n          },\n          { // JSX\n            variants: [\n              { begin: FRAGMENT.begin, end: FRAGMENT.end },\n              {\n                begin: XML_TAG.begin,\n                // we carefully check the opening tag to see if it truly\n                // is a tag and not a false positive\n                'on:begin': XML_TAG.isTrulyOpeningTag,\n                end: XML_TAG.end\n              }\n            ],\n            subLanguage: 'xml',\n            contains: [\n              {\n                begin: XML_TAG.begin,\n                end: XML_TAG.end,\n                skip: true,\n                contains: ['self']\n              }\n            ]\n          }\n        ],\n        relevance: 0\n      },\n      {\n        className: 'function',\n        beginKeywords: 'function',\n        end: /[{;]/,\n        excludeEnd: true,\n        keywords: KEYWORDS$1,\n        contains: [\n          'self',\n          hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1 }),\n          PARAMS\n        ],\n        illegal: /%/\n      },\n      {\n        // prevent this from getting swallowed up by function\n        // since they appear \"function like\"\n        beginKeywords: \"while if switch catch for\"\n      },\n      {\n        className: 'function',\n        // we have to count the parens to make sure we actually have the correct\n        // bounding ( ).  There could be any number of sub-expressions inside\n        // also surrounded by parens.\n        begin: hljs.UNDERSCORE_IDENT_RE +\n          '\\\\(' + // first parens\n          '[^()]*(\\\\(' +\n            '[^()]*(\\\\(' +\n              '[^()]*' +\n            '\\\\)[^()]*)*' +\n          '\\\\)[^()]*)*' +\n          '\\\\)\\\\s*\\\\{', // end parens\n        returnBegin:true,\n        contains: [\n          PARAMS,\n          hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1 }),\n        ]\n      },\n      // hack: prevents detection of keywords in some circumstances\n      // .keyword()\n      // $keyword = x\n      {\n        variants: [\n          { begin: '\\\\.' + IDENT_RE$1 },\n          { begin: '\\\\$' + IDENT_RE$1 }\n        ],\n        relevance: 0\n      },\n      { // ES6 class\n        className: 'class',\n        beginKeywords: 'class',\n        end: /[{;=]/,\n        excludeEnd: true,\n        illegal: /[:\"[\\]]/,\n        contains: [\n          { beginKeywords: 'extends' },\n          hljs.UNDERSCORE_TITLE_MODE\n        ]\n      },\n      {\n        begin: /\\b(?=constructor)/,\n        end: /[{;]/,\n        excludeEnd: true,\n        contains: [\n          hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1 }),\n          'self',\n          PARAMS\n        ]\n      },\n      {\n        begin: '(get|set)\\\\s+(?=' + IDENT_RE$1 + '\\\\()',\n        end: /\\{/,\n        keywords: \"get set\",\n        contains: [\n          hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1 }),\n          { begin: /\\(\\)/ }, // eat to avoid empty params\n          PARAMS\n        ]\n      },\n      {\n        begin: /\\$[(.]/ // relevance booster for a pattern common to JS libs: `$(something)` and `$.something`\n      }\n    ]\n  };\n}\n\nmodule.exports = javascript;\n","/*\nLanguage: JSON\nDescription: JSON (JavaScript Object Notation) is a lightweight data-interchange format.\nAuthor: Ivan Sagalaev \nWebsite: http://www.json.org\nCategory: common, protocols\n*/\n\nfunction json(hljs) {\n  const LITERALS = {\n    literal: 'true false null'\n  };\n  const ALLOWED_COMMENTS = [\n    hljs.C_LINE_COMMENT_MODE,\n    hljs.C_BLOCK_COMMENT_MODE\n  ];\n  const TYPES = [\n    hljs.QUOTE_STRING_MODE,\n    hljs.C_NUMBER_MODE\n  ];\n  const VALUE_CONTAINER = {\n    end: ',',\n    endsWithParent: true,\n    excludeEnd: true,\n    contains: TYPES,\n    keywords: LITERALS\n  };\n  const OBJECT = {\n    begin: /\\{/,\n    end: /\\}/,\n    contains: [\n      {\n        className: 'attr',\n        begin: /\"/,\n        end: /\"/,\n        contains: [hljs.BACKSLASH_ESCAPE],\n        illegal: '\\\\n'\n      },\n      hljs.inherit(VALUE_CONTAINER, {\n        begin: /:/\n      })\n    ].concat(ALLOWED_COMMENTS),\n    illegal: '\\\\S'\n  };\n  const ARRAY = {\n    begin: '\\\\[',\n    end: '\\\\]',\n    contains: [hljs.inherit(VALUE_CONTAINER)], // inherit is a workaround for a bug that makes shared modes with endsWithParent compile only the ending of one of the parents\n    illegal: '\\\\S'\n  };\n  TYPES.push(OBJECT, ARRAY);\n  ALLOWED_COMMENTS.forEach(function(rule) {\n    TYPES.push(rule);\n  });\n  return {\n    name: 'JSON',\n    contains: TYPES,\n    keywords: LITERALS,\n    illegal: '\\\\S'\n  };\n}\n\nmodule.exports = json;\n","// https://docs.oracle.com/javase/specs/jls/se15/html/jls-3.html#jls-3.10\nvar decimalDigits = '[0-9](_*[0-9])*';\nvar frac = `\\\\.(${decimalDigits})`;\nvar hexDigits = '[0-9a-fA-F](_*[0-9a-fA-F])*';\nvar NUMERIC = {\n  className: 'number',\n  variants: [\n    // DecimalFloatingPointLiteral\n    // including ExponentPart\n    { begin: `(\\\\b(${decimalDigits})((${frac})|\\\\.)?|(${frac}))` +\n      `[eE][+-]?(${decimalDigits})[fFdD]?\\\\b` },\n    // excluding ExponentPart\n    { begin: `\\\\b(${decimalDigits})((${frac})[fFdD]?\\\\b|\\\\.([fFdD]\\\\b)?)` },\n    { begin: `(${frac})[fFdD]?\\\\b` },\n    { begin: `\\\\b(${decimalDigits})[fFdD]\\\\b` },\n\n    // HexadecimalFloatingPointLiteral\n    { begin: `\\\\b0[xX]((${hexDigits})\\\\.?|(${hexDigits})?\\\\.(${hexDigits}))` +\n      `[pP][+-]?(${decimalDigits})[fFdD]?\\\\b` },\n\n    // DecimalIntegerLiteral\n    { begin: '\\\\b(0|[1-9](_*[0-9])*)[lL]?\\\\b' },\n\n    // HexIntegerLiteral\n    { begin: `\\\\b0[xX](${hexDigits})[lL]?\\\\b` },\n\n    // OctalIntegerLiteral\n    { begin: '\\\\b0(_*[0-7])*[lL]?\\\\b' },\n\n    // BinaryIntegerLiteral\n    { begin: '\\\\b0[bB][01](_*[01])*[lL]?\\\\b' },\n  ],\n  relevance: 0\n};\n\n/*\n Language: Kotlin\n Description: Kotlin is an OSS statically typed programming language that targets the JVM, Android, JavaScript and Native.\n Author: Sergey Mashkov \n Website: https://kotlinlang.org\n Category: common\n */\n\nfunction kotlin(hljs) {\n  const KEYWORDS = {\n    keyword:\n      'abstract as val var vararg get set class object open private protected public noinline ' +\n      'crossinline dynamic final enum if else do while for when throw try catch finally ' +\n      'import package is in fun override companion reified inline lateinit init ' +\n      'interface annotation data sealed internal infix operator out by constructor super ' +\n      'tailrec where const inner suspend typealias external expect actual',\n    built_in:\n      'Byte Short Char Int Long Boolean Float Double Void Unit Nothing',\n    literal:\n      'true false null'\n  };\n  const KEYWORDS_WITH_LABEL = {\n    className: 'keyword',\n    begin: /\\b(break|continue|return|this)\\b/,\n    starts: {\n      contains: [\n        {\n          className: 'symbol',\n          begin: /@\\w+/\n        }\n      ]\n    }\n  };\n  const LABEL = {\n    className: 'symbol',\n    begin: hljs.UNDERSCORE_IDENT_RE + '@'\n  };\n\n  // for string templates\n  const SUBST = {\n    className: 'subst',\n    begin: /\\$\\{/,\n    end: /\\}/,\n    contains: [ hljs.C_NUMBER_MODE ]\n  };\n  const VARIABLE = {\n    className: 'variable',\n    begin: '\\\\$' + hljs.UNDERSCORE_IDENT_RE\n  };\n  const STRING = {\n    className: 'string',\n    variants: [\n      {\n        begin: '\"\"\"',\n        end: '\"\"\"(?=[^\"])',\n        contains: [\n          VARIABLE,\n          SUBST\n        ]\n      },\n      // Can't use built-in modes easily, as we want to use STRING in the meta\n      // context as 'meta-string' and there's no syntax to remove explicitly set\n      // classNames in built-in modes.\n      {\n        begin: '\\'',\n        end: '\\'',\n        illegal: /\\n/,\n        contains: [ hljs.BACKSLASH_ESCAPE ]\n      },\n      {\n        begin: '\"',\n        end: '\"',\n        illegal: /\\n/,\n        contains: [\n          hljs.BACKSLASH_ESCAPE,\n          VARIABLE,\n          SUBST\n        ]\n      }\n    ]\n  };\n  SUBST.contains.push(STRING);\n\n  const ANNOTATION_USE_SITE = {\n    className: 'meta',\n    begin: '@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\\\s*:(?:\\\\s*' + hljs.UNDERSCORE_IDENT_RE + ')?'\n  };\n  const ANNOTATION = {\n    className: 'meta',\n    begin: '@' + hljs.UNDERSCORE_IDENT_RE,\n    contains: [\n      {\n        begin: /\\(/,\n        end: /\\)/,\n        contains: [\n          hljs.inherit(STRING, {\n            className: 'meta-string'\n          })\n        ]\n      }\n    ]\n  };\n\n  // https://kotlinlang.org/docs/reference/whatsnew11.html#underscores-in-numeric-literals\n  // According to the doc above, the number mode of kotlin is the same as java 8,\n  // so the code below is copied from java.js\n  const KOTLIN_NUMBER_MODE = NUMERIC;\n  const KOTLIN_NESTED_COMMENT = hljs.COMMENT(\n    '/\\\\*', '\\\\*/',\n    {\n      contains: [ hljs.C_BLOCK_COMMENT_MODE ]\n    }\n  );\n  const KOTLIN_PAREN_TYPE = {\n    variants: [\n      {\n        className: 'type',\n        begin: hljs.UNDERSCORE_IDENT_RE\n      },\n      {\n        begin: /\\(/,\n        end: /\\)/,\n        contains: [] // defined later\n      }\n    ]\n  };\n  const KOTLIN_PAREN_TYPE2 = KOTLIN_PAREN_TYPE;\n  KOTLIN_PAREN_TYPE2.variants[1].contains = [ KOTLIN_PAREN_TYPE ];\n  KOTLIN_PAREN_TYPE.variants[1].contains = [ KOTLIN_PAREN_TYPE2 ];\n\n  return {\n    name: 'Kotlin',\n    aliases: [ 'kt', 'kts' ],\n    keywords: KEYWORDS,\n    contains: [\n      hljs.COMMENT(\n        '/\\\\*\\\\*',\n        '\\\\*/',\n        {\n          relevance: 0,\n          contains: [\n            {\n              className: 'doctag',\n              begin: '@[A-Za-z]+'\n            }\n          ]\n        }\n      ),\n      hljs.C_LINE_COMMENT_MODE,\n      KOTLIN_NESTED_COMMENT,\n      KEYWORDS_WITH_LABEL,\n      LABEL,\n      ANNOTATION_USE_SITE,\n      ANNOTATION,\n      {\n        className: 'function',\n        beginKeywords: 'fun',\n        end: '[(]|$',\n        returnBegin: true,\n        excludeEnd: true,\n        keywords: KEYWORDS,\n        relevance: 5,\n        contains: [\n          {\n            begin: hljs.UNDERSCORE_IDENT_RE + '\\\\s*\\\\(',\n            returnBegin: true,\n            relevance: 0,\n            contains: [ hljs.UNDERSCORE_TITLE_MODE ]\n          },\n          {\n            className: 'type',\n            begin: //,\n            keywords: 'reified',\n            relevance: 0\n          },\n          {\n            className: 'params',\n            begin: /\\(/,\n            end: /\\)/,\n            endsParent: true,\n            keywords: KEYWORDS,\n            relevance: 0,\n            contains: [\n              {\n                begin: /:/,\n                end: /[=,\\/]/,\n                endsWithParent: true,\n                contains: [\n                  KOTLIN_PAREN_TYPE,\n                  hljs.C_LINE_COMMENT_MODE,\n                  KOTLIN_NESTED_COMMENT\n                ],\n                relevance: 0\n              },\n              hljs.C_LINE_COMMENT_MODE,\n              KOTLIN_NESTED_COMMENT,\n              ANNOTATION_USE_SITE,\n              ANNOTATION,\n              STRING,\n              hljs.C_NUMBER_MODE\n            ]\n          },\n          KOTLIN_NESTED_COMMENT\n        ]\n      },\n      {\n        className: 'class',\n        beginKeywords: 'class interface trait', // remove 'trait' when removed from KEYWORDS\n        end: /[:\\{(]|$/,\n        excludeEnd: true,\n        illegal: 'extends implements',\n        contains: [\n          {\n            beginKeywords: 'public protected internal private constructor'\n          },\n          hljs.UNDERSCORE_TITLE_MODE,\n          {\n            className: 'type',\n            begin: //,\n            excludeBegin: true,\n            excludeEnd: true,\n            relevance: 0\n          },\n          {\n            className: 'type',\n            begin: /[,:]\\s*/,\n            end: /[<\\(,]|$/,\n            excludeBegin: true,\n            returnEnd: true\n          },\n          ANNOTATION_USE_SITE,\n          ANNOTATION\n        ]\n      },\n      STRING,\n      {\n        className: 'meta',\n        begin: \"^#!/usr/bin/env\",\n        end: '$',\n        illegal: '\\n'\n      },\n      KOTLIN_NUMBER_MODE\n    ]\n  };\n}\n\nmodule.exports = kotlin;\n","/**\n * @param {string} value\n * @returns {RegExp}\n * */\n\n/**\n * @param {RegExp | string } re\n * @returns {string}\n */\nfunction source(re) {\n  if (!re) return null;\n  if (typeof re === \"string\") return re;\n\n  return re.source;\n}\n\n/**\n * @param {...(RegExp | string) } args\n * @returns {string}\n */\nfunction concat(...args) {\n  const joined = args.map((x) => source(x)).join(\"\");\n  return joined;\n}\n\n/*\nLanguage: Markdown\nRequires: xml.js\nAuthor: John Crepezzi \nWebsite: https://daringfireball.net/projects/markdown/\nCategory: common, markup\n*/\n\nfunction markdown(hljs) {\n  const INLINE_HTML = {\n    begin: /<\\/?[A-Za-z_]/,\n    end: '>',\n    subLanguage: 'xml',\n    relevance: 0\n  };\n  const HORIZONTAL_RULE = {\n    begin: '^[-\\\\*]{3,}',\n    end: '$'\n  };\n  const CODE = {\n    className: 'code',\n    variants: [\n      // TODO: fix to allow these to work with sublanguage also\n      {\n        begin: '(`{3,})[^`](.|\\\\n)*?\\\\1`*[ ]*'\n      },\n      {\n        begin: '(~{3,})[^~](.|\\\\n)*?\\\\1~*[ ]*'\n      },\n      // needed to allow markdown as a sublanguage to work\n      {\n        begin: '```',\n        end: '```+[ ]*$'\n      },\n      {\n        begin: '~~~',\n        end: '~~~+[ ]*$'\n      },\n      {\n        begin: '`.+?`'\n      },\n      {\n        begin: '(?=^( {4}|\\\\t))',\n        // use contains to gobble up multiple lines to allow the block to be whatever size\n        // but only have a single open/close tag vs one per line\n        contains: [\n          {\n            begin: '^( {4}|\\\\t)',\n            end: '(\\\\n)$'\n          }\n        ],\n        relevance: 0\n      }\n    ]\n  };\n  const LIST = {\n    className: 'bullet',\n    begin: '^[ \\t]*([*+-]|(\\\\d+\\\\.))(?=\\\\s+)',\n    end: '\\\\s+',\n    excludeEnd: true\n  };\n  const LINK_REFERENCE = {\n    begin: /^\\[[^\\n]+\\]:/,\n    returnBegin: true,\n    contains: [\n      {\n        className: 'symbol',\n        begin: /\\[/,\n        end: /\\]/,\n        excludeBegin: true,\n        excludeEnd: true\n      },\n      {\n        className: 'link',\n        begin: /:\\s*/,\n        end: /$/,\n        excludeBegin: true\n      }\n    ]\n  };\n  const URL_SCHEME = /[A-Za-z][A-Za-z0-9+.-]*/;\n  const LINK = {\n    variants: [\n      // too much like nested array access in so many languages\n      // to have any real relevance\n      {\n        begin: /\\[.+?\\]\\[.*?\\]/,\n        relevance: 0\n      },\n      // popular internet URLs\n      {\n        begin: /\\[.+?\\]\\(((data|javascript|mailto):|(?:http|ftp)s?:\\/\\/).*?\\)/,\n        relevance: 2\n      },\n      {\n        begin: concat(/\\[.+?\\]\\(/, URL_SCHEME, /:\\/\\/.*?\\)/),\n        relevance: 2\n      },\n      // relative urls\n      {\n        begin: /\\[.+?\\]\\([./?&#].*?\\)/,\n        relevance: 1\n      },\n      // whatever else, lower relevance (might not be a link at all)\n      {\n        begin: /\\[.+?\\]\\(.*?\\)/,\n        relevance: 0\n      }\n    ],\n    returnBegin: true,\n    contains: [\n      {\n        className: 'string',\n        relevance: 0,\n        begin: '\\\\[',\n        end: '\\\\]',\n        excludeBegin: true,\n        returnEnd: true\n      },\n      {\n        className: 'link',\n        relevance: 0,\n        begin: '\\\\]\\\\(',\n        end: '\\\\)',\n        excludeBegin: true,\n        excludeEnd: true\n      },\n      {\n        className: 'symbol',\n        relevance: 0,\n        begin: '\\\\]\\\\[',\n        end: '\\\\]',\n        excludeBegin: true,\n        excludeEnd: true\n      }\n    ]\n  };\n  const BOLD = {\n    className: 'strong',\n    contains: [], // defined later\n    variants: [\n      {\n        begin: /_{2}/,\n        end: /_{2}/\n      },\n      {\n        begin: /\\*{2}/,\n        end: /\\*{2}/\n      }\n    ]\n  };\n  const ITALIC = {\n    className: 'emphasis',\n    contains: [], // defined later\n    variants: [\n      {\n        begin: /\\*(?!\\*)/,\n        end: /\\*/\n      },\n      {\n        begin: /_(?!_)/,\n        end: /_/,\n        relevance: 0\n      }\n    ]\n  };\n  BOLD.contains.push(ITALIC);\n  ITALIC.contains.push(BOLD);\n\n  let CONTAINABLE = [\n    INLINE_HTML,\n    LINK\n  ];\n\n  BOLD.contains = BOLD.contains.concat(CONTAINABLE);\n  ITALIC.contains = ITALIC.contains.concat(CONTAINABLE);\n\n  CONTAINABLE = CONTAINABLE.concat(BOLD, ITALIC);\n\n  const HEADER = {\n    className: 'section',\n    variants: [\n      {\n        begin: '^#{1,6}',\n        end: '$',\n        contains: CONTAINABLE\n      },\n      {\n        begin: '(?=^.+?\\\\n[=-]{2,}$)',\n        contains: [\n          {\n            begin: '^[=-]*$'\n          },\n          {\n            begin: '^',\n            end: \"\\\\n\",\n            contains: CONTAINABLE\n          }\n        ]\n      }\n    ]\n  };\n\n  const BLOCKQUOTE = {\n    className: 'quote',\n    begin: '^>\\\\s+',\n    contains: CONTAINABLE,\n    end: '$'\n  };\n\n  return {\n    name: 'Markdown',\n    aliases: [\n      'md',\n      'mkdown',\n      'mkd'\n    ],\n    contains: [\n      HEADER,\n      INLINE_HTML,\n      LIST,\n      BOLD,\n      ITALIC,\n      BLOCKQUOTE,\n      CODE,\n      HORIZONTAL_RULE,\n      LINK,\n      LINK_REFERENCE\n    ]\n  };\n}\n\nmodule.exports = markdown;\n","/*\nLanguage: Nix\nAuthor: Domen Kožar \nDescription: Nix functional language\nWebsite: http://nixos.org/nix\n*/\n\nfunction nix(hljs) {\n  const NIX_KEYWORDS = {\n    keyword:\n      'rec with let in inherit assert if else then',\n    literal:\n      'true false or and null',\n    built_in:\n      'import abort baseNameOf dirOf isNull builtins map removeAttrs throw ' +\n      'toString derivation'\n  };\n  const ANTIQUOTE = {\n    className: 'subst',\n    begin: /\\$\\{/,\n    end: /\\}/,\n    keywords: NIX_KEYWORDS\n  };\n  const ATTRS = {\n    begin: /[a-zA-Z0-9-_]+(\\s*=)/,\n    returnBegin: true,\n    relevance: 0,\n    contains: [\n      {\n        className: 'attr',\n        begin: /\\S+/\n      }\n    ]\n  };\n  const STRING = {\n    className: 'string',\n    contains: [ ANTIQUOTE ],\n    variants: [\n      {\n        begin: \"''\",\n        end: \"''\"\n      },\n      {\n        begin: '\"',\n        end: '\"'\n      }\n    ]\n  };\n  const EXPRESSIONS = [\n    hljs.NUMBER_MODE,\n    hljs.HASH_COMMENT_MODE,\n    hljs.C_BLOCK_COMMENT_MODE,\n    STRING,\n    ATTRS\n  ];\n  ANTIQUOTE.contains = EXPRESSIONS;\n  return {\n    name: 'Nix',\n    aliases: [ \"nixos\" ],\n    keywords: NIX_KEYWORDS,\n    contains: EXPRESSIONS\n  };\n}\n\nmodule.exports = nix;\n","/*\nLanguage: .properties\nContributors: Valentin Aitken , Egor Rogov \nWebsite: https://en.wikipedia.org/wiki/.properties\nCategory: common, config\n*/\n\nfunction properties(hljs) {\n\n  // whitespaces: space, tab, formfeed\n  var WS0 = '[ \\\\t\\\\f]*';\n  var WS1 = '[ \\\\t\\\\f]+';\n  // delimiter\n  var EQUAL_DELIM = WS0+'[:=]'+WS0;\n  var WS_DELIM = WS1;\n  var DELIM = '(' + EQUAL_DELIM + '|' + WS_DELIM + ')';\n  var KEY_ALPHANUM = '([^\\\\\\\\\\\\W:= \\\\t\\\\f\\\\n]|\\\\\\\\.)+';\n  var KEY_OTHER = '([^\\\\\\\\:= \\\\t\\\\f\\\\n]|\\\\\\\\.)+';\n\n  var DELIM_AND_VALUE = {\n          // skip DELIM\n          end: DELIM,\n          relevance: 0,\n          starts: {\n            // value: everything until end of line (again, taking into account backslashes)\n            className: 'string',\n            end: /$/,\n            relevance: 0,\n            contains: [\n              { begin: '\\\\\\\\\\\\\\\\'},\n              { begin: '\\\\\\\\\\\\n' }\n            ]\n          }\n        };\n\n  return {\n    name: '.properties',\n    case_insensitive: true,\n    illegal: /\\S/,\n    contains: [\n      hljs.COMMENT('^\\\\s*[!#]', '$'),\n      // key: everything until whitespace or = or : (taking into account backslashes)\n      // case of a \"normal\" key\n      {\n        returnBegin: true,\n        variants: [\n          { begin: KEY_ALPHANUM + EQUAL_DELIM, relevance: 1 },\n          { begin: KEY_ALPHANUM + WS_DELIM, relevance: 0 }\n        ],\n        contains: [\n          {\n            className: 'attr',\n            begin: KEY_ALPHANUM,\n            endsParent: true,\n            relevance: 0\n          }\n        ],\n        starts: DELIM_AND_VALUE\n      },\n      // case of key containing non-alphanumeric chars => relevance = 0\n      {\n        begin: KEY_OTHER + DELIM,\n        returnBegin: true,\n        relevance: 0,\n        contains: [\n          {\n            className: 'meta',\n            begin: KEY_OTHER,\n            endsParent: true,\n            relevance: 0\n          }\n        ],\n        starts: DELIM_AND_VALUE\n      },\n      // case of an empty key\n      {\n        className: 'attr',\n        relevance: 0,\n        begin: KEY_OTHER + WS0 + '$'\n      }\n    ]\n  };\n}\n\nmodule.exports = properties;\n","/**\n * @param {string} value\n * @returns {RegExp}\n * */\n\n/**\n * @param {RegExp | string } re\n * @returns {string}\n */\nfunction source(re) {\n  if (!re) return null;\n  if (typeof re === \"string\") return re;\n\n  return re.source;\n}\n\n/**\n * @param {RegExp | string } re\n * @returns {string}\n */\nfunction lookahead(re) {\n  return concat('(?=', re, ')');\n}\n\n/**\n * @param {...(RegExp | string) } args\n * @returns {string}\n */\nfunction concat(...args) {\n  const joined = args.map((x) => source(x)).join(\"\");\n  return joined;\n}\n\n/*\nLanguage: Ruby\nDescription: Ruby is a dynamic, open source programming language with a focus on simplicity and productivity.\nWebsite: https://www.ruby-lang.org/\nAuthor: Anton Kovalyov \nContributors: Peter Leonov , Vasily Polovnyov , Loren Segal , Pascal Hurni , Cedric Sohrauer \nCategory: common\n*/\n\nfunction ruby(hljs) {\n  const RUBY_METHOD_RE = '([a-zA-Z_]\\\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\\\*\\\\*|[-/+%^&*~`|]|\\\\[\\\\]=?)';\n  const RUBY_KEYWORDS = {\n    keyword:\n      'and then defined module in return redo if BEGIN retry end for self when ' +\n      'next until do begin unless END rescue else break undef not super class case ' +\n      'require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor ' +\n      '__FILE__',\n    built_in: 'proc lambda',\n    literal:\n      'true false nil'\n  };\n  const YARDOCTAG = {\n    className: 'doctag',\n    begin: '@[A-Za-z]+'\n  };\n  const IRB_OBJECT = {\n    begin: '#<',\n    end: '>'\n  };\n  const COMMENT_MODES = [\n    hljs.COMMENT(\n      '#',\n      '$',\n      {\n        contains: [ YARDOCTAG ]\n      }\n    ),\n    hljs.COMMENT(\n      '^=begin',\n      '^=end',\n      {\n        contains: [ YARDOCTAG ],\n        relevance: 10\n      }\n    ),\n    hljs.COMMENT('^__END__', '\\\\n$')\n  ];\n  const SUBST = {\n    className: 'subst',\n    begin: /#\\{/,\n    end: /\\}/,\n    keywords: RUBY_KEYWORDS\n  };\n  const STRING = {\n    className: 'string',\n    contains: [\n      hljs.BACKSLASH_ESCAPE,\n      SUBST\n    ],\n    variants: [\n      {\n        begin: /'/,\n        end: /'/\n      },\n      {\n        begin: /\"/,\n        end: /\"/\n      },\n      {\n        begin: /`/,\n        end: /`/\n      },\n      {\n        begin: /%[qQwWx]?\\(/,\n        end: /\\)/\n      },\n      {\n        begin: /%[qQwWx]?\\[/,\n        end: /\\]/\n      },\n      {\n        begin: /%[qQwWx]?\\{/,\n        end: /\\}/\n      },\n      {\n        begin: /%[qQwWx]?/\n      },\n      {\n        begin: /%[qQwWx]?\\//,\n        end: /\\//\n      },\n      {\n        begin: /%[qQwWx]?%/,\n        end: /%/\n      },\n      {\n        begin: /%[qQwWx]?-/,\n        end: /-/\n      },\n      {\n        begin: /%[qQwWx]?\\|/,\n        end: /\\|/\n      },\n      // in the following expressions, \\B in the beginning suppresses recognition of ?-sequences\n      // where ? is the last character of a preceding identifier, as in: `func?4`\n      {\n        begin: /\\B\\?(\\\\\\d{1,3})/\n      },\n      {\n        begin: /\\B\\?(\\\\x[A-Fa-f0-9]{1,2})/\n      },\n      {\n        begin: /\\B\\?(\\\\u\\{?[A-Fa-f0-9]{1,6}\\}?)/\n      },\n      {\n        begin: /\\B\\?(\\\\M-\\\\C-|\\\\M-\\\\c|\\\\c\\\\M-|\\\\M-|\\\\C-\\\\M-)[\\x20-\\x7e]/\n      },\n      {\n        begin: /\\B\\?\\\\(c|C-)[\\x20-\\x7e]/\n      },\n      {\n        begin: /\\B\\?\\\\?\\S/\n      },\n      { // heredocs\n        begin: /<<[-~]?'?(\\w+)\\n(?:[^\\n]*\\n)*?\\s*\\1\\b/,\n        returnBegin: true,\n        contains: [\n          {\n            begin: /<<[-~]?'?/\n          },\n          hljs.END_SAME_AS_BEGIN({\n            begin: /(\\w+)/,\n            end: /(\\w+)/,\n            contains: [\n              hljs.BACKSLASH_ESCAPE,\n              SUBST\n            ]\n          })\n        ]\n      }\n    ]\n  };\n\n  // Ruby syntax is underdocumented, but this grammar seems to be accurate\n  // as of version 2.7.2 (confirmed with (irb and `Ripper.sexp(...)`)\n  // https://docs.ruby-lang.org/en/2.7.0/doc/syntax/literals_rdoc.html#label-Numbers\n  const decimal = '[1-9](_?[0-9])*|0';\n  const digits = '[0-9](_?[0-9])*';\n  const NUMBER = {\n    className: 'number',\n    relevance: 0,\n    variants: [\n      // decimal integer/float, optionally exponential or rational, optionally imaginary\n      {\n        begin: `\\\\b(${decimal})(\\\\.(${digits}))?([eE][+-]?(${digits})|r)?i?\\\\b`\n      },\n\n      // explicit decimal/binary/octal/hexadecimal integer,\n      // optionally rational and/or imaginary\n      {\n        begin: \"\\\\b0[dD][0-9](_?[0-9])*r?i?\\\\b\"\n      },\n      {\n        begin: \"\\\\b0[bB][0-1](_?[0-1])*r?i?\\\\b\"\n      },\n      {\n        begin: \"\\\\b0[oO][0-7](_?[0-7])*r?i?\\\\b\"\n      },\n      {\n        begin: \"\\\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\\\b\"\n      },\n\n      // 0-prefixed implicit octal integer, optionally rational and/or imaginary\n      {\n        begin: \"\\\\b0(_?[0-7])+r?i?\\\\b\"\n      }\n    ]\n  };\n\n  const PARAMS = {\n    className: 'params',\n    begin: '\\\\(',\n    end: '\\\\)',\n    endsParent: true,\n    keywords: RUBY_KEYWORDS\n  };\n\n  const RUBY_DEFAULT_CONTAINS = [\n    STRING,\n    {\n      className: 'class',\n      beginKeywords: 'class module',\n      end: '$|;',\n      illegal: /=/,\n      contains: [\n        hljs.inherit(hljs.TITLE_MODE, {\n          begin: '[A-Za-z_]\\\\w*(::\\\\w+)*(\\\\?|!)?'\n        }),\n        {\n          begin: '<\\\\s*',\n          contains: [\n            {\n              begin: '(' + hljs.IDENT_RE + '::)?' + hljs.IDENT_RE,\n              // we already get points for <, we don't need poitns\n              // for the name also\n              relevance: 0\n            }\n          ]\n        }\n      ].concat(COMMENT_MODES)\n    },\n    {\n      className: 'function',\n      // def method_name(\n      // def method_name;\n      // def method_name (end of line)\n      begin: concat(/def\\s+/, lookahead(RUBY_METHOD_RE + \"\\\\s*(\\\\(|;|$)\")),\n      relevance: 0, // relevance comes from kewords\n      keywords: \"def\",\n      end: '$|;',\n      contains: [\n        hljs.inherit(hljs.TITLE_MODE, {\n          begin: RUBY_METHOD_RE\n        }),\n        PARAMS\n      ].concat(COMMENT_MODES)\n    },\n    {\n      // swallow namespace qualifiers before symbols\n      begin: hljs.IDENT_RE + '::'\n    },\n    {\n      className: 'symbol',\n      begin: hljs.UNDERSCORE_IDENT_RE + '(!|\\\\?)?:',\n      relevance: 0\n    },\n    {\n      className: 'symbol',\n      begin: ':(?!\\\\s)',\n      contains: [\n        STRING,\n        {\n          begin: RUBY_METHOD_RE\n        }\n      ],\n      relevance: 0\n    },\n    NUMBER,\n    {\n      // negative-look forward attemps to prevent false matches like:\n      // @ident@ or $ident$ that might indicate this is not ruby at all\n      className: \"variable\",\n      begin: '(\\\\$\\\\W)|((\\\\$|@@?)(\\\\w+))(?=[^@$?])' + `(?![A-Za-z])(?![@$?'])`\n    },\n    {\n      className: 'params',\n      begin: /\\|/,\n      end: /\\|/,\n      relevance: 0, // this could be a lot of things (in other languages) other than params\n      keywords: RUBY_KEYWORDS\n    },\n    { // regexp container\n      begin: '(' + hljs.RE_STARTERS_RE + '|unless)\\\\s*',\n      keywords: 'unless',\n      contains: [\n        {\n          className: 'regexp',\n          contains: [\n            hljs.BACKSLASH_ESCAPE,\n            SUBST\n          ],\n          illegal: /\\n/,\n          variants: [\n            {\n              begin: '/',\n              end: '/[a-z]*'\n            },\n            {\n              begin: /%r\\{/,\n              end: /\\}[a-z]*/\n            },\n            {\n              begin: '%r\\\\(',\n              end: '\\\\)[a-z]*'\n            },\n            {\n              begin: '%r!',\n              end: '![a-z]*'\n            },\n            {\n              begin: '%r\\\\[',\n              end: '\\\\][a-z]*'\n            }\n          ]\n        }\n      ].concat(IRB_OBJECT, COMMENT_MODES),\n      relevance: 0\n    }\n  ].concat(IRB_OBJECT, COMMENT_MODES);\n\n  SUBST.contains = RUBY_DEFAULT_CONTAINS;\n  PARAMS.contains = RUBY_DEFAULT_CONTAINS;\n\n  // >>\n  // ?>\n  const SIMPLE_PROMPT = \"[>?]>\";\n  // irb(main):001:0>\n  const DEFAULT_PROMPT = \"[\\\\w#]+\\\\(\\\\w+\\\\):\\\\d+:\\\\d+>\";\n  const RVM_PROMPT = \"(\\\\w+-)?\\\\d+\\\\.\\\\d+\\\\.\\\\d+(p\\\\d+)?[^\\\\d][^>]+>\";\n\n  const IRB_DEFAULT = [\n    {\n      begin: /^\\s*=>/,\n      starts: {\n        end: '$',\n        contains: RUBY_DEFAULT_CONTAINS\n      }\n    },\n    {\n      className: 'meta',\n      begin: '^(' + SIMPLE_PROMPT + \"|\" + DEFAULT_PROMPT + '|' + RVM_PROMPT + ')(?=[ ])',\n      starts: {\n        end: '$',\n        contains: RUBY_DEFAULT_CONTAINS\n      }\n    }\n  ];\n\n  COMMENT_MODES.unshift(IRB_OBJECT);\n\n  return {\n    name: 'Ruby',\n    aliases: [\n      'rb',\n      'gemspec',\n      'podspec',\n      'thor',\n      'irb'\n    ],\n    keywords: RUBY_KEYWORDS,\n    illegal: /\\/\\*/,\n    contains: [\n      hljs.SHEBANG({\n        binary: \"ruby\"\n      })\n    ]\n      .concat(IRB_DEFAULT)\n      .concat(COMMENT_MODES)\n      .concat(RUBY_DEFAULT_CONTAINS)\n  };\n}\n\nmodule.exports = ruby;\n","/*\nLanguage: Scala\nCategory: functional\nAuthor: Jan Berkel \nContributors: Erik Osheim \nWebsite: https://www.scala-lang.org\n*/\n\nfunction scala(hljs) {\n  const ANNOTATION = {\n    className: 'meta',\n    begin: '@[A-Za-z]+'\n  };\n\n  // used in strings for escaping/interpolation/substitution\n  const SUBST = {\n    className: 'subst',\n    variants: [\n      {\n        begin: '\\\\$[A-Za-z0-9_]+'\n      },\n      {\n        begin: /\\$\\{/,\n        end: /\\}/\n      }\n    ]\n  };\n\n  const STRING = {\n    className: 'string',\n    variants: [\n      {\n        begin: '\"\"\"',\n        end: '\"\"\"'\n      },\n      {\n        begin: '\"',\n        end: '\"',\n        illegal: '\\\\n',\n        contains: [ hljs.BACKSLASH_ESCAPE ]\n      },\n      {\n        begin: '[a-z]+\"',\n        end: '\"',\n        illegal: '\\\\n',\n        contains: [\n          hljs.BACKSLASH_ESCAPE,\n          SUBST\n        ]\n      },\n      {\n        className: 'string',\n        begin: '[a-z]+\"\"\"',\n        end: '\"\"\"',\n        contains: [ SUBST ],\n        relevance: 10\n      }\n    ]\n\n  };\n\n  const SYMBOL = {\n    className: 'symbol',\n    begin: '\\'\\\\w[\\\\w\\\\d_]*(?!\\')'\n  };\n\n  const TYPE = {\n    className: 'type',\n    begin: '\\\\b[A-Z][A-Za-z0-9_]*',\n    relevance: 0\n  };\n\n  const NAME = {\n    className: 'title',\n    begin: /[^0-9\\n\\t \"'(),.`{}\\[\\]:;][^\\n\\t \"'(),.`{}\\[\\]:;]+|[^0-9\\n\\t \"'(),.`{}\\[\\]:;=]/,\n    relevance: 0\n  };\n\n  const CLASS = {\n    className: 'class',\n    beginKeywords: 'class object trait type',\n    end: /[:={\\[\\n;]/,\n    excludeEnd: true,\n    contains: [\n      hljs.C_LINE_COMMENT_MODE,\n      hljs.C_BLOCK_COMMENT_MODE,\n      {\n        beginKeywords: 'extends with',\n        relevance: 10\n      },\n      {\n        begin: /\\[/,\n        end: /\\]/,\n        excludeBegin: true,\n        excludeEnd: true,\n        relevance: 0,\n        contains: [ TYPE ]\n      },\n      {\n        className: 'params',\n        begin: /\\(/,\n        end: /\\)/,\n        excludeBegin: true,\n        excludeEnd: true,\n        relevance: 0,\n        contains: [ TYPE ]\n      },\n      NAME\n    ]\n  };\n\n  const METHOD = {\n    className: 'function',\n    beginKeywords: 'def',\n    end: /[:={\\[(\\n;]/,\n    excludeEnd: true,\n    contains: [ NAME ]\n  };\n\n  return {\n    name: 'Scala',\n    keywords: {\n      literal: 'true false null',\n      keyword: 'type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit'\n    },\n    contains: [\n      hljs.C_LINE_COMMENT_MODE,\n      hljs.C_BLOCK_COMMENT_MODE,\n      STRING,\n      SYMBOL,\n      TYPE,\n      METHOD,\n      CLASS,\n      hljs.C_NUMBER_MODE,\n      ANNOTATION\n    ]\n  };\n}\n\nmodule.exports = scala;\n","/*\nLanguage: Shell Session\nRequires: bash.js\nAuthor: TSUYUSATO Kitsune \nCategory: common\nAudit: 2020\n*/\n\n/** @type LanguageFn */\nfunction shell(hljs) {\n  return {\n    name: 'Shell Session',\n    aliases: [ 'console' ],\n    contains: [\n      {\n        className: 'meta',\n        // We cannot add \\s (spaces) in the regular expression otherwise it will be too broad and produce unexpected result.\n        // For instance, in the following example, it would match \"echo /path/to/home >\" as a prompt:\n        // echo /path/to/home > t.exe\n        begin: /^\\s{0,3}[/~\\w\\d[\\]()@-]*[>%$#]/,\n        starts: {\n          end: /[^\\\\](?=\\s*$)/,\n          subLanguage: 'bash'\n        }\n      }\n    ]\n  };\n}\n\nmodule.exports = shell;\n","/**\n * @param {string} value\n * @returns {RegExp}\n * */\n\n/**\n * @param {RegExp | string } re\n * @returns {string}\n */\nfunction source(re) {\n  if (!re) return null;\n  if (typeof re === \"string\") return re;\n\n  return re.source;\n}\n\n/**\n * @param {...(RegExp | string) } args\n * @returns {string}\n */\nfunction concat(...args) {\n  const joined = args.map((x) => source(x)).join(\"\");\n  return joined;\n}\n\n/**\n * Any of the passed expresssions may match\n *\n * Creates a huge this | this | that | that match\n * @param {(RegExp | string)[] } args\n * @returns {string}\n */\nfunction either(...args) {\n  const joined = '(' + args.map((x) => source(x)).join(\"|\") + \")\";\n  return joined;\n}\n\n/*\n Language: SQL\n Website: https://en.wikipedia.org/wiki/SQL\n Category: common, database\n */\n\nfunction sql(hljs) {\n  const COMMENT_MODE = hljs.COMMENT('--', '$');\n  const STRING = {\n    className: 'string',\n    variants: [\n      {\n        begin: /'/,\n        end: /'/,\n        contains: [\n          {begin: /''/ }\n        ]\n      }\n    ]\n  };\n  const QUOTED_IDENTIFIER = {\n    begin: /\"/,\n    end: /\"/,\n    contains: [ { begin: /\"\"/ } ]\n  };\n\n  const LITERALS = [\n    \"true\",\n    \"false\",\n    // Not sure it's correct to call NULL literal, and clauses like IS [NOT] NULL look strange that way.\n    // \"null\",\n    \"unknown\"\n  ];\n\n  const MULTI_WORD_TYPES = [\n    \"double precision\",\n    \"large object\",\n    \"with timezone\",\n    \"without timezone\"\n  ];\n\n  const TYPES = [\n    'bigint',\n    'binary',\n    'blob',\n    'boolean',\n    'char',\n    'character',\n    'clob',\n    'date',\n    'dec',\n    'decfloat',\n    'decimal',\n    'float',\n    'int',\n    'integer',\n    'interval',\n    'nchar',\n    'nclob',\n    'national',\n    'numeric',\n    'real',\n    'row',\n    'smallint',\n    'time',\n    'timestamp',\n    'varchar',\n    'varying', // modifier (character varying)\n    'varbinary'\n  ];\n\n  const NON_RESERVED_WORDS = [\n    \"add\",\n    \"asc\",\n    \"collation\",\n    \"desc\",\n    \"final\",\n    \"first\",\n    \"last\",\n    \"view\"\n  ];\n\n  // https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#reserved-word\n  const RESERVED_WORDS = [\n    \"abs\",\n    \"acos\",\n    \"all\",\n    \"allocate\",\n    \"alter\",\n    \"and\",\n    \"any\",\n    \"are\",\n    \"array\",\n    \"array_agg\",\n    \"array_max_cardinality\",\n    \"as\",\n    \"asensitive\",\n    \"asin\",\n    \"asymmetric\",\n    \"at\",\n    \"atan\",\n    \"atomic\",\n    \"authorization\",\n    \"avg\",\n    \"begin\",\n    \"begin_frame\",\n    \"begin_partition\",\n    \"between\",\n    \"bigint\",\n    \"binary\",\n    \"blob\",\n    \"boolean\",\n    \"both\",\n    \"by\",\n    \"call\",\n    \"called\",\n    \"cardinality\",\n    \"cascaded\",\n    \"case\",\n    \"cast\",\n    \"ceil\",\n    \"ceiling\",\n    \"char\",\n    \"char_length\",\n    \"character\",\n    \"character_length\",\n    \"check\",\n    \"classifier\",\n    \"clob\",\n    \"close\",\n    \"coalesce\",\n    \"collate\",\n    \"collect\",\n    \"column\",\n    \"commit\",\n    \"condition\",\n    \"connect\",\n    \"constraint\",\n    \"contains\",\n    \"convert\",\n    \"copy\",\n    \"corr\",\n    \"corresponding\",\n    \"cos\",\n    \"cosh\",\n    \"count\",\n    \"covar_pop\",\n    \"covar_samp\",\n    \"create\",\n    \"cross\",\n    \"cube\",\n    \"cume_dist\",\n    \"current\",\n    \"current_catalog\",\n    \"current_date\",\n    \"current_default_transform_group\",\n    \"current_path\",\n    \"current_role\",\n    \"current_row\",\n    \"current_schema\",\n    \"current_time\",\n    \"current_timestamp\",\n    \"current_path\",\n    \"current_role\",\n    \"current_transform_group_for_type\",\n    \"current_user\",\n    \"cursor\",\n    \"cycle\",\n    \"date\",\n    \"day\",\n    \"deallocate\",\n    \"dec\",\n    \"decimal\",\n    \"decfloat\",\n    \"declare\",\n    \"default\",\n    \"define\",\n    \"delete\",\n    \"dense_rank\",\n    \"deref\",\n    \"describe\",\n    \"deterministic\",\n    \"disconnect\",\n    \"distinct\",\n    \"double\",\n    \"drop\",\n    \"dynamic\",\n    \"each\",\n    \"element\",\n    \"else\",\n    \"empty\",\n    \"end\",\n    \"end_frame\",\n    \"end_partition\",\n    \"end-exec\",\n    \"equals\",\n    \"escape\",\n    \"every\",\n    \"except\",\n    \"exec\",\n    \"execute\",\n    \"exists\",\n    \"exp\",\n    \"external\",\n    \"extract\",\n    \"false\",\n    \"fetch\",\n    \"filter\",\n    \"first_value\",\n    \"float\",\n    \"floor\",\n    \"for\",\n    \"foreign\",\n    \"frame_row\",\n    \"free\",\n    \"from\",\n    \"full\",\n    \"function\",\n    \"fusion\",\n    \"get\",\n    \"global\",\n    \"grant\",\n    \"group\",\n    \"grouping\",\n    \"groups\",\n    \"having\",\n    \"hold\",\n    \"hour\",\n    \"identity\",\n    \"in\",\n    \"indicator\",\n    \"initial\",\n    \"inner\",\n    \"inout\",\n    \"insensitive\",\n    \"insert\",\n    \"int\",\n    \"integer\",\n    \"intersect\",\n    \"intersection\",\n    \"interval\",\n    \"into\",\n    \"is\",\n    \"join\",\n    \"json_array\",\n    \"json_arrayagg\",\n    \"json_exists\",\n    \"json_object\",\n    \"json_objectagg\",\n    \"json_query\",\n    \"json_table\",\n    \"json_table_primitive\",\n    \"json_value\",\n    \"lag\",\n    \"language\",\n    \"large\",\n    \"last_value\",\n    \"lateral\",\n    \"lead\",\n    \"leading\",\n    \"left\",\n    \"like\",\n    \"like_regex\",\n    \"listagg\",\n    \"ln\",\n    \"local\",\n    \"localtime\",\n    \"localtimestamp\",\n    \"log\",\n    \"log10\",\n    \"lower\",\n    \"match\",\n    \"match_number\",\n    \"match_recognize\",\n    \"matches\",\n    \"max\",\n    \"member\",\n    \"merge\",\n    \"method\",\n    \"min\",\n    \"minute\",\n    \"mod\",\n    \"modifies\",\n    \"module\",\n    \"month\",\n    \"multiset\",\n    \"national\",\n    \"natural\",\n    \"nchar\",\n    \"nclob\",\n    \"new\",\n    \"no\",\n    \"none\",\n    \"normalize\",\n    \"not\",\n    \"nth_value\",\n    \"ntile\",\n    \"null\",\n    \"nullif\",\n    \"numeric\",\n    \"octet_length\",\n    \"occurrences_regex\",\n    \"of\",\n    \"offset\",\n    \"old\",\n    \"omit\",\n    \"on\",\n    \"one\",\n    \"only\",\n    \"open\",\n    \"or\",\n    \"order\",\n    \"out\",\n    \"outer\",\n    \"over\",\n    \"overlaps\",\n    \"overlay\",\n    \"parameter\",\n    \"partition\",\n    \"pattern\",\n    \"per\",\n    \"percent\",\n    \"percent_rank\",\n    \"percentile_cont\",\n    \"percentile_disc\",\n    \"period\",\n    \"portion\",\n    \"position\",\n    \"position_regex\",\n    \"power\",\n    \"precedes\",\n    \"precision\",\n    \"prepare\",\n    \"primary\",\n    \"procedure\",\n    \"ptf\",\n    \"range\",\n    \"rank\",\n    \"reads\",\n    \"real\",\n    \"recursive\",\n    \"ref\",\n    \"references\",\n    \"referencing\",\n    \"regr_avgx\",\n    \"regr_avgy\",\n    \"regr_count\",\n    \"regr_intercept\",\n    \"regr_r2\",\n    \"regr_slope\",\n    \"regr_sxx\",\n    \"regr_sxy\",\n    \"regr_syy\",\n    \"release\",\n    \"result\",\n    \"return\",\n    \"returns\",\n    \"revoke\",\n    \"right\",\n    \"rollback\",\n    \"rollup\",\n    \"row\",\n    \"row_number\",\n    \"rows\",\n    \"running\",\n    \"savepoint\",\n    \"scope\",\n    \"scroll\",\n    \"search\",\n    \"second\",\n    \"seek\",\n    \"select\",\n    \"sensitive\",\n    \"session_user\",\n    \"set\",\n    \"show\",\n    \"similar\",\n    \"sin\",\n    \"sinh\",\n    \"skip\",\n    \"smallint\",\n    \"some\",\n    \"specific\",\n    \"specifictype\",\n    \"sql\",\n    \"sqlexception\",\n    \"sqlstate\",\n    \"sqlwarning\",\n    \"sqrt\",\n    \"start\",\n    \"static\",\n    \"stddev_pop\",\n    \"stddev_samp\",\n    \"submultiset\",\n    \"subset\",\n    \"substring\",\n    \"substring_regex\",\n    \"succeeds\",\n    \"sum\",\n    \"symmetric\",\n    \"system\",\n    \"system_time\",\n    \"system_user\",\n    \"table\",\n    \"tablesample\",\n    \"tan\",\n    \"tanh\",\n    \"then\",\n    \"time\",\n    \"timestamp\",\n    \"timezone_hour\",\n    \"timezone_minute\",\n    \"to\",\n    \"trailing\",\n    \"translate\",\n    \"translate_regex\",\n    \"translation\",\n    \"treat\",\n    \"trigger\",\n    \"trim\",\n    \"trim_array\",\n    \"true\",\n    \"truncate\",\n    \"uescape\",\n    \"union\",\n    \"unique\",\n    \"unknown\",\n    \"unnest\",\n    \"update   \",\n    \"upper\",\n    \"user\",\n    \"using\",\n    \"value\",\n    \"values\",\n    \"value_of\",\n    \"var_pop\",\n    \"var_samp\",\n    \"varbinary\",\n    \"varchar\",\n    \"varying\",\n    \"versioning\",\n    \"when\",\n    \"whenever\",\n    \"where\",\n    \"width_bucket\",\n    \"window\",\n    \"with\",\n    \"within\",\n    \"without\",\n    \"year\",\n  ];\n\n  // these are reserved words we have identified to be functions\n  // and should only be highlighted in a dispatch-like context\n  // ie, array_agg(...), etc.\n  const RESERVED_FUNCTIONS = [\n    \"abs\",\n    \"acos\",\n    \"array_agg\",\n    \"asin\",\n    \"atan\",\n    \"avg\",\n    \"cast\",\n    \"ceil\",\n    \"ceiling\",\n    \"coalesce\",\n    \"corr\",\n    \"cos\",\n    \"cosh\",\n    \"count\",\n    \"covar_pop\",\n    \"covar_samp\",\n    \"cume_dist\",\n    \"dense_rank\",\n    \"deref\",\n    \"element\",\n    \"exp\",\n    \"extract\",\n    \"first_value\",\n    \"floor\",\n    \"json_array\",\n    \"json_arrayagg\",\n    \"json_exists\",\n    \"json_object\",\n    \"json_objectagg\",\n    \"json_query\",\n    \"json_table\",\n    \"json_table_primitive\",\n    \"json_value\",\n    \"lag\",\n    \"last_value\",\n    \"lead\",\n    \"listagg\",\n    \"ln\",\n    \"log\",\n    \"log10\",\n    \"lower\",\n    \"max\",\n    \"min\",\n    \"mod\",\n    \"nth_value\",\n    \"ntile\",\n    \"nullif\",\n    \"percent_rank\",\n    \"percentile_cont\",\n    \"percentile_disc\",\n    \"position\",\n    \"position_regex\",\n    \"power\",\n    \"rank\",\n    \"regr_avgx\",\n    \"regr_avgy\",\n    \"regr_count\",\n    \"regr_intercept\",\n    \"regr_r2\",\n    \"regr_slope\",\n    \"regr_sxx\",\n    \"regr_sxy\",\n    \"regr_syy\",\n    \"row_number\",\n    \"sin\",\n    \"sinh\",\n    \"sqrt\",\n    \"stddev_pop\",\n    \"stddev_samp\",\n    \"substring\",\n    \"substring_regex\",\n    \"sum\",\n    \"tan\",\n    \"tanh\",\n    \"translate\",\n    \"translate_regex\",\n    \"treat\",\n    \"trim\",\n    \"trim_array\",\n    \"unnest\",\n    \"upper\",\n    \"value_of\",\n    \"var_pop\",\n    \"var_samp\",\n    \"width_bucket\",\n  ];\n\n  // these functions can\n  const POSSIBLE_WITHOUT_PARENS = [\n    \"current_catalog\",\n    \"current_date\",\n    \"current_default_transform_group\",\n    \"current_path\",\n    \"current_role\",\n    \"current_schema\",\n    \"current_transform_group_for_type\",\n    \"current_user\",\n    \"session_user\",\n    \"system_time\",\n    \"system_user\",\n    \"current_time\",\n    \"localtime\",\n    \"current_timestamp\",\n    \"localtimestamp\"\n  ];\n\n  // those exist to boost relevance making these very\n  // \"SQL like\" keyword combos worth +1 extra relevance\n  const COMBOS = [\n    \"create table\",\n    \"insert into\",\n    \"primary key\",\n    \"foreign key\",\n    \"not null\",\n    \"alter table\",\n    \"add constraint\",\n    \"grouping sets\",\n    \"on overflow\",\n    \"character set\",\n    \"respect nulls\",\n    \"ignore nulls\",\n    \"nulls first\",\n    \"nulls last\",\n    \"depth first\",\n    \"breadth first\"\n  ];\n\n  const FUNCTIONS = RESERVED_FUNCTIONS;\n\n  const KEYWORDS = [...RESERVED_WORDS, ...NON_RESERVED_WORDS].filter((keyword) => {\n    return !RESERVED_FUNCTIONS.includes(keyword);\n  });\n\n  const VARIABLE = {\n    className: \"variable\",\n    begin: /@[a-z0-9]+/,\n  };\n\n  const OPERATOR = {\n    className: \"operator\",\n    begin: /[-+*/=%^~]|&&?|\\|\\|?|!=?|<(?:=>?|<|>)?|>[>=]?/,\n    relevance: 0,\n  };\n\n  const FUNCTION_CALL = {\n    begin: concat(/\\b/, either(...FUNCTIONS), /\\s*\\(/),\n    keywords: {\n      built_in: FUNCTIONS\n    }\n  };\n\n  // keywords with less than 3 letters are reduced in relevancy\n  function reduceRelevancy(list, {exceptions, when} = {}) {\n    const qualifyFn = when;\n    exceptions = exceptions || [];\n    return list.map((item) => {\n      if (item.match(/\\|\\d+$/) || exceptions.includes(item)) {\n        return item;\n      } else if (qualifyFn(item)) {\n        return `${item}|0`;\n      } else {\n        return item;\n      }\n    });\n  }\n\n  return {\n    name: 'SQL',\n    case_insensitive: true,\n    // does not include {} or HTML tags ` x.length < 3 }),\n      literal: LITERALS,\n      type: TYPES,\n      built_in: POSSIBLE_WITHOUT_PARENS\n    },\n    contains: [\n      {\n        begin: either(...COMBOS),\n        keywords: {\n          $pattern: /[\\w\\.]+/,\n          keyword: KEYWORDS.concat(COMBOS),\n          literal: LITERALS,\n          type: TYPES\n        },\n      },\n      {\n        className: \"type\",\n        begin: either(...MULTI_WORD_TYPES)\n      },\n      FUNCTION_CALL,\n      VARIABLE,\n      STRING,\n      QUOTED_IDENTIFIER,\n      hljs.C_NUMBER_MODE,\n      hljs.C_BLOCK_COMMENT_MODE,\n      COMMENT_MODE,\n      OPERATOR\n    ]\n  };\n}\n\nmodule.exports = sql;\n","/**\n * @param {string} value\n * @returns {RegExp}\n * */\n\n/**\n * @param {RegExp | string } re\n * @returns {string}\n */\nfunction source(re) {\n  if (!re) return null;\n  if (typeof re === \"string\") return re;\n\n  return re.source;\n}\n\n/**\n * @param {RegExp | string } re\n * @returns {string}\n */\nfunction lookahead(re) {\n  return concat('(?=', re, ')');\n}\n\n/**\n * @param {RegExp | string } re\n * @returns {string}\n */\nfunction optional(re) {\n  return concat('(', re, ')?');\n}\n\n/**\n * @param {...(RegExp | string) } args\n * @returns {string}\n */\nfunction concat(...args) {\n  const joined = args.map((x) => source(x)).join(\"\");\n  return joined;\n}\n\n/**\n * Any of the passed expresssions may match\n *\n * Creates a huge this | this | that | that match\n * @param {(RegExp | string)[] } args\n * @returns {string}\n */\nfunction either(...args) {\n  const joined = '(' + args.map((x) => source(x)).join(\"|\") + \")\";\n  return joined;\n}\n\n/*\nLanguage: HTML, XML\nWebsite: https://www.w3.org/XML/\nCategory: common\nAudit: 2020\n*/\n\n/** @type LanguageFn */\nfunction xml(hljs) {\n  // Element names can contain letters, digits, hyphens, underscores, and periods\n  const TAG_NAME_RE = concat(/[A-Z_]/, optional(/[A-Z0-9_.-]*:/), /[A-Z0-9_.-]*/);\n  const XML_IDENT_RE = /[A-Za-z0-9._:-]+/;\n  const XML_ENTITIES = {\n    className: 'symbol',\n    begin: /&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/\n  };\n  const XML_META_KEYWORDS = {\n    begin: /\\s/,\n    contains: [\n      {\n        className: 'meta-keyword',\n        begin: /#?[a-z_][a-z1-9_-]+/,\n        illegal: /\\n/\n      }\n    ]\n  };\n  const XML_META_PAR_KEYWORDS = hljs.inherit(XML_META_KEYWORDS, {\n    begin: /\\(/,\n    end: /\\)/\n  });\n  const APOS_META_STRING_MODE = hljs.inherit(hljs.APOS_STRING_MODE, {\n    className: 'meta-string'\n  });\n  const QUOTE_META_STRING_MODE = hljs.inherit(hljs.QUOTE_STRING_MODE, {\n    className: 'meta-string'\n  });\n  const TAG_INTERNALS = {\n    endsWithParent: true,\n    illegal: /`]+/\n              }\n            ]\n          }\n        ]\n      }\n    ]\n  };\n  return {\n    name: 'HTML, XML',\n    aliases: [\n      'html',\n      'xhtml',\n      'rss',\n      'atom',\n      'xjb',\n      'xsd',\n      'xsl',\n      'plist',\n      'wsf',\n      'svg'\n    ],\n    case_insensitive: true,\n    contains: [\n      {\n        className: 'meta',\n        begin: //,\n        relevance: 10,\n        contains: [\n          XML_META_KEYWORDS,\n          QUOTE_META_STRING_MODE,\n          APOS_META_STRING_MODE,\n          XML_META_PAR_KEYWORDS,\n          {\n            begin: /\\[/,\n            end: /\\]/,\n            contains: [\n              {\n                className: 'meta',\n                begin: //,\n                contains: [\n                  XML_META_KEYWORDS,\n                  XML_META_PAR_KEYWORDS,\n                  QUOTE_META_STRING_MODE,\n                  APOS_META_STRING_MODE\n                ]\n              }\n            ]\n          }\n        ]\n      },\n      hljs.COMMENT(\n        //,\n        {\n          relevance: 10\n        }\n      ),\n      {\n        begin: //,\n        relevance: 10\n      },\n      XML_ENTITIES,\n      {\n        className: 'meta',\n        begin: /<\\?xml/,\n        end: /\\?>/,\n        relevance: 10\n      },\n      {\n        className: 'tag',\n        /*\n        The lookahead pattern (?=...) ensures that 'begin' only matches\n        ')/,\n        end: />/,\n        keywords: {\n          name: 'style'\n        },\n        contains: [ TAG_INTERNALS ],\n        starts: {\n          end: /<\\/style>/,\n          returnEnd: true,\n          subLanguage: [\n            'css',\n            'xml'\n          ]\n        }\n      },\n      {\n        className: 'tag',\n        // See the comment in the 
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +

    Google Cloud Key Management Service

    +
    +
    +

    The Google Cloud Key Management Service (KMS) allows you to create, import, and manage cryptographic keys and perform cryptographic operations in a single centralized cloud service.

    +
    +
    +

    Spring Framework on Google Cloud offers a utility template class KmsTemplate which allows you to conveniently encrypt and decrypt binary or text data.

    +
    +
    +

    Dependency Setup

    +
    +

    To begin using this library, add the spring-cloud-gcp-starter-kms artifact to your project.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +  <groupId>com.google.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-starter-kms</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +  implementation("com.google.cloud:spring-cloud-gcp-starter-kms")
    +}
    +
    +
    +
    +
    +

    Configuration

    +
    +

    The following options may be configured with Spring Framework on Google Cloud KMS libraries.

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.kms.enabled

    Enables or disables Google Cloud KMS autoconfiguration

    No

    true

    spring.cloud.gcp.kms.project-id

    Google Cloud project ID of the project using Cloud KMS APIs, if different from the one in the Spring Framework on Google Cloud Core Module.

    No

    Project ID is typically inferred from gcloud configuration.

    spring.cloud.gcp.kms.credentials.location

    Credentials file location for authenticating with the Cloud KMS APIs, if different from the ones in the Spring Framework on Google Cloud Core Module

    No

    Inferred from Application Default Credentials, typically set by gcloud.

    +
    +
    +

    Basic Usage

    +
    +

    Once you have added the spring-cloud-gcp-starter-kms to your project, the autoconfiguration class com.google.cloud.spring.autoconfigure.kms.GcpKmsAutoConfiguration will be activated for your project.

    +
    +
    +

    The com.google.cloud.spring.kms.KmsTemplate bean provided by the autoconfiguration is the entrypoint to using Spring Framework on Google Cloud support for Google KMS. This class allows you to specify a Cloud KMS key in your project via a URI string (format described below) and perform encryption/decryption with it.

    +
    +
    +

    The template class automatically validates the CRC32 checksums received responses from Cloud KMS APIs to verify correctness of the response.

    +
    +
    +

    Cloud KMS Key ID format

    +
    +

    Spring Framework on Google Cloud offers the following key syntax to specify Cloud KMS keys in your project:

    +
    +
    +
    +
     1. Shortest form - specify the key by key ring ID, and key ID.
    + The project is inferred from the spring.cloud.gcp.kms.project-id if set, otherwise
    + the default Google Cloud project (such as using application-default-credentials) is used.
    + The location is assumed to be `global`.
    +
    + {key-ring-id}/{key-id}
    +
    + 2. Short form - specify the key by location ID, key ring ID, and key ID.
    + The project is inferred from the spring.cloud.gcp.kms.project-id if set, otherwise
    + the default Google Cloud project (such as using application-default-credentials) is used.
    +
    + {location-id}/{key-ring-id}/{key-id}
    +
    + 3. Full form - specify project ID, location ID, key ring ID, and key ID
    +
    + {project-id}/{location-id}/{key-ring-id}/{key-id}
    +
    + 4. Long form - specify project ID, location ID, key ring ID, and key ID.
    + This format is equivalent to the fully-qualified resource name of a Cloud KMS key.
    +
    + projects/{project-id}/locations/{location-id}/keyRings/{key-ring-id}/cryptoKeys/{key-id}
    +
    +
    +
    +
    +
    +

    Sample

    +
    +

    A Cloud KMS Sample Application is provided which demonstrates basic encryption and decryption operations.

    +
    +
    +
    +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/kotlin.html b/3.8.13/reference/html/kotlin.html new file mode 100644 index 0000000000..b75aedba07 --- /dev/null +++ b/3.8.13/reference/html/kotlin.html @@ -0,0 +1,250 @@ + + + + + + + +Kotlin Support + + + + + + + + + +
    +
    +
    + +
    +
    +

    Kotlin Support

    +
    +
    +

    The latest version of the Spring Framework provides first-class support for Kotlin. +For Kotlin users of Spring, the Spring Framework on Google Cloud libraries work out-of-the-box and are fully interoperable with Kotlin applications.

    +
    +
    +

    For more information on building a Spring application in Kotlin, please consult the Spring Kotlin documentation.

    +
    +
    +

    Prerequisites

    +
    +

    Ensure that your Kotlin application is properly set up. +Based on your build system, you will need to include the correct Kotlin build plugin in your project:

    +
    + +
    +

    Depending on your application’s needs, you may need to augment your build configuration with compiler plugins:

    +
    +
    + +
    +
    +

    Once your Kotlin project is properly configured, the Spring Framework on Google Cloud libraries will work within your application without any additional setup.

    +
    +
    +
    +

    Sample

    +
    +

    A Kotlin sample application is provided to demonstrate a working Maven setup and various Spring Framework on Google Cloud integrations from within Kotlin.

    +
    +
    +
    +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/logging.html b/3.8.13/reference/html/logging.html new file mode 100644 index 0000000000..5409145caf --- /dev/null +++ b/3.8.13/reference/html/logging.html @@ -0,0 +1,568 @@ + + + + + + + +Cloud Logging + + + + + + + + + +
    +
    +
    + +
    +
    +

    Cloud Logging

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-logging</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-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 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. +This allows grouping of log messages by request, for example, in the Google Cloud Console Logs viewer.

    +
    +
    + + + + + +
    + + +Due to the way logging is set up, the Google Cloud project ID and credentials defined in application.properties are ignored. +Instead, you should set the GOOGLE_CLOUD_PROJECT and GOOGLE_APPLICATION_CREDENTIALS environment variables to the project ID and credentials private key location, respectively. +You can do this easily if you’re using the Google Cloud SDK, using the gcloud config set project [YOUR_PROJECT_ID] and gcloud auth application-default login commands, respectively. +
    +
    +
    +

    Web MVC Interceptor

    +
    +

    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.

    +
    +
    + + + + + +
    + + +If Spring Framework on Google Cloud Trace is enabled, the logging module disables itself and delegates log correlation to Spring Cloud Sleuth. +
    +
    +
    +

    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.

    +
    +
    +
    +

    Logback Support

    +
    +

    Currently, only Logback is supported and there are 2 possibilities to log to Cloud Logging via this library with Logback: via direct API calls and through JSON-formatted console logs.

    +
    +
    +

    Log via API

    +
    +

    A Cloud Logging appender is available using com/google/cloud/spring/logging/logback-appender.xml. +This appender builds a Cloud Logging log entry from a JUL or Logback log entry, adds a trace ID to it and sends it to Cloud Logging.

    +
    +
    +

    STACKDRIVER_LOG_NAME and STACKDRIVER_LOG_FLUSH_LEVEL environment variables can be used to customize the STACKDRIVER appender.

    +
    +
    +

    Your configuration may then look like this:

    +
    +
    +
    +
    <configuration>
    +  <include resource="com/google/cloud/spring/logging/logback-appender.xml" />
    +
    +  <root level="INFO">
    +    <appender-ref ref="STACKDRIVER" />
    +  </root>
    +</configuration>
    +
    +
    +
    +

    If you want to have more control over the log output, you can further configure the appender. +The following properties are available (see java-logging-logback project for the full list):

    +
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PropertyDefault ValueDescription

    log

    spring.log

    The Cloud Logging Log name. +This can also be set via the STACKDRIVER_LOG_NAME environmental variable.

    flushLevel

    WARN

    If a log entry with this level is encountered, trigger a flush of locally buffered log to Cloud Logging. +This can also be set via the STACKDRIVER_LOG_FLUSH_LEVEL environmental variable.

    enhancer

    Fully qualified class name for customizing a logging entry; must implement com.google.cloud.logging.LoggingEnhancer.

    loggingEventEnhancer

    Fully qualified class name for customizing a logging entry given an ILoggingEvent; must implement com.google.cloud.logging.logback.LoggingEventEnhancer.

    +
    +
    +

    Asynchronous Logging

    +
    +

    If you would like to send logs asynchronously to Cloud Logging, you can use the AsyncAppender.

    +
    +
    +

    Your configuration may then look like this:

    +
    +
    +
    +
    <configuration>
    +  <include resource="com/google/cloud/spring/logging/logback-appender.xml" />
    +
    +  <appender name="ASYNC_STACKDRIVER" class="ch.qos.logback.classic.AsyncAppender">
    +    <appender-ref ref="STACKDRIVER" />
    +  </appender>
    +
    +  <root level="INFO">
    +    <appender-ref ref="ASYNC_STACKDRIVER" />
    +  </root>
    +</configuration>
    +
    +
    +
    +
    +

    Log via Console

    +
    +

    For Logback, a com/google/cloud/spring/logging/logback-json-appender.xml file is made available for import to make it easier to configure the JSON Logback appender.

    +
    +
    +

    Your configuration may then look something like this:

    +
    +
    +
    +
    <configuration>
    +  <include resource="com/google/cloud/spring/logging/logback-json-appender.xml" />
    +
    +  <root level="INFO">
    +    <appender-ref ref="CONSOLE_JSON" />
    +  </root>
    +</configuration>
    +
    +
    +
    +

    If your application is running on Google Kubernetes Engine, Google Compute Engine or Google App Engine Flexible, your console logging is automatically saved to Google Cloud Logging. +Therefore, you can just include com/google/cloud/spring/logging/logback-json-appender.xml in your logging configuration, which logs JSON entries to the console. +The trace id will be set correctly.

    +
    +
    +

    If you want to have more control over the log output, you can further configure the appender. +The following properties are available:

    +
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PropertyDefault ValueDescription

    projectId

    +

    If not set, default value is determined in the following order:

    +
    +
    +
      +
    1. +

      SPRING_CLOUD_GCP_LOGGING_PROJECT_ID Environmental Variable.

      +
    2. +
    3. +

      Value of DefaultGcpProjectIdProvider.getProjectId()

      +
    4. +
    +
    +

    This is used to generate fully qualified Cloud Trace ID format: projects/[PROJECT-ID]/traces/[TRACE-ID].

    +
    +
    +

    This format is required to correlate trace between Cloud Trace and Cloud Logging.

    +
    +
    +

    If projectId is not set and cannot be determined, then it’ll log traceId without the fully qualified format.

    +

    traceIdMdcField

    traceId

    The MDC field name for retrieving a trace id

    spanIdMdcField

    spanId

    the MDC field name for retrieving a span id

    includeTraceId

    true

    Should the trace id be included

    includeSpanId

    true

    Should the span id be included

    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

    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. +This setting is only evaluated if includeFormattedMessage is true

    includeContextName

    true

    Should the logging context be included

    includeMessage

    false

    Should the log message with blank placeholders be included

    includeException

    false

    Should the stacktrace be included as a own field

    serviceContext

    none

    Define the Stackdriver service context data (service and version). This allows filtering of error reports for service and version in the Google Cloud Error Reporting View.

    customJson

    none

    Defines custom json data. Data will be added to the json output.

    loggingEventEnhancer

    none

    Name of a class implementing JsonLoggingEventEnhancer which modifies the JSON logging output. This tag is repeatable.

    +

    Examples are provided in the extensions package.

    +

    - Logstash Enhancer

    +
    +

    This is an example of such an Logback configuration:

    +
    +
    +
    +
    <configuration >
    +  <property name="projectId" value="${projectId:-${GOOGLE_CLOUD_PROJECT}}"/>
    +
    +  <appender name="CONSOLE_JSON" class="ch.qos.logback.core.ConsoleAppender">
    +    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
    +      <layout class="com.google.cloud.spring.logging.StackdriverJsonLayout">
    +        <projectId>${projectId}</projectId>
    +
    +        <!--<traceIdMdcField>traceId</traceIdMdcField>-->
    +        <!--<spanIdMdcField>spanId</spanIdMdcField>-->
    +        <!--<includeTraceId>true</includeTraceId>-->
    +        <!--<includeSpanId>true</includeSpanId>-->
    +        <!--<includeLevel>true</includeLevel>-->
    +        <!--<includeThreadName>true</includeThreadName>-->
    +        <!--<includeMDC>true</includeMDC>-->
    +        <!--<includeLoggerName>true</includeLoggerName>-->
    +        <!--<includeFormattedMessage>true</includeFormattedMessage>-->
    +        <!--<includeExceptionInMessage>true</includeExceptionInMessage>-->
    +        <!--<includeContextName>true</includeContextName>-->
    +        <!--<includeMessage>false</includeMessage>-->
    +        <!--<includeException>false</includeException>-->
    +        <!--<serviceContext>
    +              <service>service-name</service>
    +              <version>service-version</version>
    +            </serviceContext>-->
    +        <!--<customJson>{"custom-key": "custom-value"}</customJson>-->
    +        <!--<loggingEventEnhancer>your.package.YourLoggingEventEnhancer</loggingEventEnhancer> -->
    +      </layout>
    +    </encoder>
    +  </appender>
    +</configuration>
    +
    +
    +
    +
    +
    +

    Sample

    +
    +

    A Sample Spring Boot Application is provided to show how to use the Cloud logging starter.

    +
    +
    +
    +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/memorystore.html b/3.8.13/reference/html/memorystore.html new file mode 100644 index 0000000000..496be1e0de --- /dev/null +++ b/3.8.13/reference/html/memorystore.html @@ -0,0 +1,264 @@ + + + + + + + +Cloud Memorystore for Redis + + + + + + + + + +
    +
    +
    + +
    +
    +

    Cloud Memorystore for Redis

    +
    +
    +

    Spring Caching

    +
    +

    Cloud Memorystore for Redis provides a fully managed in-memory data store service. +Cloud Memorystore is compatible with the Redis protocol, allowing easy integration with Spring Caching.

    +
    +
    +

    All you have to do is create a Cloud Memorystore instance and use its IP address in application.properties file as spring.redis.host property value. +Everything else is exactly the same as setting up redis-backed Spring caching.

    +
    +
    + + + + + +
    + + +
    +

    Memorystore instances and your application instances have to be located in the same region.

    +
    +
    +
    +
    +

    In short, the following dependencies are needed:

    +
    +
    +
    +
    <dependency>
    +    <groupId>org.springframework.boot</groupId>
    +    <artifactId>spring-boot-starter-cache</artifactId>
    +</dependency>
    +<dependency>
    +    <groupId>org.springframework.boot</groupId>
    +    <artifactId>spring-boot-starter-data-redis</artifactId>
    +</dependency>
    +
    +
    +
    +

    For reactive applications, you can also use spring-boot-starter-data-redis-reactive instead.

    +
    +
    +

    And then you can use org.springframework.cache.annotation.Cacheable annotation for methods you’d like to be cached.

    +
    +
    +
    +
    @Cacheable("cache1")
    +public String hello(@PathVariable String name) {
    +    ....
    +}
    +
    +
    +
    +
    +

    If you are interested in a detailed how-to guide, please check Spring Boot Caching using Cloud Memorystore codelab.

    +
    +
    +

    Cloud Memorystore documentation can be found here.

    +
    +
    +
    +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/metrics.html b/3.8.13/reference/html/metrics.html new file mode 100644 index 0000000000..1fc9e5bbb1 --- /dev/null +++ b/3.8.13/reference/html/metrics.html @@ -0,0 +1,354 @@ + + + + + + + +Cloud Monitoring + + + + + + + + + +
    +
    +
    + +
    +
    +

    Cloud Monitoring

    +
    +
    +

    Google Cloud provides a service called Cloud Monitoring, and Micrometer can be used with it to easily instrument Spring Boot applications for observability.

    +
    +
    +

    Spring Boot already provides auto-configuration for Cloud Monitoring. +This module enables auto-detection of the project-id and credentials. +Also, it can be customized.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-metrics</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-metrics")
    +}
    +
    +
    +
    +

    You must enable Cloud Monitoring API from the Google Cloud Console in order to capture metrics. +Navigate to the Cloud Monitoring API for your project and make sure it’s enabled.

    +
    +
    +

    Spring Boot Starter for Cloud Monitoring uses Micrometer.

    +
    +
    +

    Configuration

    +
    +

    All configurations are optional:

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.metrics.enabled

    Auto-configure Micrometer to send metrics to Cloud Monitoring.

    No

    true

    spring.cloud.gcp.metrics.project-id

    Overrides the project ID from the Spring Framework on Google Cloud Module

    No

    spring.cloud.gcp.metrics.credentials.location

    Overrides the credentials location from the Spring Framework on Google Cloud Module

    No

    spring.cloud.gcp.metrics.credentials.encoded-key

    Overrides the credentials encoded key from the Spring Framework on Google Cloud Module

    No

    spring.cloud.gcp.metrics.credentials.scopes

    Overrides the credentials scopes from the Spring Framework on Google Cloud Module

    No

    +
    +

    You can use core Spring Boot Actuator properties to control reporting frequency, etc. +Read Spring Boot Actuator documentation for more information on Stackdriver Actuator configurations.

    +
    +
    +

    Metrics Disambiguation

    +
    +

    By default, spring-cloud-gcp-starter-metrics/the StackdriverMeterRegistry does not add any application/pod specific tags to the metrics, +thus google is unable to distinguish between multiple metric sources. +This could lead to the following warning inside your applications logs:

    +
    +
    +
    +
    2022-08-15 10:26:00.248  WARN 1 --- [trics-publisher] i.m.s.StackdriverMeterRegistry           : failed to send metrics to Stackdriver
    +
    +
    +
    +

    The actual cause message may vary:

    +
    +
    +
    +
    One or more TimeSeries could not be written:
    +One or more points were written more frequently than the maximum sampling period configured for the metric.: global{} timeSeries[4]: custom.googleapis.com/process/uptime{};
    +One or more points were written more frequently than the maximum sampling period configured for the metric.: global{} timeSeries[6]: custom.googleapis.com/system/load/average/1m{};
    +One or more points ...
    +
    +
    +
    +

    or even:

    +
    +
    +
    +
    Caused by: io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2Exception: Header size exceeded max allowed size (10240)
    +
    +
    +
    +

    (due to the error message being too long)

    +
    +
    +

    Google rejects metric updates for entries that it has received before (from another application) for the same interval. +To avoid these conflicts and in order to distinguish between applications/instances you should add a configuration similar to this:

    +
    +
    +
    +
    management:
    +  metrics:
    +    tags:
    +      app: my-foobar-service
    +      instance: ${random.uuid}
    +
    +
    +
    +

    Instead of the random uuid you could also use the pod id/the hostname or some other instance id. +Read more about custom tags here.

    +
    +
    +
    +
    +

    Sample

    +
    +

    A sample application is available.

    +
    +
    +
    +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/migration-guide-1.x.html b/3.8.13/reference/html/migration-guide-1.x.html new file mode 100644 index 0000000000..959436edff --- /dev/null +++ b/3.8.13/reference/html/migration-guide-1.x.html @@ -0,0 +1,336 @@ + + + + + + + +Migration Guide from Spring Framework on Google Cloud 1.x to 2.x + + + + + + + + + +
    +
    +
    + +
    +
    +

    Migration Guide from Spring Framework on Google Cloud 1.x to 2.x

    +
    +
    +

    Maven Group ID Change

    +
    +

    Spring Cloud unbundled Spring Framework on Google Cloud and other cloud providers from their release train. +To use the newly unbundled libraries, add the spring-cloud-gcp-dependencies bill of materials (BOM) and change the spring-cloud-gcp group IDs in your pom.xml files:

    +
    +
    +
    Before (pom.xml)
    +
    +
    <dependencyManagement>
    +  <dependencies>
    +    <dependency>
    +      <groupId>org.springframework.cloud</groupId>
    +      <artifactId>spring-cloud-dependencies</artifactId>
    +      <version>${spring-cloud.version}</version>
    +      <type>pom</type>
    +      <scope>import</scope>
    +    </dependency>
    +  </dependencies>
    +</dependencyManagement>
    +
    +<dependencies>
    +  <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter</artifactId>
    +  </dependency>
    +</dependencies>
    +
    +
    +
    +
    After (pom.xml)
    +
    +
    <dependencyManagement>
    +  <dependencies>
    +    <dependency>
    +      <groupId>com.google.cloud</groupId> (2)
    +      <artifactId>spring-cloud-gcp-dependencies</artifactId>
    +      <version>3.4.0</version>
    +      <type>pom</type>
    +      <scope>import</scope>
    +    </dependency>
    +  </dependencies>
    +</dependencyManagement>
    +
    +<dependencies>
    +  <dependency>
    +    <groupId>com.google.cloud</groupId> (3)
    +    <artifactId>spring-cloud-gcp-starter</artifactId>
    +  </dependency>
    +  ... (4)
    +</dependencies>
    +
    +
    +
    + + + + + + + + + + + + + + + + + +
    1Upgrade to Spring Cloud Ilford release
    2Explicitly add spring-cloud-gcp-dependencies to import the BOM
    3Change Group IDs to com.google.cloud
    4Add other starters as desired (i.e., spring-cloud-gcp-starter-pubsub or spring-cloud-gcp-starter-storage)
    +
    +
    +
    +

    Java Package Name Change

    +
    +

    All code in Spring Framework on Google Cloud has been moved from org.springframework.cloud.gcp over to the com.google.cloud.spring package.

    +
    +
    +
    +

    Deprecated Items Removed

    +
    +
    +
    Property spring.cloud.gcp.datastore.emulatorHost
    +
    +

    Use spring.cloud.gcp.datastore.host instead

    +
    +
    GcpPubSubHeaders.ACKNOWLEDGEMENT
    +
    +

    Use GcpPubSubHeaders.ORIGINAL_MESSAGE, which is of type BasicAcknowledgeablePubsubMessage

    +
    +
    SpannerQueryOptions.getQueryOptions()
    +
    +

    Use getOptions()

    +
    +
    PubSubTemplate.subscribe(String, MessageReceiver)
    +
    +

    Use subscribe(String, Consumer<BasicAcknowledgeablePubsubMessage>)

    +
    +
    SpannerReadOptions.getReadOptions()
    +
    +

    Use getOptions()

    +
    +
    Cloud Logging
    +
    +
    +
    +
    org.springframework.cloud.gcp.autoconfigure.logging package
    +
    +

    Use com.google.cloud.spring.logging from the spring-cloud-gcp-logging module.

    +
    +
    Cloud Logging Logback Appenders
    +
    +

    Replace org/springframework/cloud/gcp/autoconfigure/logging/logback[-json]-appender.xml with com/google/cloud/spring/logging/logback[-json]-appender.xml from the spring-cloud-gcp-logging module.

    +
    +
    logback-spring.xml
    +
    +
    <configuration>
    +  <include resource="com/google/cloud/spring/logging/logback-appender.xml" />
    +  ...
    +</configuration>
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/pubsub.html b/3.8.13/reference/html/pubsub.html new file mode 100644 index 0000000000..63b2705fe7 --- /dev/null +++ b/3.8.13/reference/html/pubsub.html @@ -0,0 +1,1506 @@ + + + + + + + +Cloud Pub/Sub + + + + + + + + + +
    +
    +
    + +
    +
    +

    Cloud Pub/Sub

    +
    +
    +

    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.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-pubsub</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-pubsub")
    +}
    +
    +
    +
    +

    This starter is also available from Spring Initializr through the GCP Messaging entry.

    +
    +
    +

    Configuration

    +
    +

    The Spring Boot starter for Google Cloud Pub/Sub provides the following configuration options.

    +
    +
    +

    Spring Framework on Google Cloud Pub/Sub API Configuration

    +
    +

    This section describes options for enabling the integration, specifying the Google Cloud project and credentials, and setting whether the APIs should connect to an emulator for local testing.

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.pubsub.enabled

    Enables or disables Pub/Sub auto-configuration

    No

    true

    spring.cloud.gcp.pubsub.project-id

    Google Cloud project ID where the Google Cloud Pub/Sub API is hosted, if different from the one in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.pubsub.credentials.location

    OAuth2 credentials for authenticating with the +Google Cloud Pub/Sub API, if different from the ones in the +Spring Framework on Google Cloud Core Module

    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 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 +Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.pubsub.credentials.scopes

    OAuth2 scope for Spring Framework on Google Cloud +Pub/Sub credentials

    No

    https://www.googleapis.com/auth/pubsub

    +
    +
    +

    Publisher/Subscriber Configuration

    +
    +

    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.

    +
    +
    + + + + + +
    + + +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 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. +However, if a per-subscription configuration is not set then the global or default configuration will be used. +
    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.pubsub.subscriber.parallel-pull-count

    The number of pull workers

    No

    1

    spring.cloud.gcp.pubsub.subscriber.max-ack-extension-period

    The maximum period a message ack deadline will be extended, in seconds

    No

    0

    spring.cloud.gcp.pubsub.subscriber.min-duration-per-ack-extension

    The lower bound for a single mod ack extension period, in seconds

    No

    0

    spring.cloud.gcp.pubsub.subscriber.max-duration-per-ack-extension

    The upper bound for a single mod ack extension period, in seconds

    No

    0

    spring.cloud.gcp.pubsub.subscriber.pull-endpoint

    The endpoint for pulling messages

    No

    pubsub.googleapis.com:443

    spring.cloud.gcp.pubsub.[subscriber,publisher].executor-threads

    Number of threads used by Subscriber instances created by SubscriberFactory

    No

    4

    spring.cloud.gcp.pubsub.[subscriber,publisher.batching].flow-control.max-outstanding-element-count

    Maximum number of outstanding elements to keep in memory before enforcing flow control.

    No

    unlimited

    spring.cloud.gcp.pubsub.[subscriber,publisher.batching].flow-control.max-outstanding-request-bytes

    Maximum number of outstanding bytes to keep in memory before enforcing flow control.

    No

    unlimited

    spring.cloud.gcp.pubsub.[subscriber,publisher.batching].flow-control.limit-exceeded-behavior

    The behavior when the specified limits are exceeded.

    No

    Block

    spring.cloud.gcp.pubsub.publisher.batching.element-count-threshold

    The element count threshold to use for batching.

    No

    1 (batching off)

    spring.cloud.gcp.pubsub.publisher.batching.request-byte-threshold

    The request byte threshold to use for batching.

    No

    1 byte (batching off)

    spring.cloud.gcp.pubsub.publisher.batching.delay-threshold-seconds

    The delay threshold to use for batching. +After this amount of time has elapsed (counting from the first element added), the elements will be wrapped up in a batch and sent.

    No

    1 ms (batching off)

    spring.cloud.gcp.pubsub.publisher.batching.enabled

    Enables batching.

    No

    false

    spring.cloud.gcp.pubsub.publisher.enable-message-ordering

    Enables message ordering.

    No

    false

    spring.cloud.gcp.pubsub.publisher.endpoint

    The publisher endpoint. +Example: "us-east1-pubsub.googleapis.com:443". +This is useful in conjunction with enabling message ordering because sending messages to the same region ensures they are received in order even when multiple publishers are used.

    No

    pubsub.googleapis.com:443

    +
    +
    Subscription-specific Configurations
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.pubsub.subscription.[subscription-name].fully-qualified-name

    The fully-qualified subscription name in the projects/[PROJECT]/subscriptions/[SUBSCRIPTION] format. When this property is present, the [subscription-name] key does not have to match any actual resources; it’s used only for logical grouping.

    No

    1

    spring.cloud.gcp.pubsub.subscription.[subscription-name].parallel-pull-count

    The number of pull workers.

    No

    1

    spring.cloud.gcp.pubsub.subscription.[subscription-name].max-ack-extension-period

    The maximum period a message ack deadline will be extended, in seconds.

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].min-duration-per-ack-extension

    The lower bound for a single mod ack extension period, in seconds

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].max-duration-per-ack-extension

    The upper bound for a single mod ack extension period, in seconds

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].pull-endpoint

    The endpoint for pulling messages.

    No

    pubsub.googleapis.com:443

    spring.cloud.gcp.pubsub.subscription.[subscription-name].executor-threads

    Number of threads used by Subscriber instances created by SubscriberFactory. Note that configuring per-subscription executor-threads will result in the creation of thread pools for both global/default and per-subscription configurations.

    No

    4

    spring.cloud.gcp.pubsub.subscription.[subscription-name].flow-control.max-outstanding-element-count

    Maximum number of outstanding elements to keep in memory before enforcing flow control.

    No

    unlimited

    spring.cloud.gcp.pubsub.subscription.[subscription-name].flow-control.max-outstanding-request-bytes

    Maximum number of outstanding bytes to keep in memory before enforcing flow control.

    No

    unlimited

    spring.cloud.gcp.pubsub.subscription.[subscription-name].flow-control.limit-exceeded-behavior

    The behavior when the specified limits are exceeded.

    No

    Block

    +
    + + + + + +
    + + +By default, subscription-specific threads are named after fully-qualified subscription name, ex: gcp-pubsub-subscriber-projects/project-id/subscriptions/subscription-name. +This can be customized, by registering a SelectiveSchedulerThreadNameProvider bean. +
    +
    +
    +
    +
    +

    GRPC Connection Settings

    +
    +

    The Pub/Sub API uses the GRPC protocol to send API requests to the Pub/Sub service. +This section describes configuration options for customizing the GRPC behavior.

    +
    +
    + + + + + +
    + + +The properties that refer to retry control the RPC retries for transient failures during the gRPC call to Cloud Pub/Sub server. +They do not control message redelivery; only message acknowledgement deadline can be used to extend or shorten the amount of time until Pub/Sub attempts redelivery. +
    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.pubsub.keepAliveIntervalMinutes

    Determines frequency of keepalive gRPC ping

    No

    5 minutes

    spring.cloud.gcp.pubsub.subscriber.retryableCodes

    RPC status codes that should be retried when pulling messages.

    No

    UNKNOWN,ABORTED,UNAVAILABLE

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.total-timeout-seconds

    TotalTimeout has ultimate control over how long the logic should keep trying the remote call until it gives up completely. +The higher the total timeout, the more retries can be attempted.

    No

    0

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.initial-retry-delay-second

    InitialRetryDelay controls the delay before the first retry. +Subsequent retries will use this value adjusted according to the RetryDelayMultiplier.

    No

    0

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.retry-delay-multiplier

    RetryDelayMultiplier controls the change in retry delay. +The retry delay of the previous call is multiplied by the RetryDelayMultiplier to calculate the retry delay for the next call.

    No

    1

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.max-retry-delay-seconds

    MaxRetryDelay puts a limit on the value of the retry delay, so that the RetryDelayMultiplier +can’t increase the retry delay higher than this amount.

    No

    0

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.max-attempts

    MaxAttempts defines the maximum number of attempts to perform. +If this value is greater than 0, and the number of attempts reaches this limit, the logic will give up retrying even if the total retry time is still lower than TotalTimeout.

    No

    0

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.jittered

    Jitter determines if the delay time should be randomized.

    No

    true

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.initial-rpc-timeout-seconds

    InitialRpcTimeout controls the timeout for the initial RPC. +Subsequent calls will use this value adjusted according to the RpcTimeoutMultiplier.

    No

    0

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.rpc-timeout-multiplier

    RpcTimeoutMultiplier controls the change in RPC timeout. +The timeout of the previous call is multiplied by the RpcTimeoutMultiplier to calculate the timeout for the next call.

    No

    1

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.max-rpc-timeout-seconds

    MaxRpcTimeout puts a limit on the value of the RPC timeout, so that the RpcTimeoutMultiplier +can’t increase the RPC timeout higher than this amount.

    No

    0

    +
    +

    Subscription-specific Configuration

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retryableCodes

    RPC status codes that should be retried when pulling messages.

    No

    UNKNOWN,ABORTED,UNAVAILABLE

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.total-timeout-seconds

    TotalTimeout has ultimate control over how long the logic should keep trying the remote call until it gives up completely. The higher the total timeout, the more retries can be attempted.

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.initial-retry-delay-second

    InitialRetryDelay controls the delay before the first retry. +Subsequent retries will use this value adjusted according to the RetryDelayMultiplier.

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.retry-delay-multiplier

    RetryDelayMultiplier controls the change in retry delay. +The retry delay of the previous call is multiplied by the RetryDelayMultiplier to calculate the retry delay for the next call.

    No

    1

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.max-retry-delay-seconds

    MaxRetryDelay puts a limit on the value of the retry delay, so that the RetryDelayMultiplier +can’t increase the retry delay higher than this amount.

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.max-attempts

    MaxAttempts defines the maximum number of attempts to perform. +If this value is greater than 0, and the number of attempts reaches this limit, the logic will give up retrying even if the total retry time is still lower than TotalTimeout.

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.jittered

    Jitter determines if the delay time should be randomized.

    No

    true

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.initial-rpc-timeout-seconds

    InitialRpcTimeout controls the timeout for the initial RPC. +Subsequent calls will use this value adjusted according to the RpcTimeoutMultiplier.

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.rpc-timeout-multiplier

    RpcTimeoutMultiplier controls the change in RPC timeout. +The timeout of the previous call is multiplied by the RpcTimeoutMultiplier to calculate the timeout for the next call.

    No

    1

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.max-rpc-timeout-seconds

    MaxRpcTimeout puts a limit on the value of the RPC timeout, so that the RpcTimeoutMultiplier +can’t increase the RPC timeout higher than this amount.

    No

    0

    +
    +
    +

    Programmatic Configuration

    +
    +

    To apply publishing customizations not covered by the properties above, you may provide custom beans of type PublisherCustomizer to post-process the Publisher.Builder object right before it is built into a Publisher. +The PublisherCustomizer beans may be annotated with Spring Framework’s @Order annotation to ensure they are applied in a particular sequence.

    +
    +
    +
    +
    +

    Spring Boot Actuator Support

    +
    +

    Cloud Pub/Sub Health Indicator

    +
    +

    If you are using Spring Boot Actuator, you can take advantage of the Cloud Pub/Sub health indicator called pubsub. +The health indicator will verify whether Cloud Pub/Sub is up and accessible by your application. +To enable it, all you need to do is add the Spring Boot Actuator to your project.

    +
    +
    +

    The pubsub 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).

    +
    +
    +
    +
    <dependency>
    +    <groupId>org.springframework.boot</groupId>
    +    <artifactId>spring-boot-starter-actuator</artifactId>
    +</dependency>
    +
    +
    +
    + + + + + +
    + + +If your application already has actuator and Cloud Pub/Sub starters, this health indicator is enabled by default. +To disable the Cloud Pub/Sub indicator, set management.health.pubsub.enabled to false. +
    +
    +
    +

    The health indicator validates the connection to Pub/Sub by pulling messages from a Pub/Sub subscription.

    +
    +
    +

    If no subscription has been specified via spring.cloud.gcp.pubsub.health.subscription, it will pull messages from a random subscription that is expected not to exist. +It will signal "up" if it is able to connect to Spring Framework on Google Cloud Pub/Sub APIs, i.e. the pull results in a response of NOT_FOUND or PERMISSION_DENIED.

    +
    +
    +

    If a custom subscription has been specified, this health indicator will signal "up" if messages are successfully pulled and (optionally) acknowledged, or when a successful pull is performed but no messages are returned from Pub/Sub. +Note that messages pulled from the subscription will not be acknowledged, unless you set the spring.cloud.gcp.pubsub.health.acknowledge-messages option to true. +So, take care not to configure a subscription that has a business impact, or instead leave the custom subscription out completely.

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    management.health.pubsub.enabled

    Whether to enable the Pub/Sub health indicator

    No

    true with Spring Boot Actuator, false otherwise

    spring.cloud.gcp.pubsub.health.subscription

    Subscription to health check against by pulling a message

    No

    Random non-existent

    spring.cloud.gcp.pubsub.health.timeout-millis

    Milliseconds to wait for response from Pub/Sub before timing out

    No

    2000

    spring.cloud.gcp.pubsub.health.acknowledge-messages

    Whether to acknowledge messages pulled from the optionally specified subscription

    No

    false

    +
    +
    +

    Cloud Pub/Sub Subscription Health Indicator

    +
    +

    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 Spring Boot Actuator to your project and the Google Cloud 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).

    +
    +
    +
    +
    <dependency>
    +    <groupId>org.springframework.boot</groupId>
    +    <artifactId>spring-boot-starter-actuator</artifactId>
    +</dependency>
    +<dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>google-cloud-monitoring</artifactId>
    +</dependency>
    +
    +
    +
    +

    The health indicator validates a subscriber’s health by checking the subscription’s message backlog and the last processed message. +A subscription’s backlog is retrieved using Google Cloud’s Monitoring Metrics. The metric used is the num_undelivered_messages for a subscription.

    +
    +
    +

    If a message has been recently processed in a reasonable time threshold, then the subscriber is healthy. +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.

    +
    +
    + + + + + +
    + + +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. +
    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    management.health.pubsub-subscriber.enabled

    Whether to enable the Pub/Sub Subscription health indicator

    No

    true with Spring Boot Actuator, false otherwise

    spring.cloud.gcp.pubsub.health.lagThreshold

    Threshold in seconds over message processing lag

    Yes

    Provided

    spring.cloud.gcp.pubsub.health.backlogThreshold

    The threshold number of messages for a subscription backlog

    Yes

    Provided

    spring.cloud.gcp.pubsub.health.lookUpInterval

    The optional interval in seconds for subscription backlog lookup

    No

    1

    spring.cloud.gcp.pubsub.health.executorThreads

    Number of threads used for Health Check Executors

    No

    4

    +
    +
    +
    +

    Pub/Sub Operations & Template

    +
    +

    PubSubOperations is an abstraction that allows Spring users to use Google Cloud Pub/Sub without depending on any Google Cloud Pub/Sub API semantics. +It provides the common set of operations needed to interact with Google Cloud Pub/Sub. +PubSubTemplate is the default implementation of PubSubOperations and it uses the Google Cloud Java Client for Pub/Sub to interact with Google Cloud Pub/Sub.

    +
    +
    +

    Publishing to a topic

    +
    +

    PubSubTemplate provides asynchronous methods to publish messages to a Google Cloud Pub/Sub topic. +The publish() method takes in a topic name to post the message to, a payload of a generic type and, optionally, a map with the message headers. +The topic name could either be a short topic name within the current project, or the fully-qualified name referring to a topic in a different project using the projects/[project_name]/topics/[topic_name] format.

    +
    +
    +

    Here is an example of how to publish a message to a Google Cloud Pub/Sub topic:

    +
    +
    +
    +
    Map<String, String> headers = Collections.singletonMap("key1", "val1");
    +pubSubTemplate.publish(topicName, "message", headers).get();
    +
    +
    +
    +
    +

    By default, the SimplePubSubMessageConverter is used to convert payloads of type byte[], ByteString, ByteBuffer, and String to Pub/Sub messages.

    +
    +
    +
    Ordering messages
    +
    +

    If you are relying on message converters and would like to provide an ordering key, use the GcpPubSubHeaders.ORDERING_KEY header. +You will also need to make sure to enable message ordering on the publisher via the spring.cloud.gcp.pubsub.publisher.enable-message-ordering property. +Additionally, if you are using multiple publishers, you will want to set the spring.cloud.gcp.pubsub.publisher.endpoint to a regional endpoint such as "us-east1-pubsub.googleapis.com:443" so that messages are sent to the same region and received in order.

    +
    +
    +
    +
    Map<String, String> headers =
    +    Collections.singletonMap(GcpPubSubHeaders.ORDERING_KEY, "key1");
    +pubSubTemplate.publish(topicName, "message1", headers).get();
    +pubSubTemplate.publish(topicName, "message2", headers).get();
    +
    +
    +
    +
    +
    +
    +

    Subscribing to a subscription

    +
    +

    Google Cloud Pub/Sub allows many subscriptions to be associated to the same topic. +PubSubTemplate allows you to listen to subscriptions via the subscribe() method. +When listening to a subscription, messages will be pulled from Google Cloud Pub/Sub asynchronously and passed to a user provided message handler. +The subscription name could either be a short subscription name within the current project, or the fully-qualified name referring to a subscription in a different project using the projects/[project_name]/subscriptions/[subscription_name] format.

    +
    +
    +
    Example
    +
    +

    Subscribe to a subscription with a message handler:

    +
    +
    +
    +
    Subscriber subscriber =
    +    pubSubTemplate.subscribe(
    +        subscriptionName,
    +        message -> {
    +          logger.info(
    +              "Message received from "
    +                  + subscriptionName
    +                  + " subscription: "
    +                  + message.getPubsubMessage().getData().toStringUtf8());
    +          message.ack();
    +        });
    +
    +
    +
    +
    +
    +
    Subscribe methods
    +
    +

    PubSubTemplate provides the following subscribe methods:

    +
    + ++++ + + + + + + + + + + +

    subscribe(String subscription, Consumer<BasicAcknowledgeablePubsubMessage> messageConsumer)

    asynchronously pulls messages and passes them to messageConsumer

    subscribeAndConvert(String subscription, + Consumer<ConvertedBasicAcknowledgeablePubsubMessage<T>> messageConsumer, + Class<T> payloadType)

    same as pull, but converts message payload to payloadType using the converter configured in the template

    +
    + + + + + +
    + + +
    +

    As of version 1.2, subscribing by itself is not enough to keep an application running. +For a command-line application, a way to keep the application running is to have a user thread(non-daemon thread) started up. A fake scheduled task creates a threadpool with non-daemon threads:

    +
    +
    +
    +
    @Scheduled (fixedRate = 1, timeUnit = TimeUnit.MINUTES)
    +public void fakeScheduledTask() {
    +    // do nothing
    +}
    +
    +
    +
    +
    +

    Another option is to pull in spring-boot-starter-web or spring-boot-starter-webflux as a dependency which will start an embedded servlet container or reactive server keeping the application running in the background

    +
    +
    +
    +
    +
    +
    +

    Pulling messages from a subscription

    +
    +

    Google Cloud Pub/Sub supports synchronous pulling of messages from a subscription. +This is different from subscribing to a subscription, in the sense that subscribing is an asynchronous task.

    +
    +
    +
    Example
    +
    +

    Pull up to 10 messages:

    +
    +
    +
    +
    int maxMessages = 10;
    +boolean returnImmediately = false;
    +List<AcknowledgeablePubsubMessage> messages =
    +    pubSubTemplate.pull(subscriptionName, maxMessages, returnImmediately);
    +
    +// acknowledge the messages
    +pubSubTemplate.ack(messages);
    +
    +messages.forEach(
    +    message ->
    +        logger.info(message.getPubsubMessage().getData().toStringUtf8()));
    +
    +
    +
    +
    +
    +
    Pull methods
    +
    +

    PubsubTemplate provides the following pull methods:

    +
    + ++++ + + + + + + + + + + + + + + + + + + +

    pull(String subscription, Integer maxMessages, + Boolean returnImmediately)

    Pulls a number of messages from a subscription, allowing for the retry settings to be configured. + Any messages received by pull() are not automatically acknowledged. See Acknowledging messages.

    +

    The maxMessages parameter is the maximum limit of how many messages to pull from a subscription in a single call; this value must be greater than 0. + You may omit this parameter by passing in null; this means there will be no limit on the number of messages pulled (maxMessages will be Integer.MAX_INTEGER).

    +

    If returnImmediately is true, the system will respond immediately even if it there are no messages available to return in the Pull response. Otherwise, the system may wait (for a bounded amount of time) until at least one message is available, rather than returning no messages.

    pullAndAck

    Works the same as the pull method and, additionally, acknowledges all received messages.

    pullNext

    Allows for a single message to be pulled and automatically acknowledged from a subscription.

    pullAndConvert

    Works the same as the pull method and, additionally, converts the Pub/Sub binary payload to an object of the desired type, using the converter configured in the template.

    +
    + + + + + +
    + + +We do not recommend setting returnImmediately to true, as it may result in delayed message delivery. +"Immediately" really means 1 second, and if Pub/Sub cannot retrieve any messages from the backend in that time, it will return 0 messages, despite having messages queue up on the topic. +Therefore, we recommend setting returnImmediately to false, or using subscribe methods from the previous section. +
    +
    +
    +
    +
    Acknowledging messages
    +
    +

    There are two ways to acknowledge messages.

    +
    +
    +
      +
    1. +

      To acknowledge multiple messages at once, you can use the PubSubTemplate.ack() method. +You can also use the PubSubTemplate.nack() for negatively acknowledging messages. +Using these methods for acknowledging messages in batches is more efficient than acknowledging messages individually, but they require the collection of messages to be from the same project.

      +
    2. +
    3. +

      To acknowledge messages individually you can use the ack() or nack() method on each of them (to acknowledge or negatively acknowledge, correspondingly).

      +
    4. +
    +
    +
    + + + + + +
    + + +All ack(), nack(), and modifyAckDeadline() methods on messages, as well as PubSubSubscriberTemplate, are implemented asynchronously, returning a ListenableFuture<Void> to enable asynchronous processing. +
    +
    +
    +
    +
    Dead Letter Topics
    +
    +

    Your application may occasionally receive a message it cannot process. +If you create your Subscription passing the Subscription.Builder argument, you can specify a DeadLetterPolicy that will forward all nack()-ed and non-ack()-ed messages after a configurable amount of redelivery attempts. +See here for more information.

    +
    +
    +
    +
    public Subscription newSubscription() {
    +	// Must use the fully-qualified topic name.
    +	String fullDeadLetterTopic = PubSubTopicUtils
    +						.toTopicName(DEAD_LETTER_TOPIC, gcpProjectIdProvider.getProjectId())
    +						.toString();
    +	return pubSubAdmin.createSubscription(Subscription.newBuilder()
    +			.setName(SUBSCRIPTION_NAME)
    +			.setTopic(TOPIC_NAME)
    +			.setDeadLetterPolicy(DeadLetterPolicy.newBuilder()
    +					.setDeadLetterTopic(fullDeadLetterTopic)
    +					.setMaxDeliveryAttempts(6)
    +					.build()));
    +}
    +
    +
    +
    +
    +

    Dead letter topics are no different than any other topic, though some 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.

    +
    +
    +
    +
    +

    JSON support

    +
    +

    For serialization and deserialization of POJOs using Jackson JSON, configure a PubSubMessageConverter bean, and the Spring Boot starter for Spring Framework on Google Cloud Pub/Sub will automatically wire it into the PubSubTemplate.

    +
    +
    +
    +
    // Note: The ObjectMapper is used to convert Java POJOs to and from JSON.
    +// You will have to configure your own instance if you are unable to depend
    +// on the ObjectMapper provided by Spring Boot starters.
    +@Bean
    +public PubSubMessageConverter pubSubMessageConverter() {
    +  return new JacksonPubSubMessageConverter(new ObjectMapper());
    +}
    +
    +
    +
    +
    + + + + + +
    + + +Alternatively, you can set it directly by calling the setMessageConverter() method on the PubSubTemplate. +Other implementations of the PubSubMessageConverter can also be configured in the same manner. +
    +
    +
    +

    Assuming you have the following class defined:

    +
    +
    +
    +
    static class TestUser {
    +
    +  String username;
    +
    +  String password;
    +
    +  public String getUsername() {
    +    return this.username;
    +  }
    +
    +  void setUsername(String username) {
    +    this.username = username;
    +  }
    +
    +  public String getPassword() {
    +    return this.password;
    +  }
    +
    +  void setPassword(String password) {
    +    this.password = password;
    +  }
    +}
    +
    +
    +
    +
    +

    You can serialize objects to JSON on publish automatically:

    +
    +
    +
    +
    TestUser user = new TestUser();
    +user.setUsername("John");
    +user.setPassword("password");
    +pubSubTemplate.publish(topicName, user);
    +
    +
    +
    +
    +

    And that’s how you convert messages to objects on pull:

    +
    +
    +
    +
    int maxMessages = 1;
    +boolean returnImmediately = false;
    +List<ConvertedAcknowledgeablePubsubMessage<TestUser>> messages =
    +    pubSubTemplate.pullAndConvert(
    +        subscriptionName, maxMessages, returnImmediately, TestUser.class);
    +
    +ConvertedAcknowledgeablePubsubMessage<TestUser> message = messages.get(0);
    +
    +// acknowledge the message
    +message.ack();
    +
    +TestUser receivedTestUser = message.getPayload();
    +
    +
    +
    +
    +

    Please refer to our Pub/Sub JSON Payload Sample App as a reference for using this functionality.

    +
    +
    +
    +
    +

    Reactive Stream Subscriber

    +
    +

    It is also possible to acquire a reactive stream backed by a subscription. +To do so, a Project Reactor dependency (io.projectreactor:reactor-core) must be added to the project. +The combination of the Pub/Sub starter and the Project Reactor dependencies will then make a PubSubReactiveFactory bean available, which can then be used to get a Publisher.

    +
    +
    +
    +
    @Autowired
    +PubSubReactiveFactory reactiveFactory;
    +
    +// ...
    +
    +Flux<AcknowledgeablePubsubMessage> flux
    +				= reactiveFactory.poll("exampleSubscription", 1000);
    +
    +
    +
    +
    +

    The Flux then represents an infinite stream of Spring Framework on Google Cloud Pub/Sub messages coming in through the specified subscription. +For unlimited demand, the Pub/Sub subscription will be polled regularly, at intervals determined by pollingPeriodMs parameter passed in when creating the Flux. +For bounded demand, the pollingPeriodMs parameter is unused. +Instead, as many messages as possible (up to the requested number) are delivered immediately, with the remaining messages delivered as they become available.

    +
    +
    +

    Any exceptions thrown by the underlying message retrieval logic will be passed as an error to the stream. +The error handling operators (Flux#retry(), Flux#onErrorResume() etc.) can be used to recover.

    +
    +
    +

    The full range of Project Reactor operations can be applied to the stream. +For example, if you only want to fetch 5 messages, you can use limitRequest operation to turn the infinite stream into a finite one:

    +
    +
    +
    +
    Flux<AcknowledgeablePubsubMessage> fiveMessageFlux = flux.limitRequest(5);
    +
    +
    +
    +
    +

    Messages flowing through the Flux should be manually acknowledged.

    +
    +
    +
    +
    flux.doOnNext(AcknowledgeablePubsubMessage::ack);
    +
    +
    +
    +
    +
    +

    Pub/Sub management

    +
    +

    PubSubAdmin is the abstraction provided by Spring Framework on Google Cloud to manage Google Cloud Pub/Sub resources. +It allows for the creation, deletion and listing of topics and subscriptions.

    +
    +
    + + + + + +
    + + +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)/<name> 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.

    +
    +
    +

    Creating a topic

    +
    +

    PubSubAdmin implements a method to create topics:

    +
    +
    +
    +
    public Topic createTopic(String topicName)
    +
    +
    +
    +
    +

    Here is an example of how to create a Google Cloud Pub/Sub topic:

    +
    +
    +
    +
    public void newTopic() {
    +    pubSubAdmin.createTopic("topicName");
    +}
    +
    +
    +
    +
    +
    +

    Deleting a topic

    +
    +

    PubSubAdmin implements a method to delete topics:

    +
    +
    +
    +
    public void deleteTopic(String topicName)
    +
    +
    +
    +
    +

    Here is an example of how to delete a Google Cloud Pub/Sub topic:

    +
    +
    +
    +
    public void deleteTopic() {
    +    pubSubAdmin.deleteTopic("topicName");
    +}
    +
    +
    +
    +
    +
    +

    Listing topics

    +
    +

    PubSubAdmin implements a method to list topics:

    +
    +
    +
    +
    public List<Topic> listTopics
    +
    +
    +
    +
    +

    Here is an example of how to list every Google Cloud Pub/Sub topic name in a project:

    +
    +
    +
    +
    List<String> topics =
    +    pubSubAdmin.listTopics().stream().map(Topic::getName).collect(Collectors.toList());
    +
    +
    +
    +
    +
    +

    Creating a subscription

    +
    +

    PubSubAdmin implements several methods to create subscriptions to existing topics:

    +
    +
    +
    +
    public Subscription createSubscription(String subscriptionName, String topicName)
    +
    +public Subscription createSubscription(String subscriptionName, String topicName, Integer ackDeadline)
    +
    +public Subscription createSubscription(String subscriptionName, String topicName, Integer ackDeadline, String pushEndpoint)
    +
    +public Subscription createSubscription(Subscriber.Builder builder)
    +
    +
    +
    +
    +

    The default value for ackDeadline is 10 seconds. +If pushEndpoint isn’t specified, the subscription uses message pulling, instead. +You can also pass a Subscription.Builder for full control over any options or features available in the client library.

    +
    +
    +

    Here is an example of how to create a Google Cloud Pub/Sub subscription:

    +
    +
    +
    +
    public Subscription newSubscription() {
    +    return pubSubAdmin.createSubscription("subscriptionName", "topicName", 15);
    +}
    +
    +
    +
    +
    +
    +

    Deleting a subscription

    +
    +

    PubSubAdmin implements a method to delete subscriptions:

    +
    +
    +
    +
    public void deleteSubscription(String subscriptionName)
    +
    +
    +
    +
    +

    Here is an example of how to delete a Google Cloud Pub/Sub subscription:

    +
    +
    +
    +
    public void deleteSubscription() {
    +    pubSubAdmin.deleteSubscription("subscriptionName");
    +}
    +
    +
    +
    +
    +
    +

    Listing subscriptions

    +
    +

    PubSubAdmin implements a method to list subscriptions:

    +
    +
    +
    +
    public List<Subscription> listSubscriptions()
    +
    +
    +
    +
    +

    Here is an example of how to list every subscription name in a project:

    +
    +
    +
    +
    List<String> subscriptions =
    +    pubSubAdmin.listSubscriptions().stream()
    +        .map(Subscription::getName)
    +        .collect(Collectors.toList());
    +
    +
    +
    +
    +
    +
    +

    Sample

    +
    +

    Sample applications for using the template and using a subscription-backed reactive stream are available.

    +
    +
    +
    +

    Test

    +
    +

    Testcontainers provides a gcloud module which offers PubSubEmulatorContainer. See more at the docs

    +
    +
    +
    +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/sagan-boot.html b/3.8.13/reference/html/sagan-boot.html new file mode 100644 index 0000000000..8eb7f835fb --- /dev/null +++ b/3.8.13/reference/html/sagan-boot.html @@ -0,0 +1,189 @@ + + + + + + + +Untitled + + + + + + + + + +
    +
    +
    + +
    + +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/sagan-index.html b/3.8.13/reference/html/sagan-index.html new file mode 100644 index 0000000000..0e80a55d64 --- /dev/null +++ b/3.8.13/reference/html/sagan-index.html @@ -0,0 +1,491 @@ + + + + + + + +Features + + + + + + + + + +
    +
    +
    + +
    +
    +
    +
    +

    The Spring Framework on Google Cloud project makes the Spring Framework a first-class citizen of Google Cloud .

    +
    +
    +
    +
    +

    Features

    +
    +
    +

    Spring Framework on Google Cloud offers a wide collection of libraries that make it easier to use Google Cloud from Spring Framework applications.

    +
    +
    +

    Project features include:

    +
    +
    +
      +
    • +

      Spring Framework on Google Cloud Pub/Sub Support (Spring Integration and Spring Cloud Stream Binder)

      +
    • +
    • +

      Spring Data Cloud Spanner

      +
    • +
    • +

      Spring Data Cloud Datastore

      +
    • +
    • +

      Spring Data Reactive Repositories for Cloud Firestore

      +
    • +
    • +

      Spring Data Cloud SQL

      +
    • +
    • +

      Google Cloud Logging & Tracing

      +
    • +
    • +

      Google Cloud Storage (Spring Resource and Spring Integration)

      +
    • +
    • +

      Google Cloud Vision API Template

      +
    • +
    • +

      Spring Security identity extraction from Google Cloud IAP headers.

      +
    • +
    • +

      Spring Security identity extraction from Firebase Authentication headers.

      +
    • +
    • +

      Google Cloud BigQuery with Spring Integration

      +
    • +
    +
    +
    +
    +
    +

    Getting Started

    +
    +
    +

    All Spring Framework on Google Cloud artifacts are made available through Maven Central.

    +
    +
    +

    Bill of Materials

    +
    +

    If you’re using Maven, you should first add the Spring Framework on Google Cloud Bill of Materials (BOM) to your pom.xml. +This will help you manage the version numbers of spring-cloud-gcp dependencies in your project.

    +
    +
    +
    +
    <dependencyManagement>
    +   <dependencies>
    +       <dependency>
    +           <groupId>com.google.cloud</groupId>
    +           <artifactId>spring-cloud-gcp-dependencies</artifactId>
    +           <version>3.8.13</version>
    +           <type>pom</type>
    +           <scope>import</scope>
    +       </dependency>
    +   </dependencies>
    +</dependencyManagement>
    +
    +
    +
    +
    +

    Starter Dependencies

    +
    +

    Spring Framework on Google Cloud offers starter dependencies through Maven to easily depend on different modules of the library. +Each starter contains all the dependencies and transitive dependencies needed to begin using their corresponding Spring Framework on Google Cloud module.

    +
    +
    +

    A sample of these artifacts are provided below.

    +
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Spring Framework on Google Cloud StarterDescriptionMaven Artifact Coordinates

    Cloud Spanner

    Provides integrations with Google Cloud Spanner

    com.google.cloud:spring-cloud-gcp-starter-data-spanner

    Cloud Datastore

    Provides integrations with Google Cloud Datastore

    com.google.cloud:spring-cloud-gcp-starter-data-datastore

    Cloud Firestore

    Provides Spring Data Reactive Repositories support for Cloud Firestore

    com.google.cloud:spring-cloud-gcp-starter-data-firestore

    Cloud Pub/Sub

    Provides integrations with Google Cloud Pub/Sub

    com.google.cloud:spring-cloud-gcp-starter-pubsub

    Logging

    Enables Cloud Logging

    com.google.cloud:spring-cloud-gcp-starter-logging

    SQL - MySQL

    Cloud SQL integrations with MySQL

    com.google.cloud:spring-cloud-gcp-starter-sql-mysql

    SQL - PostgreSQL

    Cloud SQL integrations with PostgreSQL

    com.google.cloud:spring-cloud-gcp-starter-sql-postgresql

    Storage

    Provides integrations with Google Cloud Storage and Spring Resource

    com.google.cloud:spring-cloud-gcp-starter-storage

    Trace

    Enables instrumentation with Google Cloud Trace

    com.google.cloud:spring-cloud-gcp-starter-trace

    Vision

    Provides integrations with Google Cloud Vision

    com.google.cloud:spring-cloud-gcp-starter-vision

    Security - IAP

    Extracts IAP identity information from applications deployed to Google Cloud

    com.google.cloud:spring-cloud-gcp-starter-security-iap

    Security - Firebase

    Extracts IAP identity information from applications deployed to Firebase

    com.google.cloud:spring-cloud-gcp-starter-security-firebase

    +
    +
    +
    +
    +

    Code Samples

    +
    +
    +

    The best way to learn how to use Spring Framework on Google Cloud is to consult the sample applications on Github.

    +
    +
    +

    The table below highlights several samples of the most commonly used integrations in Spring Framework on Google Cloud.

    +
    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Google Cloud IntegrationSample Application

    Cloud Pub/Sub

    spring-cloud-gcp-pubsub-sample

    Cloud Spanner

    spring-cloud-gcp-data-spanner-repository-sample

    +

    spring-cloud-gcp-data-spanner-template-sample

    Cloud Datastore

    spring-cloud-gcp-data-datastore-sample

    Cloud Firestore

    spring-cloud-gcp-data-firestore-sample

    Cloud SQL (w/ MySQL)

    spring-cloud-gcp-sql-mysql-sample

    Cloud Storage

    spring-cloud-gcp-storage-resource-sample

    Cloud Logging

    spring-cloud-gcp-logging-sample

    Cloud Trace

    spring-cloud-gcp-trace-sample

    Cloud Vision

    spring-cloud-gcp-vision-api-sample

    Cloud Security - IAP

    spring-cloud-gcp-security-iap-sample

    Cloud Security - Firebase

    spring-cloud-gcp-security-firebase-sample

    +
    +
    +
    +

    Initializr

    +
    +
    +

    Spring Initializr is a tool which generates the scaffolding code for a new Spring Boot project. +It handles the work of generating the Maven or Gradle build file so you do not have to manually add the dependencies yourself.

    +
    +
    +

    Spring Initializr offers three modules from Spring Framework on Google Cloud that you can use to generate your project.

    +
    +
    +
      +
    • +

      GCP Support: The GCP Support module contains auto-configuration support for every Spring Framework on Google Cloud integration. +Most of the autoconfiguration code is only enabled if the required dependency is added to your project.

      +
    • +
    • +

      GCP Messaging: Google Cloud Pub/Sub integrations work out of the box.

      +
    • +
    • +

      GCP Storage: Google Cloud Storage integrations work out of the box.

      +
    • +
    +
    +
    +
    +
    +

    Contact Us

    +
    +
    +

    Spring Framework on Google Cloud is an actively maintained project and we encourage users to raise issues and ask questions about the project.

    +
    +
    +

    We actively monitor the following communication channels:

    +
    +
    + +
    +
    +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/secretmanager.html b/3.8.13/reference/html/secretmanager.html new file mode 100644 index 0000000000..6f4dfc4d98 --- /dev/null +++ b/3.8.13/reference/html/secretmanager.html @@ -0,0 +1,472 @@ + + + + + + + +Secret Manager + + + + + + + + + +
    +
    +
    + +
    +
    +

    Secret Manager

    +
    +
    +

    Google Cloud Secret Manager is a secure and convenient method for storing API keys, passwords, certificates, and other sensitive data. +A detailed summary of its features can be found in the Secret Manager documentation.

    +
    +
    +

    Spring Framework on Google Cloud provides:

    +
    +
    +
      +
    • +

      A property source which allows you to specify and load the secrets of your Google Cloud project into your application context as a Bootstrap Property Source.

      +
    • +
    • +

      A SecretManagerTemplate which allows you to read, write, and update secrets in Secret Manager.

      +
    • +
    +
    +
    +

    Dependency Setup

    +
    +

    To begin using this library, add the spring-cloud-gcp-starter-secretmanager artifact to your project.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +  <groupId>com.google.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-starter-secretmanager</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +  implementation("com.google.cloud:spring-cloud-gcp-starter-secretmanager")
    +}
    +
    +
    +
    +

    Configuration

    +
    +

    By default, Spring Framework on Google Cloud Secret Manager will authenticate using Application Default Credentials. +This can be overridden using the authentication properties.

    +
    +
    + + + + + +
    + + +All the below settings must be specified in a 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 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 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 Application Default Credentials.

    spring.cloud.gcp.secretmanager.allow-default-secret

    Define the behavior when accessing a non-existent secret string/bytes.
    +If set to true, null will be returned when accessing a non-existent secret; otherwise throwing an exception.

    No

    false

    +
    +
    +
    +

    Secret Manager Property Source

    +
    +

    The Spring Framework on Google Cloud integration for Google Cloud Secret Manager enables you to use Secret Manager as a bootstrap property source.

    +
    +
    +

    This allows you to specify and load secrets from Google Cloud Secret Manager as properties into the application context during the Bootstrap Phase, which refers to the initial phase when a Spring application is being loaded.

    +
    +
    +

    The Secret Manager property source uses the following syntax to specify secrets:

    +
    +
    +
    +
    # 1. Long form - specify the project ID, secret ID, and version
    +sm://projects/<project-id>/secrets/<secret-id>/versions/<version-id>}
    +
    +# 2.  Long form - specify project ID, secret ID, and use latest version
    +sm://projects/<project-id>/secrets/<secret-id>
    +
    +# 3. Short form - specify project ID, secret ID, and version
    +sm://<project-id>/<secret-id>/<version-id>
    +
    +# 4. Short form - default project; specify secret + version
    +#
    +# The project is inferred from the spring.cloud.gcp.secretmanager.project-id setting
    +# in your bootstrap.properties (see Configuration) or from application-default credentials if
    +# this is not set.
    +sm://<secret-id>/<version>
    +
    +# 5. Shortest form - specify secret ID, use default project and latest version.
    +sm://<secret-id>
    +
    +
    +
    +

    You can use this syntax in the following places:

    +
    +
    +
      +
    1. +

      In your application.properties or bootstrap.properties files:

      +
      +
      +
      # Example of the project-secret long-form syntax.
      +spring.datasource.password=${sm://projects/my-gcp-project/secrets/my-secret}
      +
      +
      +
    2. +
    3. +

      Access the value using the @Value annotation.

      +
      +
      +
      // Example of using shortest form syntax.
      +@Value("${sm://my-secret}")
      +
      +
      +
    4. +
    +
    +
    +
    +

    Secret Manager Template

    +
    +

    The SecretManagerTemplate class simplifies operations of creating, updating, and reading secrets.

    +
    +
    +

    To begin using this class, you may inject an instance of the class using @Autowired after adding the starter dependency to your project.

    +
    +
    +
    +
    @Autowired
    +private SecretManagerTemplate secretManagerTemplate;
    +
    +
    +
    +
    +

    Please consult SecretManagerOperations for information on what operations are available for the Secret Manager template.

    +
    +
    +
    +

    Refresh secrets without restarting the application

    +
    +
      +
    1. +

      Before running your application, change the project’s configuration files as follows:

      +
      +

      import the actuator starter dependency to your project,

      +
      +
      +
      +
      <dependency>
      +    <groupId>org.springframework.boot</groupId>
      +    <artifactId>spring-boot-starter-actuator</artifactId>
      +</dependency>
      +
      +
      +
      +

      add the following properties to your project’s application.properties. The latter is used to enable Spring Boot’s Config Data API.

      +
      +
      +
      +
      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.

      +
      +
      +
      +
      spring.cloud.gcp.secretmanager.legacy=false
      +
      +
      +
    2. +
    3. +

      After running the application, update your secret stored in the Secret Manager.

      +
    4. +
    5. +

      To refresh the secret, send the following command to your application sever:

      +
      +
      +
      curl -X POST http://[host]:[port]/actuator/refresh
      +
      +
      +
      +

      Note that only @ConfigurationProperties annotated with @RefreshScope support updating secrets without restarting the application.

      +
      +
    6. +
    +
    +
    +
    +

    Allow default secret

    +
    +

    By default, when accessing a non-existent secret, the Secret Manager will throw an exception.

    +
    +
    +

    However, if your want to use a default value in such a scenario, you can add the following property to project’s properties.

    +
    +
    +
    +
    `spring.cloud.gcp.secretmanager.allow-default-secret=true`
    +
    +
    +
    +

    Therefore, a variable annotated with @Value("${${sm://application-fake}:DEFAULT}") will be resolved as DEFAULT when there is no application-fake in Secret Manager and application-fake is NOT a valid application property.

    +
    +
    +
    +

    Sample

    +
    +

    A Secret Manager Sample Application is provided which demonstrates basic property source loading and usage of the template class.

    +
    +
    +
    +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/security-firebase.html b/3.8.13/reference/html/security-firebase.html new file mode 100644 index 0000000000..1c43a5cd77 --- /dev/null +++ b/3.8.13/reference/html/security-firebase.html @@ -0,0 +1,323 @@ + + + + + + + +Firebase Authentication + + + + + + + + + +
    +
    +
    + +
    +
    +

    Firebase Authentication

    +
    +
    +

    Firebase Authentication provides backend services, easy-to-use SDKs, and ready-made UI libraries to authenticate users to your Firebase app.

    +
    +
    +

    The Security Firebase starter uses Spring Security OAuth 2.0 Resource Server functionality to extract user identity from OAuth2 Authorization header.

    +
    +
    +

    The Firebase JWT tokens are validated with rules presented here. The following claims are validated automatically:

    +
    +
    +
      +
    • +

      Expiration time: Must be in the future

      +
    • +
    • +

      Issued-at time : Must be in the past

      +
    • +
    • +

      Audience : Must be the firebase project id

      +
    • +
    • +

      Issuer: Must be "https://securetoken.google.com/<projectId> "

      +
    • +
    • +

      Authentication time : Must be in the past

      +
    • +
    • +

      Subject : Must not be empty

      +
    • +
    +
    +
    + + + + + +
    + + +If you create a custom WebSecurityConfigurerAdapter, enable extracting user identity by adding .oauth2ResourceServer().jwt() configuration to the HttpSecurity object. +If no custom WebSecurityConfigurerAdapter is present, nothing needs to be done because Spring Boot will add this customization by default. +
    +
    +
    +

    Starter Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-security-firebase</artifactId>
    +</dependency>
    +
    +
    +
    +

    Starter Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-security-firebase")
    +}
    +
    +
    +
    +

    Configuration

    +
    +

    The following properties are available.

    +
    +
    + + + + + +
    + + +Modifying public-keys-endpoint property might be useful for testing, but the defaults should not be changed in production. +
    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredDefault

    spring.cloud.gcp.security.firebase.project-id

    Overrides the Google Cloud project ID specified in the Core module.

    false

    spring.cloud.gcp.security.firebase.public-keys-endpoint

    Link to Google’s public endpoint containing Firebase public keys.

    true

    https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com

    +
    +
    +

    Sample

    +
    +

    A sample application is available. This sample app provides simple login page using firebase-ui to fetch the JWT token.

    +
    +
    +
    +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/security-iap.html b/3.8.13/reference/html/security-iap.html new file mode 100644 index 0000000000..a867ba02a7 --- /dev/null +++ b/3.8.13/reference/html/security-iap.html @@ -0,0 +1,362 @@ + + + + + + + +Cloud IAP + + + + + + + + + +
    +
    +
    + +
    +
    +

    Cloud IAP

    +
    +
    +

    Cloud Identity-Aware Proxy (IAP) provides a security layer over applications deployed to Google Cloud.

    +
    +
    +

    The IAP starter uses Spring Security OAuth 2.0 Resource Server functionality to automatically extract user identity from the proxy-injected x-goog-iap-jwt-assertion HTTP header.

    +
    +
    +

    The following claims are validated automatically:

    +
    +
    +
      +
    • +

      Issue time

      +
    • +
    • +

      Expiration time

      +
    • +
    • +

      Issuer

      +
    • +
    • +

      Audience

      +
    • +
    +
    +
    +

    The audience ("aud" claim) validation string is automatically determined when the application is running on App Engine Standard or App Engine Flexible. +This functionality relies on Cloud Resource Manager API to retrieve project details, so the following setup is needed:

    +
    +
    +
      +
    • +

      Enable Cloud Resource Manager API in Google Cloud Console.

      +
    • +
    • +

      Make sure your application has resourcemanager.projects.get permission.

      +
    • +
    +
    +
    +

    App Engine automatic audience determination can be overridden by using spring.cloud.gcp.security.iap.audience property. It supports multiple allowable audiences by providing a comma-delimited list.

    +
    +
    +

    For Compute Engine or Kubernetes Engine spring.cloud.gcp.security.iap.audience property must be provided, as the audience string depends on the specific Backend Services setup and cannot be inferred automatically. +To determine the audience value, follow directions in IAP Verify the JWT payload guide. +If spring.cloud.gcp.security.iap.audience is not provided, the application will fail to start the following message:

    +
    +
    +
    +
    No qualifying bean of type 'com.google.cloud.spring.security.iap.AudienceProvider' available.
    +
    +
    +
    + + + + + +
    + + +If you create a custom WebSecurityConfigurerAdapter, enable extracting user identity by adding .oauth2ResourceServer().jwt() configuration to the HttpSecurity object. + If no custom WebSecurityConfigurerAdapter is present, nothing needs to be done because Spring Boot will add this customization by default. +
    +
    +
    +

    Starter Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-security-iap</artifactId>
    +</dependency>
    +
    +
    +
    +

    Starter Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-security-iap")
    +}
    +
    +
    +
    +

    Configuration

    +
    +

    The following properties are available.

    +
    +
    + + + + + +
    + + +Modifying registry, algorithm, and header properties might be useful for testing, but the defaults should not be changed in production. +
    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredDefault

    spring.cloud.gcp.security.iap.registry

    Link to JWK public key registry.

    true

    https://www.gstatic.com/iap/verify/public_key-jwk

    spring.cloud.gcp.security.iap.algorithm

    Encryption algorithm used to sign the JWK token.

    true

    ES256

    spring.cloud.gcp.security.iap.header

    Header from which to extract the JWK key.

    true

    x-goog-iap-jwt-assertion

    spring.cloud.gcp.security.iap.issuer

    JWK issuer to verify.

    true

    https://cloud.google.com/iap

    spring.cloud.gcp.security.iap.audience

    Custom JWK audience to verify.

    false on App Engine; true on GCE/GKE

    +
    +
    +

    Sample

    +
    +

    A sample application is available.

    +
    +
    +
    +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/spanner-spring-data-r2dbc.html b/3.8.13/reference/html/spanner-spring-data-r2dbc.html new file mode 100644 index 0000000000..a40d5ff472 --- /dev/null +++ b/3.8.13/reference/html/spanner-spring-data-r2dbc.html @@ -0,0 +1,270 @@ + + + + + + + +Cloud Spanner Spring Data R2DBC + + + + + + + + + +
    +
    +
    + +
    +
    +

    Cloud Spanner Spring Data R2DBC

    +
    +
    +

    The Spring Data R2DBC Dialect for Cloud Spanner enables the usage of Spring Data R2DBC with Cloud Spanner.

    +
    +
    +

    The goal of the Spring Data project is to create easy and consistent ways of using data access technologies from Spring Framework applications.

    +
    +
    +

    Setup

    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
        <dependency>
    +        <groupId>com.google.cloud</groupId>
    +        <artifactId>spring-cloud-spanner-spring-data-r2dbc</artifactId>
    +    </dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-spanner-spring-data-r2dbc")
    +}
    +
    +
    +
    +
    +

    Overview

    +
    +

    Spring Data R2DBC allows you to use the convenient features of Spring Data in a reactive application. +These features include:

    +
    +
    +
      +
    • +

      Spring configuration support using Java based @Configuration classes.

      +
    • +
    • +

      Annotation based mapping metadata.

      +
    • +
    • +

      Automatic implementation of Repository interfaces.

      +
    • +
    • +

      Support for Reactive Transactions.

      +
    • +
    • +

      Schema and data initialization utilities.

      +
    • +
    +
    +
    +

    See the Spring Data R2DBC documentation for more information on how to use Spring Data R2DBC.

    +
    +
    +
    +

    Sample Application

    +
    +

    We provide a sample application which demonstrates using the Spring Data R2DBC framework with Cloud Spanner in Spring WebFlux.

    +
    +
    +
    +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/spanner.html b/3.8.13/reference/html/spanner.html new file mode 100644 index 0000000000..347eb8338e --- /dev/null +++ b/3.8.13/reference/html/spanner.html @@ -0,0 +1,2488 @@ + + + + + + + +Spring Data Cloud Spanner + + + + + + + + + +
    +
    +
    + +
    +
    +

    Spring Data Cloud Spanner

    +
    +
    +

    Spring Data is an abstraction for storing and retrieving POJOs in numerous storage technologies. +Spring Framework on Google Cloud adds Spring Data support for Google Cloud Spanner.

    +
    +
    +

    Maven coordinates for this module only, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-data-spanner</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-data-spanner")
    +}
    +
    +
    +
    +

    We provide a Spring Boot Starter for Spring Data Spanner, with which you can leverage our recommended auto-configuration setup. +To use the starter, see the coordinates see below.

    +
    +
    +

    Maven:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-data-spanner</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-data-spanner")
    +}
    +
    +
    +
    +

    This setup takes care of bringing in the latest compatible version of Cloud Java Cloud Spanner libraries as well.

    +
    +
    +

    Configuration

    +
    +

    To setup Spring Data Cloud Spanner, you have to configure the following:

    +
    +
    +
      +
    • +

      Setup the connection details to Google Cloud Spanner.

      +
    • +
    • +

      Enable Spring Data Repositories (optional).

      +
    • +
    +
    +
    +

    Cloud Spanner settings

    +
    +

    You can use the Spring Boot Starter for Spring Data Spanner to autoconfigure Google Cloud Spanner in your Spring application. +It contains all the necessary setup that makes it easy to authenticate with your Google Cloud project. +The following configuration options are available:

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.spanner.enabled

    Enables the Cloud Spanner client

    No

    true

    spring.cloud.gcp.spanner.instance-id

    Cloud Spanner instance to use

    Yes

    spring.cloud.gcp.spanner.database

    Cloud Spanner database to use

    Yes

    spring.cloud.gcp.spanner.project-id

    Google Cloud project ID where the Google Cloud Spanner API is hosted, if different from the one in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.spanner.credentials.location

    OAuth2 credentials for authenticating with the +Google Cloud Spanner API, if different from the ones in the +Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.spanner.credentials.encoded-key

    Base64-encoded OAuth2 credentials for authenticating with the +Google Cloud Spanner API, if different from the ones in the +Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.spanner.credentials.scopes

    OAuth2 scope for Spring Framework on Google +CloudSpanner credentials

    No

    https://www.googleapis.com/auth/spanner.data

    spring.cloud.gcp.spanner.createInterleavedTableDdlOnDeleteCascade

    If true, then schema statements generated by SpannerSchemaUtils for tables with interleaved parent-child relationships will be "ON DELETE CASCADE". +The schema for the tables will be "ON DELETE NO ACTION" if false.

    No

    true

    spring.cloud.gcp.spanner.numRpcChannels

    Number of gRPC channels used to connect to Cloud Spanner

    No

    4 - Determined by Cloud Spanner client library

    spring.cloud.gcp.spanner.prefetchChunks

    Number of chunks prefetched by Cloud Spanner for read and query

    No

    4 - Determined by Cloud Spanner client library

    spring.cloud.gcp.spanner.minSessions

    Minimum number of sessions maintained in the session pool

    No

    0 - Determined by Cloud Spanner client library

    spring.cloud.gcp.spanner.maxSessions

    Maximum number of sessions session pool can have

    No

    400 - Determined by Cloud Spanner client library

    spring.cloud.gcp.spanner.maxIdleSessions

    Maximum number of idle sessions session pool will maintain

    No

    0 - Determined by Cloud Spanner client library

    spring.cloud.gcp.spanner.writeSessionsFraction

    Fraction of sessions to be kept prepared for write transactions

    No

    0.2 - Determined by Cloud Spanner client library

    spring.cloud.gcp.spanner.keepAliveIntervalMinutes

    How long to keep idle sessions alive

    No

    30 - Determined by Cloud Spanner client library

    spring.cloud.gcp.spanner.failIfPoolExhausted

    If all sessions are in use, fail the request by throwing an exception. Otherwise, by default, block until a session becomes available.

    No

    false

    spring.cloud.gcp.spanner.emulator.enabled

    Enables the usage of an emulator. If this is set to true, then you should set the spring.cloud.gcp.spanner.emulator-host to the host:port of your locally running emulator instance.

    No

    false

    spring.cloud.gcp.spanner.emulator-host

    The host and port of the Spanner emulator; can be overridden to specify connecting to an already-running Spanner emulator instance.

    No

    localhost:9010

    +
    + + + + + +
    + + +For further customization of the client library SpannerOptions, provide a bean implementing SpannerOptionsCustomizer, with a single method that accepts a SpannerOptions.Builder and modifies it as necessary. +
    +
    +
    +
    +

    Repository settings

    +
    +

    Spring Data Repositories can be configured via the @EnableSpannerRepositories annotation on your main @Configuration class. +With our Spring Boot Starter for Spring Data Cloud Spanner, @EnableSpannerRepositories is automatically added. +It is not required to add it to any other class, unless there is a need to override finer grain configuration parameters provided by @EnableSpannerRepositories.

    +
    +
    +
    +

    Autoconfiguration

    +
    +

    Our Spring Boot autoconfiguration creates the following beans available in the Spring application context:

    +
    +
    +
      +
    • +

      an instance of SpannerTemplate

      +
    • +
    • +

      an instance of SpannerDatabaseAdminTemplate for generating table schemas from object hierarchies and creating and deleting tables and databases

      +
    • +
    • +

      an instance of all user-defined repositories extending SpannerRepository, CrudRepository, PagingAndSortingRepository, when repositories are enabled

      +
    • +
    • +

      an instance of DatabaseClient from the Google Cloud Java Client for Spanner, for convenience and lower level API access

      +
    • +
    +
    +
    +
    +
    +

    Object Mapping

    +
    +

    Spring Data Cloud Spanner allows you to map domain POJOs to Cloud Spanner tables via annotations:

    +
    +
    +
    +
    @Table(name = "traders")
    +public class Trader {
    +
    +	@PrimaryKey
    +	@Column(name = "trader_id")
    +	String traderId;
    +
    +	String firstName;
    +
    +	String lastName;
    +
    +	@NotMapped
    +	Double temporaryNumber;
    +}
    +
    +
    +
    +
    +

    Spring Data Cloud Spanner will ignore any property annotated with @NotMapped. +These properties will not be written to or read from Spanner.

    +
    +
    +

    Constructors

    +
    +

    Simple constructors are supported on POJOs. +The constructor arguments can be a subset of the persistent properties. +Every constructor argument needs to have the same name and type as a persistent property on the entity and the constructor should set the property from the given argument. +Arguments that are not directly set to properties are not supported.

    +
    +
    +
    +
    @Table(name = "traders")
    +public class Trader {
    +	@PrimaryKey
    +	@Column(name = "trader_id")
    +	String traderId;
    +
    +	String firstName;
    +
    +	String lastName;
    +
    +	@NotMapped
    +	Double temporaryNumber;
    +
    +	public Trader(String traderId, String firstName) {
    +	    this.traderId = traderId;
    +	    this.firstName = firstName;
    +	}
    +}
    +
    +
    +
    +
    +
    +

    Table

    +
    +

    The @Table annotation can provide the name of the Cloud Spanner table that stores instances of the annotated class, one per row. +This annotation is optional, and if not given, the name of the table is inferred from the class name with the first character uncapitalized.

    +
    +
    +
    SpEL expressions for table names
    +
    +

    In some cases, you might want the @Table table name to be determined dynamically. +To do that, you can use Spring Expression Language.

    +
    +
    +

    For example:

    +
    +
    +
    +
    @Table(name = "trades_#{tableNameSuffix}")
    +public class Trade {
    +	// ...
    +}
    +
    +
    +
    +
    +

    The table name will be resolved only if the tableNameSuffix value/bean in the Spring application context is defined. +For example, if tableNameSuffix has the value "123", the table name will resolve to trades_123.

    +
    +
    +
    +
    +

    Primary Keys

    +
    +

    For a simple table, you may only have a primary key consisting of a single column. +Even in that case, the @PrimaryKey annotation is required. +@PrimaryKey identifies the one or more ID properties corresponding to the primary key.

    +
    +
    +

    Spanner has first class support for composite primary keys of multiple columns. +You have to annotate all of your POJO’s fields that the primary key consists of with @PrimaryKey as below:

    +
    +
    +
    +
    @Table(name = "trades")
    +public class Trade {
    +	@PrimaryKey(keyOrder = 2)
    +	@Column(name = "trade_id")
    +	private String tradeId;
    +
    +	@PrimaryKey(keyOrder = 1)
    +	@Column(name = "trader_id")
    +	private String traderId;
    +
    +	private String action;
    +
    +	private BigDecimal price;
    +
    +	private Double shares;
    +
    +	private String symbol;
    +}
    +
    +
    +
    +
    +

    The keyOrder parameter of @PrimaryKey identifies the properties corresponding to the primary key columns in order, starting with 1 and increasing consecutively. +Order is important and must reflect the order defined in the Cloud Spanner schema. +In our example the DDL to create the table and its primary key is as follows:

    +
    +
    +
    +
    CREATE TABLE trades (
    +    trader_id STRING(MAX),
    +    trade_id STRING(MAX),
    +    action STRING(15),
    +    symbol STRING(10),
    +    price NUMERIC,
    +    shares FLOAT64
    +) PRIMARY KEY (trader_id, trade_id)
    +
    +
    +
    +

    Spanner does not have automatic ID generation. +For most use-cases, sequential IDs should be used with caution to avoid creating data hotspots in the system. +Read Spanner Primary Keys documentation for a better understanding of primary keys and recommended practices.

    +
    +
    +
    +

    Columns

    +
    +

    All accessible properties on POJOs are automatically recognized as a Cloud Spanner column. +Column naming is generated by the PropertyNameFieldNamingStrategy by default defined on the SpannerMappingContext bean. +The @Column annotation optionally provides a different column name than that of the property and some other settings:

    +
    +
    +
      +
    • +

      name is the optional name of the column

      +
    • +
    • +

      spannerTypeMaxLength specifies for STRING and BYTES columns the maximum length. +This setting is only used when generating DDL schema statements based on domain types.

      +
    • +
    • +

      nullable specifies if the column is created as NOT NULL. +This setting is only used when generating DDL schema statements based on domain types.

      +
    • +
    • +

      spannerType is the Cloud Spanner column type you can optionally specify. +If this is not specified then a compatible column type is inferred from the Java property type.

      +
    • +
    • +

      spannerCommitTimestamp is a boolean specifying if this property corresponds to an auto-populated commit timestamp column. +Any value set in this property will be ignored when writing to Cloud Spanner.

      +
    • +
    +
    +
    +
    +

    Embedded Objects

    +
    +

    If an object of type B is embedded as a property of A, then the columns of B will be saved in the same Cloud Spanner table as those of A.

    +
    +
    +

    If B has primary key columns, those columns will be included in the primary key of A. B can also have embedded properties. +Embedding allows reuse of columns between multiple entities, and can be useful for implementing parent-child situations, because Cloud Spanner requires child tables to include the key columns of their parents.

    +
    +
    +

    For example:

    +
    +
    +
    +
    class X {
    +  @PrimaryKey
    +  String grandParentId;
    +
    +  long age;
    +}
    +
    +class A {
    +  @PrimaryKey
    +  @Embedded
    +  X grandParent;
    +
    +  @PrimaryKey(keyOrder = 2)
    +  String parentId;
    +
    +  String value;
    +}
    +
    +@Table(name = "items")
    +class B {
    +  @PrimaryKey
    +  @Embedded
    +  A parent;
    +
    +  @PrimaryKey(keyOrder = 2)
    +  String id;
    +
    +  @Column(name = "child_value")
    +  String value;
    +}
    +
    +
    +
    +
    +

    Entities of B can be stored in a table defined as:

    +
    +
    +
    +
    CREATE TABLE items (
    +    grandParentId STRING(MAX),
    +    parentId STRING(MAX),
    +    id STRING(MAX),
    +    value STRING(MAX),
    +    child_value STRING(MAX),
    +    age INT64
    +) PRIMARY KEY (grandParentId, parentId, id)
    +
    +
    +
    +

    Note that the following restrictions apply when you use embedded objects:

    +
    +
    +
      +
    • +

      Embedded properties' column names must all be unique.

      +
    • +
    • +

      Embedded properties must not be passed through a constructor and the property must be mutable; otherwise you’ll get an error, such as SpannerDataException: Column not found. +Be careful about this restriction when you use Kotlin’s data class to hold an embedded property.

      +
    • +
    +
    +
    +
    +

    Relationships

    +
    +

    Spring Data Cloud Spanner supports parent-child relationships using the Cloud Spanner parent-child interleaved table mechanism. +Cloud Spanner interleaved tables enforce the one-to-many relationship and provide efficient queries and operations on entities of a single domain parent entity. +These relationships can be up to 7 levels deep. +Cloud Spanner also provides automatic cascading delete or enforces the deletion of child entities before parents.

    +
    +
    +

    While one-to-one and many-to-many relationships can be implemented in Cloud Spanner and Spring Data Cloud Spanner using constructs of interleaved parent-child tables, only the parent-child relationship is natively supported.

    +
    +
    +

    For example, the following Java entities:

    +
    +
    +
    +
    @Table(name = "Singers")
    +class Singer {
    +  @PrimaryKey
    +  long SingerId;
    +
    +  String FirstName;
    +
    +  String LastName;
    +
    +  byte[] SingerInfo;
    +
    +  @Interleaved
    +  List<Album> albums;
    +}
    +
    +@Table(name = "Albums")
    +class Album {
    +  @PrimaryKey
    +  long SingerId;
    +
    +  @PrimaryKey(keyOrder = 2)
    +  long AlbumId;
    +
    +  String AlbumTitle;
    +}
    +
    +
    +
    +
    +

    These classes can correspond to an existing pair of interleaved tables. +The @Interleaved annotation may be applied to Collection properties and the inner type is resolved as the child entity type. +The schema needed to create them can also be generated using the SpannerSchemaUtils and run by using the SpannerDatabaseAdminTemplate:

    +
    +
    +
    +
    @Autowired
    +SpannerSchemaUtils schemaUtils;
    +
    +@Autowired
    +SpannerDatabaseAdminTemplate databaseAdmin;
    +...
    +
    +// Get the create statmenets for all tables in the table structure rooted at Singer
    +List<String> createStrings = this.schemaUtils.getCreateTableDdlStringsForInterleavedHierarchy(Singer.class);
    +
    +// Create the tables and also create the database if necessary
    +this.databaseAdmin.executeDdlStrings(createStrings, true);
    +
    +
    +
    +
    +

    The createStrings list contains table schema statements using column names and types compatible with the provided Java type and any resolved child relationship types contained within based on the configured custom converters.

    +
    +
    +
    +
    CREATE TABLE Singers (
    +  SingerId   INT64 NOT NULL,
    +  FirstName  STRING(1024),
    +  LastName   STRING(1024),
    +  SingerInfo BYTES(MAX),
    +) PRIMARY KEY (SingerId);
    +
    +CREATE TABLE Albums (
    +  SingerId     INT64 NOT NULL,
    +  AlbumId      INT64 NOT NULL,
    +  AlbumTitle   STRING(MAX),
    +) PRIMARY KEY (SingerId, AlbumId),
    +  INTERLEAVE IN PARENT Singers ON DELETE CASCADE;
    +
    +
    +
    +

    The ON DELETE CASCADE clause indicates that Cloud Spanner will delete all Albums of a singer if the Singer is deleted. +The alternative is ON DELETE NO ACTION, where a Singer cannot be deleted until all of its Albums have already been deleted. +When using SpannerSchemaUtils to generate the schema strings, the spring.cloud.gcp.spanner.createInterleavedTableDdlOnDeleteCascade boolean setting determines if these schema are generated as ON DELETE CASCADE for true and ON DELETE NO ACTION for false.

    +
    +
    +

    Cloud Spanner restricts these relationships to 7 child layers. +A table may have multiple child tables.

    +
    +
    +

    On updating or inserting an object to Cloud Spanner, all of its referenced children objects are also updated or inserted in the same request, respectively. +On read, all of the interleaved child rows are also all read.

    +
    +
    +
    Lazy Fetch
    +
    +

    @Interleaved properties are retrieved eagerly by default, but can be fetched lazily for performance in both read and write:

    +
    +
    +
    +
    @Interleaved(lazy = true)
    +List<Album> albums;
    +
    +
    +
    +
    +

    Lazily-fetched interleaved properties are retrieved upon the first interaction with the property. +If a property marked for lazy fetching is never retrieved, then it is also skipped when saving the parent entity.

    +
    +
    +

    If used inside a transaction, subsequent operations on lazily-fetched properties use the same transaction context as that of the original parent entity.

    +
    +
    +
    +
    Declarative Filtering with @Where
    +
    +

    The @Where annotation could be applied to an entity class or to an interleaved property. +This annotation provides an SQL where clause that will be applied at the fetching of interleaved collections or the entity itself.

    +
    +
    +

    Let’s say we have an Agreement with a list of Participants which could be assigned to it. +We would like to fetch a list of currently active participants. +For security reasons, all records should remain in the database forever, even if participants become inactive. +That can be easily achieved with the @Where annotation, which is demonstrated by this example:

    +
    +
    +
    +
    @Table(name = "participants")
    +public class Participant {
    +  //...
    +  boolean active;
    +  //...
    +}
    +
    +@Table(name = "agreements")
    +public class Agreement {
    +  //...
    +  @Interleaved
    +  @Where("active = true")
    +  List<Participant> participants;
    +  Person person;
    +  //...
    +}
    +
    +
    +
    +
    +
    +
    +

    Supported Types

    +
    +

    Spring Data Cloud Spanner natively supports the following types for regular fields but also utilizes custom converters (detailed in following sections) and dozens of pre-defined Spring Data custom converters to handle other common Java types.

    +
    +
    +

    Natively supported types:

    +
    +
    +
      +
    • +

      com.google.cloud.ByteArray

      +
    • +
    • +

      com.google.cloud.Date

      +
    • +
    • +

      com.google.cloud.Timestamp

      +
    • +
    • +

      java.lang.Boolean, boolean

      +
    • +
    • +

      java.lang.Double, double

      +
    • +
    • +

      java.lang.Long, long

      +
    • +
    • +

      java.lang.Integer, int

      +
    • +
    • +

      java.lang.String

      +
    • +
    • +

      double[]

      +
    • +
    • +

      long[]

      +
    • +
    • +

      boolean[]

      +
    • +
    • +

      java.util.Date

      +
    • +
    • +

      java.time.Instant

      +
    • +
    • +

      java.sql.Date

      +
    • +
    • +

      java.time.LocalDate

      +
    • +
    • +

      java.time.LocalDateTime

      +
    • +
    +
    +
    +
    +

    JSON fields

    +
    +

    Spanner supports JSON and ARRAY<JSON> type for columns. Such property needs to be annotated with @Column(spannerType = TypeCode.JSON). JSON columns are mapped to custom POJOs and ARRAY<JSON> columns are mapped to List of custom POJOs. Read, write and query with custom SQL query are supported for JSON annotated fields.

    +
    +
    + + + + + +
    + + +Spring Boot autoconfigures a Gson bean by default. +This Gson instance is used by default to convert to and from JSON representation. +To customize, use spring.gson.* configuration properties or GsonBuilderCustomizer bean as instructed in Spring Boot documentation here. +Alternatively, you can also provide a customized bean of type Gson in your application. +
    +
    +
    +
    +
    @Table(name = "traders")
    +public class Trader {
    +
    +	@PrimaryKey
    +	@Column(name = "trader_id")
    +	String traderId;
    +
    +	@Column(spannerType = TypeCode.JSON)
    +	Details details;
    +}
    +
    +public class Details {
    +    String name;
    +    String affiliation;
    +    Boolean isActive;
    +}
    +
    +
    +
    +
    +
    +

    Lists

    +
    +

    Spanner supports ARRAY types for columns. +ARRAY columns are mapped to List fields in POJOs.

    +
    +
    +

    Example:

    +
    +
    +
    +
    List<Double> curve;
    +
    +
    +
    +
    +

    The types inside the lists can be any singular property type.

    +
    +
    +
    +

    Lists of Structs

    +
    +

    Cloud Spanner queries can construct STRUCT values that appear as columns in the result. +Cloud Spanner requires STRUCT values appear in ARRAYs at the root level: SELECT ARRAY(SELECT STRUCT(1 as val1, 2 as val2)) as pair FROM Users.

    +
    +
    +

    Spring Data Cloud Spanner will attempt to read the column STRUCT values into a property that is an Iterable of an entity type compatible with the schema of the column STRUCT value.

    +
    +
    +

    For the previous array-select example, the following property can be mapped with the constructed ARRAY<STRUCT> column: List<TwoInts> pair; where the TwoInts type is defined:

    +
    +
    +
    +
    class TwoInts {
    +
    +  int val1;
    +
    +  int val2;
    +}
    +
    +
    +
    +
    +
    +

    Custom types

    +
    +

    Custom converters can be used to extend the type support for user defined types.

    +
    +
    +
      +
    1. +

      Converters need to implement the org.springframework.core.convert.converter.Converter interface in both directions.

      +
    2. +
    3. +

      The user defined type needs to be mapped to one of the basic types supported by Spanner:

      +
      +
        +
      • +

        com.google.cloud.ByteArray

        +
      • +
      • +

        com.google.cloud.Date

        +
      • +
      • +

        com.google.cloud.Timestamp

        +
      • +
      • +

        java.lang.Boolean, boolean

        +
      • +
      • +

        java.lang.Double, double

        +
      • +
      • +

        java.lang.Long, long

        +
      • +
      • +

        java.lang.String

        +
      • +
      • +

        double[]

        +
      • +
      • +

        long[]

        +
      • +
      • +

        boolean[]

        +
      • +
      • +

        enum types

        +
      • +
      +
      +
    4. +
    5. +

      An instance of both Converters needs to be passed to a ConverterAwareMappingSpannerEntityProcessor, which then has to be made available as a @Bean for SpannerEntityProcessor.

      +
    6. +
    +
    +
    +

    For example:

    +
    +
    +

    We would like to have a field of type Person on our Trade POJO:

    +
    +
    +
    +
    @Table(name = "trades")
    +public class Trade {
    +  //...
    +  Person person;
    +  //...
    +}
    +
    +
    +
    +
    +

    Where Person is a simple class:

    +
    +
    +
    +
    public class Person {
    +
    +  public String firstName;
    +  public String lastName;
    +
    +}
    +
    +
    +
    +
    +

    We have to define the two converters:

    +
    +
    +
    +
      public class PersonWriteConverter implements Converter<Person, String> {
    +
    +    @Override
    +    public String convert(Person person) {
    +      return person.firstName + " " + person.lastName;
    +    }
    +  }
    +
    +  public class PersonReadConverter implements Converter<String, Person> {
    +
    +    @Override
    +    public Person convert(String s) {
    +      Person person = new Person();
    +      person.firstName = s.split(" ")[0];
    +      person.lastName = s.split(" ")[1];
    +      return person;
    +    }
    +  }
    +
    +
    +
    +
    +

    That will be configured in our @Configuration file:

    +
    +
    +
    +
    @Configuration
    +public class ConverterConfiguration {
    +
    +	@Bean
    +	public SpannerEntityProcessor spannerEntityProcessor(SpannerMappingContext spannerMappingContext) {
    +		return new ConverterAwareMappingSpannerEntityProcessor(spannerMappingContext,
    +				Arrays.asList(new PersonWriteConverter()),
    +				Arrays.asList(new PersonReadConverter()));
    +	}
    +}
    +
    +
    +
    +
    +

    Note that ConverterAwareMappingSpannerEntityProcessor takes a list of Converters for write and read operations to support multiple user-defined types. +When there are duplicate Converters for a user-defined class in the list, it chooses the first matching item in the lists. +This means that, for a user-defined class U, write operations use the first Converter<U, …​> from the write Converters and read operations use the first Converter<…​, U> from the read Converters.

    +
    +
    +
    +

    Custom Converter for Struct Array Columns

    +
    +

    If a Converter<Struct, A> is provided, then properties of type List<A> can be used in your entity types.

    +
    +
    +
    +
    +

    Spanner Operations & Template

    +
    +

    SpannerOperations and its implementation, SpannerTemplate, provides the Template pattern familiar to Spring developers. +It provides:

    +
    +
    +
      +
    • +

      Resource management

      +
    • +
    • +

      One-stop-shop to Spanner operations with the Spring Data POJO mapping and conversion features

      +
    • +
    • +

      Exception conversion

      +
    • +
    +
    +
    +

    Using the autoconfigure provided by our Spring Boot Starter for Spanner, your Spring application context will contain a fully configured SpannerTemplate object that you can easily autowire in your application:

    +
    +
    +
    +
    @SpringBootApplication
    +public class SpannerTemplateExample {
    +
    +	@Autowired
    +	SpannerTemplate spannerTemplate;
    +
    +	public void doSomething() {
    +		this.spannerTemplate.delete(Trade.class, KeySet.all());
    +		//...
    +		Trade t = new Trade();
    +		//...
    +		this.spannerTemplate.insert(t);
    +		//...
    +		List<Trade> tradesByAction = spannerTemplate.findAll(Trade.class);
    +		//...
    +	}
    +}
    +
    +
    +
    +
    +

    The Template API provides convenience methods for:

    +
    +
    +
      +
    • +

      Reads, and by providing SpannerReadOptions and +SpannerQueryOptions

      +
      +
        +
      • +

        Stale read

        +
      • +
      • +

        Read with secondary indices

        +
      • +
      • +

        Read with limits and offsets

        +
      • +
      • +

        Read with sorting

        +
      • +
      +
      +
    • +
    • +

      Queries

      +
    • +
    • +

      DML operations (delete, insert, update, upsert)

      +
    • +
    • +

      Partial reads

      +
      +
        +
      • +

        You can define a set of columns to be read into your entity

        +
      • +
      +
      +
    • +
    • +

      Partial writes

      +
      +
        +
      • +

        Persist only a few properties from your entity

        +
      • +
      +
      +
    • +
    • +

      Read-only transactions

      +
    • +
    • +

      Locking read-write transactions

      +
    • +
    +
    +
    +

    SQL Query

    +
    +

    Cloud Spanner has SQL support for running read-only queries. +All the query related methods start with query on SpannerTemplate. +By using SpannerTemplate, you can run SQL queries that map to POJOs:

    +
    +
    +
    +
    List<Trade> trades = this.spannerTemplate.query(Trade.class, Statement.of("SELECT * FROM trades"));
    +
    +
    +
    +
    +
    +

    Read

    +
    +

    Spanner exposes a Read API for reading single row or multiple rows in a table or in a secondary index.

    +
    +
    +

    Using SpannerTemplate you can run reads, as the following example shows:

    +
    +
    +
    +
    List<Trade> trades = this.spannerTemplate.readAll(Trade.class);
    +
    +
    +
    +
    +

    Main benefit of reads over queries is reading multiple rows of a certain pattern of keys is much easier using the features of the KeySet class.

    +
    +
    +
    +

    Advanced reads

    +
    +
    Stale read
    +
    +

    All reads and queries are strong reads by default. +A strong read is a read at a current time and is guaranteed to see all data that has been committed up until the start of this read. +An exact staleness read is read at a timestamp in the past. +Cloud Spanner allows you to determine how current the data should be when you read data. +With SpannerTemplate you can specify the Timestamp by setting it on SpannerQueryOptions or SpannerReadOptions to the appropriate read or query methods:

    +
    +
    +

    Reads:

    +
    +
    +
    +
    // a read with options:
    +SpannerReadOptions spannerReadOptions = new SpannerReadOptions().setTimestamp(myTimestamp);
    +List<Trade> trades = this.spannerTemplate.readAll(Trade.class, spannerReadOptions);
    +
    +
    +
    +
    +

    Queries:

    +
    +
    +
    +
    // a query with options:
    +SpannerQueryOptions spannerQueryOptions = new SpannerQueryOptions().setTimestamp(myTimestamp);
    +List<Trade> trades = this.spannerTemplate.query(Trade.class, Statement.of("SELECT * FROM trades"), spannerQueryOptions);
    +
    +
    +
    +
    +

    You can also read with bounded staleness by setting .setTimestampBound(TimestampBound.ofMinReadTimestamp(myTimestamp)) on the query and read options objects. +Bounded staleness lets Cloud Spanner choose any point in time later than or equal to the given timestampBound, but it cannot be used inside transactions.

    +
    +
    +
    +
    Read from a secondary index
    +
    +

    Using a secondary index is available for Reads via the Template API and it is also implicitly available via SQL for Queries.

    +
    +
    +

    The following shows how to read rows from a table using a secondary index simply by setting index on SpannerReadOptions:

    +
    +
    +
    +
    SpannerReadOptions spannerReadOptions = new SpannerReadOptions().setIndex("TradesByTrader");
    +List<Trade> trades = this.spannerTemplate.readAll(Trade.class, spannerReadOptions);
    +
    +
    +
    +
    +
    +
    Read with offsets and limits
    +
    +

    Limits and offsets are only supported by Queries. +The following will get only the first two rows of the query:

    +
    +
    +
    +
    SpannerQueryOptions spannerQueryOptions = new SpannerQueryOptions().setLimit(2).setOffset(3);
    +List<Trade> trades = this.spannerTemplate.query(Trade.class, Statement.of("SELECT * FROM trades"), spannerQueryOptions);
    +
    +
    +
    +
    +

    Note that the above is equivalent of running SELECT * FROM trades LIMIT 2 OFFSET 3.

    +
    +
    +
    +
    Sorting
    +
    +

    Reads by keys do not support sorting. +However, queries on the Template API support sorting through standard SQL and also via Spring Data Sort API:

    +
    +
    +
    +
    List<Trade> trades = this.spannerTemplate.queryAll(Trade.class, Sort.by("action"));
    +
    +
    +
    +
    +

    If the provided sorted field name is that of a property of the domain type, then the column name corresponding to that property will be used in the query. +Otherwise, the given field name is assumed to be the name of the column in the Cloud Spanner table. +Sorting on columns of Cloud Spanner types STRING and BYTES can be done while ignoring case:

    +
    +
    +
    +
    Sort.by(Order.desc("action").ignoreCase())
    +
    +
    +
    +
    +
    +
    Partial read
    +
    +

    Partial read is only possible when using Queries. +In case the rows returned by the query have fewer columns than the entity that it will be mapped to, Spring Data will map the returned columns only. +This setting also applies to nested structs and their corresponding nested POJO properties.

    +
    +
    +
    +
    List<Trade> trades = this.spannerTemplate.query(Trade.class, Statement.of("SELECT action, symbol FROM trades"),
    +    new SpannerQueryOptions().setAllowMissingResultSetColumns(true));
    +
    +
    +
    +
    +

    If the setting is set to false, then an exception will be thrown if there are missing columns in the query result.

    +
    +
    +
    +
    Summary of options for Query vs Read
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Feature

    Query supports it

    Read supports it

    SQL

    yes

    no

    Partial read

    yes

    no

    Limits

    yes

    no

    Offsets

    yes

    no

    Secondary index

    yes

    yes

    Read using index range

    no

    yes

    Sorting

    yes

    no

    +
    +
    +
    +

    Write / Update

    +
    +

    The write methods of SpannerOperations accept a POJO and writes all of its properties to Spanner. +The corresponding Spanner table and entity metadata is obtained from the given object’s actual type.

    +
    +
    +

    If a POJO was retrieved from Spanner and its primary key properties values were changed and then written or updated, the operation will occur as if against a row with the new primary key values. +The row with the original primary key values will not be affected.

    +
    +
    +
    Insert
    +
    +

    The insert method of SpannerOperations accepts a POJO and writes all of its properties to Spanner, which means the operation will fail if a row with the POJO’s primary key already exists in the table.

    +
    +
    +
    +
    Trade t = new Trade();
    +this.spannerTemplate.insert(t);
    +
    +
    +
    +
    +
    +
    Update
    +
    +

    The update method of SpannerOperations accepts a POJO and writes all of its properties to Spanner, which means the operation will fail if the POJO’s primary key does not already exist in the table.

    +
    +
    +
    +
    // t was retrieved from a previous operation
    +this.spannerTemplate.update(t);
    +
    +
    +
    +
    +
    +
    Upsert
    +
    +

    The upsert method of SpannerOperations accepts a POJO and writes all of its properties to Spanner using update-or-insert.

    +
    +
    +
    +
    // t was retrieved from a previous operation or it's new
    +this.spannerTemplate.upsert(t);
    +
    +
    +
    +
    +
    +
    Partial Update
    +
    +

    The update methods of SpannerOperations operate by default on all properties within the given object, but also accept String[] and Optional<Set<String>> of column names. +If the Optional of set of column names is empty, then all columns are written to Spanner. +However, if the Optional is occupied by an empty set, then no columns will be written.

    +
    +
    +
    +
    // t was retrieved from a previous operation or it's new
    +this.spannerTemplate.update(t, "symbol", "action");
    +
    +
    +
    +
    +
    +
    +

    DML

    +
    +

    DML statements can be run by using SpannerOperations.executeDmlStatement. +Inserts, updates, and deletions can affect any number of rows and entities.

    +
    +
    +

    You can run partitioned DML updates by using the executePartitionedDmlStatement method. +Partitioned DML queries have performance benefits but also have restrictions and cannot be used inside transactions.

    +
    +
    +
    +

    Transactions

    +
    +

    SpannerOperations provides methods to run java.util.Function objects within a single transaction while making available the read and write methods from SpannerOperations.

    +
    +
    +
    Read/Write Transaction
    +
    +

    Read and write transactions are provided by SpannerOperations via the performReadWriteTransaction method:

    +
    +
    +
    +
    @Autowired
    +SpannerOperations mySpannerOperations;
    +
    +public String doWorkInsideTransaction() {
    +  return mySpannerOperations.performReadWriteTransaction(
    +    transActionSpannerOperations -> {
    +      // Work with transActionSpannerOperations here.
    +      // It is also a SpannerOperations object.
    +
    +      return "transaction completed";
    +    }
    +  );
    +}
    +
    +
    +
    +
    +

    The performReadWriteTransaction method accepts a Function that is provided an instance of a SpannerOperations object. +The final returned value and type of the function is determined by the user. +You can use this object just as you would a regular SpannerOperations with a few exceptions:

    +
    +
    +
      +
    • +

      Its read functionality cannot perform stale reads, because all reads and writes happen at the single point in time of the transaction.

      +
    • +
    • +

      It cannot perform sub-transactions via performReadWriteTransaction or performReadOnlyTransaction.

      +
    • +
    +
    +
    +

    As these read-write transactions are locking, it is recommended that you use the performReadOnlyTransaction if your function does not perform any writes.

    +
    +
    +
    +
    Read-only Transaction
    +
    +

    The performReadOnlyTransaction method is used to perform read-only transactions using a SpannerOperations:

    +
    +
    +
    +
    @Autowired
    +SpannerOperations mySpannerOperations;
    +
    +public String doWorkInsideTransaction() {
    +  return mySpannerOperations.performReadOnlyTransaction(
    +    transActionSpannerOperations -> {
    +      // Work with transActionSpannerOperations here.
    +      // It is also a SpannerOperations object.
    +
    +      return "transaction completed";
    +    }
    +  );
    +}
    +
    +
    +
    +
    +

    The performReadOnlyTransaction method accepts a Function that is provided an instance of a +SpannerOperations object. +This method also accepts a ReadOptions object, but the only attribute used is the timestamp used to determine the snapshot in time to perform the reads in the transaction. +If the timestamp is not set in the read options the transaction is run against the current state of the database. +The final returned value and type of the function is determined by the user. +You can use this object just as you would a regular SpannerOperations with +a few exceptions:

    +
    +
    +
      +
    • +

      Its read functionality cannot perform stale reads (other than the staleness set on the entire transaction), because all reads happen at the single point in time of the transaction.

      +
    • +
    • +

      It cannot perform sub-transactions via performReadWriteTransaction or performReadOnlyTransaction

      +
    • +
    • +

      It cannot perform any write operations.

      +
    • +
    +
    +
    +

    Because read-only transactions are non-locking and can be performed on points in time in the past, these are recommended for functions that do not perform write operations.

    +
    +
    +
    +
    Declarative Transactions with @Transactional Annotation
    +
    +

    This feature requires a bean of SpannerTransactionManager, which is provided when using spring-cloud-gcp-starter-data-spanner.

    +
    +
    +

    SpannerTemplate and SpannerRepository support running methods with the @Transactional annotation as transactions. +If a method annotated with @Transactional calls another method also annotated, then both methods will work within the same transaction. +performReadOnlyTransaction and performReadWriteTransaction cannot be used in @Transactional annotated methods because Cloud Spanner does not support transactions within transactions.

    +
    +
    +
    +
    +

    DML Statements

    +
    +

    SpannerTemplate supports DML Statements. +DML statements can also be run in transactions by using performReadWriteTransaction or by using the @Transactional annotation.

    +
    +
    +
    +
    +

    Repositories

    +
    +

    Spring Data Repositories are a powerful abstraction that can save you a lot of boilerplate code.

    +
    +
    +

    For example:

    +
    +
    +
    +
    public interface TraderRepository extends SpannerRepository<Trader, String> {
    +}
    +
    +
    +
    +
    +

    Spring Data generates a working implementation of the specified interface, which can be conveniently autowired into an application.

    +
    +
    +

    The Trader type parameter to SpannerRepository refers to the underlying domain type. +The second type parameter, String in this case, refers to the type of the key of the domain type.

    +
    +
    +

    For POJOs with a composite primary key, this ID type parameter can be any descendant of Object[] compatible with all primary key properties, any descendant of Iterable, or com.google.cloud.spanner.Key. +If the domain POJO type only has a single primary key column, then the primary key property type can be used or the Key type.

    +
    +
    +

    For example in case of Trades, that belong to a Trader, TradeRepository would look like this:

    +
    +
    +
    +
    public interface TradeRepository extends SpannerRepository<Trade, String[]> {
    +
    +}
    +
    +
    +
    +
    +
    +
    public class MyApplication {
    +
    +	@Autowired
    +	SpannerTemplate spannerTemplate;
    +
    +	@Autowired
    +	StudentRepository studentRepository;
    +
    +	public void demo() {
    +
    +		this.tradeRepository.deleteAll();
    +		String traderId = "demo_trader";
    +		Trade t = new Trade();
    +		t.symbol = stock;
    +		t.action = action;
    +		t.traderId = traderId;
    +		t.price = new BigDecimal("100.0");
    +		t.shares = 12345.6;
    +		this.spannerTemplate.insert(t);
    +
    +		Iterable<Trade> allTrades = this.tradeRepository.findAll();
    +
    +		int count = this.tradeRepository.countByAction("BUY");
    +
    +	}
    +}
    +
    +
    +
    +
    +

    CRUD Repository

    +
    +

    CrudRepository methods work as expected, with one thing Spanner specific: the save and saveAll methods work as update-or-insert.

    +
    +
    +
    +

    Paging and Sorting Repository

    +
    +

    You can also use PagingAndSortingRepository with Spanner Spring Data. +The sorting and pageable findAll methods available from this interface operate on the current state of the Spanner database. +As a result, beware that the state of the database (and the results) might change when moving page to page.

    +
    +
    +
    +

    Spanner Repository

    +
    +

    The SpannerRepository extends the PagingAndSortingRepository, but adds the read-only and the read-write transaction functionality provided by Spanner. +These transactions work very similarly to those of SpannerOperations, but is specific to the repository’s domain type and provides repository functions instead of template functions.

    +
    +
    +

    For example, this is a read-only transaction:

    +
    +
    +
    +
    @Autowired
    +SpannerRepository myRepo;
    +
    +public String doWorkInsideTransaction() {
    +  return myRepo.performReadOnlyTransaction(
    +    transactionSpannerRepo -> {
    +      // Work with the single-transaction transactionSpannerRepo here.
    +      // This is a SpannerRepository object.
    +
    +      return "transaction completed";
    +    }
    +  );
    +}
    +
    +
    +
    +
    +

    When creating custom repositories for your own domain types and query methods, you can extend SpannerRepository to access Cloud Spanner-specific features as well as all features from PagingAndSortingRepository and CrudRepository.

    +
    +
    +
    +
    +

    Query Methods

    +
    +

    SpannerRepository supports Query Methods. +Described in the following sections, these are methods residing in your custom repository interfaces of which implementations are generated based on their names and annotations. +Query Methods can read, write, and delete entities in Cloud Spanner. +Parameters to these methods can be any Cloud Spanner data type supported directly or via custom configured converters. +Parameters can also be of type Struct or POJOs. +If a POJO is given as a parameter, it will be converted to a Struct with the same type-conversion logic as used to create write mutations. +Comparisons using Struct parameters are limited to what is available with Cloud Spanner.

    +
    +
    +

    Query methods by convention

    +
    +
    +
    public interface TradeRepository extends SpannerRepository<Trade, String[]> {
    +    List<Trade> findByAction(String action);
    +
    +	int countByAction(String action);
    +
    +	// Named methods are powerful, but can get unwieldy
    +	List<Trade> findTop3DistinctByActionAndSymbolIgnoreCaseOrTraderIdOrderBySymbolDesc(
    +  			String action, String symbol, String traderId);
    +}
    +
    +
    +
    +
    +

    In the example above, the query methods in TradeRepository are generated based on the name of the methods, using the Spring Data Query creation naming convention.

    +
    +
    +

    List<Trade> findByAction(String action) would translate to a SELECT * FROM trades WHERE action = ?.

    +
    +
    +

    The function List<Trade> findTop3DistinctByActionAndSymbolIgnoreCaseOrTraderIdOrderBySymbolDesc(String action, String symbol, String traderId); will be translated as the equivalent of this SQL query:

    +
    +
    +
    +
    SELECT DISTINCT * FROM trades
    +WHERE ACTION = ? AND LOWER(SYMBOL) = LOWER(?) AND TRADER_ID = ?
    +ORDER BY SYMBOL DESC
    +LIMIT 3
    +
    +
    +
    +

    The following filter options are supported:

    +
    +
    +
      +
    • +

      Equality

      +
    • +
    • +

      Greater than or equals

      +
    • +
    • +

      Greater than

      +
    • +
    • +

      Less than or equals

      +
    • +
    • +

      Less than

      +
    • +
    • +

      Is null

      +
    • +
    • +

      Is not null

      +
    • +
    • +

      Is true

      +
    • +
    • +

      Is false

      +
    • +
    • +

      Like a string

      +
    • +
    • +

      Not like a string

      +
    • +
    • +

      Contains a string

      +
    • +
    • +

      Not contains a string

      +
    • +
    • +

      In

      +
    • +
    • +

      Not in

      +
    • +
    +
    +
    +

    Note that the phrase SymbolIgnoreCase is translated to LOWER(SYMBOL) = LOWER(?) indicating a non-case-sensitive matching. +The IgnoreCase phrase may only be appended to fields that correspond to columns of type STRING or BYTES. +The Spring Data "AllIgnoreCase" phrase appended at the end of the method name is not supported.

    +
    +
    +

    The Like or NotLike naming conventions:

    +
    +
    +
    +
    List<Trade> findBySymbolLike(String symbolFragment);
    +
    +
    +
    +
    +

    The param symbolFragment can contain wildcard characters for string matching such as _ and %.

    +
    +
    +

    The Contains and NotContains naming conventions:

    +
    +
    +
    +
    List<Trade> findBySymbolContains(String symbolFragment);
    +
    +
    +
    +
    +

    The param symbolFragment is a regular expression that is checked for occurrences.

    +
    +
    +

    The In and NotIn keywords must be used with Iterable corresponding parameters.

    +
    +
    +

    Delete queries are also supported. +For example, query methods such as deleteByAction or removeByAction delete entities found by findByAction. +The delete operation happens in a single transaction.

    +
    +
    +

    Delete queries can have the following return types: +* An integer type that is the number of entities deleted +* A collection of entities that were deleted +* void

    +
    +
    +
    +

    Custom SQL/DML query methods

    +
    +

    The example above for List<Trade> fetchByActionNamedQuery(String action) does not match the Spring Data Query creation naming convention, so we have to map a parametrized Spanner SQL query to it.

    +
    +
    +

    The SQL query for the method can be mapped to repository methods in one of two ways:

    +
    +
    +
      +
    • +

      namedQueries properties file

      +
    • +
    • +

      using the @Query annotation

      +
    • +
    +
    +
    +

    The names of the tags of the SQL correspond to the @Param annotated names of the method parameters.

    +
    +
    +

    Interleaved properties are loaded eagerly, unless they are annotated with @Interleaved(lazy = true).

    +
    +
    +

    Custom SQL query methods can accept a single Sort or Pageable parameter that is applied on top of the specified custom query. +It is the recommended way to control the sort order of the results, which is not guaranteed by the ORDER BY clause in the SQL query. +This is due to the fact that the user-provided query is used as a sub-query, and Cloud Spanner doesn’t preserve order in subquery results.

    +
    +
    +

    You might want to use ORDER BY with LIMIT to obtain the top records, according to a specified order. +However, to ensure the correct sort order of the final result set, sort options have to be passed in with a Pageable.

    +
    +
    +
    +
    	@Query("SELECT * FROM trades")
    +	List<Trade> fetchTrades(Pageable pageable);
    +
    +	@Query("SELECT * FROM trades ORDER BY price DESC LIMIT 1")
    + 	Trade topTrade(Pageable pageable);
    +
    +
    +
    +
    +

    This can be used:

    +
    +
    +
    +
    	List<Trade> customSortedTrades = tradeRepository.fetchTrades(PageRequest
    +  				.of(2, 2, org.springframework.data.domain.Sort.by(Order.asc("id"))));
    +
    +
    +
    +
    +

    The results would be sorted by "id" in ascending order.

    +
    +
    +

    Your query method can also return non-entity types:

    +
    +
    +
    +
      	@Query("SELECT COUNT(1) FROM trades WHERE action = @action")
    +  	int countByActionQuery(String action);
    +
    +  	@Query("SELECT EXISTS(SELECT COUNT(1) FROM trades WHERE action = @action)")
    +  	boolean existsByActionQuery(String action);
    +
    +  	@Query("SELECT action FROM trades WHERE action = @action LIMIT 1")
    +  	String getFirstString(@Param("action") String action);
    +
    +  	@Query("SELECT action FROM trades WHERE action = @action")
    +  	List<String> getFirstStringList(@Param("action") String action);
    +
    +
    +
    +
    +

    DML statements can also be run by query methods, but the only possible return value is a long representing the number of affected rows. +The dmlStatement boolean setting must be set on @Query to indicate that the query method is run as a DML statement.

    +
    +
    +
    +
      	@Query(value = "DELETE FROM trades WHERE action = @action", dmlStatement = true)
    +  	long deleteByActionQuery(String action);
    +
    +
    +
    +
    +
    Query methods with named queries properties
    +
    +

    By default, the namedQueriesLocation attribute on @EnableSpannerRepositories points to the META-INF/spanner-named-queries.properties file. +You can specify the query for a method in the properties file by providing the SQL as the value for the "interface.method" property:

    +
    +
    +
    +
    Trade.fetchByActionNamedQuery=SELECT * FROM trades WHERE trades.action = @tag0
    +
    +
    +
    +
    +
    public interface TradeRepository extends SpannerRepository<Trade, String[]> {
    +	// This method uses the query from the properties file instead of one generated based on name.
    +	List<Trade> fetchByActionNamedQuery(@Param("tag0") String action);
    +}
    +
    +
    +
    +
    +
    +
    Query methods with annotation
    +
    +

    Using the @Query annotation:

    +
    +
    +
    +
    public interface TradeRepository extends SpannerRepository<Trade, String[]> {
    +    @Query("SELECT * FROM trades WHERE trades.action = @tag0")
    +    List<Trade> fetchByActionNamedQuery(@Param("tag0") String action);
    +}
    +
    +
    +
    +
    +

    Table names can be used directly. +For example, "trades" in the above example. +Alternatively, table names can be resolved from the @Table annotation on domain classes as well. +In this case, the query should refer to table names with fully qualified class names between : +characters: :fully.qualified.ClassName:. +A full example would look like:

    +
    +
    +
    +
    @Query("SELECT * FROM :com.example.Trade: WHERE trades.action = @tag0")
    +List<Trade> fetchByActionNamedQuery(String action);
    +
    +
    +
    +
    +

    This allows table names evaluated with SpEL to be used in custom queries.

    +
    +
    +

    SpEL can also be used to provide SQL parameters:

    +
    +
    +
    +
    @Query("SELECT * FROM :com.example.Trade: WHERE trades.action = @tag0
    +  AND price > #{#priceRadius * -1} AND price < #{#priceRadius * 2}")
    +List<Trade> fetchByActionNamedQuery(String action, Double priceRadius);
    +
    +
    +
    +
    +

    When using the IN SQL clause, remember to use IN UNNEST(@iterableParam) to specify a single Iterable parameter. +You can also use a fixed number of singular parameters such as IN (@stringParam1, @stringParam2).

    +
    +
    +
    +
    +

    Projections

    +
    +

    Spring Data Spanner supports projections. +You can define projection interfaces based on domain types and add query methods that return them in your repository:

    +
    +
    +
    +
    public interface TradeProjection {
    +
    +	String getAction();
    +
    +	@Value("#{target.symbol + ' ' + target.action}")
    +	String getSymbolAndAction();
    +}
    +
    +public interface TradeRepository extends SpannerRepository<Trade, Key> {
    +
    +	List<Trade> findByTraderId(String traderId);
    +
    +	List<TradeProjection> findByAction(String action);
    +
    +	@Query("SELECT action, symbol FROM trades WHERE action = @action")
    +	List<TradeProjection> findByQuery(String action);
    +}
    +
    +
    +
    +
    +

    Projections can be provided by name-convention-based query methods as well as by custom SQL queries. +If using custom SQL queries, you can further restrict the columns retrieved from Spanner to just those required by the projection to improve performance.

    +
    +
    +

    Properties of projection types defined using SpEL use the fixed name target for the underlying domain object. +As a result accessing underlying properties take the form target.<property-name>.

    +
    +
    +
    +

    Empty result handling in repository methods

    +
    +

    Java java.util.Optional can be used to indicate the potential absence of a return value.

    +
    +
    +

    Alternatively, query methods can return the result without a wrapper. +In that case the absence of a query result is indicated by returning null. +Repository methods returning collections are guaranteed never to return null but rather the corresponding empty collection.

    +
    +
    + + + + + +
    + + +You can enable nullability checks. For more details please see Spring Framework’s nullability docs. +
    +
    +
    +
    +

    REST Repositories

    +
    +

    When running with Spring Boot, repositories can be exposed as REST services by simply adding this dependency to your pom file:

    +
    +
    +
    +
    <dependency>
    +  <groupId>org.springframework.boot</groupId>
    +  <artifactId>spring-boot-starter-data-rest</artifactId>
    +</dependency>
    +
    +
    +
    +

    If you prefer to configure parameters (such as path), you can use @RepositoryRestResource annotation:

    +
    +
    +
    +
    @RepositoryRestResource(collectionResourceRel = "trades", path = "trades")
    +public interface TradeRepository extends SpannerRepository<Trade, Key> {
    +}
    +
    +
    +
    +
    + + + + + +
    + + +For classes that have composite keys (multiple @PrimaryKey fields), only the Key type is supported for the repository ID type. +
    +
    +
    +

    For example, you can retrieve all Trade objects in the repository by using curl http://<server>:<port>/trades, or any specific trade via curl http://<server>:<port>/trades/<trader_id>,<trade_id>.

    +
    +
    +

    The separator between your primary key components, id and trader_id in this case, is a comma by default, but can be configured to any string not found in your key values by extending the SpannerKeyIdConverter class:

    +
    +
    +
    +
    @Component
    +class MySpecialIdConverter extends SpannerKeyIdConverter {
    +
    +    @Override
    +    protected String getUrlIdSeparator() {
    +        return ":";
    +    }
    +}
    +
    +
    +
    +
    +

    You can also write trades using curl -XPOST -H"Content-Type: application/json" -d@test.json http://<server>:<port>/trades/ where the file test.json holds the JSON representation of a Trade object.

    +
    +
    +
    +
    +

    Database and Schema Admin

    +
    +

    Databases and tables inside Spanner instances can be created automatically from SpannerPersistentEntity objects:

    +
    +
    +
    +
    @Autowired
    +private SpannerSchemaUtils spannerSchemaUtils;
    +
    +@Autowired
    +private SpannerDatabaseAdminTemplate spannerDatabaseAdminTemplate;
    +
    +public void createTable(SpannerPersistentEntity entity) {
    +	if(!spannerDatabaseAdminTemplate.tableExists(entity.tableName()){
    +
    +	  // The boolean parameter indicates that the database will be created if it does not exist.
    +	  spannerDatabaseAdminTemplate.executeDdlStrings(Arrays.asList(
    +            spannerSchemaUtils.getCreateTableDDLString(entity.getType())), true);
    +	}
    +}
    +
    +
    +
    +
    +

    Schemas can be generated for entire object hierarchies with interleaved relationships and composite keys.

    +
    +
    +
    +

    Events

    +
    +

    Spring Data Cloud Spanner publishes events extending the Spring Framework’s ApplicationEvent to the context that can be received by ApplicationListener beans you register.

    +
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeDescriptionContents

    AfterReadEvent

    Published immediately after entities are read by key from Cloud Spanner by SpannerTemplate

    The entities loaded. The read options and key-set originally specified for the load operation.

    AfterQueryEvent

    Published immediately after entities are read by query from Cloud Spanner by SpannerTemplate

    The entities loaded. The query options and query statement originally specified for the load operation.

    BeforeExecuteDmlEvent

    Published immediately before DML statements are executed by SpannerTemplate

    The DML statement to execute.

    AfterExecuteDmlEvent

    Published immediately after DML statements are executed by SpannerTemplate

    The DML statement to execute and the number of rows affected by the operation as reported by Cloud Spanner.

    BeforeSaveEvent

    Published immediately before upsert/update/insert operations are executed by SpannerTemplate

    The mutations to be sent to Cloud Spanner, the entities to be saved, and optionally the properties in those entities to save.

    AfterSaveEvent

    Published immediately after upsert/update/insert operations are executed by SpannerTemplate

    The mutations sent to Cloud Spanner, the entities to be saved, and optionally the properties in those entities to save.

    BeforeDeleteEvent

    Published immediately before delete operations are executed by SpannerTemplate

    The mutations to be sent to Cloud Spanner. The target entities, keys, or entity type originally specified for the delete operation.

    AfterDeleteEvent

    Published immediately after delete operations are executed by SpannerTemplate

    The mutations sent to Cloud Spanner. The target entities, keys, or entity type originally specified for the delete operation.

    +
    +
    +

    Auditing

    +
    +

    Spring Data Cloud Spanner supports the @LastModifiedDate and @LastModifiedBy auditing annotations for properties:

    +
    +
    +
    +
    @Table
    +public class SimpleEntity {
    +    @PrimaryKey
    +    String id;
    +
    +    @LastModifiedBy
    +    String lastUser;
    +
    +    @LastModifiedDate
    +    DateTime lastTouched;
    +}
    +
    +
    +
    +
    +

    Upon insert, update, or save, these properties will be set automatically by the framework before mutations are generated and saved to Cloud Spanner.

    +
    +
    +

    To take advantage of these features, add the @EnableSpannerAuditing annotation to your configuration class and provide a bean for an AuditorAware<A> implementation where the type A is the desired property type annotated by @LastModifiedBy:

    +
    +
    +
    +
    @Configuration
    +@EnableSpannerAuditing
    +public class Config {
    +
    +    @Bean
    +    public AuditorAware<String> auditorProvider() {
    +        return () -> Optional.of("YOUR_USERNAME_HERE");
    +    }
    +}
    +
    +
    +
    +
    +

    The AuditorAware interface contains a single method that supplies the value for fields annotated by @LastModifiedBy and can be of any type. +One alternative is to use Spring Security’s User type:

    +
    +
    +
    +
    class SpringSecurityAuditorAware implements AuditorAware<User> {
    +
    +  public Optional<User> getCurrentAuditor() {
    +
    +    return Optional.ofNullable(SecurityContextHolder.getContext())
    +			  .map(SecurityContext::getAuthentication)
    +			  .filter(Authentication::isAuthenticated)
    +			  .map(Authentication::getPrincipal)
    +			  .map(User.class::cast);
    +  }
    +}
    +
    +
    +
    +
    +

    You can also set a custom provider for properties annotated @LastModifiedDate by providing a bean for DateTimeProvider and providing the bean name to @EnableSpannerAuditing(dateTimeProviderRef = "customDateTimeProviderBean").

    +
    +
    +
    +

    Multi-Instance Usage

    +
    +

    Your application can be configured to use multiple Cloud Spanner instances or databases by providing a custom bean for DatabaseIdProvider. +The default bean uses the instance ID, database name, and project ID options you configured in application.properties.

    +
    +
    +
    +
        @Bean
    +    public DatabaseIdProvider databaseIdProvider() {
    +        // return custom connection options provider
    +    }
    +
    +
    +
    +
    +

    The DatabaseId given by this provider is used as the target database name and instance of each operation Spring Data Cloud Spanner executes. +By providing a custom implementation of this bean (for example, supplying a thread-local DatabaseId), you can direct your application to use multiple instances or databases.

    +
    +
    +

    Database administrative operations, such as creating tables using SpannerDatabaseAdminTemplate, will also utilize the provided DatabaseId.

    +
    +
    +

    If you would like to configure every aspect of each connection (such as pool size and retry settings), you can supply a bean for Supplier<DatabaseClient>.

    +
    +
    +
    +

    Spring Boot Actuator Support

    +
    +

    Cloud Spanner Health Indicator

    +
    +

    If you are using Spring Boot Actuator, you can take advantage of the Cloud Spanner health indicator called spanner. +The health indicator will verify whether Cloud Spanner is up and accessible by your application. +To enable it, all you need to do is add the Spring Boot Actuator to your project.

    +
    +
    +

    The spanner 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).

    +
    +
    +
    +
    <dependency>
    +    <groupId>org.springframework.boot</groupId>
    +    <artifactId>spring-boot-starter-actuator</artifactId>
    +</dependency>
    +
    +
    +
    + + + + + +
    + + +If your application already has actuator and Cloud Spanner starters, this health indicator is enabled by default. +To disable the Cloud Spanner indicator, set management.health.spanner.enabled to false. +
    +
    +
    +

    The health indicator validates the connection to Spanner by executing a query. +A query to validate can be configured via spring.cloud.gcp.spanner.health.query property.

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    management.health.spanner.enabled

    Whether to enable the Spanner health indicator

    No

    true with Spring Boot Actuator, false otherwise

    spring.cloud.gcp.spanner.health.query

    A query to validate

    No

    SELECT 1

    +
    +
    +
    +

    Cloud Spanner Emulator

    +
    +

    The Cloud SDK provides a local, in-memory emulator for Cloud Spanner, which you can use to develop and test your application. As the emulator stores data only in memory, it will not persist data across runs. It is intended to help you use Cloud Spanner for local development and testing, not for production deployments.

    +
    +
    +

    In order to set up and start the emulator, you can follow these steps.

    +
    +
    +

    This command can be used to create Cloud Spanner instances:

    +
    +
    +
    +
    $ gcloud spanner instances create <instance-name> --config=emulator-config --description="<description>" --nodes=1
    +
    +
    +
    +

    Once the Spanner emulator is running, ensure that the following properties are set in your application.properties of your Spring application:

    +
    +
    +
    +
    spring.cloud.gcp.spanner.emulator.enabled=true
    +
    +
    +
    +

    Note that the default emulator hostname and port (i.e., localhost:9010) is used. If you prefer a customized value, ensure the following property is set in your application.properties of your Spring application:

    +
    +
    +
    +
    spring.cloud.gcp.spanner.emulator-host=ip:port
    +
    +
    +
    + +
    +

    Test

    +
    +

    Testcontainers provides a gcloud module which offers SpannerEmulatorContainer. See more at the docs

    +
    +
    +
    +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/spring-cloud-bus-pubsub.html b/3.8.13/reference/html/spring-cloud-bus-pubsub.html new file mode 100644 index 0000000000..81aa0fe703 --- /dev/null +++ b/3.8.13/reference/html/spring-cloud-bus-pubsub.html @@ -0,0 +1,277 @@ + + + + + + + +Spring Cloud Bus + + + + + + + + + +
    +
    +
    + +
    +
    +

    Spring Cloud Bus

    +
    +
    +

    Using Cloud Pub/Sub as the Spring Cloud Bus implementation is as simple as importing the spring-cloud-gcp-starter-bus-pubsub starter.

    +
    +
    +

    This starter brings in the Spring Cloud Stream binder for Cloud Pub/Sub, which is used to both publish and subscribe to the bus. +If the bus topic (named springCloudBus by default) does not exist, the binder automatically creates it. +The binder also creates anonymous subscriptions for each project using the spring-cloud-gcp-starter-bus-pubsub starter.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +  <groupId>com.google.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-starter-bus-pubsub</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-bus-pubsub")
    +}
    +
    +
    +
    +
    +

    Configuration Management with Spring Cloud Config and Spring Cloud Bus

    +
    +

    Spring Cloud Bus can be used to push configuration changes from a Spring Cloud Config server to the clients listening on the same bus.

    +
    +
    +

    To use Spring Framework on Google Cloud Pub/Sub as the bus implementation, both the configuration server and the configuration client need the spring-cloud-gcp-starter-bus-pubsub dependency.

    +
    +
    +

    All other configuration is standard to Spring Cloud Config.

    +
    +
    +
    +spring cloud bus over pubsub +
    +
    +
    +

    Spring Cloud Config Server typically runs on port 8888, and can read configuration from a variety of source control systems such as GitHub, and even from the local filesystem. +When the server is notified that new configuration is available, it fetches the updated configuration and sends a notification (RefreshRemoteApplicationEvent) out via Spring Cloud Bus.

    +
    +
    +

    When configuration is stored locally, config server polls the parent directory for changes. +With configuration stored in source control repository, such as GitHub, the config server needs to be notified that a new version of configuration is available. +In a deployed server, this would be done automatically through a GitHub webhook, but in a local testing scenario, the /monitor HTTP endpoint needs to be invoked manually.

    +
    +
    +
    +
    curl -X POST http://localhost:8888/monitor -H "X-Github-Event: push" -H "Content-Type: application/json" -d '{"commits": [{"modified": ["application.properties"]}]}'
    +
    +
    +
    +

    By adding the spring-cloud-gcp-starter-bus-pubsub dependency, you instruct Spring Cloud Bus to use Cloud Pub/Sub to broadcast configuration changes. +Spring Cloud Bus will then create a topic named springCloudBus, as well as a subscription for each configuration client.

    +
    +
    +

    The configuration server happens to also be a configuration client, subscribing to the configuration changes that it sends out. +Thus, in a scenario with one configuration server and one configuration client, two anonymous subscriptions to the springCloudBus topic are created. +However, a config server disables configuration refresh by default (see ConfigServerBootstrapApplicationListener for more details).

    +
    +
    +

    A demo application showing configuration management and distribution over a Cloud Pub/Sub-powered bus is available. +The sample contains two examples of configuration management with Spring Cloud Bus: one monitoring a local file system, and the other retrieving configuration from a GitHub repository.

    +
    +
    +
    +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/spring-cloud-gcp.html b/3.8.13/reference/html/spring-cloud-gcp.html new file mode 100644 index 0000000000..8432f58015 --- /dev/null +++ b/3.8.13/reference/html/spring-cloud-gcp.html @@ -0,0 +1,12334 @@ + + + + + + + + +Spring Framework on Google Cloud + + + + + + + + + +
    +
    +
    + +
    +
    +
    +
    +

    3.8.13

    +
    +
    +
    +
    +

    1. Introduction

    +
    +
    +

    The Spring Framework on Google Cloud project makes the Spring Framework a first-class citizen of Google Cloud .

    +
    +
    +

    Spring Framework on Google Cloud lets you leverage the power and simplicity of the Spring Framework to:

    +
    +
    +
      +
    • +

      Publish and subscribe to Google Cloud Pub/Sub topics

      +
    • +
    • +

      Configure Spring JDBC with a few properties to use Google Cloud SQL

      +
    • +
    • +

      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

      +
    • +
    • +

      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

      +
    • +
    • +

      Analyze your images for text, objects, and other content with Google Cloud Vision

      +
    • +
    +
    +
    +
    +
    +

    2. Getting Started

    +
    +
    +

    This section describes how to get up to speed with Spring Framework on Google Cloud libraries.

    +
    +
    +

    2.1. Compatibility with Spring Project Versions

    +
    +

    Spring Framework on Google Cloud has dependency and transitive dependencies on Spring Projects. The table below outlines the versions of Spring Cloud, Spring Boot and Spring Framework versions that are compatible with certain Spring Framework on Google Cloud version.

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + +
    Spring Framework on Google CloudSpring CloudSpring BootSpring Framework

    2.x

    2020.0.x (3.0/Illford)

    2.4.x, 2.5.x

    5.3.x

    3.x

    2021.0.x (3.1/Jubilee)

    2.6.x, 2.7.x

    5.3.x

    +
    +
    +

    2.2. Setting up Dependencies

    +
    +

    All Spring Framework on Google Cloud artifacts are made available through Maven Central. +The following resources are provided to help you setup the libraries for your project:

    +
    +
    +
      +
    • +

      Maven Bill of Materials for dependency management

      +
    • +
    • +

      Starter Dependencies for depending on Spring Framework on Google Cloud modules

      +
    • +
    +
    +
    +

    You may also consult our Github project to examine the code or build directly from source.

    +
    +
    +

    2.2.1. Bill of Materials

    +
    +

    The Spring Framework on Google Cloud Bill of Materials (BOM) contains the versions of all the dependencies it uses.

    +
    +
    +

    If you’re a Maven user, adding the following to your pom.xml file will allow you omit any Spring Framework on Google Cloud dependency version numbers from your configuration. +Instead, the version of the BOM you’re using determines the versions of the used dependencies.

    +
    +
    +
    +
    <dependencyManagement>
    +   <dependencies>
    +       <dependency>
    +           <groupId>com.google.cloud</groupId>
    +           <artifactId>spring-cloud-gcp-dependencies</artifactId>
    +           <version>3.8.13</version>
    +           <type>pom</type>
    +           <scope>import</scope>
    +       </dependency>
    +   </dependencies>
    +</dependencyManagement>
    +
    +
    +
    +

    Or, if you’re a Gradle user:

    +
    +
    +
    +
    dependencies {
    +    implementation platform("com.google.cloud:spring-cloud-gcp-dependencies:3.8.13")
    +}
    +
    +
    +
    +

    In the following sections, it will be assumed you are using the Spring Framework on Google Cloud BOM and the dependency snippets will not contain versions.

    +
    +
    +
    +

    2.2.2. Starter Dependencies

    +
    +

    Spring Framework on Google Cloud offers starter dependencies through Maven to easily depend on different modules of the library. +Each starter contains all the dependencies and transitive dependencies needed to begin using their corresponding Spring Framework on Google Cloud module.

    +
    +
    +

    For example, if you wish to write a Spring application with Cloud Pub/Sub, you would include the spring-cloud-gcp-starter-pubsub dependency in your project. +You do not need to include the underlying spring-cloud-gcp-pubsub dependency, because the starter dependency includes it.

    +
    +
    +

    A summary of these artifacts are provided below.

    +
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Spring Framework on Google Cloud StarterDescriptionMaven Artifact Name

    Core

    Automatically configure authentication and Google project settings

    com.google.cloud:spring-cloud-gcp-starter

    Cloud Spanner

    Provides integrations with Google Cloud Spanner

    com.google.cloud:spring-cloud-gcp-starter-data-spanner

    Cloud Datastore

    Provides integrations with Google Cloud Datastore

    com.google.cloud:spring-cloud-gcp-starter-data-datastore

    Cloud Pub/Sub

    Provides integrations with Google Cloud Pub/Sub

    com.google.cloud:spring-cloud-gcp-starter-pubsub

    Logging

    Enables Cloud Logging

    com.google.cloud:spring-cloud-gcp-starter-logging

    SQL - MySQL

    Cloud SQL integrations with MySQL

    com.google.cloud:spring-cloud-gcp-starter-sql-mysql

    SQL - PostgreSQL

    Cloud SQL integrations with PostgreSQL

    com.google.cloud:spring-cloud-gcp-starter-sql-postgresql

    Storage

    Provides integrations with Google Cloud Storage and Spring Resource

    com.google.cloud:spring-cloud-gcp-starter-storage

    Config

    Enables usage of Google Runtime Configuration API as a Spring Cloud Config server

    com.google.cloud:spring-cloud-gcp-starter-config

    Trace

    Enables instrumentation with Google Cloud Trace

    com.google.cloud:spring-cloud-gcp-starter-trace

    Vision

    Provides integrations with Google Cloud Vision

    com.google.cloud:spring-cloud-gcp-starter-vision

    Security - IAP

    Provides a security layer over applications deployed to Google Cloud

    com.google.cloud:spring-cloud-gcp-starter-security-iap

    Security - Firebase

    Provides a security layer over applications deployed to Firebase

    com.google.cloud:spring-cloud-gcp-starter-security-firebase

    +
    +
    +

    2.2.3. Spring Initializr

    +
    +

    Spring Initializr is a tool which generates the scaffolding code for a new Spring Boot project. +It handles the work of generating the Maven or Gradle build file so you do not have to manually add the dependencies yourself.

    +
    +
    +

    Spring Initializr offers three modules from Spring Framework on Google Cloud that you can use to generate your project.

    +
    +
    +
      +
    • +

      GCP Support: The GCP Support module contains auto-configuration support for every Spring Framework on Google Cloud integration. +Most of the autoconfiguration code is only enabled if the required dependency is added to your project.

      +
    • +
    • +

      GCP Messaging: Google Cloud Pub/Sub integrations work out of the box.

      +
    • +
    • +

      GCP Storage: Google Cloud Storage integrations work out of the box.

      +
    • +
    +
    +
    +
    +
    +

    2.3. Learning Spring Framework on Google Cloud

    +
    +

    There are a variety of resources to help you learn how to use Spring Framework on Google Cloud libraries.

    +
    +
    +

    2.3.1. Sample Applications

    +
    +

    The easiest way to learn how to use Spring Framework on Google Cloud is to consult the sample applications on Github. +Spring Framework on Google Cloud provides sample applications which demonstrate how to use every integration in the library. +The table below highlights several samples of the most commonly used integrations in Spring Framework on Google Cloud.

    +
    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Google Cloud IntegrationSample Application

    Cloud Pub/Sub

    spring-cloud-gcp-pubsub-sample

    Cloud Spanner

    spring-cloud-gcp-data-spanner-repository-sample

    +

    spring-cloud-gcp-data-spanner-template-sample

    Datastore

    spring-cloud-gcp-data-datastore-sample

    Cloud SQL (w/ MySQL)

    spring-cloud-gcp-sql-mysql-sample

    Cloud Storage

    spring-cloud-gcp-storage-resource-sample

    Cloud Logging

    spring-cloud-gcp-logging-sample

    Trace

    spring-cloud-gcp-trace-sample

    Cloud Vision

    spring-cloud-gcp-vision-api-sample

    Cloud Security - IAP

    spring-cloud-gcp-security-iap-sample

    Cloud Security - Firebase

    spring-cloud-gcp-security-firebase-sample

    +
    +

    Each sample application demonstrates how to use Spring Framework on Google Cloud libraries in context and how to setup the dependencies for the project. +The applications are fully functional and can be deployed to Google Cloud as well. +If you are interested, you may consult guides for deploying an application to AppEngine and to Google Kubernetes Engine.

    +
    +
    +
    +

    2.3.2. Codelabs

    +
    +

    For a more hands-on approach, there are several guides and codelabs to help you get up to speed. +These guides provide step-by-step instructions for building an application using Spring Framework on Google Cloud.

    +
    +
    +

    Some examples include:

    +
    + +
    +

    The full collection of Spring codelabs can be found on the Google Developer Codelabs page.

    +
    +
    +
    +
    +
    +
    +

    3. Spring Framework on Google Cloud Core

    +
    +
    +

    Each Spring Framework on Google Cloud module uses GcpProjectIdProvider and CredentialsProvider to get the Google Cloud project ID and access credentials.

    +
    +
    +

    Spring Framework on Google Cloud provides a Spring Boot starter to auto-configure the core components.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter")
    +}
    +
    +
    +
    +

    3.1. Configuration

    +
    +

    The following options may be configured with Spring Cloud core.

    +
    + ++++++ + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.core.enabled

    Enables or disables Google Cloud core auto configuration

    No

    true

    +
    +
    +

    3.2. Project ID

    +
    +

    GcpProjectIdProvider is a functional interface that returns a Google Cloud project ID string.

    +
    +
    +
    +
    public interface GcpProjectIdProvider {
    +    String getProjectId();
    +}
    +
    +
    +
    +
    +

    The Spring Framework on Google Cloud starter auto-configures a GcpProjectIdProvider. +If a spring.cloud.gcp.project-id property is specified, the provided GcpProjectIdProvider returns that property value.

    +
    +
    +
    +
    spring.cloud.gcp.project-id=my-gcp-project-id
    +
    +
    +
    +
    +

    Otherwise, the project ID is discovered based on an +ordered list of rules:

    +
    +
    +
      +
    1. +

      The project ID specified by the GOOGLE_CLOUD_PROJECT environment variable

      +
    2. +
    3. +

      The Google App Engine project ID

      +
    4. +
    5. +

      The project ID specified in the JSON credentials file pointed by the GOOGLE_APPLICATION_CREDENTIALS environment variable

      +
    6. +
    7. +

      The Google Cloud SDK project ID

      +
    8. +
    9. +

      The Google Compute Engine project ID, from the Google Compute Engine Metadata Server

      +
    10. +
    +
    +
    +
    +

    3.3. Credentials

    +
    +

    CredentialsProvider is a functional interface that returns the credentials to authenticate and authorize calls to Google Cloud Client Libraries.

    +
    +
    +
    +
    public interface CredentialsProvider {
    +  Credentials getCredentials() throws IOException;
    +}
    +
    +
    +
    +
    +

    The Spring Framework on Google Cloud starter auto-configures a CredentialsProvider. +It uses the spring.cloud.gcp.credentials.location property to locate the OAuth2 private key of a Google service account. +Keep in mind this property is a Spring Resource, so the credentials file can be obtained from a number of different locations such as the file system, classpath, URL, etc. +The next example specifies the credentials location property in the file system.

    +
    +
    +
    +
    spring.cloud.gcp.credentials.location=file:/usr/local/key.json
    +
    +
    +
    +

    Alternatively, you can set the credentials by directly specifying the spring.cloud.gcp.credentials.encoded-key property. +The value should be the base64-encoded account private key in JSON format.

    +
    +
    +

    If that credentials aren’t specified through properties, the starter tries to discover credentials from a number of places:

    +
    +
    +
      +
    1. +

      Credentials file pointed to by the GOOGLE_APPLICATION_CREDENTIALS environment variable

      +
    2. +
    3. +

      Credentials provided by the Google Cloud SDK gcloud auth application-default login command

      +
    4. +
    5. +

      Google App Engine built-in credentials

      +
    6. +
    7. +

      Google Cloud Shell built-in credentials

      +
    8. +
    9. +

      Google Compute Engine built-in credentials

      +
    10. +
    +
    +
    +

    If your app is running on Google App Engine or Google Compute Engine, in most cases, you should omit the spring.cloud.gcp.credentials.location property and, instead, let the Spring Framework on Google Cloud Starter get the correct credentials for those environments. +On App Engine Standard, the App Identity service account credentials are used, on App Engine Flexible, the Flexible service account credential are used and on Google Compute Engine, the Compute Engine Default Service Account is used.

    +
    +
    +

    3.3.1. Scopes

    +
    +

    By default, the credentials provided by the Spring Framework on Google Cloud Starter contain scopes for every service supported by Spring Framework on Google Cloud.

    +
    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Service

    Scope

    Spanner

    www.googleapis.com/auth/spanner.admin, www.googleapis.com/auth/spanner.data

    Datastore

    www.googleapis.com/auth/datastore

    Pub/Sub

    www.googleapis.com/auth/pubsub

    Storage (Read Only)

    www.googleapis.com/auth/devstorage.read_only

    Storage (Read/Write)

    www.googleapis.com/auth/devstorage.read_write

    Runtime Config

    www.googleapis.com/auth/cloudruntimeconfig

    Trace (Append)

    www.googleapis.com/auth/trace.append

    Cloud Platform

    www.googleapis.com/auth/cloud-platform

    Vision

    www.googleapis.com/auth/cloud-vision

    +
    +

    The Spring Framework on Google Cloud starter allows you to configure a custom scope list for the provided credentials. +To do that, specify a comma-delimited list of Google OAuth2 scopes in the spring.cloud.gcp.credentials.scopes property.

    +
    +
    +

    spring.cloud.gcp.credentials.scopes is a comma-delimited list of Google OAuth2 scopes for Google Cloud services that the credentials returned by the provided CredentialsProvider support.

    +
    +
    +
    +
    spring.cloud.gcp.credentials.scopes=https://www.googleapis.com/auth/pubsub,https://www.googleapis.com/auth/sqlservice.admin
    +
    +
    +
    +

    You can also use DEFAULT_SCOPES placeholder as a scope to represent the starters default scopes, and append the additional scopes you need to add.

    +
    +
    +
    +
    spring.cloud.gcp.credentials.scopes=DEFAULT_SCOPES,https://www.googleapis.com/auth/cloud-vision
    +
    +
    +
    +
    +
    +

    3.4. Environment

    +
    +

    GcpEnvironmentProvider is a functional interface, auto-configured by the Spring Framework on Google Cloud starter, that returns a GcpEnvironment enum. +The provider can help determine programmatically in which Google Cloud environment (App Engine Flexible, App Engine Standard, Kubernetes Engine or Compute Engine) the application is deployed.

    +
    +
    +
    +
    public interface GcpEnvironmentProvider {
    +    GcpEnvironment getCurrentEnvironment();
    +}
    +
    +
    +
    +
    +
    +

    3.5. Customizing bean scope

    +
    +

    Spring Framework on Google Cloud starters autoconfigure all necessary beans in the default singleton scope. +If you need a particular bean or set of beans to be recreated dynamically (for example, to rotate credentials), there are two options:

    +
    +
    +
      +
    1. +

      Annotate custom beans of the necessary types with @RefreshScope. +This makes the most sense if your application is already redefining those beans.

      +
    2. +
    3. +

      Override the scope for autoconfigured beans by listing them in the Spring Cloud property spring.cloud.refresh.extra-refreshable.

      +
      +

      For example, the beans involved in Cloud Pub/Sub subscription could be marked as refreshable as follows:

      +
      +
    4. +
    +
    +
    +
    +
    spring.cloud.refresh.extra-refreshable=com.google.cloud.spring.pubsub.support.SubscriberFactory,\
    +  com.google.cloud.spring.pubsub.core.subscriber.PubSubSubscriberTemplate
    +
    +
    +
    + + + + + +
    + + +
    +

    SmartLifecycle beans, such as Spring Integration adapters, do not currently support @RefreshScope. +If your application refreshes any beans used by such SmartLifecycle objects, it may also have to restart the beans manually when RefreshScopeRefreshedEvent is detected, such as in the Cloud Pub/Sub example below:

    +
    +
    +
    +
    @Autowired
    +private PubSubInboundChannelAdapter pubSubAdapter;
    +
    +@EventListener(RefreshScopeRefreshedEvent.class)
    +public void onRefreshScope(RefreshScopeRefreshedEvent event) {
    +  this.pubSubAdapter.stop();
    +  this.pubSubAdapter.start();
    +}
    +
    +
    +
    +
    +
    +
    +
    +

    3.6. Spring Initializr

    +
    +

    This starter is available from Spring Initializr through the GCP Support entry.

    +
    +
    +
    +
    +
    +

    4. Cloud Storage

    +
    +
    +

    Google Cloud Storage allows storing any types of files in single or multiple regions. +A Spring Boot starter is provided to auto-configure the various Storage components.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-storage</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-storage")
    +}
    +
    +
    +
    +

    This starter is also available from Spring Initializr through the GCP Storage entry.

    +
    +
    +

    4.1. Using Cloud Storage

    +
    +

    The starter automatically configures and registers a Storage bean in the Spring application context. +The Storage bean (Javadoc) can be used to list/create/update/delete buckets (a group of objects with similar permissions and resiliency requirements) and objects.

    +
    +
    +
    +
    @Autowired
    +private Storage storage;
    +
    +public void createFile() {
    +    Bucket bucket = storage.create(BucketInfo.of("my-app-storage-bucket"));
    +
    +    storage.create(
    +        BlobInfo.newBuilder("my-app-storage-bucket", "subdirectory/my-file").build(),
    +            "file contents".getBytes()
    +    );
    +}
    +
    +
    +
    +
    +
    +

    4.2. Cloud Storage Objects As Spring Resources

    +
    +

    Spring Resources are an abstraction for a number of low-level resources, such as file system files, classpath files, servlet context-relative files, etc. +Spring Framework on Google Cloud adds a new resource type: a Google Cloud Storage (GCS) object.

    +
    +
    +

    The Spring Resource Abstraction for Google Cloud Storage allows GCS objects to be accessed by their GCS URL using the @Value annotation:

    +
    +
    +
    +
    @Value("gs://[YOUR_GCS_BUCKET]/[GCS_FILE_NAME]")
    +private Resource gcsResource;
    +
    +
    +
    +
    +

    …​or the Spring application context

    +
    +
    +
    +
    SpringApplication.run(...).getResource("gs://[YOUR_GCS_BUCKET]/[GCS_FILE_NAME]");
    +
    +
    +
    +
    +

    This creates a Resource object that can be used to read the object, among other possible operations.

    +
    +
    +

    It is also possible to write to a Resource, although a WriteableResource is required.

    +
    +
    +
    +
    @Value("gs://[YOUR_GCS_BUCKET]/[GCS_FILE_NAME]")
    +private Resource gcsResource;
    +...
    +try (OutputStream os = ((WritableResource) gcsResource).getOutputStream()) {
    +  os.write("foo".getBytes());
    +}
    +
    +
    +
    +
    +

    To work with the Resource as a Google Cloud Storage resource, cast it to GoogleStorageResource.

    +
    +
    +

    If the resource path refers to an object on Google Cloud Storage (as opposed to a bucket), then the getBlob method can be called to obtain a Blob. +This type represents a GCS file, which has associated metadata, such as content-type, that can be set. +The createSignedUrl method can also be used to obtain signed URLs for GCS objects. +However, creating signed URLs requires that the resource was created using service account credentials.

    +
    +
    + + + + + +
    + + +
    +

    As of v2.0.2+, the GoogleStorageResource.getURL() method returns the Bucket or Blob 's selfLink value, rather than attempting to convert the URI a URL object that nearly-always threw a MalformedURLException. +This value is notably different from GoogleStorageResource.getURI(), which returns the more commonly used gs://my-bucket/my-object identifier. +Returning a valid URL is necessary to support some features in the Spring ecosystem, such as spring.resources.static-locations.

    +
    +
    +
    +
    +

    The Spring Boot Starter for Google Cloud Storage auto-configures the Storage bean required by the spring-cloud-gcp-storage module, based on the CredentialsProvider provided by the Spring Framework on Google Cloud Starter.

    +
    +
    +

    4.2.1. Setting the Content Type

    +
    +

    You can set the content-type of Google Cloud Storage files from their corresponding Resource objects:

    +
    +
    +
    +
    ((GoogleStorageResource)gcsResource).getBlob().toBuilder().setContentType("text/html").build().update();
    +
    +
    +
    +
    +
    +
    +

    4.3. Configuration

    +
    +

    The Spring Boot Starter for Google Cloud Storage provides the following configuration options:

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.storage.enabled

    Enables the Google Cloud storage APIs.

    No

    true

    spring.cloud.gcp.storage.auto-create-files

    Creates files and buckets on Google Cloud Storage when writes are made to non-existent files

    No

    true

    spring.cloud.gcp.storage.credentials.location

    OAuth2 credentials for authenticating with the Google Cloud Storage API, if different from the ones in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.storage.credentials.encoded-key

    Base64-encoded contents of OAuth2 account private key for authenticating with the Google Cloud Storage API, if different from the ones in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.storage.credentials.scopes

    OAuth2 scope for Spring Framework on Google Cloud Storage credentials

    No

    www.googleapis.com/auth/devstorage.read_write

    +
    +
    +

    4.4. Sample

    +
    +

    A sample application and a codelab are available.

    +
    +
    +
    +
    +
    +

    5. Cloud SQL

    +
    +
    +

    Spring Framework on Google Cloud adds integrations with +Spring JDBC and Spring R2DBC so you can run your MySQL or PostgreSQL databases in 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.

    +
    +
    +

    5.1. JDBC Support

    +
    +

    Maven and Gradle coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +

    To use MySQL:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-sql-mysql</artifactId>
    +</dependency>
    +
    +
    +
    +
    +
    dependencies {
    +implementation("com.google.cloud:spring-cloud-gcp-starter-sql-mysql")
    +}
    +
    +
    +
    +

    To use PostgreSQL:

    +
    +
    +
    +
    <dependency>
    +<groupId>com.google.cloud</groupId>
    +<artifactId>spring-cloud-gcp-starter-sql-postgresql</artifactId>
    +</dependency>
    +
    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-sql-postgresql")
    +}
    +
    +
    +
    +

    5.1.1. Prerequisites

    +
    +

    In order to use the Spring Boot Starters for Google Cloud SQL, the Google Cloud SQL API must be enabled in your Google Cloud project.

    +
    +
    +

    To do that, go to the API library page of the Google Cloud Console, search for "Cloud SQL API" and enable the option that is called "Cloud SQL" .

    +
    +
    +
    +

    5.1.2. Spring Boot Starter for Google Cloud SQL

    +
    +

    The Spring Boot Starters for Google Cloud SQL provide an auto-configured DataSource object. +Coupled with Spring JDBC, it provides a +JdbcTemplate object bean that allows for operations such as querying and modifying a database.

    +
    +
    +
    +
    public List<Map<String, Object>> listUsers() {
    +    return jdbcTemplate.queryForList("SELECT * FROM user;");
    +}
    +
    +
    +
    +
    +

    You can rely on +Spring Boot data source auto-configuration 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).

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Property name

    Description

    Required

    Default value

    spring.datasource.username

    Database username

    No

    MySQL: root; PostgreSQL: postgres

    spring.datasource.password

    Database password

    No

    null

    spring.datasource.driver-class-name

    JDBC driver to use.

    No

    MySQL: com.mysql.cj.jdbc.Driver; PostgreSQL: org.postgresql.Driver

    +
    + + + + + +
    + + +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. +
    +
    +
    +
    DataSource creation flow
    +
    +

    Spring Boot starter for Google Cloud SQL registers a CloudSqlEnvironmentPostProcessor that provides a correctly formatted spring.datasource.url property to the environment based on the properties mentioned above. +It also provides defaults for spring.datasource.username and spring.datasource.driver-class-name, which can be overridden. +The starter also configures credentials for the JDBC connection based on the properties below.

    +
    +
    +

    The user properties and the properties provided by the CloudSqlEnvironmentPostProcessor are then used by Spring Boot to create the DataSource. +You can select the type of connection pool (e.g., Tomcat, HikariCP, etc.) by adding their dependency to the classpath.

    +
    +
    +

    Using the created DataSource in conjunction with Spring JDBC provides you with a fully configured and operational JdbcTemplate object that you can use to interact with your SQL database. +You can connect to your database with as little as a database and instance names.

    +
    +
    +
    +
    +
    +

    5.2. R2DBC Support

    +
    +

    Maven and Gradle coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +

    To use MySQL:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-sql-mysql-r2dbc</artifactId>
    +</dependency>
    +
    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-sql-mysql-r2dbc")
    +}
    +
    +
    +
    +

    To use PostgreSQL with Spring Boot 2.6:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-sql-postgres-r2dbc</artifactId>
    +</dependency>
    +
    +
    +
    +
    +
    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):

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-sql-postgres-r2dbc</artifactId>
    +    <exclusions>
    +        <exclusion>
    +            <groupId>io.r2dbc</groupId>
    +            <artifactId>r2dbc-postgresql</artifactId>
    +        </exclusion>
    +    </exclusions>
    +</dependency>
    +
    +<dependency>
    +    <groupId>org.postgresql</groupId>
    +    <artifactId>r2dbc-postgresql</artifactId>
    +    <version>0.9.1.RELEASE</version>
    +</dependency>
    +
    +
    +
    +

    5.2.1. Prerequisites

    +
    +

    In order to use the Spring Boot Starters for Google Cloud SQL, the Google Cloud SQL API must be enabled in your Google Cloud project.

    +
    +
    +

    To do that, go to the API library page of the Google Cloud Console, search for "Cloud SQL API" and enable the option that is called "Cloud SQL".

    +
    +
    +
    +

    5.2.2. Spring Boot Starter for Google Cloud SQL

    +
    +

    The Cloud SQL R2DBC starter provides a customized io.r2dbc.spi.ConnectionFactory bean for connecting to Cloud SQL with the help of the Cloud SQL Socket Factory. +Similar to the JDBC support, you can connect to your database with as little as a database and instance names.

    +
    +
    +

    A higher level convenience object +R2dbcEntityTemplate is also provided for operations such as querying and modifying a database.

    +
    +
    +
    +
    @Autowired R2dbcEntityTemplate template;
    +
    +public Flux<String> listUsers() {
    +  return template.select(User.class).all().map(user -> user.toString());
    +}
    +
    +
    +
    +
    +

    Standard R2DBC properties like the SQL username, spring.r2dbc.username, and password, spring.r2dbc.password can be 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

    +
    + + + + + +
    + + +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 . +
    +
    +
    +
    ConnectionFactory creation flow
    +
    +

    Spring Framework on Google Cloud starter for Google Cloud SQL registers a R2dbcCloudSqlEnvironmentPostProcessor that provides a correctly formatted spring.r2dbc.url property to the environment based on the properties mentioned above. +It also provides a default value for spring.r2dbc.username, which can be overridden. +The starter also configures credentials for the R2DBC connection based on the properties below.

    +
    +
    +

    The user properties and the properties provided by the R2dbcCloudSqlEnvironmentPostProcessor are then used by Spring Boot to create the ConnectionFactory.

    +
    +
    +

    The customized ConnectionFactory is then ready to connect to Cloud SQL. The rest of Spring Data R2DBC objects built on it ( R2dbcEntityTemplate, DatabaseClient) are automatically configured and operational, ready to interact with your SQL database.

    +
    +
    +
    +
    +
    +

    5.3. Cloud SQL IAM database authentication

    +
    +

    Currently, Cloud SQL only supports IAM database authentication for PostgreSQL. +It allows you to connect to the database using an IAM account, rather than a predefined database username and password. +You will need to do the following to enable it:

    +
    +
    +
      +
    1. +

      In your database instance settings, turn on the cloudsql.iam_authentication flag.

      +
    2. +
    3. +

      Add the IAM user or service account to the list of database users.

      +
    4. +
    5. +

      In the application settings, set spring.cloud.gcp.sql.enableIamAuth to true. Note that this will also set the database protocol sslmode to disabled, as it’s required for IAM authentication to work. +However, it doesn’t compromise the security of the communication because the connection is always encrypted.

      +
    6. +
    7. +

      Set spring.datasource.username to the IAM user or service account created in step 2. Note that IAM user or service account still needs to be granted permissions before modifying or querying the database.

      +
    8. +
    +
    +
    +
    +

    5.4. Cloud SQL Configuration Properties

    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Property name

    Description

    Required

    Default value

    spring.cloud.gcp.sql.enabled

    Enables or disables Cloud SQL auto configuration

    No

    true

    spring.cloud.gcp.sql.jdbc.enabled

    Enables or disables Cloud SQL auto-configuration for JDBC

    No

    true

    spring.cloud.gcp.sql.r2dbc.enabled

    Enables or disables Cloud SQL auto-configuration for R2DBC

    No

    true

    spring.cloud.gcp.sql.database-name

    Name of the database to connect to.

    Yes

    spring.cloud.gcp.sql.instance-connection-name

    A string containing a Google Cloud SQL instance’s project ID, region and name, each separated by a colon.

    Yes

    For example, my-project-id:my-region:my-instance-name.

    spring.cloud.gcp.sql.ip-types

    Allows you to specify a comma delimited list of preferred IP types for connecting to a Cloud SQL instance. Left unconfigured Cloud SQL Socket Factory will default it to PUBLIC,PRIVATE. See Cloud SQL Socket Factory - Specifying IP Types

    No

    PUBLIC,PRIVATE

    spring.cloud.gcp.sql.credentials.location

    File system path to the Google OAuth2 credentials private key file. +Used to authenticate and authorize new connections to a Google Cloud SQL instance.

    No

    Default credentials provided by the Spring Framework on Google Cloud Core Starter

    spring.cloud.gcp.sql.credentials.encoded-key

    Base64-encoded contents of OAuth2 account private key in JSON format. +Used to authenticate and authorize new connections to a Google Cloud SQL instance.

    No

    Default credentials provided by the Spring Framework on Google Cloud Core Starter

    spring.cloud.gcp.sql.enableIamAuth

    Specifies whether to enable IAM database authentication (PostgreSQL only).

    No

    False

    +
    +
    +

    5.5. Troubleshooting tips

    +
    +

    5.5.1. Connection issues

    +
    +

    If you’re not able to connect to a database and see an endless loop of Connecting to Cloud SQL instance […​] on IP […​], it’s likely that exceptions are being thrown and logged at a level lower than your logger’s level. +This may be the case with HikariCP, if your logger is set to INFO or higher level.

    +
    +
    +

    To see what’s going on in the background, you should add a logback.xml file to your application resources folder, that looks like this:

    +
    +
    +
    +
    <?xml version="1.0" encoding="UTF-8"?>
    +<configuration>
    +  <include resource="org/springframework/boot/logging/logback/base.xml"/>
    +  <logger name="com.zaxxer.hikari.pool" level="DEBUG"/>
    +</configuration>
    +
    +
    +
    +
    +

    5.5.2. Errors like c.g.cloud.sql.core.SslSocketFactory : Re-throwing cached exception due to attempt to refresh instance information too soon after error

    +
    +

    If you see a lot of errors like this in a loop and can’t connect to your database, this is usually a symptom that something isn’t right with the permissions of your credentials or the Google Cloud SQL API is not enabled. +Verify that the Google Cloud SQL API is enabled in the Cloud Console and that your service account has the necessary IAM roles.

    +
    +
    +

    To find out what’s causing the issue, you can enable DEBUG logging level as mentioned above.

    +
    +
    +
    +

    5.5.3. PostgreSQL: java.net.SocketException: already connected issue

    +
    +

    We found this exception to be common if your Maven project’s parent is spring-boot version 1.5.x, or in any other circumstance that would cause the version of the org.postgresql:postgresql dependency to be an older one (e.g., 9.4.1212.jre7).

    +
    +
    +

    To fix this, re-declare the dependency in its correct version. +For example, in Maven:

    +
    +
    +
    +
    <dependency>
    +  <groupId>org.postgresql</groupId>
    +  <artifactId>postgresql</artifactId>
    +  <version>42.1.1</version>
    +</dependency>
    +
    +
    +
    +
    + +
    +
    +
    +

    6. Cloud Pub/Sub

    +
    +
    +

    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.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-pubsub</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-pubsub")
    +}
    +
    +
    +
    +

    This starter is also available from Spring Initializr through the GCP Messaging entry.

    +
    +
    +

    6.1. Configuration

    +
    +

    The Spring Boot starter for Google Cloud Pub/Sub provides the following configuration options.

    +
    +
    +

    6.1.1. Spring Framework on Google Cloud Pub/Sub API Configuration

    +
    +

    This section describes options for enabling the integration, specifying the Google Cloud project and credentials, and setting whether the APIs should connect to an emulator for local testing.

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.pubsub.enabled

    Enables or disables Pub/Sub auto-configuration

    No

    true

    spring.cloud.gcp.pubsub.project-id

    Google Cloud project ID where the Google Cloud Pub/Sub API is hosted, if different from the one in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.pubsub.credentials.location

    OAuth2 credentials for authenticating with the +Google Cloud Pub/Sub API, if different from the ones in the +Spring Framework on Google Cloud Core Module

    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 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 +Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.pubsub.credentials.scopes

    OAuth2 scope for Spring Framework on Google Cloud +Pub/Sub credentials

    No

    www.googleapis.com/auth/pubsub

    +
    +
    +

    6.1.2. Publisher/Subscriber Configuration

    +
    +

    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.

    +
    +
    + + + + + +
    + + +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 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. +However, if a per-subscription configuration is not set then the global or default configuration will be used. +
    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.pubsub.subscriber.parallel-pull-count

    The number of pull workers

    No

    1

    spring.cloud.gcp.pubsub.subscriber.max-ack-extension-period

    The maximum period a message ack deadline will be extended, in seconds

    No

    0

    spring.cloud.gcp.pubsub.subscriber.min-duration-per-ack-extension

    The lower bound for a single mod ack extension period, in seconds

    No

    0

    spring.cloud.gcp.pubsub.subscriber.max-duration-per-ack-extension

    The upper bound for a single mod ack extension period, in seconds

    No

    0

    spring.cloud.gcp.pubsub.subscriber.pull-endpoint

    The endpoint for pulling messages

    No

    pubsub.googleapis.com:443

    spring.cloud.gcp.pubsub.[subscriber,publisher].executor-threads

    Number of threads used by Subscriber instances created by SubscriberFactory

    No

    4

    spring.cloud.gcp.pubsub.[subscriber,publisher.batching].flow-control.max-outstanding-element-count

    Maximum number of outstanding elements to keep in memory before enforcing flow control.

    No

    unlimited

    spring.cloud.gcp.pubsub.[subscriber,publisher.batching].flow-control.max-outstanding-request-bytes

    Maximum number of outstanding bytes to keep in memory before enforcing flow control.

    No

    unlimited

    spring.cloud.gcp.pubsub.[subscriber,publisher.batching].flow-control.limit-exceeded-behavior

    The behavior when the specified limits are exceeded.

    No

    Block

    spring.cloud.gcp.pubsub.publisher.batching.element-count-threshold

    The element count threshold to use for batching.

    No

    1 (batching off)

    spring.cloud.gcp.pubsub.publisher.batching.request-byte-threshold

    The request byte threshold to use for batching.

    No

    1 byte (batching off)

    spring.cloud.gcp.pubsub.publisher.batching.delay-threshold-seconds

    The delay threshold to use for batching. +After this amount of time has elapsed (counting from the first element added), the elements will be wrapped up in a batch and sent.

    No

    1 ms (batching off)

    spring.cloud.gcp.pubsub.publisher.batching.enabled

    Enables batching.

    No

    false

    spring.cloud.gcp.pubsub.publisher.enable-message-ordering

    Enables message ordering.

    No

    false

    spring.cloud.gcp.pubsub.publisher.endpoint

    The publisher endpoint. +Example: "us-east1-pubsub.googleapis.com:443". +This is useful in conjunction with enabling message ordering because sending messages to the same region ensures they are received in order even when multiple publishers are used.

    No

    pubsub.googleapis.com:443

    +
    +
    Subscription-specific Configurations
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.pubsub.subscription.[subscription-name].fully-qualified-name

    The fully-qualified subscription name in the projects/[PROJECT]/subscriptions/[SUBSCRIPTION] format. When this property is present, the [subscription-name] key does not have to match any actual resources; it’s used only for logical grouping.

    No

    1

    spring.cloud.gcp.pubsub.subscription.[subscription-name].parallel-pull-count

    The number of pull workers.

    No

    1

    spring.cloud.gcp.pubsub.subscription.[subscription-name].max-ack-extension-period

    The maximum period a message ack deadline will be extended, in seconds.

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].min-duration-per-ack-extension

    The lower bound for a single mod ack extension period, in seconds

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].max-duration-per-ack-extension

    The upper bound for a single mod ack extension period, in seconds

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].pull-endpoint

    The endpoint for pulling messages.

    No

    pubsub.googleapis.com:443

    spring.cloud.gcp.pubsub.subscription.[subscription-name].executor-threads

    Number of threads used by Subscriber instances created by SubscriberFactory. Note that configuring per-subscription executor-threads will result in the creation of thread pools for both global/default and per-subscription configurations.

    No

    4

    spring.cloud.gcp.pubsub.subscription.[subscription-name].flow-control.max-outstanding-element-count

    Maximum number of outstanding elements to keep in memory before enforcing flow control.

    No

    unlimited

    spring.cloud.gcp.pubsub.subscription.[subscription-name].flow-control.max-outstanding-request-bytes

    Maximum number of outstanding bytes to keep in memory before enforcing flow control.

    No

    unlimited

    spring.cloud.gcp.pubsub.subscription.[subscription-name].flow-control.limit-exceeded-behavior

    The behavior when the specified limits are exceeded.

    No

    Block

    +
    + + + + + +
    + + +By default, subscription-specific threads are named after fully-qualified subscription name, ex: gcp-pubsub-subscriber-projects/project-id/subscriptions/subscription-name. +This can be customized, by registering a SelectiveSchedulerThreadNameProvider bean. +
    +
    +
    +
    +
    +

    6.1.3. GRPC Connection Settings

    +
    +

    The Pub/Sub API uses the GRPC protocol to send API requests to the Pub/Sub service. +This section describes configuration options for customizing the GRPC behavior.

    +
    +
    + + + + + +
    + + +The properties that refer to retry control the RPC retries for transient failures during the gRPC call to Cloud Pub/Sub server. +They do not control message redelivery; only message acknowledgement deadline can be used to extend or shorten the amount of time until Pub/Sub attempts redelivery. +
    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.pubsub.keepAliveIntervalMinutes

    Determines frequency of keepalive gRPC ping

    No

    5 minutes

    spring.cloud.gcp.pubsub.subscriber.retryableCodes

    RPC status codes that should be retried when pulling messages.

    No

    UNKNOWN,ABORTED,UNAVAILABLE

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.total-timeout-seconds

    TotalTimeout has ultimate control over how long the logic should keep trying the remote call until it gives up completely. +The higher the total timeout, the more retries can be attempted.

    No

    0

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.initial-retry-delay-second

    InitialRetryDelay controls the delay before the first retry. +Subsequent retries will use this value adjusted according to the RetryDelayMultiplier.

    No

    0

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.retry-delay-multiplier

    RetryDelayMultiplier controls the change in retry delay. +The retry delay of the previous call is multiplied by the RetryDelayMultiplier to calculate the retry delay for the next call.

    No

    1

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.max-retry-delay-seconds

    MaxRetryDelay puts a limit on the value of the retry delay, so that the RetryDelayMultiplier +can’t increase the retry delay higher than this amount.

    No

    0

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.max-attempts

    MaxAttempts defines the maximum number of attempts to perform. +If this value is greater than 0, and the number of attempts reaches this limit, the logic will give up retrying even if the total retry time is still lower than TotalTimeout.

    No

    0

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.jittered

    Jitter determines if the delay time should be randomized.

    No

    true

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.initial-rpc-timeout-seconds

    InitialRpcTimeout controls the timeout for the initial RPC. +Subsequent calls will use this value adjusted according to the RpcTimeoutMultiplier.

    No

    0

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.rpc-timeout-multiplier

    RpcTimeoutMultiplier controls the change in RPC timeout. +The timeout of the previous call is multiplied by the RpcTimeoutMultiplier to calculate the timeout for the next call.

    No

    1

    spring.cloud.gcp.pubsub.[subscriber,publisher].retry.max-rpc-timeout-seconds

    MaxRpcTimeout puts a limit on the value of the RPC timeout, so that the RpcTimeoutMultiplier +can’t increase the RPC timeout higher than this amount.

    No

    0

    +
    +

    Subscription-specific Configuration

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retryableCodes

    RPC status codes that should be retried when pulling messages.

    No

    UNKNOWN,ABORTED,UNAVAILABLE

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.total-timeout-seconds

    TotalTimeout has ultimate control over how long the logic should keep trying the remote call until it gives up completely. The higher the total timeout, the more retries can be attempted.

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.initial-retry-delay-second

    InitialRetryDelay controls the delay before the first retry. +Subsequent retries will use this value adjusted according to the RetryDelayMultiplier.

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.retry-delay-multiplier

    RetryDelayMultiplier controls the change in retry delay. +The retry delay of the previous call is multiplied by the RetryDelayMultiplier to calculate the retry delay for the next call.

    No

    1

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.max-retry-delay-seconds

    MaxRetryDelay puts a limit on the value of the retry delay, so that the RetryDelayMultiplier +can’t increase the retry delay higher than this amount.

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.max-attempts

    MaxAttempts defines the maximum number of attempts to perform. +If this value is greater than 0, and the number of attempts reaches this limit, the logic will give up retrying even if the total retry time is still lower than TotalTimeout.

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.jittered

    Jitter determines if the delay time should be randomized.

    No

    true

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.initial-rpc-timeout-seconds

    InitialRpcTimeout controls the timeout for the initial RPC. +Subsequent calls will use this value adjusted according to the RpcTimeoutMultiplier.

    No

    0

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.rpc-timeout-multiplier

    RpcTimeoutMultiplier controls the change in RPC timeout. +The timeout of the previous call is multiplied by the RpcTimeoutMultiplier to calculate the timeout for the next call.

    No

    1

    spring.cloud.gcp.pubsub.subscription.[subscription-name].retry.max-rpc-timeout-seconds

    MaxRpcTimeout puts a limit on the value of the RPC timeout, so that the RpcTimeoutMultiplier +can’t increase the RPC timeout higher than this amount.

    No

    0

    +
    +
    +

    6.1.4. Programmatic Configuration

    +
    +

    To apply publishing customizations not covered by the properties above, you may provide custom beans of type PublisherCustomizer to post-process the Publisher.Builder object right before it is built into a Publisher. +The PublisherCustomizer beans may be annotated with Spring Framework’s @Order annotation to ensure they are applied in a particular sequence.

    +
    +
    +
    +
    +

    6.2. Spring Boot Actuator Support

    +
    +

    6.2.1. Cloud Pub/Sub Health Indicator

    +
    +

    If you are using Spring Boot Actuator, you can take advantage of the Cloud Pub/Sub health indicator called pubsub. +The health indicator will verify whether Cloud Pub/Sub is up and accessible by your application. +To enable it, all you need to do is add the Spring Boot Actuator to your project.

    +
    +
    +

    The pubsub indicator will then roll up to the overall application status visible at localhost:8080/actuator/health (use the management.endpoint.health.show-details property to view per-indicator details).

    +
    +
    +
    +
    <dependency>
    +    <groupId>org.springframework.boot</groupId>
    +    <artifactId>spring-boot-starter-actuator</artifactId>
    +</dependency>
    +
    +
    +
    + + + + + +
    + + +If your application already has actuator and Cloud Pub/Sub starters, this health indicator is enabled by default. +To disable the Cloud Pub/Sub indicator, set management.health.pubsub.enabled to false. +
    +
    +
    +

    The health indicator validates the connection to Pub/Sub by pulling messages from a Pub/Sub subscription.

    +
    +
    +

    If no subscription has been specified via spring.cloud.gcp.pubsub.health.subscription, it will pull messages from a random subscription that is expected not to exist. +It will signal "up" if it is able to connect to Spring Framework on Google Cloud Pub/Sub APIs, i.e. the pull results in a response of NOT_FOUND or PERMISSION_DENIED.

    +
    +
    +

    If a custom subscription has been specified, this health indicator will signal "up" if messages are successfully pulled and (optionally) acknowledged, or when a successful pull is performed but no messages are returned from Pub/Sub. +Note that messages pulled from the subscription will not be acknowledged, unless you set the spring.cloud.gcp.pubsub.health.acknowledge-messages option to true. +So, take care not to configure a subscription that has a business impact, or instead leave the custom subscription out completely.

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    management.health.pubsub.enabled

    Whether to enable the Pub/Sub health indicator

    No

    true with Spring Boot Actuator, false otherwise

    spring.cloud.gcp.pubsub.health.subscription

    Subscription to health check against by pulling a message

    No

    Random non-existent

    spring.cloud.gcp.pubsub.health.timeout-millis

    Milliseconds to wait for response from Pub/Sub before timing out

    No

    2000

    spring.cloud.gcp.pubsub.health.acknowledge-messages

    Whether to acknowledge messages pulled from the optionally specified subscription

    No

    false

    +
    +
    +

    6.2.2. Cloud Pub/Sub Subscription Health Indicator

    +
    +

    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 Spring Boot Actuator to your project and the Google Cloud 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 localhost:8080/actuator/health (use the management.endpoint.health.show-details property to view per-indicator details).

    +
    +
    +
    +
    <dependency>
    +    <groupId>org.springframework.boot</groupId>
    +    <artifactId>spring-boot-starter-actuator</artifactId>
    +</dependency>
    +<dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>google-cloud-monitoring</artifactId>
    +</dependency>
    +
    +
    +
    +

    The health indicator validates a subscriber’s health by checking the subscription’s message backlog and the last processed message. +A subscription’s backlog is retrieved using Google Cloud’s Monitoring Metrics. The metric used is the num_undelivered_messages for a subscription.

    +
    +
    +

    If a message has been recently processed in a reasonable time threshold, then the subscriber is healthy. +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.

    +
    +
    + + + + + +
    + + +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. +
    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    management.health.pubsub-subscriber.enabled

    Whether to enable the Pub/Sub Subscription health indicator

    No

    true with Spring Boot Actuator, false otherwise

    spring.cloud.gcp.pubsub.health.lagThreshold

    Threshold in seconds over message processing lag

    Yes

    Provided

    spring.cloud.gcp.pubsub.health.backlogThreshold

    The threshold number of messages for a subscription backlog

    Yes

    Provided

    spring.cloud.gcp.pubsub.health.lookUpInterval

    The optional interval in seconds for subscription backlog lookup

    No

    1

    spring.cloud.gcp.pubsub.health.executorThreads

    Number of threads used for Health Check Executors

    No

    4

    +
    +
    +
    +

    6.3. Pub/Sub Operations & Template

    +
    +

    PubSubOperations is an abstraction that allows Spring users to use Google Cloud Pub/Sub without depending on any Google Cloud Pub/Sub API semantics. +It provides the common set of operations needed to interact with Google Cloud Pub/Sub. +PubSubTemplate is the default implementation of PubSubOperations and it uses the Google Cloud Java Client for Pub/Sub to interact with Google Cloud Pub/Sub.

    +
    +
    +

    6.3.1. Publishing to a topic

    +
    +

    PubSubTemplate provides asynchronous methods to publish messages to a Google Cloud Pub/Sub topic. +The publish() method takes in a topic name to post the message to, a payload of a generic type and, optionally, a map with the message headers. +The topic name could either be a short topic name within the current project, or the fully-qualified name referring to a topic in a different project using the projects/[project_name]/topics/[topic_name] format.

    +
    +
    +

    Here is an example of how to publish a message to a Google Cloud Pub/Sub topic:

    +
    +
    +
    +
    Map<String, String> headers = Collections.singletonMap("key1", "val1");
    +pubSubTemplate.publish(topicName, "message", headers).get();
    +
    +
    +
    +
    +

    By default, the SimplePubSubMessageConverter is used to convert payloads of type byte[], ByteString, ByteBuffer, and String to Pub/Sub messages.

    +
    +
    +
    Ordering messages
    +
    +

    If you are relying on message converters and would like to provide an ordering key, use the GcpPubSubHeaders.ORDERING_KEY header. +You will also need to make sure to enable message ordering on the publisher via the spring.cloud.gcp.pubsub.publisher.enable-message-ordering property. +Additionally, if you are using multiple publishers, you will want to set the spring.cloud.gcp.pubsub.publisher.endpoint to a regional endpoint such as "us-east1-pubsub.googleapis.com:443" so that messages are sent to the same region and received in order.

    +
    +
    +
    +
    Map<String, String> headers =
    +    Collections.singletonMap(GcpPubSubHeaders.ORDERING_KEY, "key1");
    +pubSubTemplate.publish(topicName, "message1", headers).get();
    +pubSubTemplate.publish(topicName, "message2", headers).get();
    +
    +
    +
    +
    +
    +
    +

    6.3.2. Subscribing to a subscription

    +
    +

    Google Cloud Pub/Sub allows many subscriptions to be associated to the same topic. +PubSubTemplate allows you to listen to subscriptions via the subscribe() method. +When listening to a subscription, messages will be pulled from Google Cloud Pub/Sub asynchronously and passed to a user provided message handler. +The subscription name could either be a short subscription name within the current project, or the fully-qualified name referring to a subscription in a different project using the projects/[project_name]/subscriptions/[subscription_name] format.

    +
    +
    +
    Example
    +
    +

    Subscribe to a subscription with a message handler:

    +
    +
    +
    +
    Subscriber subscriber =
    +    pubSubTemplate.subscribe(
    +        subscriptionName,
    +        message -> {
    +          logger.info(
    +              "Message received from "
    +                  + subscriptionName
    +                  + " subscription: "
    +                  + message.getPubsubMessage().getData().toStringUtf8());
    +          message.ack();
    +        });
    +
    +
    +
    +
    +
    +
    Subscribe methods
    +
    +

    PubSubTemplate provides the following subscribe methods:

    +
    + ++++ + + + + + + + + + + +

    subscribe(String subscription, Consumer<BasicAcknowledgeablePubsubMessage> messageConsumer)

    asynchronously pulls messages and passes them to messageConsumer

    subscribeAndConvert(String subscription, + Consumer<ConvertedBasicAcknowledgeablePubsubMessage<T>> messageConsumer, + Class<T> payloadType)

    same as pull, but converts message payload to payloadType using the converter configured in the template

    +
    + + + + + +
    + + +
    +

    As of version 1.2, subscribing by itself is not enough to keep an application running. +For a command-line application, a way to keep the application running is to have a user thread(non-daemon thread) started up. A fake scheduled task creates a threadpool with non-daemon threads:

    +
    +
    +
    +
    @Scheduled (fixedRate = 1, timeUnit = TimeUnit.MINUTES)
    +public void fakeScheduledTask() {
    +    // do nothing
    +}
    +
    +
    +
    +
    +

    Another option is to pull in spring-boot-starter-web or spring-boot-starter-webflux as a dependency which will start an embedded servlet container or reactive server keeping the application running in the background

    +
    +
    +
    +
    +
    +
    +

    6.3.3. Pulling messages from a subscription

    +
    +

    Google Cloud Pub/Sub supports synchronous pulling of messages from a subscription. +This is different from subscribing to a subscription, in the sense that subscribing is an asynchronous task.

    +
    +
    +
    Example
    +
    +

    Pull up to 10 messages:

    +
    +
    +
    +
    int maxMessages = 10;
    +boolean returnImmediately = false;
    +List<AcknowledgeablePubsubMessage> messages =
    +    pubSubTemplate.pull(subscriptionName, maxMessages, returnImmediately);
    +
    +// acknowledge the messages
    +pubSubTemplate.ack(messages);
    +
    +messages.forEach(
    +    message ->
    +        logger.info(message.getPubsubMessage().getData().toStringUtf8()));
    +
    +
    +
    +
    +
    +
    Pull methods
    +
    +

    PubsubTemplate provides the following pull methods:

    +
    + ++++ + + + + + + + + + + + + + + + + + + +

    pull(String subscription, Integer maxMessages, + Boolean returnImmediately)

    Pulls a number of messages from a subscription, allowing for the retry settings to be configured. + Any messages received by pull() are not automatically acknowledged. See Acknowledging messages.

    +

    The maxMessages parameter is the maximum limit of how many messages to pull from a subscription in a single call; this value must be greater than 0. + You may omit this parameter by passing in null; this means there will be no limit on the number of messages pulled (maxMessages will be Integer.MAX_INTEGER).

    +

    If returnImmediately is true, the system will respond immediately even if it there are no messages available to return in the Pull response. Otherwise, the system may wait (for a bounded amount of time) until at least one message is available, rather than returning no messages.

    pullAndAck

    Works the same as the pull method and, additionally, acknowledges all received messages.

    pullNext

    Allows for a single message to be pulled and automatically acknowledged from a subscription.

    pullAndConvert

    Works the same as the pull method and, additionally, converts the Pub/Sub binary payload to an object of the desired type, using the converter configured in the template.

    +
    + + + + + +
    + + +We do not recommend setting returnImmediately to true, as it may result in delayed message delivery. +"Immediately" really means 1 second, and if Pub/Sub cannot retrieve any messages from the backend in that time, it will return 0 messages, despite having messages queue up on the topic. +Therefore, we recommend setting returnImmediately to false, or using subscribe methods from the previous section. +
    +
    +
    +
    +
    Acknowledging messages
    +
    +

    There are two ways to acknowledge messages.

    +
    +
    +
      +
    1. +

      To acknowledge multiple messages at once, you can use the PubSubTemplate.ack() method. +You can also use the PubSubTemplate.nack() for negatively acknowledging messages. +Using these methods for acknowledging messages in batches is more efficient than acknowledging messages individually, but they require the collection of messages to be from the same project.

      +
    2. +
    3. +

      To acknowledge messages individually you can use the ack() or nack() method on each of them (to acknowledge or negatively acknowledge, correspondingly).

      +
    4. +
    +
    +
    + + + + + +
    + + +All ack(), nack(), and modifyAckDeadline() methods on messages, as well as PubSubSubscriberTemplate, are implemented asynchronously, returning a ListenableFuture<Void> to enable asynchronous processing. +
    +
    +
    +
    +
    Dead Letter Topics
    +
    +

    Your application may occasionally receive a message it cannot process. +If you create your Subscription passing the Subscription.Builder argument, you can specify a DeadLetterPolicy that will forward all nack()-ed and non-ack()-ed messages after a configurable amount of redelivery attempts. +See here for more information.

    +
    +
    +
    +
    public Subscription newSubscription() {
    +    // Must use the fully-qualified topic name.
    +    String fullDeadLetterTopic = PubSubTopicUtils
    +                        .toTopicName(DEAD_LETTER_TOPIC, gcpProjectIdProvider.getProjectId())
    +                        .toString();
    +    return pubSubAdmin.createSubscription(Subscription.newBuilder()
    +            .setName(SUBSCRIPTION_NAME)
    +            .setTopic(TOPIC_NAME)
    +            .setDeadLetterPolicy(DeadLetterPolicy.newBuilder()
    +                    .setDeadLetterTopic(fullDeadLetterTopic)
    +                    .setMaxDeliveryAttempts(6)
    +                    .build()));
    +}
    +
    +
    +
    +
    +

    Dead letter topics are no different than any other topic, though some 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.

    +
    +
    +
    +
    +

    6.3.4. JSON support

    +
    +

    For serialization and deserialization of POJOs using Jackson JSON, configure a PubSubMessageConverter bean, and the Spring Boot starter for Spring Framework on Google Cloud Pub/Sub will automatically wire it into the PubSubTemplate.

    +
    +
    +
    +
    // Note: The ObjectMapper is used to convert Java POJOs to and from JSON.
    +// You will have to configure your own instance if you are unable to depend
    +// on the ObjectMapper provided by Spring Boot starters.
    +@Bean
    +public PubSubMessageConverter pubSubMessageConverter() {
    +  return new JacksonPubSubMessageConverter(new ObjectMapper());
    +}
    +
    +
    +
    +
    + + + + + +
    + + +Alternatively, you can set it directly by calling the setMessageConverter() method on the PubSubTemplate. +Other implementations of the PubSubMessageConverter can also be configured in the same manner. +
    +
    +
    +

    Assuming you have the following class defined:

    +
    +
    +
    +
    static class TestUser {
    +
    +  String username;
    +
    +  String password;
    +
    +  public String getUsername() {
    +    return this.username;
    +  }
    +
    +  void setUsername(String username) {
    +    this.username = username;
    +  }
    +
    +  public String getPassword() {
    +    return this.password;
    +  }
    +
    +  void setPassword(String password) {
    +    this.password = password;
    +  }
    +}
    +
    +
    +
    +
    +

    You can serialize objects to JSON on publish automatically:

    +
    +
    +
    +
    TestUser user = new TestUser();
    +user.setUsername("John");
    +user.setPassword("password");
    +pubSubTemplate.publish(topicName, user);
    +
    +
    +
    +
    +

    And that’s how you convert messages to objects on pull:

    +
    +
    +
    +
    int maxMessages = 1;
    +boolean returnImmediately = false;
    +List<ConvertedAcknowledgeablePubsubMessage<TestUser>> messages =
    +    pubSubTemplate.pullAndConvert(
    +        subscriptionName, maxMessages, returnImmediately, TestUser.class);
    +
    +ConvertedAcknowledgeablePubsubMessage<TestUser> message = messages.get(0);
    +
    +// acknowledge the message
    +message.ack();
    +
    +TestUser receivedTestUser = message.getPayload();
    +
    +
    +
    +
    +

    Please refer to our Pub/Sub JSON Payload Sample App as a reference for using this functionality.

    +
    +
    +
    +
    +

    6.4. Reactive Stream Subscriber

    +
    +

    It is also possible to acquire a reactive stream backed by a subscription. +To do so, a Project Reactor dependency (io.projectreactor:reactor-core) must be added to the project. +The combination of the Pub/Sub starter and the Project Reactor dependencies will then make a PubSubReactiveFactory bean available, which can then be used to get a Publisher.

    +
    +
    +
    +
    @Autowired
    +PubSubReactiveFactory reactiveFactory;
    +
    +// ...
    +
    +Flux<AcknowledgeablePubsubMessage> flux
    +                = reactiveFactory.poll("exampleSubscription", 1000);
    +
    +
    +
    +
    +

    The Flux then represents an infinite stream of Spring Framework on Google Cloud Pub/Sub messages coming in through the specified subscription. +For unlimited demand, the Pub/Sub subscription will be polled regularly, at intervals determined by pollingPeriodMs parameter passed in when creating the Flux. +For bounded demand, the pollingPeriodMs parameter is unused. +Instead, as many messages as possible (up to the requested number) are delivered immediately, with the remaining messages delivered as they become available.

    +
    +
    +

    Any exceptions thrown by the underlying message retrieval logic will be passed as an error to the stream. +The error handling operators (Flux#retry(), Flux#onErrorResume() etc.) can be used to recover.

    +
    +
    +

    The full range of Project Reactor operations can be applied to the stream. +For example, if you only want to fetch 5 messages, you can use limitRequest operation to turn the infinite stream into a finite one:

    +
    +
    +
    +
    Flux<AcknowledgeablePubsubMessage> fiveMessageFlux = flux.limitRequest(5);
    +
    +
    +
    +
    +

    Messages flowing through the Flux should be manually acknowledged.

    +
    +
    +
    +
    flux.doOnNext(AcknowledgeablePubsubMessage::ack);
    +
    +
    +
    +
    +
    +

    6.5. Pub/Sub management

    +
    +

    PubSubAdmin is the abstraction provided by Spring Framework on Google Cloud to manage Google Cloud Pub/Sub resources. +It allows for the creation, deletion and listing of topics and subscriptions.

    +
    +
    + + + + + +
    + + +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)/<name> 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.

    +
    +
    +

    6.5.1. Creating a topic

    +
    +

    PubSubAdmin implements a method to create topics:

    +
    +
    +
    +
    public Topic createTopic(String topicName)
    +
    +
    +
    +
    +

    Here is an example of how to create a Google Cloud Pub/Sub topic:

    +
    +
    +
    +
    public void newTopic() {
    +    pubSubAdmin.createTopic("topicName");
    +}
    +
    +
    +
    +
    +
    +

    6.5.2. Deleting a topic

    +
    +

    PubSubAdmin implements a method to delete topics:

    +
    +
    +
    +
    public void deleteTopic(String topicName)
    +
    +
    +
    +
    +

    Here is an example of how to delete a Google Cloud Pub/Sub topic:

    +
    +
    +
    +
    public void deleteTopic() {
    +    pubSubAdmin.deleteTopic("topicName");
    +}
    +
    +
    +
    +
    +
    +

    6.5.3. Listing topics

    +
    +

    PubSubAdmin implements a method to list topics:

    +
    +
    +
    +
    public List<Topic> listTopics
    +
    +
    +
    +
    +

    Here is an example of how to list every Google Cloud Pub/Sub topic name in a project:

    +
    +
    +
    +
    List<String> topics =
    +    pubSubAdmin.listTopics().stream().map(Topic::getName).collect(Collectors.toList());
    +
    +
    +
    +
    +
    +

    6.5.4. Creating a subscription

    +
    +

    PubSubAdmin implements several methods to create subscriptions to existing topics:

    +
    +
    +
    +
    public Subscription createSubscription(String subscriptionName, String topicName)
    +
    +public Subscription createSubscription(String subscriptionName, String topicName, Integer ackDeadline)
    +
    +public Subscription createSubscription(String subscriptionName, String topicName, Integer ackDeadline, String pushEndpoint)
    +
    +public Subscription createSubscription(Subscriber.Builder builder)
    +
    +
    +
    +
    +

    The default value for ackDeadline is 10 seconds. +If pushEndpoint isn’t specified, the subscription uses message pulling, instead. +You can also pass a Subscription.Builder for full control over any options or features available in the client library.

    +
    +
    +

    Here is an example of how to create a Google Cloud Pub/Sub subscription:

    +
    +
    +
    +
    public Subscription newSubscription() {
    +    return pubSubAdmin.createSubscription("subscriptionName", "topicName", 15);
    +}
    +
    +
    +
    +
    +
    +

    6.5.5. Deleting a subscription

    +
    +

    PubSubAdmin implements a method to delete subscriptions:

    +
    +
    +
    +
    public void deleteSubscription(String subscriptionName)
    +
    +
    +
    +
    +

    Here is an example of how to delete a Google Cloud Pub/Sub subscription:

    +
    +
    +
    +
    public void deleteSubscription() {
    +    pubSubAdmin.deleteSubscription("subscriptionName");
    +}
    +
    +
    +
    +
    +
    +

    6.5.6. Listing subscriptions

    +
    +

    PubSubAdmin implements a method to list subscriptions:

    +
    +
    +
    +
    public List<Subscription> listSubscriptions()
    +
    +
    +
    +
    +

    Here is an example of how to list every subscription name in a project:

    +
    +
    +
    +
    List<String> subscriptions =
    +    pubSubAdmin.listSubscriptions().stream()
    +        .map(Subscription::getName)
    +        .collect(Collectors.toList());
    +
    +
    +
    +
    +
    +
    +

    6.6. Sample

    +
    +

    Sample applications for using the template and using a subscription-backed reactive stream are available.

    +
    +
    +
    +

    6.7. Test

    +
    +

    Testcontainers provides a gcloud module which offers PubSubEmulatorContainer. See more at the docs

    +
    +
    +
    +
    +
    +

    7. Spring Integration

    +
    +
    +

    Spring Framework on Google Cloud provides Spring Integration adapters that allow your applications to use Enterprise Integration Patterns backed up by Google Cloud services.

    +
    +
    +

    7.1. Channel Adapters for Cloud Pub/Sub

    +
    +

    The channel adapters for Google Cloud Pub/Sub connect your Spring 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.

    +
    +
    +

    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.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-pubsub</artifactId>
    +</dependency>
    +<dependency>
    +    <groupId>org.springframework.integration</groupId>
    +    <artifactId>spring-integration-core</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-pubsub")
    +    implementation("org.springframework.integration:spring-integration-core")
    +}
    +
    +
    +
    +

    7.1.1. Inbound channel adapter (using Pub/Sub Streaming Pull)

    +
    +

    PubSubInboundChannelAdapter is the inbound channel adapter for Spring Framework on Google Cloud Pub/Sub that listens to a Spring Framework on Google Cloud Pub/Sub subscription for new messages. +It converts new messages to an internal Spring Message and then sends it to the bound output channel.

    +
    +
    +

    Google Pub/Sub treats message payloads as byte arrays. +So, by default, the inbound channel adapter will construct the Spring Message with byte[] as the payload. +However, you can change the desired payload type by setting the payloadType property of the PubSubInboundChannelAdapter. +The PubSubInboundChannelAdapter delegates the conversion to the desired payload type to the PubSubMessageConverter configured in the PubSubTemplate.

    +
    +
    +

    To use the inbound channel adapter, a PubSubInboundChannelAdapter must be provided and configured on the user application side.

    +
    +
    + + + + + +
    + + +The subscription name could either be a short subscription name within the current project, or the fully-qualified name referring to a subscription in a different project using the projects/[project_name]/subscriptions/[subscription_name] format. +
    +
    +
    +
    +
    @Bean
    +public MessageChannel pubsubInputChannel() {
    +    return new PublishSubscribeChannel();
    +}
    +
    +@Bean
    +public PubSubInboundChannelAdapter messageChannelAdapter(
    +    @Qualifier("pubsubInputChannel") MessageChannel inputChannel,
    +    PubSubTemplate pubsubTemplate) {
    +    PubSubInboundChannelAdapter adapter =
    +        new PubSubInboundChannelAdapter(pubsubTemplate, "subscriptionName");
    +    adapter.setOutputChannel(inputChannel);
    +    adapter.setAckMode(AckMode.MANUAL);
    +
    +    return adapter;
    +}
    +
    +
    +
    +
    +

    In the example, we first specify the MessageChannel where the adapter is going to write incoming messages to. +The MessageChannel implementation isn’t important here. +Depending on your use case, you might want to use a MessageChannel other than PublishSubscribeChannel.

    +
    +
    +

    Then, we declare a PubSubInboundChannelAdapter bean. +It requires the channel we just created and a SubscriberFactory, which creates Subscriber objects from the Google Cloud Java Client for Pub/Sub. +The Spring Boot starter for Spring Framework on Google Cloud Pub/Sub provides a configured PubSubSubscriberOperations object.

    +
    +
    +
    Acknowledging messages and handling failures
    +
    +

    When working with Cloud Pub/Sub, it is important to understand the concept of ackDeadline — the amount of time Cloud Pub/Sub will wait until attempting redelivery of an outstanding message. +Each subscription has a default ackDeadline applied to all messages sent to it. +Additionally, the Cloud Pub/Sub client library can extend each streamed message’s ackDeadline until the message processing completes, fails or until the maximum extension period elapses.

    +
    +
    + + + + + +
    + + +In the Pub/Sub client library, default maximum extension period is an hour. However, Spring Framework on Google Cloud disables this auto-extension behavior. +Use the spring.cloud.gcp.pubsub.subscriber.max-ack-extension-period property to re-enable it. +
    +
    +
    +

    Acknowledging (acking) a message removes it from Pub/Sub’s known outstanding messages. Nacking a message resets its acknowledgement deadline to 0, forcing immediate redelivery. +This could be useful in a load balanced architecture, where one of the subscribers is having issues but others are available to process messages.

    +
    +
    +

    The PubSubInboundChannelAdapter supports three acknowledgement modes: the default AckMode.AUTO (automatic acking on processing success and nacking on exception), as well as two modes for additional manual control: AckMode.AUTO_ACK (automatic acking on success but no action on exception) and AckMode.MANUAL (no automatic actions at all; both acking and nacking have to be done manually).

    +
    + + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Table 1. Acknowledgement mode behavior
    AUTOAUTO_ACKMANUAL

    Message processing completes successfully

    ack, no redelivery

    ack, no redelivery

    <no action>*

    Message processing fails, but error handler completes successfully**

    ack, no redelivery

    ack, no redelivery

    <no action>*

    Message processing fails; no error handler present

    nack, immediate redelivery

    <no action>*

    <no action>*

    Message processing fails, and error handler throws an exception

    nack, immediate redelivery

    <no action>*

    <no action>*

    +
    +

    * <no action> means that the message will be neither acked nor nacked. +Cloud Pub/Sub will attempt redelivery according to subscription ackDeadline setting and the max-ack-extension-period client library setting.

    +
    +
    +

    ** For the adapter, "success" means the Spring Integration flow processed without raising an exception, so successful message processing and the successful completion of an error handler both result in the same behavior (message will be acknowledged). +To trigger default error behavior (nacking in AUTO mode; neither acking nor nacking in AUTO_ACK mode), propagate the error back to the adapter by throwing an exception from the Error Handling flow.

    +
    +
    +
    Manual acking/nacking
    +
    +

    The adapter attaches a BasicAcknowledgeablePubsubMessage object to the Message headers. +Users can extract the BasicAcknowledgeablePubsubMessage using the GcpPubSubHeaders.ORIGINAL_MESSAGE key and use it to ack (or nack) a message.

    +
    +
    +
    +
    @Bean
    +@ServiceActivator(inputChannel = "pubsubInputChannel")
    +public MessageHandler messageReceiver() {
    +    return message -> {
    +        LOGGER.info("Message arrived! Payload: " + new String((byte[]) message.getPayload()));
    +        BasicAcknowledgeablePubsubMessage originalMessage =
    +              message.getHeaders().get(GcpPubSubHeaders.ORIGINAL_MESSAGE, BasicAcknowledgeablePubsubMessage.class);
    +        originalMessage.ack();
    +    };
    +}
    +
    +
    +
    +
    +
    +
    Error Handling
    +
    +

    If you want to have more control over message processing in case of an error, you need to associate the PubSubInboundChannelAdapter with a Spring Integration error channel and specify the behavior to be invoked with @ServiceActivator.

    +
    +
    + + + + + +
    + + +In order to activate the default behavior (nacking in AUTO mode; neither acking nor nacking in AUTO_ACK mode), your error handler has to throw an exception. +Otherwise, the adapter will assume that processing completed successfully and will ack the message. +
    +
    +
    +
    +
    @Bean
    +public MessageChannel pubsubInputChannel() {
    +    return new PublishSubscribeChannel();
    +}
    +
    +@Bean
    +public PubSubInboundChannelAdapter messageChannelAdapter(
    +    @Qualifier("pubsubInputChannel") MessageChannel inputChannel,
    +    PubSubTemplate pubsubTemplate) {
    +    PubSubInboundChannelAdapter adapter =
    +        new PubSubInboundChannelAdapter(pubsubTemplate, "subscriptionName");
    +    adapter.setOutputChannel(inputChannel);
    +    adapter.setAckMode(AckMode.AUTO_ACK);
    +    adapter.setErrorChannelName("pubsubErrors");
    +
    +    return adapter;
    +}
    +
    +@ServiceActivator(inputChannel =  "pubsubErrors")
    +public void pubsubErrorHandler(Message<MessagingException> message) {
    +    LOGGER.warn("This message will be automatically acked because error handler completes successfully");
    +}
    +
    +
    +
    +
    +

    If you would prefer to manually ack or nack the message, you can do it by retrieving the header of the exception payload:

    +
    +
    +
    +
    @ServiceActivator(inputChannel =  "pubsubErrors")
    +public void pubsubErrorHandler(Message<MessagingException> exceptionMessage) {
    +
    +    BasicAcknowledgeablePubsubMessage originalMessage =
    +      (BasicAcknowledgeablePubsubMessage)exceptionMessage.getPayload().getFailedMessage()
    +        .getHeaders().get(GcpPubSubHeaders.ORIGINAL_MESSAGE);
    +
    +    originalMessage.nack();
    +}
    +
    +
    +
    +
    +
    +
    +
    +

    7.1.2. Pollable Message Source (using Pub/Sub Synchronous Pull)

    +
    +

    While PubSubInboundChannelAdapter, through the underlying Asynchronous Pull Pub/Sub mechanism, provides the best performance for high-volume applications that receive a steady flow of messages, it can create load balancing anomalies due to message caching. +This behavior is most obvious when publishing a large batch of small messages that take a long time to process individually. +It manifests as one subscriber taking up most messages, even if multiple subscribers are available to take on the work. +For a more detailed explanation of this scenario, see Spring Framework on Google Cloud Pub/Sub documentation.

    +
    +
    +

    In such a scenario, a PubSubMessageSource can help spread the load between different subscribers more evenly.

    +
    +
    +

    As with the Inbound Channel Adapter, the message source has a configurable acknowledgement mode, payload type, and header mapping.

    +
    +
    +

    The default behavior is to return from the synchronous pull operation immediately if no messages are present. +This can be overridden by using setBlockOnPull() method to wait for at least one message to arrive.

    +
    +
    +

    By default, PubSubMessageSource pulls from the subscription one message at a time. +To pull a batch of messages on each request, use the setMaxFetchSize() method to set the batch size.

    +
    +
    + + + + + +
    + + +The subscription name could either be a short subscription name within the current project, or the fully-qualified name referring to a subscription in a different project using the projects/[project_name]/subscriptions/[subscription_name] format. +
    +
    +
    +
    +
    @Bean
    +@InboundChannelAdapter(channel = "pubsubInputChannel", poller = @Poller(fixedDelay = "100"))
    +public MessageSource<Object> pubsubAdapter(PubSubTemplate pubSubTemplate) {
    +    PubSubMessageSource messageSource = new PubSubMessageSource(pubSubTemplate,  "exampleSubscription");
    +    messageSource.setAckMode(AckMode.MANUAL);
    +    messageSource.setPayloadType(String.class);
    +    messageSource.setBlockOnPull(true);
    +    messageSource.setMaxFetchSize(100);
    +    return messageSource;
    +}
    +
    +
    +
    +
    +

    The @InboundChannelAdapter annotation above ensures that the configured MessageSource is polled for messages, which are then available for manipulation with any Spring Integration mechanism on the pubsubInputChannel message channel. +For example, messages can be retrieved in a method annotated with @ServiceActivator, as seen below.

    +
    +
    +

    For additional flexibility, PubSubMessageSource attaches an AcknowledgeablePubSubMessage object to the GcpPubSubHeaders.ORIGINAL_MESSAGE message header. +The object can be used for manually (n)acking the message.

    +
    +
    +
    +
    @ServiceActivator(inputChannel = "pubsubInputChannel")
    +public void messageReceiver(String payload,
    +        @Header(GcpPubSubHeaders.ORIGINAL_MESSAGE) AcknowledgeablePubsubMessage message)
    +            throws InterruptedException {
    +    LOGGER.info("Message arrived by Synchronous Pull! Payload: " + payload);
    +    message.ack();
    +}
    +
    +
    +
    +
    + + + + + +
    + + +AcknowledgeablePubSubMessage objects acquired by synchronous pull are aware of their own acknowledgement IDs. +Streaming pull does not expose this information due to limitations of the underlying API, and returns BasicAcknowledgeablePubsubMessage objects that allow acking/nacking individual messages, but not extracting acknowledgement IDs for future processing. +
    +
    +
    +
    +

    7.1.3. Outbound channel adapter

    +
    +

    PubSubMessageHandler is the outbound channel adapter for Spring Framework on Google Cloud Pub/Sub that listens for new messages on a Spring MessageChannel. +It uses PubSubTemplate to post them to a Spring Framework on Google Cloud Pub/Sub topic.

    +
    +
    +

    To construct a Pub/Sub representation of the message, the outbound channel adapter needs to convert the Spring Message payload to a byte array representation expected by Pub/Sub. +It delegates this conversion to the PubSubTemplate. +To customize the conversion, you can specify a PubSubMessageConverter in the PubSubTemplate that should convert the Object payload and headers of the Spring Message to a PubsubMessage.

    +
    +
    +

    To use the outbound channel adapter, a PubSubMessageHandler bean must be provided and configured on the user application side.

    +
    +
    + + + + + +
    + + +The topic name could either be a short topic name within the current project, or the fully-qualified name referring to a topic in a different project using the projects/[project_name]/topics/[topic_name] format. +
    +
    +
    +
    +
    @Bean
    +@ServiceActivator(inputChannel = "pubsubOutputChannel")
    +public MessageHandler messageSender(PubSubTemplate pubsubTemplate) {
    +    return new PubSubMessageHandler(pubsubTemplate, "topicName");
    +}
    +
    +
    +
    +
    +

    The provided PubSubTemplate contains all the necessary configuration to publish messages to a Spring Framework on Google Cloud Pub/Sub topic.

    +
    +
    +

    PubSubMessageHandler publishes messages asynchronously by default. +A publish timeout can be configured for synchronous publishing. +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.

    +
    +
    +
    +
    adapter.setPublishCallback(
    +    new ListenableFutureCallback<String>() {
    +      @Override
    +      public void onFailure(Throwable ex) {}
    +
    +      @Override
    +      public void onSuccess(String result) {}
    +    });
    +
    +
    +
    +
    +

    To override the default topic you can use the GcpPubSubHeaders.TOPIC header.

    +
    +
    +
    +
    @Autowired
    +private MessageChannel pubsubOutputChannel;
    +
    +public void handleMessage(Message<?> msg) throws MessagingException {
    +    final Message<?> message = MessageBuilder
    +        .withPayload(msg.getPayload())
    +        .setHeader(GcpPubSubHeaders.TOPIC, "customTopic").build();
    +    pubsubOutputChannel.send(message);
    +}
    +
    +
    +
    +
    +

    It is also possible to set an SpEL expression for the topic with the setTopicExpression() or setTopicExpressionString() methods.

    +
    +
    +
    +
    PubSubMessageHandler adapter = new PubSubMessageHandler(pubSubTemplate, "myDefaultTopic");
    +adapter.setTopicExpressionString("headers['sendToTopic']");
    +
    +
    +
    +
    +
    +

    7.1.4. Header mapping

    +
    +

    These channel adapters contain header mappers that allow you to map, or filter out, headers from Spring to Google Cloud Pub/Sub messages, and vice-versa. +By default, the inbound channel adapter maps every header on the Google Cloud Pub/Sub messages to the Spring messages produced by the adapter. +The outbound channel adapter maps every header from Spring messages into Google Cloud Pub/Sub ones, except the ones added by Spring and some special headers, like headers with key "id", "timestamp", "gcp_pubsub_acknowledgement", and "gcp_pubsub_ordering_key". +In the process, the outbound mapper also converts the value of the headers into string.

    +
    +
    +

    Note that you can provide the GcpPubSubHeaders.ORDERING_KEY ("gcp_pubsub_ordering_key") header, which will be automatically mapped to PubsubMessage.orderingKey property, and excluded from the headers in the published message. +Remember to set spring.cloud.gcp.pubsub.publisher.enable-message-ordering to true, if you are publishing messages with this header.

    +
    +
    +

    Each adapter declares a setHeaderMapper() method to let you further customize which headers you want to map from Spring to Google Cloud Pub/Sub, and vice-versa.

    +
    +
    +

    For example, to filter out headers "foo", "bar" and all headers starting with the prefix "prefix_", you can use setHeaderMapper() along with the PubSubHeaderMapper implementation provided by this module.

    +
    +
    +
    +
    PubSubMessageHandler adapter = ...
    +...
    +PubSubHeaderMapper headerMapper = new PubSubHeaderMapper();
    +headerMapper.setOutboundHeaderPatterns("!foo", "!bar", "!prefix_*", "*");
    +adapter.setHeaderMapper(headerMapper);
    +
    +
    +
    +
    + + + + + +
    + + +The order in which the patterns are declared in PubSubHeaderMapper.setOutboundHeaderPatterns() and PubSubHeaderMapper.setInboundHeaderPatterns() matters. +The first patterns have precedence over the following ones. +
    +
    +
    +

    In the previous example, the "*" pattern means every header is mapped. +However, because it comes last in the list, the previous patterns take precedence.

    +
    +
    + +
    +
    +

    7.2. Channel Adapters for Google Cloud Storage

    +
    +

    The channel adapters for Google Cloud Storage allow you to read and write files to Google Cloud Storage through MessageChannels.

    +
    +
    +

    Spring Framework on Google Cloud provides two inbound adapters, GcsInboundFileSynchronizingMessageSource and GcsStreamingMessageSource, and one outbound adapter, GcsMessageHandler.

    +
    +
    +

    The Spring Integration Channel Adapters for Google Cloud Storage are included in the spring-cloud-gcp-storage module.

    +
    +
    +

    To use the Storage portion of Spring Integration for Spring Framework on Google Cloud, you must also provide the spring-integration-file dependency, since it isn’t pulled transitively.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-storage</artifactId>
    +</dependency>
    +<dependency>
    +    <groupId>org.springframework.integration</groupId>
    +    <artifactId>spring-integration-file</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-storage")
    +    implementation("org.springframework.integration:spring-integration-file")
    +}
    +
    +
    +
    +

    7.2.1. Inbound channel adapter

    +
    +

    The Google Cloud Storage inbound channel adapter polls a Google Cloud Storage bucket for new files and sends each of them in a Message payload to the MessageChannel specified in the @InboundChannelAdapter annotation. +The files are temporarily stored in a folder in the local file system.

    +
    +
    +

    Here is an example of how to configure a Google Cloud Storage inbound channel adapter.

    +
    +
    +
    +
    @Bean
    +@InboundChannelAdapter(channel = "new-file-channel", poller = @Poller(fixedDelay = "5000"))
    +public MessageSource<File> synchronizerAdapter(Storage gcs) {
    +  GcsInboundFileSynchronizer synchronizer = new GcsInboundFileSynchronizer(gcs);
    +  synchronizer.setRemoteDirectory("your-gcs-bucket");
    +
    +  GcsInboundFileSynchronizingMessageSource synchAdapter =
    +          new GcsInboundFileSynchronizingMessageSource(synchronizer);
    +  synchAdapter.setLocalDirectory(new File("local-directory"));
    +
    +  return synchAdapter;
    +}
    +
    +
    +
    +
    +
    +

    7.2.2. Inbound streaming channel adapter

    +
    +

    The inbound streaming channel adapter is similar to the normal inbound channel adapter, except it does not require files to be stored in the file system.

    +
    +
    +

    Here is an example of how to configure a Google Cloud Storage inbound streaming channel adapter.

    +
    +
    +
    +
    @Bean
    +@InboundChannelAdapter(channel = "streaming-channel", poller = @Poller(fixedDelay = "5000"))
    +public MessageSource<InputStream> streamingAdapter(Storage gcs) {
    +  GcsStreamingMessageSource adapter =
    +          new GcsStreamingMessageSource(new GcsRemoteFileTemplate(new GcsSessionFactory(gcs)));
    +  adapter.setRemoteDirectory("your-gcs-bucket");
    +  return adapter;
    +}
    +
    +
    +
    +
    +

    If you would like to process the files in your bucket in a specific order, you may pass in a Comparator<BlobInfo> to the constructor GcsStreamingMessageSource to sort the files being processed.

    +
    +
    +
    +

    7.2.3. Outbound channel adapter

    +
    +

    The outbound channel adapter allows files to be written to Google Cloud Storage. +When it receives a Message containing a payload of type File, it writes that file to the Google Cloud Storage bucket specified in the adapter.

    +
    +
    +

    Here is an example of how to configure a Google Cloud Storage outbound channel adapter.

    +
    +
    +
    +
    @Bean
    +@ServiceActivator(inputChannel = "writeFiles")
    +public MessageHandler outboundChannelAdapter(Storage gcs) {
    +  GcsMessageHandler outboundChannelAdapter = new GcsMessageHandler(new GcsSessionFactory(gcs));
    +  outboundChannelAdapter.setRemoteDirectoryExpression(new ValueExpression<>("your-gcs-bucket"));
    +
    +  return outboundChannelAdapter;
    +}
    +
    +
    +
    +
    + +
    +
    +
    +
    +

    8. Spring Cloud Stream

    +
    +
    +

    Spring Framework on Google Cloud provides a Spring Cloud Stream binder to Google Cloud Pub/Sub.

    +
    + +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-pubsub-stream-binder</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-pubsub-stream-binder")
    +}
    +
    +
    +
    +

    8.1. Overview

    +
    +

    This binder binds producers to Google Cloud Pub/Sub topics and consumers to subscriptions.

    +
    +
    + + + + + +
    + + +Partitioning is currently not supported by this binder. +
    +
    +
    +
    +

    8.2. 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.<channelName>.<consumer|producer>.auto-create-resources property, which is turned ON by default.

    +
    +
    +

    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 configuration section for other Pub/Sub parameters.

    +
    +
    + + + + + +
    + + +To use this binder with a running emulator, configure its host and port via spring.cloud.gcp.pubsub.emulator-host. +
    +
    +
    +

    8.2.1. Producer Synchronous Sending Configuration

    +
    +

    By default, this binder will send messages to Cloud Pub/Sub asynchronously. +If synchronous sending is preferred (for example, to allow propagating errors back to the sender), set spring.cloud.stream.gcp.pubsub.default.producer.sync property to true.

    +
    +
    +
    +

    8.2.2. Producer Destination Configuration

    +
    +

    If automatic resource creation is turned ON and the topic corresponding to the destination name does not exist, it will be created.

    +
    +
    +

    For example, for the following configuration, a topic called myEvents would be created.

    +
    +
    +
    application.properties
    +
    +
    spring.cloud.stream.bindings.events.destination=myEvents
    +spring.cloud.stream.gcp.pubsub.bindings.events.producer.auto-create-resources=true
    +
    +
    +
    +
    +

    8.2.3. Consumer Destination Configuration

    +
    +

    A PubSubInboundChannelAdapter will be configured for your consumer endpoint. +You may adjust the ack mode of the consumer endpoint using the ack-mode property. +The ack mode controls how messages will be acknowledged when they are successfully received. +The three possible options are: AUTO (default), AUTO_ACK, and MANUAL. +These options are described in detail in the Pub/Sub channel adapter documentation.

    +
    +
    +
    application.properties
    +
    +
    # How to set the ACK mode of the consumer endpoint.
    +spring.cloud.stream.gcp.pubsub.bindings.{CONSUMER_NAME}.consumer.ack-mode=AUTO_ACK
    +
    +
    +
    +

    With automatic resource creation turned ON for a consumer, the library creates a topic and/or a subscription if they do not exist. +The topic name becomes the same as the destination name, and the subscription name follows these rules (in order of precedence):

    +
    +
    +
      +
    • +

      A user-defined, pre-existing subscription (use spring.cloud.stream.gcp.pubsub.bindings.{CONSUMER_NAME}.consumer.subscriptionName)

      +
    • +
    • +

      A consumer group using the topic name (use spring.cloud.stream.bindings.events.group to create a subscription named <topicName>.<group>)

      +
    • +
    • +

      If neither of the above are specified, the library creates an anonymous subscription with the name anonymous.<destinationName>.<randomUUID>. +Then when the binder shuts down, the library automatically cleans up all Pub/Sub subscriptions created for anonymous consumer groups.

      +
    • +
    +
    +
    +

    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
    +
    +
    +
    +

    Only an anonymous subscription named anonymous.myEvents.a6d83782-c5a3-4861-ac38-e6e2af15a7be is created and later cleaned up.

    +
    +
    +

    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
    +
    +# specify consumer group, and avoid anonymous consumer group generation
    +spring.cloud.stream.bindings.events.group=consumerGroup1
    +
    +
    +
    +

    These resources will be created:

    +
    +
    +
      +
    • +

      A topic named myEvents

      +
    • +
    • +

      A subscription named myEvents.consumerGroup1

      +
    • +
    +
    +
    +
    +

    8.2.4. Header Mapping

    +
    +

    You can filter incoming and outgoing message headers with allowHeaders property. +For example, for a consumer to allow only two headers, provide a comma separated list like this:

    +
    +
    +
    application.properties
    +
    +
    spring.cloud.stream.gcp.pubsub.bindings.<consumerFunction>-in-0.consumer.allowedHeaders=allowed1, allowed2
    +
    +
    +
    +

    Where <consumerFunction> 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.

    +
    +
    +

    A similar style is applicable for producers as well. For example:

    +
    +
    +
    application.properties
    +
    +
    spring.cloud.stream.gcp.pubsub.bindings.<producerFunction>-out-0.producer.allowedHeaders=allowed3,allowed4
    +
    +
    +
    +

    Where <producerFunction> 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.

    +
    +
    +
    +

    8.2.5. Endpoint Customization

    +
    +

    You may customize channel routing by defining a ConsumerEndpointCustomizer in your autoconfiguration. This is useful if you want to customize the default configurations provided by the Pub/Sub Spring Cloud Stream Binder.

    +
    +
    +

    The example below demonstrates how to use a ConsumerEndpointCustomizer to override the default error channel configured by the binder.

    +
    +
    +
    +
    @Bean
    +public ConsumerEndpointCustomizer<PubSubInboundChannelAdapter> messageChannelAdapter() {
    +    return (endpoint, destinationName, group) -> {
    +        NamedComponent namedComponent = (NamedComponent) endpoint.getOutputChannel();
    +        String channelName = namedComponent.getBeanName();
    +        endpoint.setErrorChannelName(channelName + ".errors");
    +    };
    +}
    +
    +
    +
    +
    +
    +
    +

    8.3. Binding with Functions

    +
    +

    Since version 3.0, Spring Cloud Stream supports a functional programming model natively. +This means that the only requirement for turning your application into a sink is presence of a java.util.function.Consumer bean in the application context.

    +
    +
    +
    +
    @Bean
    +public Consumer<UserMessage> logUserMessage() {
    +  return userMessage -> {
    +    // process message
    +  }
    +};
    +
    +
    +
    +

    A source application is one where a Supplier bean is present. +It can return an object, in which case Spring Cloud Stream will invoke the supplier repeatedly. +Alternatively, the function can return a reactive stream, which will be used as is.

    +
    +
    +
    +
    @Bean
    +Supplier<Flux<UserMessage>> generateUserMessages() {
    +  return () -> /* flux creation logic */;
    +}
    +
    +
    +
    +

    A processor application works similarly to a source application, except it is triggered by presence of a Function bean.

    +
    +
    +
    +

    8.4. 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 */));
    +    }
    +}
    +
    +
    +
    +
    +

    8.5. 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.

    +
    +
    +
    +
    public interface PollableSink {
    +
    +    @Input("input")
    +    PollableMessageSource input();
    +}
    +
    +
    +
    +
    +

    The PollableMessageSource can then be injected and queried, as needed.

    +
    +
    +
    +
    @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.

    +
    +
    +
    +

    8.6. Sample

    +
    +

    Sample applications are available:

    +
    + +
    +
    +
    +
    +

    9. Spring Cloud Bus

    +
    +
    +

    Using Cloud Pub/Sub as the Spring Cloud Bus implementation is as simple as importing the spring-cloud-gcp-starter-bus-pubsub starter.

    +
    +
    +

    This starter brings in the Spring Cloud Stream binder for Cloud Pub/Sub, which is used to both publish and subscribe to the bus. +If the bus topic (named springCloudBus by default) does not exist, the binder automatically creates it. +The binder also creates anonymous subscriptions for each project using the spring-cloud-gcp-starter-bus-pubsub starter.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +  <groupId>com.google.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-starter-bus-pubsub</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-bus-pubsub")
    +}
    +
    +
    +
    +
    +

    9.1. Configuration Management with Spring Cloud Config and Spring Cloud Bus

    +
    +

    Spring Cloud Bus can be used to push configuration changes from a Spring Cloud Config server to the clients listening on the same bus.

    +
    +
    +

    To use Spring Framework on Google Cloud Pub/Sub as the bus implementation, both the configuration server and the configuration client need the spring-cloud-gcp-starter-bus-pubsub dependency.

    +
    +
    +

    All other configuration is standard to Spring Cloud Config.

    +
    +
    +
    +spring cloud bus over pubsub +
    +
    +
    +

    Spring Cloud Config Server typically runs on port 8888, and can read configuration from a variety of source control systems such as GitHub, and even from the local filesystem. +When the server is notified that new configuration is available, it fetches the updated configuration and sends a notification (RefreshRemoteApplicationEvent) out via Spring Cloud Bus.

    +
    +
    +

    When configuration is stored locally, config server polls the parent directory for changes. +With configuration stored in source control repository, such as GitHub, the config server needs to be notified that a new version of configuration is available. +In a deployed server, this would be done automatically through a GitHub webhook, but in a local testing scenario, the /monitor HTTP endpoint needs to be invoked manually.

    +
    +
    +
    +
    curl -X POST http://localhost:8888/monitor -H "X-Github-Event: push" -H "Content-Type: application/json" -d '{"commits": [{"modified": ["application.properties"]}]}'
    +
    +
    +
    +

    By adding the spring-cloud-gcp-starter-bus-pubsub dependency, you instruct Spring Cloud Bus to use Cloud Pub/Sub to broadcast configuration changes. +Spring Cloud Bus will then create a topic named springCloudBus, as well as a subscription for each configuration client.

    +
    +
    +

    The configuration server happens to also be a configuration client, subscribing to the configuration changes that it sends out. +Thus, in a scenario with one configuration server and one configuration client, two anonymous subscriptions to the springCloudBus topic are created. +However, a config server disables configuration refresh by default (see ConfigServerBootstrapApplicationListener for more details).

    +
    +
    +

    A demo application showing configuration management and distribution over a Cloud Pub/Sub-powered bus is available. +The sample contains two examples of configuration management with Spring Cloud Bus: one monitoring a local file system, and the other retrieving configuration from a GitHub repository.

    +
    +
    +
    +
    +
    +

    10. Cloud Trace

    +
    +
    +

    Google Cloud provides a managed distributed tracing service called Cloud Trace, and Spring Cloud Sleuth 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 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.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-trace</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-trace")
    +}
    +
    +
    +
    +

    You must enable Cloud Trace API from the Google Cloud Console in order to capture traces. +Navigate to the Cloud Trace API for your project and make sure it’s enabled.

    +
    +
    + + + + + +
    + + +
    +

    If you are already using a Zipkin server capturing trace information from multiple platform/frameworks, you can also use a Stackdriver Zipkin proxy to forward those traces to Cloud Trace without modifying existing applications.

    +
    +
    +
    +
    +

    10.1. Tracing

    +
    +

    Spring Cloud Sleuth uses the Brave tracer to generate traces. +This integration enables Brave to use the 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. +A canonical example of the propagation usage is a web server that receives an HTTP request, which triggers other HTTP requests from the server before returning an HTTP response to the original caller.

    +
    +
    +

    In the case of StackdriverTracePropagation, first it looks for trace context in the 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 CUSTOM propagation type.

    +
    +
    + + + + + +
    + + +
    +

    The value of the x-cloud-trace-context key can be formatted in three different ways:

    +
    +
    +
      +
    • +

      x-cloud-trace-context: TRACE_ID

      +
    • +
    • +

      x-cloud-trace-context: TRACE_ID/SPAN_ID

      +
    • +
    • +

      x-cloud-trace-context: TRACE_ID/SPAN_ID;o=TRACE_TRUE

      +
    • +
    +
    +
    +

    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 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.

    +
    +
    +
    +
    +
    +

    10.2. Spring Boot Starter for Cloud Trace

    +
    +

    Spring Boot Starter for Cloud Trace uses Spring Cloud Sleuth and auto-configures a StackdriverSender that sends the Sleuth’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.project-id

    Overrides the project ID from the Spring Framework on Google Cloud Module

    No

    spring.cloud.gcp.trace.credentials.location

    Overrides the credentials location from the Spring Framework on Google Cloud Module

    No

    spring.cloud.gcp.trace.credentials.encoded-key

    Overrides the credentials encoded key from the Spring Framework on Google Cloud Module

    No

    spring.cloud.gcp.trace.credentials.scopes

    Overrides the credentials scopes from the Spring Framework on Google Cloud Module

    No

    spring.cloud.gcp.trace.num-executor-threads

    Number of threads used by the Trace executor

    No

    4

    spring.cloud.gcp.trace.authority

    HTTP/2 authority the channel claims to be connecting to.

    No

    spring.cloud.gcp.trace.compression

    Name of the compression to use in Trace calls

    No

    spring.cloud.gcp.trace.deadline-ms

    Call deadline in milliseconds

    No

    spring.cloud.gcp.trace.max-inbound-size

    Maximum size for inbound messages

    No

    spring.cloud.gcp.trace.max-outbound-size

    Maximum size for outbound messages

    No

    spring.cloud.gcp.trace.wait-for-ready

    Waits for the channel to be ready in case of a transient failure

    No

    false

    spring.cloud.gcp.trace.messageTimeout

    Timeout in seconds before pending spans will be sent in batches to Google Cloud Trace. (previously spring.zipkin.messageTimeout)

    No

    1

    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 for more information on Sleuth 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
    +
    +
    +
    + + + + + +
    + + +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:

    +
    +
    +
      +
    • +

      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.

      +
    • +
    +
    +
    +
    +

    10.3. 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<Span> 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.

    +
    +
    +
    +

    10.4. Customizing spans

    +
    +

    You can add additional tags and annotations to spans by using the brave.SpanCustomizer, which is available in the application context.

    +
    +
    +

    Here’s an example that uses WebMvcConfigurer to configure an MVC interceptor that adds two extra tags to all web controller spans.

    +
    +
    +
    +
    @SpringBootApplication
    +public class Application implements WebMvcConfigurer {
    +
    +    public static void main(String[] args) {
    +        SpringApplication.run(Application.class, args);
    +    }
    +
    +    @Autowired
    +    private SpanCustomizer spanCustomizer;
    +
    +    @Override
    +    public void addInterceptors(InterceptorRegistry registry) {
    +        registry.addInterceptor(new HandlerInterceptor() {
    +            @Override
    +            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    +                spanCustomizer.tag("session-id", request.getSession().getId());
    +                spanCustomizer.tag("environment", "QA");
    +
    +                return true;
    +            }
    +        });
    +    }
    +}
    +
    +
    +
    +
    +

    You can then search and filter traces based on these additional tags in the Cloud Trace service.

    +
    +
    +
    +

    10.5. Integration with Logging

    +
    +

    Integration with Cloud Logging is available through the Cloud Logging Support. +If the Trace integration is used together with the Logging one, the request logs will be associated to the corresponding traces. +The trace logs can be viewed by going to the Google Cloud Console Trace List, selecting a trace and pressing the Logs → View link in the Details section.

    +
    +
    +
    +

    10.6. Pub/Sub Trace Instrumentation (Experimental)

    +
    +

    You can enable trace instrumentation and propagation for Pub/Sub messages by using the spring.cloud.gcp.trace.pubsub.enabled=true property. +It’s set to false by default, but when set to true, trace spans will be created and propagated to Cloud Trace whenever the application sends or receives messages through PubSubTemplate or any other integration that builds on top of PubSubTemplate, such as the 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
    +
    +
    +
    +
    +

    10.7. Sample

    +
    +

    A sample application and a codelab are available.

    +
    +
    +
    +
    +
    +

    11. Cloud Logging

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-logging</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-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 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. +This allows grouping of log messages by request, for example, in the Google Cloud Console Logs viewer.

    +
    +
    + + + + + +
    + + +Due to the way logging is set up, the Google Cloud project ID and credentials defined in application.properties are ignored. +Instead, you should set the GOOGLE_CLOUD_PROJECT and GOOGLE_APPLICATION_CREDENTIALS environment variables to the project ID and credentials private key location, respectively. +You can do this easily if you’re using the Google Cloud SDK, using the gcloud config set project [YOUR_PROJECT_ID] and gcloud auth application-default login commands, respectively. +
    +
    +
    +

    11.1. Web MVC Interceptor

    +
    +

    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.

    +
    +
    + + + + + +
    + + +If Spring Framework on Google Cloud Trace is enabled, the logging module disables itself and delegates log correlation to Spring Cloud Sleuth. +
    +
    +
    +

    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.

    +
    +
    +
    +

    11.2. Logback Support

    +
    +

    Currently, only Logback is supported and there are 2 possibilities to log to Cloud Logging via this library with Logback: via direct API calls and through JSON-formatted console logs.

    +
    +
    +

    11.2.1. Log via API

    +
    +

    A Cloud Logging appender is available using com/google/cloud/spring/logging/logback-appender.xml. +This appender builds a Cloud Logging log entry from a JUL or Logback log entry, adds a trace ID to it and sends it to Cloud Logging.

    +
    +
    +

    STACKDRIVER_LOG_NAME and STACKDRIVER_LOG_FLUSH_LEVEL environment variables can be used to customize the STACKDRIVER appender.

    +
    +
    +

    Your configuration may then look like this:

    +
    +
    +
    +
    <configuration>
    +  <include resource="com/google/cloud/spring/logging/logback-appender.xml" />
    +
    +  <root level="INFO">
    +    <appender-ref ref="STACKDRIVER" />
    +  </root>
    +</configuration>
    +
    +
    +
    +

    If you want to have more control over the log output, you can further configure the appender. +The following properties are available (see java-logging-logback project for the full list):

    +
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PropertyDefault ValueDescription

    log

    spring.log

    The Cloud Logging Log name. +This can also be set via the STACKDRIVER_LOG_NAME environmental variable.

    flushLevel

    WARN

    If a log entry with this level is encountered, trigger a flush of locally buffered log to Cloud Logging. +This can also be set via the STACKDRIVER_LOG_FLUSH_LEVEL environmental variable.

    enhancer

    Fully qualified class name for customizing a logging entry; must implement com.google.cloud.logging.LoggingEnhancer.

    loggingEventEnhancer

    Fully qualified class name for customizing a logging entry given an ILoggingEvent; must implement com.google.cloud.logging.logback.LoggingEventEnhancer.

    +
    +
    +

    11.2.2. Asynchronous Logging

    +
    +

    If you would like to send logs asynchronously to Cloud Logging, you can use the AsyncAppender.

    +
    +
    +

    Your configuration may then look like this:

    +
    +
    +
    +
    <configuration>
    +  <include resource="com/google/cloud/spring/logging/logback-appender.xml" />
    +
    +  <appender name="ASYNC_STACKDRIVER" class="ch.qos.logback.classic.AsyncAppender">
    +    <appender-ref ref="STACKDRIVER" />
    +  </appender>
    +
    +  <root level="INFO">
    +    <appender-ref ref="ASYNC_STACKDRIVER" />
    +  </root>
    +</configuration>
    +
    +
    +
    +
    +

    11.2.3. Log via Console

    +
    +

    For Logback, a com/google/cloud/spring/logging/logback-json-appender.xml file is made available for import to make it easier to configure the JSON Logback appender.

    +
    +
    +

    Your configuration may then look something like this:

    +
    +
    +
    +
    <configuration>
    +  <include resource="com/google/cloud/spring/logging/logback-json-appender.xml" />
    +
    +  <root level="INFO">
    +    <appender-ref ref="CONSOLE_JSON" />
    +  </root>
    +</configuration>
    +
    +
    +
    +

    If your application is running on Google Kubernetes Engine, Google Compute Engine or Google App Engine Flexible, your console logging is automatically saved to Google Cloud Logging. +Therefore, you can just include com/google/cloud/spring/logging/logback-json-appender.xml in your logging configuration, which logs JSON entries to the console. +The trace id will be set correctly.

    +
    +
    +

    If you want to have more control over the log output, you can further configure the appender. +The following properties are available:

    +
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PropertyDefault ValueDescription

    projectId

    +

    If not set, default value is determined in the following order:

    +
    +
    +
      +
    1. +

      SPRING_CLOUD_GCP_LOGGING_PROJECT_ID Environmental Variable.

      +
    2. +
    3. +

      Value of DefaultGcpProjectIdProvider.getProjectId()

      +
    4. +
    +
    +

    This is used to generate fully qualified Cloud Trace ID format: projects/[PROJECT-ID]/traces/[TRACE-ID].

    +
    +
    +

    This format is required to correlate trace between Cloud Trace and Cloud Logging.

    +
    +
    +

    If projectId is not set and cannot be determined, then it’ll log traceId without the fully qualified format.

    +

    traceIdMdcField

    traceId

    The MDC field name for retrieving a trace id

    spanIdMdcField

    spanId

    the MDC field name for retrieving a span id

    includeTraceId

    true

    Should the trace id be included

    includeSpanId

    true

    Should the span id be included

    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

    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. +This setting is only evaluated if includeFormattedMessage is true

    includeContextName

    true

    Should the logging context be included

    includeMessage

    false

    Should the log message with blank placeholders be included

    includeException

    false

    Should the stacktrace be included as a own field

    serviceContext

    none

    Define the Stackdriver service context data (service and version). This allows filtering of error reports for service and version in the Google Cloud Error Reporting View.

    customJson

    none

    Defines custom json data. Data will be added to the json output.

    loggingEventEnhancer

    none

    Name of a class implementing JsonLoggingEventEnhancer which modifies the JSON logging output. This tag is repeatable.

    +

    Examples are provided in the extensions package.

    +

    - Logstash Enhancer

    +
    +

    This is an example of such an Logback configuration:

    +
    +
    +
    +
    <configuration >
    +  <property name="projectId" value="${projectId:-${GOOGLE_CLOUD_PROJECT}}"/>
    +
    +  <appender name="CONSOLE_JSON" class="ch.qos.logback.core.ConsoleAppender">
    +    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
    +      <layout class="com.google.cloud.spring.logging.StackdriverJsonLayout">
    +        <projectId>${projectId}</projectId>
    +
    +        <!--<traceIdMdcField>traceId</traceIdMdcField>-->
    +        <!--<spanIdMdcField>spanId</spanIdMdcField>-->
    +        <!--<includeTraceId>true</includeTraceId>-->
    +        <!--<includeSpanId>true</includeSpanId>-->
    +        <!--<includeLevel>true</includeLevel>-->
    +        <!--<includeThreadName>true</includeThreadName>-->
    +        <!--<includeMDC>true</includeMDC>-->
    +        <!--<includeLoggerName>true</includeLoggerName>-->
    +        <!--<includeFormattedMessage>true</includeFormattedMessage>-->
    +        <!--<includeExceptionInMessage>true</includeExceptionInMessage>-->
    +        <!--<includeContextName>true</includeContextName>-->
    +        <!--<includeMessage>false</includeMessage>-->
    +        <!--<includeException>false</includeException>-->
    +        <!--<serviceContext>
    +              <service>service-name</service>
    +              <version>service-version</version>
    +            </serviceContext>-->
    +        <!--<customJson>{"custom-key": "custom-value"}</customJson>-->
    +        <!--<loggingEventEnhancer>your.package.YourLoggingEventEnhancer</loggingEventEnhancer> -->
    +      </layout>
    +    </encoder>
    +  </appender>
    +</configuration>
    +
    +
    +
    +
    +
    +

    11.3. Sample

    +
    +

    A Sample Spring Boot Application is provided to show how to use the Cloud logging starter.

    +
    +
    +
    +
    +
    +

    12. Cloud Monitoring

    +
    +
    +

    Google Cloud provides a service called Cloud Monitoring, and Micrometer can be used with it to easily instrument Spring Boot applications for observability.

    +
    +
    +

    Spring Boot already provides auto-configuration for Cloud Monitoring. +This module enables auto-detection of the project-id and credentials. +Also, it can be customized.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-metrics</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-metrics")
    +}
    +
    +
    +
    +

    You must enable Cloud Monitoring API from the Google Cloud Console in order to capture metrics. +Navigate to the Cloud Monitoring API for your project and make sure it’s enabled.

    +
    +
    +

    Spring Boot Starter for Cloud Monitoring uses Micrometer.

    +
    +
    +

    12.1. Configuration

    +
    +

    All configurations are optional:

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.metrics.enabled

    Auto-configure Micrometer to send metrics to Cloud Monitoring.

    No

    true

    spring.cloud.gcp.metrics.project-id

    Overrides the project ID from the Spring Framework on Google Cloud Module

    No

    spring.cloud.gcp.metrics.credentials.location

    Overrides the credentials location from the Spring Framework on Google Cloud Module

    No

    spring.cloud.gcp.metrics.credentials.encoded-key

    Overrides the credentials encoded key from the Spring Framework on Google Cloud Module

    No

    spring.cloud.gcp.metrics.credentials.scopes

    Overrides the credentials scopes from the Spring Framework on Google Cloud Module

    No

    +
    +

    You can use core Spring Boot Actuator properties to control reporting frequency, etc. +Read Spring Boot Actuator documentation for more information on Stackdriver Actuator configurations.

    +
    +
    +

    12.1.1. Metrics Disambiguation

    +
    +

    By default, spring-cloud-gcp-starter-metrics/the StackdriverMeterRegistry does not add any application/pod specific tags to the metrics, +thus google is unable to distinguish between multiple metric sources. +This could lead to the following warning inside your applications logs:

    +
    +
    +
    +
    2022-08-15 10:26:00.248  WARN 1 --- [trics-publisher] i.m.s.StackdriverMeterRegistry           : failed to send metrics to Stackdriver
    +
    +
    +
    +

    The actual cause message may vary:

    +
    +
    +
    +
    One or more TimeSeries could not be written:
    +One or more points were written more frequently than the maximum sampling period configured for the metric.: global{} timeSeries[4]: custom.googleapis.com/process/uptime{};
    +One or more points were written more frequently than the maximum sampling period configured for the metric.: global{} timeSeries[6]: custom.googleapis.com/system/load/average/1m{};
    +One or more points ...
    +
    +
    +
    +

    or even:

    +
    +
    +
    +
    Caused by: io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2Exception: Header size exceeded max allowed size (10240)
    +
    +
    +
    +

    (due to the error message being too long)

    +
    +
    +

    Google rejects metric updates for entries that it has received before (from another application) for the same interval. +To avoid these conflicts and in order to distinguish between applications/instances you should add a configuration similar to this:

    +
    +
    +
    +
    management:
    +  metrics:
    +    tags:
    +      app: my-foobar-service
    +      instance: ${random.uuid}
    +
    +
    +
    +

    Instead of the random uuid you could also use the pod id/the hostname or some other instance id. +Read more about custom tags here.

    +
    +
    +
    +
    +

    12.2. Sample

    +
    +

    A sample application is available.

    +
    +
    +
    +
    +
    +

    13. Spring Data Cloud Spanner

    +
    +
    +

    Spring Data is an abstraction for storing and retrieving POJOs in numerous storage technologies. +Spring Framework on Google Cloud adds Spring Data support for Google Cloud Spanner.

    +
    +
    +

    Maven coordinates for this module only, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-data-spanner</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-data-spanner")
    +}
    +
    +
    +
    +

    We provide a Spring Boot Starter for Spring Data Spanner, with which you can leverage our recommended auto-configuration setup. +To use the starter, see the coordinates see below.

    +
    +
    +

    Maven:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-data-spanner</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-data-spanner")
    +}
    +
    +
    +
    +

    This setup takes care of bringing in the latest compatible version of Cloud Java Cloud Spanner libraries as well.

    +
    +
    +

    13.1. Configuration

    +
    +

    To setup Spring Data Cloud Spanner, you have to configure the following:

    +
    +
    +
      +
    • +

      Setup the connection details to Google Cloud Spanner.

      +
    • +
    • +

      Enable Spring Data Repositories (optional).

      +
    • +
    +
    +
    +

    13.1.1. Cloud Spanner settings

    +
    +

    You can use the Spring Boot Starter for Spring Data Spanner to autoconfigure Google Cloud Spanner in your Spring application. +It contains all the necessary setup that makes it easy to authenticate with your Google Cloud project. +The following configuration options are available:

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.spanner.enabled

    Enables the Cloud Spanner client

    No

    true

    spring.cloud.gcp.spanner.instance-id

    Cloud Spanner instance to use

    Yes

    spring.cloud.gcp.spanner.database

    Cloud Spanner database to use

    Yes

    spring.cloud.gcp.spanner.project-id

    Google Cloud project ID where the Google Cloud Spanner API is hosted, if different from the one in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.spanner.credentials.location

    OAuth2 credentials for authenticating with the +Google Cloud Spanner API, if different from the ones in the +Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.spanner.credentials.encoded-key

    Base64-encoded OAuth2 credentials for authenticating with the +Google Cloud Spanner API, if different from the ones in the +Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.spanner.credentials.scopes

    OAuth2 scope for Spring Framework on Google +CloudSpanner credentials

    No

    www.googleapis.com/auth/spanner.data

    spring.cloud.gcp.spanner.createInterleavedTableDdlOnDeleteCascade

    If true, then schema statements generated by SpannerSchemaUtils for tables with interleaved parent-child relationships will be "ON DELETE CASCADE". +The schema for the tables will be "ON DELETE NO ACTION" if false.

    No

    true

    spring.cloud.gcp.spanner.numRpcChannels

    Number of gRPC channels used to connect to Cloud Spanner

    No

    4 - Determined by Cloud Spanner client library

    spring.cloud.gcp.spanner.prefetchChunks

    Number of chunks prefetched by Cloud Spanner for read and query

    No

    4 - Determined by Cloud Spanner client library

    spring.cloud.gcp.spanner.minSessions

    Minimum number of sessions maintained in the session pool

    No

    0 - Determined by Cloud Spanner client library

    spring.cloud.gcp.spanner.maxSessions

    Maximum number of sessions session pool can have

    No

    400 - Determined by Cloud Spanner client library

    spring.cloud.gcp.spanner.maxIdleSessions

    Maximum number of idle sessions session pool will maintain

    No

    0 - Determined by Cloud Spanner client library

    spring.cloud.gcp.spanner.writeSessionsFraction

    Fraction of sessions to be kept prepared for write transactions

    No

    0.2 - Determined by Cloud Spanner client library

    spring.cloud.gcp.spanner.keepAliveIntervalMinutes

    How long to keep idle sessions alive

    No

    30 - Determined by Cloud Spanner client library

    spring.cloud.gcp.spanner.failIfPoolExhausted

    If all sessions are in use, fail the request by throwing an exception. Otherwise, by default, block until a session becomes available.

    No

    false

    spring.cloud.gcp.spanner.emulator.enabled

    Enables the usage of an emulator. If this is set to true, then you should set the spring.cloud.gcp.spanner.emulator-host to the host:port of your locally running emulator instance.

    No

    false

    spring.cloud.gcp.spanner.emulator-host

    The host and port of the Spanner emulator; can be overridden to specify connecting to an already-running Spanner emulator instance.

    No

    localhost:9010

    +
    + + + + + +
    + + +For further customization of the client library SpannerOptions, provide a bean implementing SpannerOptionsCustomizer, with a single method that accepts a SpannerOptions.Builder and modifies it as necessary. +
    +
    +
    +
    +

    13.1.2. Repository settings

    +
    +

    Spring Data Repositories can be configured via the @EnableSpannerRepositories annotation on your main @Configuration class. +With our Spring Boot Starter for Spring Data Cloud Spanner, @EnableSpannerRepositories is automatically added. +It is not required to add it to any other class, unless there is a need to override finer grain configuration parameters provided by @EnableSpannerRepositories.

    +
    +
    +
    +

    13.1.3. Autoconfiguration

    +
    +

    Our Spring Boot autoconfiguration creates the following beans available in the Spring application context:

    +
    +
    +
      +
    • +

      an instance of SpannerTemplate

      +
    • +
    • +

      an instance of SpannerDatabaseAdminTemplate for generating table schemas from object hierarchies and creating and deleting tables and databases

      +
    • +
    • +

      an instance of all user-defined repositories extending SpannerRepository, CrudRepository, PagingAndSortingRepository, when repositories are enabled

      +
    • +
    • +

      an instance of DatabaseClient from the Google Cloud Java Client for Spanner, for convenience and lower level API access

      +
    • +
    +
    +
    +
    +
    +

    13.2. Object Mapping

    +
    +

    Spring Data Cloud Spanner allows you to map domain POJOs to Cloud Spanner tables via annotations:

    +
    +
    +
    +
    @Table(name = "traders")
    +public class Trader {
    +
    +    @PrimaryKey
    +    @Column(name = "trader_id")
    +    String traderId;
    +
    +    String firstName;
    +
    +    String lastName;
    +
    +    @NotMapped
    +    Double temporaryNumber;
    +}
    +
    +
    +
    +
    +

    Spring Data Cloud Spanner will ignore any property annotated with @NotMapped. +These properties will not be written to or read from Spanner.

    +
    +
    +

    13.2.1. Constructors

    +
    +

    Simple constructors are supported on POJOs. +The constructor arguments can be a subset of the persistent properties. +Every constructor argument needs to have the same name and type as a persistent property on the entity and the constructor should set the property from the given argument. +Arguments that are not directly set to properties are not supported.

    +
    +
    +
    +
    @Table(name = "traders")
    +public class Trader {
    +    @PrimaryKey
    +    @Column(name = "trader_id")
    +    String traderId;
    +
    +    String firstName;
    +
    +    String lastName;
    +
    +    @NotMapped
    +    Double temporaryNumber;
    +
    +    public Trader(String traderId, String firstName) {
    +        this.traderId = traderId;
    +        this.firstName = firstName;
    +    }
    +}
    +
    +
    +
    +
    +
    +

    13.2.2. Table

    +
    +

    The @Table annotation can provide the name of the Cloud Spanner table that stores instances of the annotated class, one per row. +This annotation is optional, and if not given, the name of the table is inferred from the class name with the first character uncapitalized.

    +
    +
    +
    SpEL expressions for table names
    +
    +

    In some cases, you might want the @Table table name to be determined dynamically. +To do that, you can use Spring Expression Language.

    +
    +
    +

    For example:

    +
    +
    +
    +
    @Table(name = "trades_#{tableNameSuffix}")
    +public class Trade {
    +    // ...
    +}
    +
    +
    +
    +
    +

    The table name will be resolved only if the tableNameSuffix value/bean in the Spring application context is defined. +For example, if tableNameSuffix has the value "123", the table name will resolve to trades_123.

    +
    +
    +
    +
    +

    13.2.3. Primary Keys

    +
    +

    For a simple table, you may only have a primary key consisting of a single column. +Even in that case, the @PrimaryKey annotation is required. +@PrimaryKey identifies the one or more ID properties corresponding to the primary key.

    +
    +
    +

    Spanner has first class support for composite primary keys of multiple columns. +You have to annotate all of your POJO’s fields that the primary key consists of with @PrimaryKey as below:

    +
    +
    +
    +
    @Table(name = "trades")
    +public class Trade {
    +    @PrimaryKey(keyOrder = 2)
    +    @Column(name = "trade_id")
    +    private String tradeId;
    +
    +    @PrimaryKey(keyOrder = 1)
    +    @Column(name = "trader_id")
    +    private String traderId;
    +
    +    private String action;
    +
    +    private BigDecimal price;
    +
    +    private Double shares;
    +
    +    private String symbol;
    +}
    +
    +
    +
    +
    +

    The keyOrder parameter of @PrimaryKey identifies the properties corresponding to the primary key columns in order, starting with 1 and increasing consecutively. +Order is important and must reflect the order defined in the Cloud Spanner schema. +In our example the DDL to create the table and its primary key is as follows:

    +
    +
    +
    +
    CREATE TABLE trades (
    +    trader_id STRING(MAX),
    +    trade_id STRING(MAX),
    +    action STRING(15),
    +    symbol STRING(10),
    +    price NUMERIC,
    +    shares FLOAT64
    +) PRIMARY KEY (trader_id, trade_id)
    +
    +
    +
    +

    Spanner does not have automatic ID generation. +For most use-cases, sequential IDs should be used with caution to avoid creating data hotspots in the system. +Read Spanner Primary Keys documentation for a better understanding of primary keys and recommended practices.

    +
    +
    +
    +

    13.2.4. Columns

    +
    +

    All accessible properties on POJOs are automatically recognized as a Cloud Spanner column. +Column naming is generated by the PropertyNameFieldNamingStrategy by default defined on the SpannerMappingContext bean. +The @Column annotation optionally provides a different column name than that of the property and some other settings:

    +
    +
    +
      +
    • +

      name is the optional name of the column

      +
    • +
    • +

      spannerTypeMaxLength specifies for STRING and BYTES columns the maximum length. +This setting is only used when generating DDL schema statements based on domain types.

      +
    • +
    • +

      nullable specifies if the column is created as NOT NULL. +This setting is only used when generating DDL schema statements based on domain types.

      +
    • +
    • +

      spannerType is the Cloud Spanner column type you can optionally specify. +If this is not specified then a compatible column type is inferred from the Java property type.

      +
    • +
    • +

      spannerCommitTimestamp is a boolean specifying if this property corresponds to an auto-populated commit timestamp column. +Any value set in this property will be ignored when writing to Cloud Spanner.

      +
    • +
    +
    +
    +
    +

    13.2.5. Embedded Objects

    +
    +

    If an object of type B is embedded as a property of A, then the columns of B will be saved in the same Cloud Spanner table as those of A.

    +
    +
    +

    If B has primary key columns, those columns will be included in the primary key of A. B can also have embedded properties. +Embedding allows reuse of columns between multiple entities, and can be useful for implementing parent-child situations, because Cloud Spanner requires child tables to include the key columns of their parents.

    +
    +
    +

    For example:

    +
    +
    +
    +
    class X {
    +  @PrimaryKey
    +  String grandParentId;
    +
    +  long age;
    +}
    +
    +class A {
    +  @PrimaryKey
    +  @Embedded
    +  X grandParent;
    +
    +  @PrimaryKey(keyOrder = 2)
    +  String parentId;
    +
    +  String value;
    +}
    +
    +@Table(name = "items")
    +class B {
    +  @PrimaryKey
    +  @Embedded
    +  A parent;
    +
    +  @PrimaryKey(keyOrder = 2)
    +  String id;
    +
    +  @Column(name = "child_value")
    +  String value;
    +}
    +
    +
    +
    +
    +

    Entities of B can be stored in a table defined as:

    +
    +
    +
    +
    CREATE TABLE items (
    +    grandParentId STRING(MAX),
    +    parentId STRING(MAX),
    +    id STRING(MAX),
    +    value STRING(MAX),
    +    child_value STRING(MAX),
    +    age INT64
    +) PRIMARY KEY (grandParentId, parentId, id)
    +
    +
    +
    +

    Note that the following restrictions apply when you use embedded objects:

    +
    +
    +
      +
    • +

      Embedded properties' column names must all be unique.

      +
    • +
    • +

      Embedded properties must not be passed through a constructor and the property must be mutable; otherwise you’ll get an error, such as SpannerDataException: Column not found. +Be careful about this restriction when you use Kotlin’s data class to hold an embedded property.

      +
    • +
    +
    +
    +
    +

    13.2.6. Relationships

    +
    +

    Spring Data Cloud Spanner supports parent-child relationships using the Cloud Spanner parent-child interleaved table mechanism. +Cloud Spanner interleaved tables enforce the one-to-many relationship and provide efficient queries and operations on entities of a single domain parent entity. +These relationships can be up to 7 levels deep. +Cloud Spanner also provides automatic cascading delete or enforces the deletion of child entities before parents.

    +
    +
    +

    While one-to-one and many-to-many relationships can be implemented in Cloud Spanner and Spring Data Cloud Spanner using constructs of interleaved parent-child tables, only the parent-child relationship is natively supported.

    +
    +
    +

    For example, the following Java entities:

    +
    +
    +
    +
    @Table(name = "Singers")
    +class Singer {
    +  @PrimaryKey
    +  long SingerId;
    +
    +  String FirstName;
    +
    +  String LastName;
    +
    +  byte[] SingerInfo;
    +
    +  @Interleaved
    +  List<Album> albums;
    +}
    +
    +@Table(name = "Albums")
    +class Album {
    +  @PrimaryKey
    +  long SingerId;
    +
    +  @PrimaryKey(keyOrder = 2)
    +  long AlbumId;
    +
    +  String AlbumTitle;
    +}
    +
    +
    +
    +
    +

    These classes can correspond to an existing pair of interleaved tables. +The @Interleaved annotation may be applied to Collection properties and the inner type is resolved as the child entity type. +The schema needed to create them can also be generated using the SpannerSchemaUtils and run by using the SpannerDatabaseAdminTemplate:

    +
    +
    +
    +
    @Autowired
    +SpannerSchemaUtils schemaUtils;
    +
    +@Autowired
    +SpannerDatabaseAdminTemplate databaseAdmin;
    +...
    +
    +// Get the create statmenets for all tables in the table structure rooted at Singer
    +List<String> createStrings = this.schemaUtils.getCreateTableDdlStringsForInterleavedHierarchy(Singer.class);
    +
    +// Create the tables and also create the database if necessary
    +this.databaseAdmin.executeDdlStrings(createStrings, true);
    +
    +
    +
    +
    +

    The createStrings list contains table schema statements using column names and types compatible with the provided Java type and any resolved child relationship types contained within based on the configured custom converters.

    +
    +
    +
    +
    CREATE TABLE Singers (
    +  SingerId   INT64 NOT NULL,
    +  FirstName  STRING(1024),
    +  LastName   STRING(1024),
    +  SingerInfo BYTES(MAX),
    +) PRIMARY KEY (SingerId);
    +
    +CREATE TABLE Albums (
    +  SingerId     INT64 NOT NULL,
    +  AlbumId      INT64 NOT NULL,
    +  AlbumTitle   STRING(MAX),
    +) PRIMARY KEY (SingerId, AlbumId),
    +  INTERLEAVE IN PARENT Singers ON DELETE CASCADE;
    +
    +
    +
    +

    The ON DELETE CASCADE clause indicates that Cloud Spanner will delete all Albums of a singer if the Singer is deleted. +The alternative is ON DELETE NO ACTION, where a Singer cannot be deleted until all of its Albums have already been deleted. +When using SpannerSchemaUtils to generate the schema strings, the spring.cloud.gcp.spanner.createInterleavedTableDdlOnDeleteCascade boolean setting determines if these schema are generated as ON DELETE CASCADE for true and ON DELETE NO ACTION for false.

    +
    +
    +

    Cloud Spanner restricts these relationships to 7 child layers. +A table may have multiple child tables.

    +
    +
    +

    On updating or inserting an object to Cloud Spanner, all of its referenced children objects are also updated or inserted in the same request, respectively. +On read, all of the interleaved child rows are also all read.

    +
    +
    +
    Lazy Fetch
    +
    +

    @Interleaved properties are retrieved eagerly by default, but can be fetched lazily for performance in both read and write:

    +
    +
    +
    +
    @Interleaved(lazy = true)
    +List<Album> albums;
    +
    +
    +
    +
    +

    Lazily-fetched interleaved properties are retrieved upon the first interaction with the property. +If a property marked for lazy fetching is never retrieved, then it is also skipped when saving the parent entity.

    +
    +
    +

    If used inside a transaction, subsequent operations on lazily-fetched properties use the same transaction context as that of the original parent entity.

    +
    +
    +
    +
    Declarative Filtering with @Where
    +
    +

    The @Where annotation could be applied to an entity class or to an interleaved property. +This annotation provides an SQL where clause that will be applied at the fetching of interleaved collections or the entity itself.

    +
    +
    +

    Let’s say we have an Agreement with a list of Participants which could be assigned to it. +We would like to fetch a list of currently active participants. +For security reasons, all records should remain in the database forever, even if participants become inactive. +That can be easily achieved with the @Where annotation, which is demonstrated by this example:

    +
    +
    +
    +
    @Table(name = "participants")
    +public class Participant {
    +  //...
    +  boolean active;
    +  //...
    +}
    +
    +@Table(name = "agreements")
    +public class Agreement {
    +  //...
    +  @Interleaved
    +  @Where("active = true")
    +  List<Participant> participants;
    +  Person person;
    +  //...
    +}
    +
    +
    +
    +
    +
    +
    +

    13.2.7. Supported Types

    +
    +

    Spring Data Cloud Spanner natively supports the following types for regular fields but also utilizes custom converters (detailed in following sections) and dozens of pre-defined Spring Data custom converters to handle other common Java types.

    +
    +
    +

    Natively supported types:

    +
    +
    +
      +
    • +

      com.google.cloud.ByteArray

      +
    • +
    • +

      com.google.cloud.Date

      +
    • +
    • +

      com.google.cloud.Timestamp

      +
    • +
    • +

      java.lang.Boolean, boolean

      +
    • +
    • +

      java.lang.Double, double

      +
    • +
    • +

      java.lang.Long, long

      +
    • +
    • +

      java.lang.Integer, int

      +
    • +
    • +

      java.lang.String

      +
    • +
    • +

      double[]

      +
    • +
    • +

      long[]

      +
    • +
    • +

      boolean[]

      +
    • +
    • +

      java.util.Date

      +
    • +
    • +

      java.time.Instant

      +
    • +
    • +

      java.sql.Date

      +
    • +
    • +

      java.time.LocalDate

      +
    • +
    • +

      java.time.LocalDateTime

      +
    • +
    +
    +
    +
    +

    13.2.8. JSON fields

    +
    +

    Spanner supports JSON and ARRAY<JSON> type for columns. Such property needs to be annotated with @Column(spannerType = TypeCode.JSON). JSON columns are mapped to custom POJOs and ARRAY<JSON> columns are mapped to List of custom POJOs. Read, write and query with custom SQL query are supported for JSON annotated fields.

    +
    +
    + + + + + +
    + + +Spring Boot autoconfigures a Gson bean by default. +This Gson instance is used by default to convert to and from JSON representation. +To customize, use spring.gson.* configuration properties or GsonBuilderCustomizer bean as instructed in Spring Boot documentation here. +Alternatively, you can also provide a customized bean of type Gson in your application. +
    +
    +
    +
    +
    @Table(name = "traders")
    +public class Trader {
    +
    +    @PrimaryKey
    +    @Column(name = "trader_id")
    +    String traderId;
    +
    +    @Column(spannerType = TypeCode.JSON)
    +    Details details;
    +}
    +
    +public class Details {
    +    String name;
    +    String affiliation;
    +    Boolean isActive;
    +}
    +
    +
    +
    +
    +
    +

    13.2.9. Lists

    +
    +

    Spanner supports ARRAY types for columns. +ARRAY columns are mapped to List fields in POJOs.

    +
    +
    +

    Example:

    +
    +
    +
    +
    List<Double> curve;
    +
    +
    +
    +
    +

    The types inside the lists can be any singular property type.

    +
    +
    +
    +

    13.2.10. Lists of Structs

    +
    +

    Cloud Spanner queries can construct STRUCT values that appear as columns in the result. +Cloud Spanner requires STRUCT values appear in ARRAYs at the root level: SELECT ARRAY(SELECT STRUCT(1 as val1, 2 as val2)) as pair FROM Users.

    +
    +
    +

    Spring Data Cloud Spanner will attempt to read the column STRUCT values into a property that is an Iterable of an entity type compatible with the schema of the column STRUCT value.

    +
    +
    +

    For the previous array-select example, the following property can be mapped with the constructed ARRAY<STRUCT> column: List<TwoInts> pair; where the TwoInts type is defined:

    +
    +
    +
    +
    class TwoInts {
    +
    +  int val1;
    +
    +  int val2;
    +}
    +
    +
    +
    +
    +
    +

    13.2.11. Custom types

    +
    +

    Custom converters can be used to extend the type support for user defined types.

    +
    +
    +
      +
    1. +

      Converters need to implement the org.springframework.core.convert.converter.Converter interface in both directions.

      +
    2. +
    3. +

      The user defined type needs to be mapped to one of the basic types supported by Spanner:

      +
      +
        +
      • +

        com.google.cloud.ByteArray

        +
      • +
      • +

        com.google.cloud.Date

        +
      • +
      • +

        com.google.cloud.Timestamp

        +
      • +
      • +

        java.lang.Boolean, boolean

        +
      • +
      • +

        java.lang.Double, double

        +
      • +
      • +

        java.lang.Long, long

        +
      • +
      • +

        java.lang.String

        +
      • +
      • +

        double[]

        +
      • +
      • +

        long[]

        +
      • +
      • +

        boolean[]

        +
      • +
      • +

        enum types

        +
      • +
      +
      +
    4. +
    5. +

      An instance of both Converters needs to be passed to a ConverterAwareMappingSpannerEntityProcessor, which then has to be made available as a @Bean for SpannerEntityProcessor.

      +
    6. +
    +
    +
    +

    For example:

    +
    +
    +

    We would like to have a field of type Person on our Trade POJO:

    +
    +
    +
    +
    @Table(name = "trades")
    +public class Trade {
    +  //...
    +  Person person;
    +  //...
    +}
    +
    +
    +
    +
    +

    Where Person is a simple class:

    +
    +
    +
    +
    public class Person {
    +
    +  public String firstName;
    +  public String lastName;
    +
    +}
    +
    +
    +
    +
    +

    We have to define the two converters:

    +
    +
    +
    +
      public class PersonWriteConverter implements Converter<Person, String> {
    +
    +    @Override
    +    public String convert(Person person) {
    +      return person.firstName + " " + person.lastName;
    +    }
    +  }
    +
    +  public class PersonReadConverter implements Converter<String, Person> {
    +
    +    @Override
    +    public Person convert(String s) {
    +      Person person = new Person();
    +      person.firstName = s.split(" ")[0];
    +      person.lastName = s.split(" ")[1];
    +      return person;
    +    }
    +  }
    +
    +
    +
    +
    +

    That will be configured in our @Configuration file:

    +
    +
    +
    +
    @Configuration
    +public class ConverterConfiguration {
    +
    +    @Bean
    +    public SpannerEntityProcessor spannerEntityProcessor(SpannerMappingContext spannerMappingContext) {
    +        return new ConverterAwareMappingSpannerEntityProcessor(spannerMappingContext,
    +                Arrays.asList(new PersonWriteConverter()),
    +                Arrays.asList(new PersonReadConverter()));
    +    }
    +}
    +
    +
    +
    +
    +

    Note that ConverterAwareMappingSpannerEntityProcessor takes a list of Converters for write and read operations to support multiple user-defined types. +When there are duplicate Converters for a user-defined class in the list, it chooses the first matching item in the lists. +This means that, for a user-defined class U, write operations use the first Converter<U, …​> from the write Converters and read operations use the first Converter<…​, U> from the read Converters.

    +
    +
    +
    +

    13.2.12. Custom Converter for Struct Array Columns

    +
    +

    If a Converter<Struct, A> is provided, then properties of type List<A> can be used in your entity types.

    +
    +
    +
    +
    +

    13.3. Spanner Operations & Template

    +
    +

    SpannerOperations and its implementation, SpannerTemplate, provides the Template pattern familiar to Spring developers. +It provides:

    +
    +
    +
      +
    • +

      Resource management

      +
    • +
    • +

      One-stop-shop to Spanner operations with the Spring Data POJO mapping and conversion features

      +
    • +
    • +

      Exception conversion

      +
    • +
    +
    +
    +

    Using the autoconfigure provided by our Spring Boot Starter for Spanner, your Spring application context will contain a fully configured SpannerTemplate object that you can easily autowire in your application:

    +
    +
    +
    +
    @SpringBootApplication
    +public class SpannerTemplateExample {
    +
    +    @Autowired
    +    SpannerTemplate spannerTemplate;
    +
    +    public void doSomething() {
    +        this.spannerTemplate.delete(Trade.class, KeySet.all());
    +        //...
    +        Trade t = new Trade();
    +        //...
    +        this.spannerTemplate.insert(t);
    +        //...
    +        List<Trade> tradesByAction = spannerTemplate.findAll(Trade.class);
    +        //...
    +    }
    +}
    +
    +
    +
    +
    +

    The Template API provides convenience methods for:

    +
    +
    +
      +
    • +

      Reads, and by providing SpannerReadOptions and +SpannerQueryOptions

      +
      +
        +
      • +

        Stale read

        +
      • +
      • +

        Read with secondary indices

        +
      • +
      • +

        Read with limits and offsets

        +
      • +
      • +

        Read with sorting

        +
      • +
      +
      +
    • +
    • +

      Queries

      +
    • +
    • +

      DML operations (delete, insert, update, upsert)

      +
    • +
    • +

      Partial reads

      +
      +
        +
      • +

        You can define a set of columns to be read into your entity

        +
      • +
      +
      +
    • +
    • +

      Partial writes

      +
      +
        +
      • +

        Persist only a few properties from your entity

        +
      • +
      +
      +
    • +
    • +

      Read-only transactions

      +
    • +
    • +

      Locking read-write transactions

      +
    • +
    +
    +
    +

    13.3.1. SQL Query

    +
    +

    Cloud Spanner has SQL support for running read-only queries. +All the query related methods start with query on SpannerTemplate. +By using SpannerTemplate, you can run SQL queries that map to POJOs:

    +
    +
    +
    +
    List<Trade> trades = this.spannerTemplate.query(Trade.class, Statement.of("SELECT * FROM trades"));
    +
    +
    +
    +
    +
    +

    13.3.2. Read

    +
    +

    Spanner exposes a Read API for reading single row or multiple rows in a table or in a secondary index.

    +
    +
    +

    Using SpannerTemplate you can run reads, as the following example shows:

    +
    +
    +
    +
    List<Trade> trades = this.spannerTemplate.readAll(Trade.class);
    +
    +
    +
    +
    +

    Main benefit of reads over queries is reading multiple rows of a certain pattern of keys is much easier using the features of the KeySet class.

    +
    +
    +
    +

    13.3.3. Advanced reads

    +
    +
    Stale read
    +
    +

    All reads and queries are strong reads by default. +A strong read is a read at a current time and is guaranteed to see all data that has been committed up until the start of this read. +An exact staleness read is read at a timestamp in the past. +Cloud Spanner allows you to determine how current the data should be when you read data. +With SpannerTemplate you can specify the Timestamp by setting it on SpannerQueryOptions or SpannerReadOptions to the appropriate read or query methods:

    +
    +
    +

    Reads:

    +
    +
    +
    +
    // a read with options:
    +SpannerReadOptions spannerReadOptions = new SpannerReadOptions().setTimestamp(myTimestamp);
    +List<Trade> trades = this.spannerTemplate.readAll(Trade.class, spannerReadOptions);
    +
    +
    +
    +
    +

    Queries:

    +
    +
    +
    +
    // a query with options:
    +SpannerQueryOptions spannerQueryOptions = new SpannerQueryOptions().setTimestamp(myTimestamp);
    +List<Trade> trades = this.spannerTemplate.query(Trade.class, Statement.of("SELECT * FROM trades"), spannerQueryOptions);
    +
    +
    +
    +
    +

    You can also read with bounded staleness by setting .setTimestampBound(TimestampBound.ofMinReadTimestamp(myTimestamp)) on the query and read options objects. +Bounded staleness lets Cloud Spanner choose any point in time later than or equal to the given timestampBound, but it cannot be used inside transactions.

    +
    +
    +
    +
    Read from a secondary index
    +
    +

    Using a secondary index is available for Reads via the Template API and it is also implicitly available via SQL for Queries.

    +
    +
    +

    The following shows how to read rows from a table using a secondary index simply by setting index on SpannerReadOptions:

    +
    +
    +
    +
    SpannerReadOptions spannerReadOptions = new SpannerReadOptions().setIndex("TradesByTrader");
    +List<Trade> trades = this.spannerTemplate.readAll(Trade.class, spannerReadOptions);
    +
    +
    +
    +
    +
    +
    Read with offsets and limits
    +
    +

    Limits and offsets are only supported by Queries. +The following will get only the first two rows of the query:

    +
    +
    +
    +
    SpannerQueryOptions spannerQueryOptions = new SpannerQueryOptions().setLimit(2).setOffset(3);
    +List<Trade> trades = this.spannerTemplate.query(Trade.class, Statement.of("SELECT * FROM trades"), spannerQueryOptions);
    +
    +
    +
    +
    +

    Note that the above is equivalent of running SELECT * FROM trades LIMIT 2 OFFSET 3.

    +
    +
    +
    +
    Sorting
    +
    +

    Reads by keys do not support sorting. +However, queries on the Template API support sorting through standard SQL and also via Spring Data Sort API:

    +
    +
    +
    +
    List<Trade> trades = this.spannerTemplate.queryAll(Trade.class, Sort.by("action"));
    +
    +
    +
    +
    +

    If the provided sorted field name is that of a property of the domain type, then the column name corresponding to that property will be used in the query. +Otherwise, the given field name is assumed to be the name of the column in the Cloud Spanner table. +Sorting on columns of Cloud Spanner types STRING and BYTES can be done while ignoring case:

    +
    +
    +
    +
    Sort.by(Order.desc("action").ignoreCase())
    +
    +
    +
    +
    +
    +
    Partial read
    +
    +

    Partial read is only possible when using Queries. +In case the rows returned by the query have fewer columns than the entity that it will be mapped to, Spring Data will map the returned columns only. +This setting also applies to nested structs and their corresponding nested POJO properties.

    +
    +
    +
    +
    List<Trade> trades = this.spannerTemplate.query(Trade.class, Statement.of("SELECT action, symbol FROM trades"),
    +    new SpannerQueryOptions().setAllowMissingResultSetColumns(true));
    +
    +
    +
    +
    +

    If the setting is set to false, then an exception will be thrown if there are missing columns in the query result.

    +
    +
    +
    +
    Summary of options for Query vs Read
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Feature

    Query supports it

    Read supports it

    SQL

    yes

    no

    Partial read

    yes

    no

    Limits

    yes

    no

    Offsets

    yes

    no

    Secondary index

    yes

    yes

    Read using index range

    no

    yes

    Sorting

    yes

    no

    +
    +
    +
    +

    13.3.4. Write / Update

    +
    +

    The write methods of SpannerOperations accept a POJO and writes all of its properties to Spanner. +The corresponding Spanner table and entity metadata is obtained from the given object’s actual type.

    +
    +
    +

    If a POJO was retrieved from Spanner and its primary key properties values were changed and then written or updated, the operation will occur as if against a row with the new primary key values. +The row with the original primary key values will not be affected.

    +
    +
    +
    Insert
    +
    +

    The insert method of SpannerOperations accepts a POJO and writes all of its properties to Spanner, which means the operation will fail if a row with the POJO’s primary key already exists in the table.

    +
    +
    +
    +
    Trade t = new Trade();
    +this.spannerTemplate.insert(t);
    +
    +
    +
    +
    +
    +
    Update
    +
    +

    The update method of SpannerOperations accepts a POJO and writes all of its properties to Spanner, which means the operation will fail if the POJO’s primary key does not already exist in the table.

    +
    +
    +
    +
    // t was retrieved from a previous operation
    +this.spannerTemplate.update(t);
    +
    +
    +
    +
    +
    +
    Upsert
    +
    +

    The upsert method of SpannerOperations accepts a POJO and writes all of its properties to Spanner using update-or-insert.

    +
    +
    +
    +
    // t was retrieved from a previous operation or it's new
    +this.spannerTemplate.upsert(t);
    +
    +
    +
    +
    +
    +
    Partial Update
    +
    +

    The update methods of SpannerOperations operate by default on all properties within the given object, but also accept String[] and Optional<Set<String>> of column names. +If the Optional of set of column names is empty, then all columns are written to Spanner. +However, if the Optional is occupied by an empty set, then no columns will be written.

    +
    +
    +
    +
    // t was retrieved from a previous operation or it's new
    +this.spannerTemplate.update(t, "symbol", "action");
    +
    +
    +
    +
    +
    +
    +

    13.3.5. DML

    +
    +

    DML statements can be run by using SpannerOperations.executeDmlStatement. +Inserts, updates, and deletions can affect any number of rows and entities.

    +
    +
    +

    You can run partitioned DML updates by using the executePartitionedDmlStatement method. +Partitioned DML queries have performance benefits but also have restrictions and cannot be used inside transactions.

    +
    +
    +
    +

    13.3.6. Transactions

    +
    +

    SpannerOperations provides methods to run java.util.Function objects within a single transaction while making available the read and write methods from SpannerOperations.

    +
    +
    +
    Read/Write Transaction
    +
    +

    Read and write transactions are provided by SpannerOperations via the performReadWriteTransaction method:

    +
    +
    +
    +
    @Autowired
    +SpannerOperations mySpannerOperations;
    +
    +public String doWorkInsideTransaction() {
    +  return mySpannerOperations.performReadWriteTransaction(
    +    transActionSpannerOperations -> {
    +      // Work with transActionSpannerOperations here.
    +      // It is also a SpannerOperations object.
    +
    +      return "transaction completed";
    +    }
    +  );
    +}
    +
    +
    +
    +
    +

    The performReadWriteTransaction method accepts a Function that is provided an instance of a SpannerOperations object. +The final returned value and type of the function is determined by the user. +You can use this object just as you would a regular SpannerOperations with a few exceptions:

    +
    +
    +
      +
    • +

      Its read functionality cannot perform stale reads, because all reads and writes happen at the single point in time of the transaction.

      +
    • +
    • +

      It cannot perform sub-transactions via performReadWriteTransaction or performReadOnlyTransaction.

      +
    • +
    +
    +
    +

    As these read-write transactions are locking, it is recommended that you use the performReadOnlyTransaction if your function does not perform any writes.

    +
    +
    +
    +
    Read-only Transaction
    +
    +

    The performReadOnlyTransaction method is used to perform read-only transactions using a SpannerOperations:

    +
    +
    +
    +
    @Autowired
    +SpannerOperations mySpannerOperations;
    +
    +public String doWorkInsideTransaction() {
    +  return mySpannerOperations.performReadOnlyTransaction(
    +    transActionSpannerOperations -> {
    +      // Work with transActionSpannerOperations here.
    +      // It is also a SpannerOperations object.
    +
    +      return "transaction completed";
    +    }
    +  );
    +}
    +
    +
    +
    +
    +

    The performReadOnlyTransaction method accepts a Function that is provided an instance of a +SpannerOperations object. +This method also accepts a ReadOptions object, but the only attribute used is the timestamp used to determine the snapshot in time to perform the reads in the transaction. +If the timestamp is not set in the read options the transaction is run against the current state of the database. +The final returned value and type of the function is determined by the user. +You can use this object just as you would a regular SpannerOperations with +a few exceptions:

    +
    +
    +
      +
    • +

      Its read functionality cannot perform stale reads (other than the staleness set on the entire transaction), because all reads happen at the single point in time of the transaction.

      +
    • +
    • +

      It cannot perform sub-transactions via performReadWriteTransaction or performReadOnlyTransaction

      +
    • +
    • +

      It cannot perform any write operations.

      +
    • +
    +
    +
    +

    Because read-only transactions are non-locking and can be performed on points in time in the past, these are recommended for functions that do not perform write operations.

    +
    +
    +
    +
    Declarative Transactions with @Transactional Annotation
    +
    +

    This feature requires a bean of SpannerTransactionManager, which is provided when using spring-cloud-gcp-starter-data-spanner.

    +
    +
    +

    SpannerTemplate and SpannerRepository support running methods with the @Transactional annotation as transactions. +If a method annotated with @Transactional calls another method also annotated, then both methods will work within the same transaction. +performReadOnlyTransaction and performReadWriteTransaction cannot be used in @Transactional annotated methods because Cloud Spanner does not support transactions within transactions.

    +
    +
    +
    +
    +

    13.3.7. DML Statements

    +
    +

    SpannerTemplate supports DML Statements. +DML statements can also be run in transactions by using performReadWriteTransaction or by using the @Transactional annotation.

    +
    +
    +
    +
    +

    13.4. Repositories

    +
    +

    Spring Data Repositories are a powerful abstraction that can save you a lot of boilerplate code.

    +
    +
    +

    For example:

    +
    +
    +
    +
    public interface TraderRepository extends SpannerRepository<Trader, String> {
    +}
    +
    +
    +
    +
    +

    Spring Data generates a working implementation of the specified interface, which can be conveniently autowired into an application.

    +
    +
    +

    The Trader type parameter to SpannerRepository refers to the underlying domain type. +The second type parameter, String in this case, refers to the type of the key of the domain type.

    +
    +
    +

    For POJOs with a composite primary key, this ID type parameter can be any descendant of Object[] compatible with all primary key properties, any descendant of Iterable, or com.google.cloud.spanner.Key. +If the domain POJO type only has a single primary key column, then the primary key property type can be used or the Key type.

    +
    +
    +

    For example in case of Trades, that belong to a Trader, TradeRepository would look like this:

    +
    +
    +
    +
    public interface TradeRepository extends SpannerRepository<Trade, String[]> {
    +
    +}
    +
    +
    +
    +
    +
    +
    public class MyApplication {
    +
    +    @Autowired
    +    SpannerTemplate spannerTemplate;
    +
    +    @Autowired
    +    StudentRepository studentRepository;
    +
    +    public void demo() {
    +
    +        this.tradeRepository.deleteAll();
    +        String traderId = "demo_trader";
    +        Trade t = new Trade();
    +        t.symbol = stock;
    +        t.action = action;
    +        t.traderId = traderId;
    +        t.price = new BigDecimal("100.0");
    +        t.shares = 12345.6;
    +        this.spannerTemplate.insert(t);
    +
    +        Iterable<Trade> allTrades = this.tradeRepository.findAll();
    +
    +        int count = this.tradeRepository.countByAction("BUY");
    +
    +    }
    +}
    +
    +
    +
    +
    +

    13.4.1. CRUD Repository

    +
    +

    CrudRepository methods work as expected, with one thing Spanner specific: the save and saveAll methods work as update-or-insert.

    +
    +
    +
    +

    13.4.2. Paging and Sorting Repository

    +
    +

    You can also use PagingAndSortingRepository with Spanner Spring Data. +The sorting and pageable findAll methods available from this interface operate on the current state of the Spanner database. +As a result, beware that the state of the database (and the results) might change when moving page to page.

    +
    +
    +
    +

    13.4.3. Spanner Repository

    +
    +

    The SpannerRepository extends the PagingAndSortingRepository, but adds the read-only and the read-write transaction functionality provided by Spanner. +These transactions work very similarly to those of SpannerOperations, but is specific to the repository’s domain type and provides repository functions instead of template functions.

    +
    +
    +

    For example, this is a read-only transaction:

    +
    +
    +
    +
    @Autowired
    +SpannerRepository myRepo;
    +
    +public String doWorkInsideTransaction() {
    +  return myRepo.performReadOnlyTransaction(
    +    transactionSpannerRepo -> {
    +      // Work with the single-transaction transactionSpannerRepo here.
    +      // This is a SpannerRepository object.
    +
    +      return "transaction completed";
    +    }
    +  );
    +}
    +
    +
    +
    +
    +

    When creating custom repositories for your own domain types and query methods, you can extend SpannerRepository to access Cloud Spanner-specific features as well as all features from PagingAndSortingRepository and CrudRepository.

    +
    +
    +
    +
    +

    13.5. Query Methods

    +
    +

    SpannerRepository supports Query Methods. +Described in the following sections, these are methods residing in your custom repository interfaces of which implementations are generated based on their names and annotations. +Query Methods can read, write, and delete entities in Cloud Spanner. +Parameters to these methods can be any Cloud Spanner data type supported directly or via custom configured converters. +Parameters can also be of type Struct or POJOs. +If a POJO is given as a parameter, it will be converted to a Struct with the same type-conversion logic as used to create write mutations. +Comparisons using Struct parameters are limited to what is available with Cloud Spanner.

    +
    +
    +

    13.5.1. Query methods by convention

    +
    +
    +
    public interface TradeRepository extends SpannerRepository<Trade, String[]> {
    +    List<Trade> findByAction(String action);
    +
    +    int countByAction(String action);
    +
    +    // Named methods are powerful, but can get unwieldy
    +    List<Trade> findTop3DistinctByActionAndSymbolIgnoreCaseOrTraderIdOrderBySymbolDesc(
    +            String action, String symbol, String traderId);
    +}
    +
    +
    +
    +
    +

    In the example above, the query methods in TradeRepository are generated based on the name of the methods, using the Spring Data Query creation naming convention.

    +
    +
    +

    List<Trade> findByAction(String action) would translate to a SELECT * FROM trades WHERE action = ?.

    +
    +
    +

    The function List<Trade> findTop3DistinctByActionAndSymbolIgnoreCaseOrTraderIdOrderBySymbolDesc(String action, String symbol, String traderId); will be translated as the equivalent of this SQL query:

    +
    +
    +
    +
    SELECT DISTINCT * FROM trades
    +WHERE ACTION = ? AND LOWER(SYMBOL) = LOWER(?) AND TRADER_ID = ?
    +ORDER BY SYMBOL DESC
    +LIMIT 3
    +
    +
    +
    +

    The following filter options are supported:

    +
    +
    +
      +
    • +

      Equality

      +
    • +
    • +

      Greater than or equals

      +
    • +
    • +

      Greater than

      +
    • +
    • +

      Less than or equals

      +
    • +
    • +

      Less than

      +
    • +
    • +

      Is null

      +
    • +
    • +

      Is not null

      +
    • +
    • +

      Is true

      +
    • +
    • +

      Is false

      +
    • +
    • +

      Like a string

      +
    • +
    • +

      Not like a string

      +
    • +
    • +

      Contains a string

      +
    • +
    • +

      Not contains a string

      +
    • +
    • +

      In

      +
    • +
    • +

      Not in

      +
    • +
    +
    +
    +

    Note that the phrase SymbolIgnoreCase is translated to LOWER(SYMBOL) = LOWER(?) indicating a non-case-sensitive matching. +The IgnoreCase phrase may only be appended to fields that correspond to columns of type STRING or BYTES. +The Spring Data "AllIgnoreCase" phrase appended at the end of the method name is not supported.

    +
    +
    +

    The Like or NotLike naming conventions:

    +
    +
    +
    +
    List<Trade> findBySymbolLike(String symbolFragment);
    +
    +
    +
    +
    +

    The param symbolFragment can contain wildcard characters for string matching such as _ and %.

    +
    +
    +

    The Contains and NotContains naming conventions:

    +
    +
    +
    +
    List<Trade> findBySymbolContains(String symbolFragment);
    +
    +
    +
    +
    +

    The param symbolFragment is a regular expression that is checked for occurrences.

    +
    +
    +

    The In and NotIn keywords must be used with Iterable corresponding parameters.

    +
    +
    +

    Delete queries are also supported. +For example, query methods such as deleteByAction or removeByAction delete entities found by findByAction. +The delete operation happens in a single transaction.

    +
    +
    +

    Delete queries can have the following return types: +* An integer type that is the number of entities deleted +* A collection of entities that were deleted +* void

    +
    +
    +
    +

    13.5.2. Custom SQL/DML query methods

    +
    +

    The example above for List<Trade> fetchByActionNamedQuery(String action) does not match the Spring Data Query creation naming convention, so we have to map a parametrized Spanner SQL query to it.

    +
    +
    +

    The SQL query for the method can be mapped to repository methods in one of two ways:

    +
    +
    +
      +
    • +

      namedQueries properties file

      +
    • +
    • +

      using the @Query annotation

      +
    • +
    +
    +
    +

    The names of the tags of the SQL correspond to the @Param annotated names of the method parameters.

    +
    +
    +

    Interleaved properties are loaded eagerly, unless they are annotated with @Interleaved(lazy = true).

    +
    +
    +

    Custom SQL query methods can accept a single Sort or Pageable parameter that is applied on top of the specified custom query. +It is the recommended way to control the sort order of the results, which is not guaranteed by the ORDER BY clause in the SQL query. +This is due to the fact that the user-provided query is used as a sub-query, and Cloud Spanner doesn’t preserve order in subquery results.

    +
    +
    +

    You might want to use ORDER BY with LIMIT to obtain the top records, according to a specified order. +However, to ensure the correct sort order of the final result set, sort options have to be passed in with a Pageable.

    +
    +
    +
    +
        @Query("SELECT * FROM trades")
    +    List<Trade> fetchTrades(Pageable pageable);
    +
    +    @Query("SELECT * FROM trades ORDER BY price DESC LIMIT 1")
    +    Trade topTrade(Pageable pageable);
    +
    +
    +
    +
    +

    This can be used:

    +
    +
    +
    +
        List<Trade> customSortedTrades = tradeRepository.fetchTrades(PageRequest
    +                .of(2, 2, org.springframework.data.domain.Sort.by(Order.asc("id"))));
    +
    +
    +
    +
    +

    The results would be sorted by "id" in ascending order.

    +
    +
    +

    Your query method can also return non-entity types:

    +
    +
    +
    +
        @Query("SELECT COUNT(1) FROM trades WHERE action = @action")
    +    int countByActionQuery(String action);
    +
    +    @Query("SELECT EXISTS(SELECT COUNT(1) FROM trades WHERE action = @action)")
    +    boolean existsByActionQuery(String action);
    +
    +    @Query("SELECT action FROM trades WHERE action = @action LIMIT 1")
    +    String getFirstString(@Param("action") String action);
    +
    +    @Query("SELECT action FROM trades WHERE action = @action")
    +    List<String> getFirstStringList(@Param("action") String action);
    +
    +
    +
    +
    +

    DML statements can also be run by query methods, but the only possible return value is a long representing the number of affected rows. +The dmlStatement boolean setting must be set on @Query to indicate that the query method is run as a DML statement.

    +
    +
    +
    +
        @Query(value = "DELETE FROM trades WHERE action = @action", dmlStatement = true)
    +    long deleteByActionQuery(String action);
    +
    +
    +
    +
    +
    Query methods with named queries properties
    +
    +

    By default, the namedQueriesLocation attribute on @EnableSpannerRepositories points to the META-INF/spanner-named-queries.properties file. +You can specify the query for a method in the properties file by providing the SQL as the value for the "interface.method" property:

    +
    +
    +
    +
    Trade.fetchByActionNamedQuery=SELECT * FROM trades WHERE trades.action = @tag0
    +
    +
    +
    +
    +
    public interface TradeRepository extends SpannerRepository<Trade, String[]> {
    +    // This method uses the query from the properties file instead of one generated based on name.
    +    List<Trade> fetchByActionNamedQuery(@Param("tag0") String action);
    +}
    +
    +
    +
    +
    +
    +
    Query methods with annotation
    +
    +

    Using the @Query annotation:

    +
    +
    +
    +
    public interface TradeRepository extends SpannerRepository<Trade, String[]> {
    +    @Query("SELECT * FROM trades WHERE trades.action = @tag0")
    +    List<Trade> fetchByActionNamedQuery(@Param("tag0") String action);
    +}
    +
    +
    +
    +
    +

    Table names can be used directly. +For example, "trades" in the above example. +Alternatively, table names can be resolved from the @Table annotation on domain classes as well. +In this case, the query should refer to table names with fully qualified class names between : +characters: :fully.qualified.ClassName:. +A full example would look like:

    +
    +
    +
    +
    @Query("SELECT * FROM :com.example.Trade: WHERE trades.action = @tag0")
    +List<Trade> fetchByActionNamedQuery(String action);
    +
    +
    +
    +
    +

    This allows table names evaluated with SpEL to be used in custom queries.

    +
    +
    +

    SpEL can also be used to provide SQL parameters:

    +
    +
    +
    +
    @Query("SELECT * FROM :com.example.Trade: WHERE trades.action = @tag0
    +  AND price > #{#priceRadius * -1} AND price < #{#priceRadius * 2}")
    +List<Trade> fetchByActionNamedQuery(String action, Double priceRadius);
    +
    +
    +
    +
    +

    When using the IN SQL clause, remember to use IN UNNEST(@iterableParam) to specify a single Iterable parameter. +You can also use a fixed number of singular parameters such as IN (@stringParam1, @stringParam2).

    +
    +
    +
    +
    +

    13.5.3. Projections

    +
    +

    Spring Data Spanner supports projections. +You can define projection interfaces based on domain types and add query methods that return them in your repository:

    +
    +
    +
    +
    public interface TradeProjection {
    +
    +    String getAction();
    +
    +    @Value("#{target.symbol + ' ' + target.action}")
    +    String getSymbolAndAction();
    +}
    +
    +public interface TradeRepository extends SpannerRepository<Trade, Key> {
    +
    +    List<Trade> findByTraderId(String traderId);
    +
    +    List<TradeProjection> findByAction(String action);
    +
    +    @Query("SELECT action, symbol FROM trades WHERE action = @action")
    +    List<TradeProjection> findByQuery(String action);
    +}
    +
    +
    +
    +
    +

    Projections can be provided by name-convention-based query methods as well as by custom SQL queries. +If using custom SQL queries, you can further restrict the columns retrieved from Spanner to just those required by the projection to improve performance.

    +
    +
    +

    Properties of projection types defined using SpEL use the fixed name target for the underlying domain object. +As a result accessing underlying properties take the form target.<property-name>.

    +
    +
    +
    +

    13.5.4. Empty result handling in repository methods

    +
    +

    Java java.util.Optional can be used to indicate the potential absence of a return value.

    +
    +
    +

    Alternatively, query methods can return the result without a wrapper. +In that case the absence of a query result is indicated by returning null. +Repository methods returning collections are guaranteed never to return null but rather the corresponding empty collection.

    +
    +
    + + + + + +
    + + +You can enable nullability checks. For more details please see Spring Framework’s nullability docs. +
    +
    +
    +
    +

    13.5.5. REST Repositories

    +
    +

    When running with Spring Boot, repositories can be exposed as REST services by simply adding this dependency to your pom file:

    +
    +
    +
    +
    <dependency>
    +  <groupId>org.springframework.boot</groupId>
    +  <artifactId>spring-boot-starter-data-rest</artifactId>
    +</dependency>
    +
    +
    +
    +

    If you prefer to configure parameters (such as path), you can use @RepositoryRestResource annotation:

    +
    +
    +
    +
    @RepositoryRestResource(collectionResourceRel = "trades", path = "trades")
    +public interface TradeRepository extends SpannerRepository<Trade, Key> {
    +}
    +
    +
    +
    +
    + + + + + +
    + + +For classes that have composite keys (multiple @PrimaryKey fields), only the Key type is supported for the repository ID type. +
    +
    +
    +

    For example, you can retrieve all Trade objects in the repository by using curl http://<server>:<port>/trades, or any specific trade via curl http://<server>:<port>/trades/<trader_id>,<trade_id>.

    +
    +
    +

    The separator between your primary key components, id and trader_id in this case, is a comma by default, but can be configured to any string not found in your key values by extending the SpannerKeyIdConverter class:

    +
    +
    +
    +
    @Component
    +class MySpecialIdConverter extends SpannerKeyIdConverter {
    +
    +    @Override
    +    protected String getUrlIdSeparator() {
    +        return ":";
    +    }
    +}
    +
    +
    +
    +
    +

    You can also write trades using curl -XPOST -H"Content-Type: application/json" -d@test.json http://<server>:<port>/trades/ where the file test.json holds the JSON representation of a Trade object.

    +
    +
    +
    +
    +

    13.6. Database and Schema Admin

    +
    +

    Databases and tables inside Spanner instances can be created automatically from SpannerPersistentEntity objects:

    +
    +
    +
    +
    @Autowired
    +private SpannerSchemaUtils spannerSchemaUtils;
    +
    +@Autowired
    +private SpannerDatabaseAdminTemplate spannerDatabaseAdminTemplate;
    +
    +public void createTable(SpannerPersistentEntity entity) {
    +    if(!spannerDatabaseAdminTemplate.tableExists(entity.tableName()){
    +
    +      // The boolean parameter indicates that the database will be created if it does not exist.
    +      spannerDatabaseAdminTemplate.executeDdlStrings(Arrays.asList(
    +            spannerSchemaUtils.getCreateTableDDLString(entity.getType())), true);
    +    }
    +}
    +
    +
    +
    +
    +

    Schemas can be generated for entire object hierarchies with interleaved relationships and composite keys.

    +
    +
    +
    +

    13.7. Events

    +
    +

    Spring Data Cloud Spanner publishes events extending the Spring Framework’s ApplicationEvent to the context that can be received by ApplicationListener beans you register.

    +
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeDescriptionContents

    AfterReadEvent

    Published immediately after entities are read by key from Cloud Spanner by SpannerTemplate

    The entities loaded. The read options and key-set originally specified for the load operation.

    AfterQueryEvent

    Published immediately after entities are read by query from Cloud Spanner by SpannerTemplate

    The entities loaded. The query options and query statement originally specified for the load operation.

    BeforeExecuteDmlEvent

    Published immediately before DML statements are executed by SpannerTemplate

    The DML statement to execute.

    AfterExecuteDmlEvent

    Published immediately after DML statements are executed by SpannerTemplate

    The DML statement to execute and the number of rows affected by the operation as reported by Cloud Spanner.

    BeforeSaveEvent

    Published immediately before upsert/update/insert operations are executed by SpannerTemplate

    The mutations to be sent to Cloud Spanner, the entities to be saved, and optionally the properties in those entities to save.

    AfterSaveEvent

    Published immediately after upsert/update/insert operations are executed by SpannerTemplate

    The mutations sent to Cloud Spanner, the entities to be saved, and optionally the properties in those entities to save.

    BeforeDeleteEvent

    Published immediately before delete operations are executed by SpannerTemplate

    The mutations to be sent to Cloud Spanner. The target entities, keys, or entity type originally specified for the delete operation.

    AfterDeleteEvent

    Published immediately after delete operations are executed by SpannerTemplate

    The mutations sent to Cloud Spanner. The target entities, keys, or entity type originally specified for the delete operation.

    +
    +
    +

    13.8. Auditing

    +
    +

    Spring Data Cloud Spanner supports the @LastModifiedDate and @LastModifiedBy auditing annotations for properties:

    +
    +
    +
    +
    @Table
    +public class SimpleEntity {
    +    @PrimaryKey
    +    String id;
    +
    +    @LastModifiedBy
    +    String lastUser;
    +
    +    @LastModifiedDate
    +    DateTime lastTouched;
    +}
    +
    +
    +
    +
    +

    Upon insert, update, or save, these properties will be set automatically by the framework before mutations are generated and saved to Cloud Spanner.

    +
    +
    +

    To take advantage of these features, add the @EnableSpannerAuditing annotation to your configuration class and provide a bean for an AuditorAware<A> implementation where the type A is the desired property type annotated by @LastModifiedBy:

    +
    +
    +
    +
    @Configuration
    +@EnableSpannerAuditing
    +public class Config {
    +
    +    @Bean
    +    public AuditorAware<String> auditorProvider() {
    +        return () -> Optional.of("YOUR_USERNAME_HERE");
    +    }
    +}
    +
    +
    +
    +
    +

    The AuditorAware interface contains a single method that supplies the value for fields annotated by @LastModifiedBy and can be of any type. +One alternative is to use Spring Security’s User type:

    +
    +
    +
    +
    class SpringSecurityAuditorAware implements AuditorAware<User> {
    +
    +  public Optional<User> getCurrentAuditor() {
    +
    +    return Optional.ofNullable(SecurityContextHolder.getContext())
    +              .map(SecurityContext::getAuthentication)
    +              .filter(Authentication::isAuthenticated)
    +              .map(Authentication::getPrincipal)
    +              .map(User.class::cast);
    +  }
    +}
    +
    +
    +
    +
    +

    You can also set a custom provider for properties annotated @LastModifiedDate by providing a bean for DateTimeProvider and providing the bean name to @EnableSpannerAuditing(dateTimeProviderRef = "customDateTimeProviderBean").

    +
    +
    +
    +

    13.9. Multi-Instance Usage

    +
    +

    Your application can be configured to use multiple Cloud Spanner instances or databases by providing a custom bean for DatabaseIdProvider. +The default bean uses the instance ID, database name, and project ID options you configured in application.properties.

    +
    +
    +
    +
        @Bean
    +    public DatabaseIdProvider databaseIdProvider() {
    +        // return custom connection options provider
    +    }
    +
    +
    +
    +
    +

    The DatabaseId given by this provider is used as the target database name and instance of each operation Spring Data Cloud Spanner executes. +By providing a custom implementation of this bean (for example, supplying a thread-local DatabaseId), you can direct your application to use multiple instances or databases.

    +
    +
    +

    Database administrative operations, such as creating tables using SpannerDatabaseAdminTemplate, will also utilize the provided DatabaseId.

    +
    +
    +

    If you would like to configure every aspect of each connection (such as pool size and retry settings), you can supply a bean for Supplier<DatabaseClient>.

    +
    +
    +
    +

    13.10. Spring Boot Actuator Support

    +
    +

    13.10.1. Cloud Spanner Health Indicator

    +
    +

    If you are using Spring Boot Actuator, you can take advantage of the Cloud Spanner health indicator called spanner. +The health indicator will verify whether Cloud Spanner is up and accessible by your application. +To enable it, all you need to do is add the Spring Boot Actuator to your project.

    +
    +
    +

    The spanner indicator will then roll up to the overall application status visible at localhost:8080/actuator/health (use the management.endpoint.health.show-details property to view per-indicator details).

    +
    +
    +
    +
    <dependency>
    +    <groupId>org.springframework.boot</groupId>
    +    <artifactId>spring-boot-starter-actuator</artifactId>
    +</dependency>
    +
    +
    +
    + + + + + +
    + + +If your application already has actuator and Cloud Spanner starters, this health indicator is enabled by default. +To disable the Cloud Spanner indicator, set management.health.spanner.enabled to false. +
    +
    +
    +

    The health indicator validates the connection to Spanner by executing a query. +A query to validate can be configured via spring.cloud.gcp.spanner.health.query property.

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    management.health.spanner.enabled

    Whether to enable the Spanner health indicator

    No

    true with Spring Boot Actuator, false otherwise

    spring.cloud.gcp.spanner.health.query

    A query to validate

    No

    SELECT 1

    +
    +
    +
    +

    13.11. Cloud Spanner Emulator

    +
    +

    The Cloud SDK provides a local, in-memory emulator for Cloud Spanner, which you can use to develop and test your application. As the emulator stores data only in memory, it will not persist data across runs. It is intended to help you use Cloud Spanner for local development and testing, not for production deployments.

    +
    +
    +

    In order to set up and start the emulator, you can follow these steps.

    +
    +
    +

    This command can be used to create Cloud Spanner instances:

    +
    +
    +
    +
    $ gcloud spanner instances create <instance-name> --config=emulator-config --description="<description>" --nodes=1
    +
    +
    +
    +

    Once the Spanner emulator is running, ensure that the following properties are set in your application.properties of your Spring application:

    +
    +
    +
    +
    spring.cloud.gcp.spanner.emulator.enabled=true
    +
    +
    +
    +

    Note that the default emulator hostname and port (i.e., localhost:9010) is used. If you prefer a customized value, ensure the following property is set in your application.properties of your Spring application:

    +
    +
    +
    +
    spring.cloud.gcp.spanner.emulator-host=ip:port
    +
    +
    +
    + +
    +

    13.13. Test

    +
    +

    Testcontainers provides a gcloud module which offers SpannerEmulatorContainer. See more at the docs

    +
    +
    +
    +
    +
    +

    14. Cloud Spanner Spring Data R2DBC

    +
    +
    +

    The Spring Data R2DBC Dialect for Cloud Spanner enables the usage of Spring Data R2DBC with Cloud Spanner.

    +
    +
    +

    The goal of the Spring Data project is to create easy and consistent ways of using data access technologies from Spring Framework applications.

    +
    +
    +

    14.1. Setup

    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
        <dependency>
    +        <groupId>com.google.cloud</groupId>
    +        <artifactId>spring-cloud-spanner-spring-data-r2dbc</artifactId>
    +    </dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-spanner-spring-data-r2dbc")
    +}
    +
    +
    +
    +
    +

    14.2. Overview

    +
    +

    Spring Data R2DBC allows you to use the convenient features of Spring Data in a reactive application. +These features include:

    +
    +
    +
      +
    • +

      Spring configuration support using Java based @Configuration classes.

      +
    • +
    • +

      Annotation based mapping metadata.

      +
    • +
    • +

      Automatic implementation of Repository interfaces.

      +
    • +
    • +

      Support for Reactive Transactions.

      +
    • +
    • +

      Schema and data initialization utilities.

      +
    • +
    +
    +
    +

    See the Spring Data R2DBC documentation for more information on how to use Spring Data R2DBC.

    +
    +
    +
    +

    14.3. Sample Application

    +
    +

    We provide a sample application which demonstrates using the Spring Data R2DBC framework with Cloud Spanner in Spring WebFlux.

    +
    +
    +
    +
    +
    +

    15. Spring Data Cloud Datastore

    +
    +
    + + + + + +
    + + +This integration is fully compatible with Firestore in Datastore Mode, but not with Firestore in Native Mode. +
    +
    +
    +

    Spring Data is an abstraction for storing and retrieving POJOs in numerous storage technologies. +Spring Framework on Google Cloud adds Spring Data support for Google Cloud Firestore in Datastore mode.

    +
    +
    +

    Maven coordinates for this module only, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-data-datastore</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-data-datastore")
    +}
    +
    +
    +
    +

    We provide a Spring Boot Starter for Spring Data Datastore, with which you can use our recommended auto-configuration setup. +To use the starter, see the coordinates below.

    +
    +
    +

    Maven:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-data-datastore</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-data-datastore")
    +}
    +
    +
    +
    +

    This setup takes care of bringing in the latest compatible version of Cloud Java Cloud Datastore libraries as well.

    +
    +
    +

    15.1. Configuration

    +
    +

    To setup Spring Data Cloud Datastore, you have to configure the following:

    +
    +
    +
      +
    • +

      Setup the connection details to Google Cloud Datastore.

      +
    • +
    +
    +
    +

    15.1.1. Cloud Datastore settings

    +
    +

    You can use the Spring Boot Starter for Spring Data Datastore to autoconfigure Google Cloud Datastore in your Spring application. +It contains all the necessary setup that makes it easy to authenticate with your Google Cloud project. +The following configuration options are available:

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.datastore.enabled

    Enables the Cloud Datastore client

    No

    true

    spring.cloud.gcp.datastore.project-id

    Google Cloud project ID where the Google Cloud Datastore API is hosted, if different from the one in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.datastore.database-id

    Google Cloud project can host multiple databases. You can specify which database will be used. If not specified, the database id will be "(default)".

    No

    spring.cloud.gcp.datastore.credentials.location

    OAuth2 credentials for authenticating with the Google Cloud Datastore API, if different from the ones in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.datastore.credentials.encoded-key

    Base64-encoded OAuth2 credentials for authenticating with the Google Cloud Datastore API, if different from the ones in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.datastore.credentials.scopes

    OAuth2 scope for Spring Framework on Google CloudDatastore credentials

    No

    www.googleapis.com/auth/datastore

    spring.cloud.gcp.datastore.namespace

    The Cloud Datastore namespace to use

    No

    the Default namespace of Cloud Datastore in your Google Cloud project

    spring.cloud.gcp.datastore.host

    The hostname:port of the datastore service or emulator to connect to. Can be used to connect to a manually started Datastore Emulator. If the autoconfigured emulator is enabled, this property will be ignored and localhost:<emulator_port> will be used.

    No

    spring.cloud.gcp.datastore.emulator.enabled

    To enable the auto configuration to start a local instance of the Datastore Emulator.

    No

    false

    spring.cloud.gcp.datastore.emulator.port

    The local port to use for the Datastore Emulator

    No

    8081

    spring.cloud.gcp.datastore.emulator.consistency

    The consistency to use for the Datastore Emulator instance

    No

    0.9

    spring.cloud.gcp.datastore.emulator.store-on-disk

    Configures whether or not the emulator should persist any data to disk.

    No

    true

    spring.cloud.gcp.datastore.emulator.data-dir

    The directory to be used to store/retrieve data/config for an emulator run.

    No

    The default value is <USER_CONFIG_DIR>/emulators/datastore. See the gcloud documentation for finding your USER_CONFIG_DIR.

    +
    +
    +

    15.1.2. Repository settings

    +
    +

    Spring Data Repositories can be configured via the @EnableDatastoreRepositories annotation on your main @Configuration class. +With our Spring Boot Starter for Spring Data Cloud Datastore, @EnableDatastoreRepositories is automatically added. +It is not required to add it to any other class, unless there is a need to override finer grain configuration parameters provided by @EnableDatastoreRepositories.

    +
    +
    +
    +

    15.1.3. Autoconfiguration

    +
    +

    Our Spring Boot autoconfiguration creates the following beans available in the Spring application context:

    +
    +
    +
      +
    • +

      an instance of DatastoreTemplate

      +
    • +
    • +

      an instance of all user defined repositories extending CrudRepository, PagingAndSortingRepository, and DatastoreRepository (an extension of PagingAndSortingRepository with additional Cloud Datastore features) when repositories are enabled

      +
    • +
    • +

      an instance of Datastore from the Google Cloud Java Client for Datastore, for convenience and lower level API access

      +
    • +
    +
    +
    +
    +

    15.1.4. Datastore Emulator Autoconfiguration

    +
    +

    This Spring Boot autoconfiguration can also configure and start a local Datastore Emulator server if enabled by property.

    +
    +
    +

    It is useful for integration testing, but not for production.

    +
    +
    +

    When enabled, the spring.cloud.gcp.datastore.host property will be ignored and the Datastore autoconfiguration itself will be forced to connect to the autoconfigured local emulator instance.

    +
    +
    +

    It will create an instance of LocalDatastoreHelper as a bean that stores the DatastoreOptions to get the Datastore client connection to the emulator for convenience and lower level API for local access. +The emulator will be properly stopped after the Spring application context shutdown.

    +
    +
    +
    +
    +

    15.2. Object Mapping

    +
    +

    Spring Data Cloud Datastore allows you to map domain POJOs to Cloud Datastore kinds and entities via annotations:

    +
    +
    +
    +
    @Entity(name = "traders")
    +public class Trader {
    +
    +    @Id
    +    @Field(name = "trader_id")
    +    String traderId;
    +
    +    String firstName;
    +
    +    String lastName;
    +
    +    @Transient
    +    Double temporaryNumber;
    +}
    +
    +
    +
    +
    +

    Spring Data Cloud Datastore will ignore any property annotated with @Transient. +These properties will not be written to or read from Cloud Datastore.

    +
    +
    +

    15.2.1. Constructors

    +
    +

    Simple constructors are supported on POJOs. +The constructor arguments can be a subset of the persistent properties. +Every constructor argument needs to have the same name and type as a persistent property on the entity and the constructor should set the property from the given argument. +Arguments that are not directly set to properties are not supported.

    +
    +
    +
    +
    @Entity(name = "traders")
    +public class Trader {
    +
    +    @Id
    +    @Field(name = "trader_id")
    +    String traderId;
    +
    +    String firstName;
    +
    +    String lastName;
    +
    +    @Transient
    +    Double temporaryNumber;
    +
    +    public Trader(String traderId, String firstName) {
    +        this.traderId = traderId;
    +        this.firstName = firstName;
    +    }
    +}
    +
    +
    +
    +
    +
    +

    15.2.2. Kind

    +
    +

    The @Entity annotation can provide the name of the Cloud Datastore kind that stores instances of the annotated class, one per row.

    +
    +
    +
    +

    15.2.3. Keys

    +
    +

    @Id identifies the property corresponding to the ID value.

    +
    +
    +

    You must annotate one of your POJO’s fields as the ID value, because every entity in Cloud Datastore requires a single ID value:

    +
    +
    +
    +
    @Entity(name = "trades")
    +public class Trade {
    +    @Id
    +    @Field(name = "trade_id")
    +    String tradeId;
    +
    +    @Field(name = "trader_id")
    +    String traderId;
    +
    +    String action;
    +
    +    Double price;
    +
    +    Double shares;
    +
    +    String symbol;
    +}
    +
    +
    +
    +
    +

    Datastore can automatically allocate integer ID values. +If a POJO instance with a Long ID property is written to Cloud Datastore with null as the ID value, then Spring Data Cloud Datastore will obtain a newly allocated ID value from Cloud Datastore and set that in the POJO for saving. +Because primitive long ID properties cannot be null and default to 0, keys will not be allocated.

    +
    +
    +
    +

    15.2.4. Fields

    +
    +

    All accessible properties on POJOs are automatically recognized as a Cloud Datastore field. +Field naming is generated by the PropertyNameFieldNamingStrategy by default defined on the DatastoreMappingContext bean. +The @Field annotation optionally provides a different field name than that of the property.

    +
    +
    +
    +

    15.2.5. Supported Types

    +
    +

    Spring Data Cloud Datastore supports the following types for regular fields and elements of collections:

    +
    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeStored as

    com.google.cloud.Timestamp

    com.google.cloud.datastore.TimestampValue

    com.google.cloud.datastore.Blob

    com.google.cloud.datastore.BlobValue

    com.google.cloud.datastore.LatLng

    com.google.cloud.datastore.LatLngValue

    java.lang.Boolean, boolean

    com.google.cloud.datastore.BooleanValue

    java.lang.Double, double

    com.google.cloud.datastore.DoubleValue

    java.lang.Long, long

    com.google.cloud.datastore.LongValue

    java.lang.Integer, int

    com.google.cloud.datastore.LongValue

    java.lang.String

    com.google.cloud.datastore.StringValue

    com.google.cloud.datastore.Entity

    com.google.cloud.datastore.EntityValue

    com.google.cloud.datastore.Key

    com.google.cloud.datastore.KeyValue

    byte[]

    com.google.cloud.datastore.BlobValue

    Java enum values

    com.google.cloud.datastore.StringValue

    +
    +

    In addition, all types that can be converted to the ones listed in the table by +org.springframework.core.convert.support.DefaultConversionService are supported.

    +
    +
    +
    +

    15.2.6. Custom types

    +
    +

    Custom converters can be used extending the type support for user defined types.

    +
    +
    +
      +
    1. +

      Converters need to implement the org.springframework.core.convert.converter.Converter interface in both directions.

      +
    2. +
    3. +

      The user defined type needs to be mapped to one of the basic types supported by Cloud Datastore.

      +
    4. +
    5. +

      An instance of both Converters (read and write) needs to be passed to the DatastoreCustomConversions constructor, which then has to be made available as a @Bean for DatastoreCustomConversions.

      +
    6. +
    +
    +
    +

    For example:

    +
    +
    +

    We would like to have a field of type Album on our Singer POJO and want it to be stored as a string property:

    +
    +
    +
    +
    @Entity
    +public class Singer {
    +
    +    @Id
    +    String singerId;
    +
    +    String name;
    +
    +    Album album;
    +}
    +
    +
    +
    +
    +

    Where Album is a simple class:

    +
    +
    +
    +
    public class Album {
    +    String albumName;
    +
    +    LocalDate date;
    +}
    +
    +
    +
    +
    +

    We have to define the two converters:

    +
    +
    +
    +
        // Converter to write custom Album type
    +    static final Converter<Album, String> ALBUM_STRING_CONVERTER =
    +            new Converter<Album, String>() {
    +                @Override
    +                public String convert(Album album) {
    +                    return album.getAlbumName() + " " + album.getDate().format(DateTimeFormatter.ISO_DATE);
    +                }
    +            };
    +
    +    // Converters to read custom Album type
    +    static final Converter<String, Album> STRING_ALBUM_CONVERTER =
    +            new Converter<String, Album>() {
    +                @Override
    +                public Album convert(String s) {
    +                    String[] parts = s.split(" ");
    +                    return new Album(parts[0], LocalDate.parse(parts[parts.length - 1], DateTimeFormatter.ISO_DATE));
    +                }
    +            };
    +
    +
    +
    +
    +

    That will be configured in our @Configuration file:

    +
    +
    +
    +
    @Configuration
    +public class ConverterConfiguration {
    +    @Bean
    +    public DatastoreCustomConversions datastoreCustomConversions() {
    +        return new DatastoreCustomConversions(
    +                Arrays.asList(
    +                        ALBUM_STRING_CONVERTER,
    +                        STRING_ALBUM_CONVERTER));
    +    }
    +}
    +
    +
    +
    +
    +
    +

    15.2.7. Collections and arrays

    +
    +

    Arrays and collections (types that implement java.util.Collection) of supported types are supported. +They are stored as com.google.cloud.datastore.ListValue. +Elements are converted to Cloud Datastore supported types individually. byte[] is an exception, it is converted to +com.google.cloud.datastore.Blob.

    +
    +
    +
    +

    15.2.8. Custom Converter for collections

    +
    +

    Users can provide converters from List<?> to the custom collection type. +Only read converter is necessary, the Collection API is used on the write side to convert a collection to the internal list type.

    +
    +
    +

    Collection converters need to implement the org.springframework.core.convert.converter.Converter interface.

    +
    +
    +

    Example:

    +
    +
    +

    Let’s improve the Singer class from the previous example. +Instead of a field of type Album, we would like to have a field of type Set<Album>:

    +
    +
    +
    +
    @Entity
    +public class Singer {
    +
    +    @Id
    +    String singerId;
    +
    +    String name;
    +
    +    Set<Album> albums;
    +}
    +
    +
    +
    +
    +

    We have to define a read converter only:

    +
    +
    +
    +
    static final Converter<List<?>, Set<?>> LIST_SET_CONVERTER =
    +            new Converter<List<?>, Set<?>>() {
    +                @Override
    +                public Set<?> convert(List<?> source) {
    +                    return Collections.unmodifiableSet(new HashSet<>(source));
    +                }
    +            };
    +
    +
    +
    +
    +

    And add it to the list of custom converters:

    +
    +
    +
    +
    @Configuration
    +public class ConverterConfiguration {
    +    @Bean
    +    public DatastoreCustomConversions datastoreCustomConversions() {
    +        return new DatastoreCustomConversions(
    +                Arrays.asList(
    +                        LIST_SET_CONVERTER,
    +                        ALBUM_STRING_CONVERTER,
    +                        STRING_ALBUM_CONVERTER));
    +    }
    +}
    +
    +
    +
    +
    +
    +

    15.2.9. Inheritance Hierarchies

    +
    +

    Java entity types related by inheritance can be stored in the same Kind. +When reading and querying entities using DatastoreRepository or DatastoreTemplate with a superclass as the type parameter, you can receive instances of subclasses if you annotate the superclass and its subclasses with DiscriminatorField and DiscriminatorValue:

    +
    +
    +
    +
    @Entity(name = "pets")
    +@DiscriminatorField(field = "pet_type")
    +abstract class Pet {
    +    @Id
    +    Long id;
    +
    +    abstract String speak();
    +}
    +
    +@DiscriminatorValue("cat")
    +class Cat extends Pet {
    +    @Override
    +    String speak() {
    +        return "meow";
    +    }
    +}
    +
    +@DiscriminatorValue("dog")
    +class Dog extends Pet {
    +    @Override
    +    String speak() {
    +        return "woof";
    +    }
    +}
    +
    +@DiscriminatorValue("pug")
    +class Pug extends Dog {
    +    @Override
    +    String speak() {
    +        return "woof woof";
    +    }
    +}
    +
    +
    +
    +
    +

    Instances of all 3 types are stored in the pets Kind. +Because a single Kind is used, all classes in the hierarchy must share the same ID property and no two instances of any type in the hierarchy can share the same ID value.

    +
    +
    +

    Entity rows in Cloud Datastore store their respective types' DiscriminatorValue in a field specified by the root superclass’s DiscriminatorField (pet_type in this case). +Reads and queries using a given type parameter will match each entity with its specific type. +For example, reading a List<Pet> will produce a list containing instances of all 3 types. +However, reading a List<Dog> will produce a list containing only Dog and Pug instances. +You can include the pet_type discrimination field in your Java entities, but its type must be convertible to a collection or array of String. +Any value set in the discrimination field will be overwritten upon write to Cloud Datastore.

    +
    +
    +
    +
    +

    15.3. Relationships

    +
    +

    There are three ways to represent relationships between entities that are described in this section:

    +
    +
    +
      +
    • +

      Embedded entities stored directly in the field of the containing entity

      +
    • +
    • +

      @Descendant annotated properties for one-to-many relationships

      +
    • +
    • +

      @Reference annotated properties for general relationships without hierarchy

      +
    • +
    • +

      @LazyReference similar to @Reference, but the entities are lazy-loaded when the property is accessed. +(Note that the keys of the children are retrieved when the parent entity is loaded.)

      +
    • +
    +
    +
    +

    15.3.1. Embedded Entities

    +
    +

    Fields whose types are also annotated with @Entity are converted to EntityValue and stored inside the parent entity.

    +
    +
    +

    Here is an example of Cloud Datastore entity containing an embedded entity in JSON:

    +
    +
    +
    +
    {
    +  "name" : "Alexander",
    +  "age" : 47,
    +  "child" : {"name" : "Philip"  }
    +}
    +
    +
    +
    +

    This corresponds to a simple pair of Java entities:

    +
    +
    +
    +
    import com.google.cloud.spring.data.datastore.core.mapping.Entity;
    +import org.springframework.data.annotation.Id;
    +
    +@Entity("parents")
    +public class Parent {
    +  @Id
    +  String name;
    +
    +  Child child;
    +}
    +
    +@Entity
    +public class Child {
    +  String name;
    +}
    +
    +
    +
    +
    +

    Child entities are not stored in their own kind. +They are stored in their entirety in the child field of the parents kind.

    +
    +
    +

    Multiple levels of embedded entities are supported.

    +
    +
    + + + + + +
    + + +Embedded entities don’t need to have @Id field, it is only required for top level entities. +
    +
    +
    +

    Example:

    +
    +
    +

    Entities can hold embedded entities that are their own type. +We can store trees in Cloud Datastore using this feature:

    +
    +
    +
    +
    import com.google.cloud.spring.data.datastore.core.mapping.Embedded;
    +import com.google.cloud.spring.data.datastore.core.mapping.Entity;
    +import org.springframework.data.annotation.Id;
    +
    +@Entity
    +public class EmbeddableTreeNode {
    +  @Id
    +  long value;
    +
    +  EmbeddableTreeNode left;
    +
    +  EmbeddableTreeNode right;
    +
    +  Map<String, Long> longValues;
    +
    +  Map<String, List<Timestamp>> listTimestamps;
    +
    +  public EmbeddableTreeNode(long value, EmbeddableTreeNode left, EmbeddableTreeNode right) {
    +    this.value = value;
    +    this.left = left;
    +    this.right = right;
    +  }
    +}
    +
    +
    +
    +
    +
    Maps
    +
    +

    Maps will be stored as embedded entities where the key values become the field names in the embedded entity. +The value types in these maps can be any regularly supported property type, and the key values will be converted to String using the configured converters.

    +
    +
    +

    Also, a collection of entities can be embedded; it will be converted to ListValue on write.

    +
    +
    +

    Example:

    +
    +
    +

    Instead of a binary tree from the previous example, we would like to store a general tree +(each node can have an arbitrary number of children) in Cloud Datastore. +To do that, we need to create a field of type List<EmbeddableTreeNode>:

    +
    +
    +
    +
    import com.google.cloud.spring.data.datastore.core.mapping.Embedded;
    +import org.springframework.data.annotation.Id;
    +
    +public class EmbeddableTreeNode {
    +  @Id
    +  long value;
    +
    +  List<EmbeddableTreeNode> children;
    +
    +  Map<String, EmbeddableTreeNode> siblingNodes;
    +
    +  Map<String, Set<EmbeddableTreeNode>> subNodeGroups;
    +
    +  public EmbeddableTreeNode(List<EmbeddableTreeNode> children) {
    +    this.children = children;
    +  }
    +}
    +
    +
    +
    +
    +

    Because Maps are stored as entities, they can further hold embedded entities:

    +
    +
    +
      +
    • +

      Singular embedded objects in the value can be stored in the values of embedded Maps.

      +
    • +
    • +

      Collections of embedded objects in the value can also be stored as the values of embedded Maps.

      +
    • +
    • +

      Maps in the value are further stored as embedded entities with the same rules applied recursively for their values.

      +
    • +
    +
    +
    +
    +
    +

    15.3.2. Ancestor-Descendant Relationships

    +
    +

    Parent-child relationships are supported via the @Descendants annotation.

    +
    +
    +

    Unlike embedded children, descendants are fully-formed entities residing in their own kinds. +The parent entity does not have an extra field to hold the descendant entities. +Instead, the relationship is captured in the descendants' keys, which refer to their parent entities:

    +
    +
    +
    +
    import com.google.cloud.spring.data.datastore.core.mapping.Descendants;
    +import com.google.cloud.spring.data.datastore.core.mapping.Entity;
    +import org.springframework.data.annotation.Id;
    +
    +@Entity("orders")
    +public class ShoppingOrder {
    +  @Id
    +  long id;
    +
    +  @Descendants
    +  List<Item> items;
    +}
    +
    +@Entity("purchased_item")
    +public class Item {
    +  @Id
    +  Key purchasedItemKey;
    +
    +  String name;
    +
    +  Timestamp timeAddedToOrder;
    +}
    +
    +
    +
    +
    +

    For example, an instance of a GQL key-literal representation for Item would also contain the parent ShoppingOrder ID value:

    +
    +
    +
    +
    Key(orders, '12345', purchased_item, 'eggs')
    +
    +
    +
    +

    The GQL key-literal representation for the parent ShoppingOrder would be:

    +
    +
    +
    +
    Key(orders, '12345')
    +
    +
    +
    +

    The Cloud Datastore entities exist separately in their own kinds.

    +
    +
    +

    The ShoppingOrder:

    +
    +
    +
    +
    {
    +  "id" : 12345
    +}
    +
    +
    +
    +

    The two items inside that order:

    +
    +
    +
    +
    {
    +  "purchasedItemKey" : Key(orders, '12345', purchased_item, 'eggs'),
    +  "name" : "eggs",
    +  "timeAddedToOrder" : "2014-09-27 12:30:00.45-8:00"
    +}
    +
    +{
    +  "purchasedItemKey" : Key(orders, '12345', purchased_item, 'sausage'),
    +  "name" : "sausage",
    +  "timeAddedToOrder" : "2014-09-28 11:30:00.45-9:00"
    +}
    +
    +
    +
    +

    The parent-child relationship structure of objects is stored in Cloud Datastore using Datastore’s ancestor relationships. +Because the relationships are defined by the Ancestor mechanism, there is no extra column needed in either the parent or child entity to store this relationship. +The relationship link is part of the descendant entity’s key value. +These relationships can be many levels deep.

    +
    +
    +

    Properties holding child entities must be collection-like, but they can be any of the supported inter-convertible collection-like types that are supported for regular properties such as List, arrays, Set, etc…​ +Child items must have Key as their ID type because Cloud Datastore stores the ancestor relationship link inside the keys of the children.

    +
    +
    +

    Reading or saving an entity automatically causes all subsequent levels of children under that entity to be read or saved, respectively. +If a new child is created and added to a property annotated @Descendants and the key property is left null, then a new key will be allocated for that child. +The ordering of the retrieved children may not be the same as the ordering in the original property that was saved.

    +
    +
    +

    Child entities cannot be moved from the property of one parent to that of another unless the child’s key property is set to null or a value that contains the new parent as an ancestor. +Since Cloud Datastore entity keys can have multiple parents, it is possible that a child entity appears in the property of multiple parent entities. +Because entity keys are immutable in Cloud Datastore, to change the key of a child you must delete the existing one and re-save it with the new key.

    +
    +
    +
    +

    15.3.3. Key Reference Relationships

    +
    +

    General relationships can be stored using the @Reference annotation.

    +
    +
    +
    +
    import org.springframework.data.annotation.Reference;
    +import org.springframework.data.annotation.Id;
    +
    +@Entity
    +public class ShoppingOrder {
    +  @Id
    +  long id;
    +
    +  @Reference
    +  List<Item> items;
    +
    +  @Reference
    +  Item specialSingleItem;
    +}
    +
    +@Entity
    +public class Item {
    +  @Id
    +  Key purchasedItemKey;
    +
    +  String name;
    +
    +  Timestamp timeAddedToOrder;
    +}
    +
    +
    +
    +
    +

    @Reference relationships are between fully-formed entities residing in their own kinds. +The relationship between ShoppingOrder and Item entities are stored as a Key field inside ShoppingOrder, which are resolved to the underlying Java entity type by Spring Data Cloud Datastore:

    +
    +
    +
    +
    {
    +  "id" : 12345,
    +  "specialSingleItem" : Key(item, "milk"),
    +  "items" : [ Key(item, "eggs"), Key(item, "sausage") ]
    +}
    +
    +
    +
    +

    Reference properties can either be singular or collection-like. +These properties correspond to actual columns in the entity and Cloud Datastore Kind that hold the key values of the referenced entities. +The referenced entities are full-fledged entities of other Kinds.

    +
    +
    +

    Similar to the @Descendants relationships, reading or writing an entity will recursively read or write all of the referenced entities at all levels. +If referenced entities have null ID values, then they will be saved as new entities and will have ID values allocated by Cloud Datastore. +There are no requirements for relationships between the key of an entity and the keys that entity holds as references. +The order of collection-like reference properties is not preserved when reading back from Cloud Datastore.

    +
    +
    +
    +
    +

    15.4. Datastore Operations & Template

    +
    +

    DatastoreOperations and its implementation, DatastoreTemplate, provides the Template pattern familiar to Spring developers.

    +
    +
    +

    Using the auto-configuration provided by Spring Boot Starter for Datastore, your Spring application context will contain a fully configured DatastoreTemplate object that you can autowire in your application:

    +
    +
    +
    +
    @SpringBootApplication
    +public class DatastoreTemplateExample {
    +
    +    @Autowired
    +    DatastoreTemplate datastoreTemplate;
    +
    +    public void doSomething() {
    +        this.datastoreTemplate.deleteAll(Trader.class);
    +        //...
    +        Trader t = new Trader();
    +        //...
    +        this.datastoreTemplate.save(t);
    +        //...
    +        List<Trader> traders = datastoreTemplate.findAll(Trader.class);
    +        //...
    +    }
    +}
    +
    +
    +
    +
    +

    The Template API provides convenience methods for:

    +
    +
    +
      +
    • +

      Write operations (saving and deleting)

      +
    • +
    • +

      Read-write transactions

      +
    • +
    +
    +
    +

    15.4.1. GQL Query

    +
    +

    In addition to retrieving entities by their IDs, you can also submit queries.

    +
    +
    +
    +
      <T> Iterable<T> query(Query<? extends BaseEntity> query, Class<T> entityClass);
    +
    +  <A, T> Iterable<T> query(Query<A> query, Function<A, T> entityFunc);
    +
    +  Iterable<Key> queryKeys(Query<Key> query);
    +
    +
    +
    +
    +

    These methods, respectively, allow querying for:

    +
    +
    +
      +
    • +

      entities mapped by a given entity class using all the same mapping and converting features

      +
    • +
    • +

      arbitrary types produced by a given mapping function

      +
    • +
    • +

      only the Cloud Datastore keys of the entities found by the query

      +
    • +
    +
    +
    +
    +

    15.4.2. Find by ID(s)

    +
    +

    Using DatastoreTemplate you can find entities by id. For example:

    +
    +
    +
    +
    Trader trader = this.datastoreTemplate.findById("trader1", Trader.class);
    +
    +List<Trader> traders = this.datastoreTemplate.findAllById(Arrays.asList("trader1", "trader2"), Trader.class);
    +
    +List<Trader> allTraders = this.datastoreTemplate.findAll(Trader.class);
    +
    +
    +
    +
    +

    Cloud Datastore uses key-based reads with strong consistency, but queries with eventual consistency. +In the example above the first two reads utilize keys, while the third is run by using a query based on the corresponding Kind of Trader.

    +
    +
    +
    Indexes
    +
    +

    By default, all fields are indexed. +To disable indexing on a particular field, @Unindexed annotation can be used.

    +
    +
    +

    Example:

    +
    +
    +
    +
    import com.google.cloud.spring.data.datastore.core.mapping.Unindexed;
    +
    +public class ExampleItem {
    +    long indexedField;
    +
    +    @Unindexed
    +    long unindexedField;
    +
    +    @Unindexed
    +    List<String> unindexedListField;
    +}
    +
    +
    +
    +
    +

    When using queries directly or via Query Methods, Cloud Datastore requires composite custom indexes if the select statement is not SELECT * or if there is more than one filtering condition in the WHERE clause.

    +
    +
    +
    +
    Read with offsets, limits, and sorting
    +
    +

    DatastoreRepository and custom-defined entity repositories implement the Spring Data PagingAndSortingRepository, which supports offsets and limits using page numbers and page sizes. +Paging and sorting options are also supported in DatastoreTemplate by supplying a DatastoreQueryOptions to findAll.

    +
    +
    +
    +
    Partial read
    +
    +

    This feature is not supported yet.

    +
    +
    +
    +
    +

    15.4.3. Write / Update

    +
    +

    The write methods of DatastoreOperations accept a POJO and writes all of its properties to Datastore. +The required Datastore kind and entity metadata is obtained from the given object’s actual type.

    +
    +
    +

    If a POJO was retrieved from Datastore and its ID value was changed and then written or updated, the operation will occur as if against a row with the new ID value. +The entity with the original ID value will not be affected.

    +
    +
    +
    +
    Trader t = new Trader();
    +this.datastoreTemplate.save(t);
    +
    +
    +
    +
    +

    The save method behaves as update-or-insert. +In contrast, the insert method will fail if an entity already exists.

    +
    +
    +
    Partial Update
    +
    +

    This feature is not supported yet.

    +
    +
    +
    +
    +

    15.4.4. Transactions

    +
    +

    Read and write transactions are provided by DatastoreOperations via the performTransaction method:

    +
    +
    +
    +
    @Autowired
    +DatastoreOperations myDatastoreOperations;
    +
    +public String doWorkInsideTransaction() {
    +  return myDatastoreOperations.performTransaction(
    +    transactionDatastoreOperations -> {
    +      // Work with transactionDatastoreOperations here.
    +      // It is also a DatastoreOperations object.
    +
    +      return "transaction completed";
    +    }
    +  );
    +}
    +
    +
    +
    +
    +

    The performTransaction method accepts a Function that is provided an instance of a DatastoreOperations object. +The final returned value and type of the function is determined by the user. +You can use this object just as you would a regular DatastoreOperations with an exception:

    +
    +
    +
      +
    • +

      It cannot perform sub-transactions.

      +
    • +
    +
    +
    +

    Because of Cloud Datastore’s consistency guarantees, there are limitations to the operations and relationships among entities used inside transactions.

    +
    +
    +
    Declarative Transactions with @Transactional Annotation
    +
    +

    This feature requires a bean of DatastoreTransactionManager, which is provided when using spring-cloud-gcp-starter-data-datastore.

    +
    +
    +

    DatastoreTemplate and DatastoreRepository support running methods with the @Transactional annotation as transactions. +If a method annotated with @Transactional calls another method also annotated, then both methods will work within the same transaction. +performTransaction cannot be used in @Transactional annotated methods because Cloud Datastore does not support transactions within transactions.

    +
    +
    +

    Other Google Cloud database-related integrations like Spanner and Firestore can introduce PlatformTransactionManager beans, and can interfere with Datastore Transaction Manager. To disambiguate, explicitly specify the name of the transaction manager bean for such @Transactional methods. Example:

    +
    +
    +
    +
    @Transactional(transactionManager = "datastoreTransactionManager")
    +
    +
    +
    +
    +
    +
    +

    15.4.5. Read-Write Support for Maps

    +
    +

    You can work with Maps of type Map<String, ?> instead of with entity objects by directly reading and writing them to and from Cloud Datastore.

    +
    +
    + + + + + +
    + + +This is a different situation than using entity objects that contain Map properties. +
    +
    +
    +

    The map keys are used as field names for a Datastore entity and map values are converted to Datastore supported types. +Only simple types are supported (i.e. collections are not supported). +Converters for custom value types can be added (see Custom types section).

    +
    +
    +

    Example:

    +
    +
    +
    +
    Map<String, Long> map = new HashMap<>();
    +map.put("field1", 1L);
    +map.put("field2", 2L);
    +map.put("field3", 3L);
    +
    +keyForMap = datastoreTemplate.createKey("kindName", "id");
    +
    +// write a map
    +datastoreTemplate.writeMap(keyForMap, map);
    +
    +// read a map
    +Map<String, Long> loadedMap = datastoreTemplate.findByIdAsMap(keyForMap, Long.class);
    +
    +
    +
    +
    +
    +
    +

    15.5. Repositories

    +
    +

    Spring Data Repositories are an abstraction that can reduce boilerplate code.

    +
    +
    +

    For example:

    +
    +
    +
    +
    public interface TraderRepository extends DatastoreRepository<Trader, String> {
    +}
    +
    +
    +
    +
    +

    Spring Data generates a working implementation of the specified interface, which can be autowired into an application.

    +
    +
    +

    The Trader type parameter to DatastoreRepository refers to the underlying domain type. +The second type parameter, String in this case, refers to the type of the key of the domain type.

    +
    +
    +
    +
    public class MyApplication {
    +
    +    @Autowired
    +    TraderRepository traderRepository;
    +
    +    public void demo() {
    +
    +        this.traderRepository.deleteAll();
    +        String traderId = "demo_trader";
    +        Trader t = new Trader();
    +        t.traderId = traderId;
    +        this.tradeRepository.save(t);
    +
    +        Iterable<Trader> allTraders = this.traderRepository.findAll();
    +
    +        int count = this.traderRepository.count();
    +    }
    +}
    +
    +
    +
    +
    +

    Repositories allow you to define custom Query Methods (detailed in the following sections) for retrieving, counting, and deleting based on filtering and paging parameters. +Filtering parameters can be of types supported by your configured custom converters.

    +
    +
    +

    15.5.1. Query methods by convention

    +
    +
    +
    public interface TradeRepository extends DatastoreRepository<Trade, String[]> {
    +  List<Trader> findByAction(String action);
    +
    +  // throws an exception if no results
    +  Trader findOneByAction(String action);
    +
    +  // because of the annotation, returns null if no results
    +  @Nullable
    +  Trader getByAction(String action);
    +
    +  Optional<Trader> getOneByAction(String action);
    +
    +  int countByAction(String action);
    +
    +  boolean existsByAction(String action);
    +
    +  List<Trade> findTop3ByActionAndSymbolAndPriceGreaterThanAndPriceLessThanOrEqualOrderBySymbolDesc(
    +            String action, String symbol, double priceFloor, double priceCeiling);
    +
    +  Page<TestEntity> findByAction(String action, Pageable pageable);
    +
    +  Slice<TestEntity> findBySymbol(String symbol, Pageable pageable);
    +
    +  List<TestEntity> findBySymbol(String symbol, Sort sort);
    +
    +  Stream<TestEntity> findBySymbol(String symbol);
    +}
    +
    +
    +
    +
    +

    In the example above the query methods in TradeRepository are generated based on the name of the methods using the Spring Data Query creation naming convention.

    +
    +
    + + + + + +
    + + +You can refer to nested fields using Spring Data JPA Property Expressions +
    +
    +
    +

    Cloud Datastore only supports filter components joined by AND, and the following operations:

    +
    +
    +
      +
    • +

      equals

      +
    • +
    • +

      greater than or equals

      +
    • +
    • +

      greater than

      +
    • +
    • +

      less than or equals

      +
    • +
    • +

      less than

      +
    • +
    • +

      is null

      +
    • +
    +
    +
    +

    After writing a custom repository interface specifying just the signatures of these methods, implementations are generated for you and can be used with an auto-wired instance of the repository. +Because of Cloud Datastore’s requirement that explicitly selected fields must all appear in a composite index together, find name-based query methods are run as SELECT *.

    +
    +
    +

    Delete queries are also supported. +For example, query methods such as deleteByAction or removeByAction delete entities found by findByAction. +Delete queries are run as separate read and delete operations instead of as a single transaction because Cloud Datastore cannot query in transactions unless ancestors for queries are specified. +As a result, removeBy and deleteBy name-convention query methods cannot be used inside transactions via either performInTransaction or @Transactional annotation.

    +
    +
    +

    Delete queries can have the following return types:

    +
    +
    +
      +
    • +

      An integer type that is the number of entities deleted

      +
    • +
    • +

      A collection of entities that were deleted

      +
    • +
    • +

      'void'

      +
    • +
    +
    +
    +

    Methods can have org.springframework.data.domain.Pageable parameter to control pagination and sorting, or org.springframework.data.domain.Sort parameter to control sorting only. +See Spring Data documentation for details.

    +
    +
    +

    For returning multiple items in a repository method, we support Java collections as well as org.springframework.data.domain.Page and org.springframework.data.domain.Slice. +If a method’s return type is org.springframework.data.domain.Page, the returned object will include current page, total number of results and total number of pages.

    +
    +
    + + + + + +
    + + +Methods that return Page run an additional query to compute total number of pages. +Methods that return Slice, on the other hand, do not run any additional queries and, therefore, are much more efficient. +
    +
    +
    +
    +

    15.5.2. Empty result handling in repository methods

    +
    +

    Java java.util.Optional can be used to indicate the potential absence of a return value.

    +
    +
    +

    Alternatively, query methods can return the result without a wrapper. +In that case the absence of a query result is indicated by returning null. +Repository methods returning collections are guaranteed never to return null but rather the corresponding empty collection.

    +
    +
    + + + + + +
    + + +You can enable nullability checks. For more details please see Spring Framework’s nullability docs. +
    +
    +
    +
    +

    15.5.3. Query by example

    +
    +

    Query by Example is an alternative querying technique. +It enables dynamic query generation based on a user-provided object. See Spring Data Documentation for details.

    +
    +
    +
    Unsupported features:
    +
    +
      +
    1. +

      Currently, only equality queries are supported (no ignore-case matching, regexp matching, etc.).

      +
    2. +
    3. +

      Per-field matchers are not supported.

      +
    4. +
    5. +

      Embedded entities matching is not supported.

      +
    6. +
    7. +

      Projection is not supported.

      +
    8. +
    +
    +
    +

    For example, if you want to find all users with the last name "Smith", you would use the following code:

    +
    +
    +
    +
    userRepository.findAll(
    +    Example.of(new User(null, null, "Smith"))
    +
    +
    +
    +
    +

    null fields are not used in the filter by default. If you want to include them, you would use the following code:

    +
    +
    +
    +
    userRepository.findAll(
    +    Example.of(new User(null, null, "Smith"), ExampleMatcher.matching().withIncludeNullValues())
    +
    +
    +
    +
    +

    You can also extend query specification initially defined by an example in FluentQuery’s chaining style:

    +
    +
    +
    +
    userRepository.findBy(
    +    Example.of(new User(null, null, "Smith")), q -> q.sortBy(Sort.by("firstName")).firstValue());
    +
    +userRepository.findBy(
    +    Example.of(new User(null, null, "Smith")), FetchableFluentQuery::stream);
    +
    +
    +
    +
    +
    +

    15.5.4. Custom GQL query methods

    +
    +

    Custom GQL queries can be mapped to repository methods in one of two ways:

    +
    +
    +
      +
    • +

      namedQueries properties file

      +
    • +
    • +

      using the @Query annotation

      +
    • +
    +
    +
    +
    Query methods with annotation
    +
    +

    Using the @Query annotation:

    +
    +
    +

    The names of the tags of the GQL correspond to the @Param annotated names of the method parameters.

    +
    +
    +
    +
    public interface TraderRepository extends DatastoreRepository<Trader, String> {
    +
    +  @Query("SELECT * FROM traders WHERE name = @trader_name")
    +  List<Trader> tradersByName(@Param("trader_name") String traderName);
    +
    +  @Query("SELECT * FROM traders WHERE name = @trader_name")
    +  Stream<Trader> tradersStreamByName(@Param("trader_name") String traderName);
    +
    +  @Query("SELECT * FROM  test_entities_ci WHERE name = @trader_name")
    +  TestEntity getOneTestEntity(@Param("trader_name") String traderName);
    +
    +  @Query("SELECT * FROM traders WHERE name = @trader_name")
    +  List<Trader> tradersByNameSort(@Param("trader_name") String traderName, Sort sort);
    +
    +  @Query("SELECT * FROM traders WHERE name = @trader_name")
    +  Slice<Trader> tradersByNameSlice(@Param("trader_name") String traderName, Pageable pageable);
    +
    +  @Query("SELECT * FROM traders WHERE name = @trader_name")
    +  Page<Trader> tradersByNamePage(@Param("trader_name") String traderName, Pageable pageable);
    +}
    +
    +
    +
    +
    +

    When the return type is Slice or Pageable, the result set cursor that points to the position just after the page is preserved in the returned Slice or Page object. To take advantage of the cursor to query for the next page or slice, use result.getPageable().next().

    +
    +
    + + + + + +
    + + +Page requires the total count of entities produced by the query. Therefore, the first query will have to retrieve all of the records just to count them. Instead, we recommend using the Slice return type, because it does not require an additional count query. +
    +
    +
    +
    +
     Slice<Trader> slice1 = tradersByNamePage("Dave", PageRequest.of(0, 5));
    + Slice<Trader> slice2 = tradersByNamePage("Dave", slice1.getPageable().next());
    +
    +
    +
    +
    + + + + + +
    + + +You cannot use these Query Methods in repositories where the type parameter is a subclass of another class +annotated with DiscriminatorField. +
    +
    +
    +

    The following parameter types are supported:

    +
    +
    +
      +
    • +

      com.google.cloud.Timestamp

      +
    • +
    • +

      com.google.cloud.datastore.Blob

      +
    • +
    • +

      com.google.cloud.datastore.Key

      +
    • +
    • +

      com.google.cloud.datastore.Cursor

      +
    • +
    • +

      java.lang.Boolean

      +
    • +
    • +

      java.lang.Double

      +
    • +
    • +

      java.lang.Long

      +
    • +
    • +

      java.lang.String

      +
    • +
    • +

      enum values. +These are queried as String values.

      +
    • +
    +
    +
    +

    With the exception of Cursor, array forms of each of the types are also supported.

    +
    +
    +

    If you would like to obtain the count of items of a query or if there are any items returned by the query, set the count = true or exists = true properties of the @Query annotation, respectively. +The return type of the query method in these cases should be an integer type or a boolean type.

    +
    +
    +

    Cloud Datastore provides provides the SELECT __key__ FROM …​ special column for all kinds that retrieves the Key of each row. +Selecting this special __key__ column is especially useful and efficient for count and exists queries.

    +
    +
    +

    You can also query for non-entity types:

    +
    +
    +
    +
    @Query(value = "SELECT __key__ from test_entities_ci")
    +List<Key> getKeys();
    +
    +@Query(value = "SELECT __key__ from test_entities_ci limit 1")
    +Key getKey();
    +
    +
    +
    +
    +

    In order to use @Id annotated fields in custom queries, use __key__ keyword for the field name. The parameter type should be of Key, as in the following example.

    +
    +
    +

    Repository method:

    +
    +
    +
    +
    @Query("select * from  test_entities_ci where size = @size and __key__ = @id")
    +LinkedList<TestEntity> findEntities(@Param("size") long size, @Param("id") Key id);
    +
    +
    +
    +
    +

    Generate a key from id value using DatastoreTemplate.createKey method and use it as a parameter for the repository method:

    +
    +
    +
    +
    this.testEntityRepository.findEntities(1L, datastoreTemplate.createKey(TestEntity.class, 1L))
    +
    +
    +
    +
    +

    SpEL can be used to provide GQL parameters:

    +
    +
    +
    +
    @Query("SELECT * FROM |com.example.Trade| WHERE trades.action = @act
    +  AND price > :#{#priceRadius * -1} AND price < :#{#priceRadius * 2}")
    +List<Trade> fetchByActionNamedQuery(@Param("act") String action, @Param("priceRadius") Double r);
    +
    +
    +
    +
    +

    Kind names can be directly written in the GQL annotations. +Kind names can also be resolved from the @Entity annotation on domain classes.

    +
    +
    +

    In this case, the query should refer to table names with fully qualified class names surrounded by | characters: |fully.qualified.ClassName|. +This is useful when SpEL expressions appear in the kind name provided to the @Entity annotation. +For example:

    +
    +
    +
    +
    @Query("SELECT * FROM |com.example.Trade| WHERE trades.action = @act")
    +List<Trade> fetchByActionNamedQuery(@Param("act") String action);
    +
    +
    +
    +
    +
    +
    Query methods with named queries properties
    +
    +

    You can also specify queries with Cloud Datastore parameter tags and SpEL expressions in properties files.

    +
    +
    +

    By default, the namedQueriesLocation attribute on @EnableDatastoreRepositories points to the META-INF/datastore-named-queries.properties file. +You can specify the query for a method in the properties file by providing the GQL as the value for the "interface.method" property:

    +
    +
    + + + + + +
    + + +You cannot use these Query Methods in repositories where the type parameter is a subclass of another class +annotated with DiscriminatorField. +
    +
    +
    +
    +
    Trader.fetchByName=SELECT * FROM traders WHERE name = @tag0
    +
    +
    +
    +
    +
    public interface TraderRepository extends DatastoreRepository<Trader, String> {
    +
    +    // This method uses the query from the properties file instead of one generated based on name.
    +    List<Trader> fetchByName(@Param("tag0") String traderName);
    +
    +}
    +
    +
    +
    +
    +
    +
    +

    15.5.5. Transactions

    +
    +

    These transactions work very similarly to those of DatastoreOperations, but is specific to the repository’s domain type and provides repository functions instead of template functions.

    +
    +
    +

    For example, this is a read-write transaction:

    +
    +
    +
    +
    @Autowired
    +DatastoreRepository myRepo;
    +
    +public String doWorkInsideTransaction() {
    +  return myRepo.performTransaction(
    +    transactionDatastoreRepo -> {
    +      // Work with the single-transaction transactionDatastoreRepo here.
    +      // This is a DatastoreRepository object.
    +
    +      return "transaction completed";
    +    }
    +  );
    +}
    +
    +
    +
    +
    +
    +

    15.5.6. Projections

    +
    +

    Spring Data Cloud Datastore supports projections. +You can define projection interfaces based on domain types and add query methods that return them in your repository:

    +
    +
    +
    +
    public interface TradeProjection {
    +
    +    String getAction();
    +
    +    @Value("#{target.symbol + ' ' + target.action}")
    +    String getSymbolAndAction();
    +}
    +
    +public interface TradeRepository extends DatastoreRepository<Trade, Key> {
    +
    +    List<Trade> findByTraderId(String traderId);
    +
    +    List<TradeProjection> findByAction(String action);
    +
    +    @Query("SELECT action, symbol FROM trades WHERE action = @action")
    +    List<TradeProjection> findByQuery(String action);
    +}
    +
    +
    +
    +
    +

    Projections can be provided by name-convention-based query methods as well as by custom GQL queries. +If using custom GQL queries, you can further restrict the fields retrieved from Cloud Datastore to just those required by the projection. +However, custom select statements (those not using SELECT *) require composite indexes containing the selected fields.

    +
    +
    +

    Properties of projection types defined using SpEL use the fixed name target for the underlying domain object. +As a result, accessing underlying properties take the form target.<property-name>.

    +
    +
    +
    +

    15.5.7. REST Repositories

    +
    +

    When running with Spring Boot, repositories can be exposed as REST services by simply adding this dependency to your pom file:

    +
    +
    +
    +
    <dependency>
    +  <groupId>org.springframework.boot</groupId>
    +  <artifactId>spring-boot-starter-data-rest</artifactId>
    +</dependency>
    +
    +
    +
    +

    If you prefer to configure parameters (such as path), you can use @RepositoryRestResource annotation:

    +
    +
    +
    +
    @RepositoryRestResource(collectionResourceRel = "trades", path = "trades")
    +public interface TradeRepository extends DatastoreRepository<Trade, String[]> {
    +}
    +
    +
    +
    +
    +

    For example, you can retrieve all Trade objects in the repository by using curl http://<server>:<port>/trades, or any specific trade via curl http://<server>:<port>/trades/<trader_id>.

    +
    +
    +

    You can also write trades using curl -XPOST -H"Content-Type: application/json" -d@test.json http://<server>:<port>/trades/ where the file test.json holds the JSON representation of a Trade object.

    +
    +
    +

    To delete trades, you can use curl -XDELETE http://<server>:<port>/trades/<trader_id>

    +
    +
    +
    +
    +

    15.6. Events

    +
    +

    Spring Data Cloud Datastore publishes events extending the Spring Framework’s ApplicationEvent to the context that can be received by ApplicationListener beans you register.

    +
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeDescriptionContents

    AfterFindByKeyEvent

    Published immediately after read by-key operations are run by DatastoreTemplate

    The entities read from Cloud Datastore and the original keys in the request.

    AfterQueryEvent

    Published immediately after read byquery operations are run by DatastoreTemplate

    The entities read from Cloud Datastore and the original query in the request.

    BeforeSaveEvent

    Published immediately before save operations are run by DatastoreTemplate

    The entities to be sent to Cloud Datastore and the original Java objects being saved.

    AfterSaveEvent

    Published immediately after save operations are run by DatastoreTemplate

    The entities sent to Cloud Datastore and the original Java objects being saved.

    BeforeDeleteEvent

    Published immediately before delete operations are run by DatastoreTemplate

    The keys to be sent to Cloud Datastore. The target entities, ID values, or entity type originally specified for the delete operation.

    AfterDeleteEvent

    Published immediately after delete operations are run by DatastoreTemplate

    The keys sent to Cloud Datastore. The target entities, ID values, or entity type originally specified for the delete operation.

    +
    +
    +

    15.7. Auditing

    +
    +

    Spring Data Cloud Datastore supports the @LastModifiedDate and @LastModifiedBy auditing annotations for properties:

    +
    +
    +
    +
    @Entity
    +public class SimpleEntity {
    +    @Id
    +    String id;
    +
    +    @LastModifiedBy
    +    String lastUser;
    +
    +    @LastModifiedDate
    +    DateTime lastTouched;
    +}
    +
    +
    +
    +
    +

    Upon insert, update, or save, these properties will be set automatically by the framework before Datastore entities are generated and saved to Cloud Datastore.

    +
    +
    +

    To take advantage of these features, add the @EnableDatastoreAuditing annotation to your configuration class and provide a bean for an AuditorAware<A> implementation where the type A is the desired property type annotated by @LastModifiedBy:

    +
    +
    +
    +
    @Configuration
    +@EnableDatastoreAuditing
    +public class Config {
    +
    +    @Bean
    +    public AuditorAware<String> auditorProvider() {
    +        return () -> Optional.of("YOUR_USERNAME_HERE");
    +    }
    +}
    +
    +
    +
    +
    +

    The AuditorAware interface contains a single method that supplies the value for fields annotated by @LastModifiedBy and can be of any type. +One alternative is to use Spring Security’s User type:

    +
    +
    +
    +
    class SpringSecurityAuditorAware implements AuditorAware<User> {
    +
    +  public Optional<User> getCurrentAuditor() {
    +
    +    return Optional.ofNullable(SecurityContextHolder.getContext())
    +              .map(SecurityContext::getAuthentication)
    +              .filter(Authentication::isAuthenticated)
    +              .map(Authentication::getPrincipal)
    +              .map(User.class::cast);
    +  }
    +}
    +
    +
    +
    +
    +

    You can also set a custom provider for properties annotated @LastModifiedDate by providing a bean for DateTimeProvider and providing the bean name to @EnableDatastoreAuditing(dateTimeProviderRef = "customDateTimeProviderBean").

    +
    +
    +
    +

    15.8. Partitioning Data by Namespace

    +
    +

    You can partition your data by using more than one namespace. +This is the recommended method for multi-tenancy in Cloud Datastore.

    +
    +
    +
    +
        @Bean
    +    public DatastoreNamespaceProvider namespaceProvider() {
    +        // return custom Supplier of a namespace string.
    +    }
    +
    +
    +
    +
    +

    The DatastoreNamespaceProvider is a synonym for Supplier<String>. +By providing a custom implementation of this bean (for example, supplying a thread-local namespace name), you can direct your application to use multiple namespaces. +Every read, write, query, and transaction you perform will utilize the namespace provided by this supplier.

    +
    +
    +

    Note that your provided namespace in application.properties will be ignored if you define a namespace provider bean.

    +
    +
    +
    +

    15.9. Spring Boot Actuator Support

    +
    +

    15.9.1. Cloud Datastore Health Indicator

    +
    +

    If you are using Spring Boot Actuator, you can take advantage of the Cloud Datastore health indicator called datastore. +The health indicator will verify whether Cloud Datastore is up and accessible by your application. +To enable it, all you need to do is add the Spring Boot Actuator to your project.

    +
    +
    +
    +
    <dependency>
    +    <groupId>org.springframework.boot</groupId>
    +    <artifactId>spring-boot-starter-actuator</artifactId>
    +</dependency>
    +
    +
    +
    +
    +
    +

    15.10. Sample

    +
    +

    A Simple Spring Boot Application and more advanced Sample Spring Boot Application are provided to show how to use the Spring Data Cloud Datastore starter and template.

    +
    +
    +
    +

    15.11. Test

    +
    +

    Testcontainers provides a gcloud module which offers DatastoreEmulatorContainer. See more at the docs

    +
    +
    +
    +
    +
    +

    16. Spring Data Cloud Firestore

    +
    +
    + + + + + +
    + + +Currently some features are not supported: query by example, projections, and auditing. +
    +
    +
    +

    Spring Data is an abstraction for storing and retrieving POJOs in numerous storage technologies. +Spring Framework on Google Cloud adds Spring Data Reactive Repositories support for Google Cloud Firestore in native mode, providing reactive template and repositories support. +To begin using this library, add the spring-cloud-gcp-data-firestore artifact to your project.

    +
    +
    +

    Maven coordinates for this module only, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +  <groupId>com.google.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-data-firestore</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +  implementation("com.google.cloud:spring-cloud-gcp-data-firestore")
    +}
    +
    +
    +
    +

    We provide a Spring Boot Starter for Spring Data Firestore, with which you can use our recommended auto-configuration setup. To use the starter, see the coordinates below.

    +
    +
    +
    +
    <dependency>
    +  <groupId>com.google.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-starter-data-firestore</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +  implementation("com.google.cloud:spring-cloud-gcp-starter-data-firestore")
    +}
    +
    +
    +
    +

    16.1. Configuration

    +
    +

    16.1.1. Properties

    +
    +

    The Spring Boot starter for Google Cloud Firestore provides the following configuration options:

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.firestore.enabled

    Enables or disables Firestore auto-configuration

    No

    true

    spring.cloud.gcp.firestore.project-id

    Google Cloud project ID where the Google Cloud Firestore API is hosted, if different from the one in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.firestore.database-id

    Google Cloud project can host multiple databases. You can specify which database will be used. If not specified, the database id will be "(default)".

    No

    spring.cloud.gcp.firestore.emulator.enabled

    Enables the usage of an emulator. If this is set to true, then you should set the spring.cloud.gcp.firestore.host-port to the host:port of your locally running emulator instance

    No

    false

    spring.cloud.gcp.firestore.host-port

    The host and port of the Firestore service; can be overridden to specify connecting to an already-running Firestore emulator instance.

    No

    firestore.googleapis.com:443 (the host/port of official Firestore service)

    spring.cloud.gcp.firestore.credentials.location

    OAuth2 credentials for authenticating with the Google Cloud Firestore API, if different from the ones in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.firestore.credentials.encoded-key

    Base64-encoded OAuth2 credentials for authenticating with the Google Cloud Firestore API, if different from the ones in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.firestore.credentials.scopes

    OAuth2 scope for Spring Framework on Google CloudFirestore credentials

    No

    www.googleapis.com/auth/datastore

    +
    +
    +

    16.1.2. Supported types

    +
    +

    You may use the following field types when defining your persistent entities or when binding query parameters:

    +
    +
    +
      +
    • +

      Long

      +
    • +
    • +

      Integer

      +
    • +
    • +

      Double

      +
    • +
    • +

      Float

      +
    • +
    • +

      String

      +
    • +
    • +

      Boolean

      +
    • +
    • +

      Character

      +
    • +
    • +

      Date

      +
    • +
    • +

      Map

      +
    • +
    • +

      List

      +
    • +
    • +

      Enum

      +
    • +
    • +

      com.google.cloud.Timestamp

      +
    • +
    • +

      com.google.cloud.firestore.GeoPoint

      +
    • +
    • +

      com.google.cloud.firestore.Blob

      +
    • +
    +
    +
    +
    +

    16.1.3. Reactive Repository settings

    +
    +

    Spring Data Repositories can be configured via the @EnableReactiveFirestoreRepositories annotation on your main @Configuration class. +With our Spring Boot Starter for Spring Data Cloud Firestore, @EnableReactiveFirestoreRepositories is automatically added. +It is not required to add it to any other class, unless there is a need to override finer grain configuration parameters provided by @EnableReactiveFirestoreRepositories.

    +
    +
    +
    +

    16.1.4. Autoconfiguration

    +
    +

    Our Spring Boot autoconfiguration creates the following beans available in the Spring application context:

    +
    +
    +
      +
    • +

      an instance of FirestoreTemplate

      +
    • +
    • +

      instances of all user defined repositories extending FirestoreReactiveRepository (an extension of ReactiveCrudRepository with additional Cloud Firestore features) when repositories are enabled

      +
    • +
    • +

      an instance of Firestore from the Google Cloud Java Client for Firestore, for convenience and lower level API access

      +
    • +
    +
    +
    +
    +
    +

    16.2. Object Mapping

    +
    +

    Spring Data Cloud Firestore allows you to map domain POJOs to Cloud Firestore collections and documents via annotations:

    +
    +
    +
    +
    import com.google.cloud.firestore.annotation.DocumentId;
    +import com.google.cloud.spring.data.firestore.Document;
    +
    +@Document(collectionName = "usersCollection")
    +public class User {
    +  /** Used to test @PropertyName annotation on a field. */
    +  @PropertyName("drink")
    +  public String favoriteDrink;
    +
    +  @DocumentId private String name;
    +
    +  private Integer age;
    +
    +  public User() {}
    +
    +  public String getName() {
    +    return this.name;
    +  }
    +
    +  public void setName(String name) {
    +    this.name = name;
    +  }
    +
    +  public Integer getAge() {
    +    return this.age;
    +  }
    +
    +  public void setAge(Integer age) {
    +    this.age = age;
    +  }
    +}
    +
    +
    +
    +
    +

    @Document(collectionName = "usersCollection") annotation configures the collection name for the documents of this type. +This annotation is optional, by default the collection name is derived from the class name.

    +
    +
    +

    @DocumentId annotation marks a field to be used as document id. +This annotation is required and the annotated field can only be of String type.

    +
    +
    + + + + + +
    + + +If the property annotated with @DocumentId is null the document id is generated automatically when the entity is saved. +
    +
    +
    + + + + + +
    + + +Internally we use Firestore client library object mapping. See the documentation for supported annotations. +
    +
    +
    +

    16.2.1. Embedded entities and lists

    +
    +

    Spring Data Cloud Firestore supports embedded properties of custom types and lists. +Given a custom POJO definition, you can have properties of this type or lists of this type in your entities. +They are stored as embedded documents (or arrays, correspondingly) in the Cloud Firestore.

    +
    +
    +

    Example:

    +
    +
    +
    +
    @Document(collectionName = "usersCollection")
    +public class User {
    +  /** Used to test @PropertyName annotation on a field. */
    +  @PropertyName("drink")
    +  public String favoriteDrink;
    +
    +  @DocumentId private String name;
    +
    +  private Integer age;
    +
    +  private List<String> pets;
    +
    +  private List<Address> addresses;
    +
    +  private Address homeAddress;
    +
    +  public List<String> getPets() {
    +    return this.pets;
    +  }
    +
    +  public void setPets(List<String> pets) {
    +    this.pets = pets;
    +  }
    +
    +  public List<Address> getAddresses() {
    +    return this.addresses;
    +  }
    +
    +  public void setAddresses(List<Address> addresses) {
    +    this.addresses = addresses;
    +  }
    +
    +  public Timestamp getUpdateTime() {
    +    return updateTime;
    +  }
    +
    +  public void setUpdateTime(Timestamp updateTime) {
    +    this.updateTime = updateTime;
    +  }
    +
    +  @PropertyName("address")
    +  public Address getHomeAddress() {
    +    return this.homeAddress;
    +  }
    +
    +  @PropertyName("address")
    +  public void setHomeAddress(Address homeAddress) {
    +    this.homeAddress = homeAddress;
    +  }
    +  public static class Address {
    +    String streetAddress;
    +    String country;
    +
    +    public Address() {}
    +  }
    +}
    +
    +
    +
    +
    +
    +
    +

    16.3. Reactive Repositories

    +
    +

    Spring Data Repositories is an abstraction that can reduce boilerplate code.

    +
    +
    +

    For example:

    +
    +
    +
    +
    public interface UserRepository extends FirestoreReactiveRepository<User> {
    +  Flux<User> findBy(Pageable pageable);
    +
    +  Flux<User> findByAge(Integer age);
    +
    +  Flux<User> findByAge(Integer age, Sort sort);
    +
    +  Flux<User> findByAgeOrderByNameDesc(Integer age);
    +
    +  Flux<User> findAllByOrderByAge();
    +
    +  Flux<User> findByAgeNot(Integer age);
    +
    +  Flux<User> findByNameAndAge(String name, Integer age);
    +
    +  Flux<User> findByHomeAddressCountry(String country);
    +
    +  Flux<User> findByFavoriteDrink(String drink);
    +
    +  Flux<User> findByAgeGreaterThanAndAgeLessThan(Integer age1, Integer age2);
    +
    +  Flux<User> findByAgeGreaterThan(Integer age);
    +
    +  Flux<User> findByAgeGreaterThan(Integer age, Sort sort);
    +
    +  Flux<User> findByAgeGreaterThan(Integer age, Pageable pageable);
    +
    +  Flux<User> findByAgeIn(List<Integer> ages);
    +
    +  Flux<User> findByAgeNotIn(List<Integer> ages);
    +
    +  Flux<User> findByAgeAndPetsContains(Integer age, List<String> pets);
    +
    +  Flux<User> findByNameAndPetsContains(String name, List<String> pets);
    +
    +  Flux<User> findByPetsContains(List<String> pets);
    +
    +  Flux<User> findByPetsContainsAndAgeIn(String pets, List<Integer> ages);
    +
    +  Mono<Long> countByAgeIsGreaterThan(Integer age);
    +}
    +
    +
    +
    +
    +

    Spring Data generates a working implementation of the specified interface, which can be autowired into an application.

    +
    +
    +

    The User type parameter to FirestoreReactiveRepository refers to the underlying domain type.

    +
    +
    + + + + + +
    + + +You can refer to nested fields using Spring Data JPA Property Expressions +
    +
    +
    +
    +
    public class MyApplication {
    +
    +  @Autowired UserRepository userRepository;
    +
    +  void writeReadDeleteTest() {
    +    List<User.Address> addresses =
    +        Arrays.asList(
    +            new User.Address("123 Alice st", "US"), new User.Address("1 Alice ave", "US"));
    +    User.Address homeAddress = new User.Address("10 Alice blvd", "UK");
    +    User alice = new User("Alice", 29, null, addresses, homeAddress);
    +    User bob = new User("Bob", 60);
    +
    +    this.userRepository.save(alice).block();
    +    this.userRepository.save(bob).block();
    +
    +    assertThat(this.userRepository.count().block()).isEqualTo(2);
    +    assertThat(this.userRepository.findAll().map(User::getName).collectList().block())
    +        .containsExactlyInAnyOrder("Alice", "Bob");
    +
    +    User aliceLoaded = this.userRepository.findById("Alice").block();
    +    assertThat(aliceLoaded.getAddresses()).isEqualTo(addresses);
    +    assertThat(aliceLoaded.getHomeAddress()).isEqualTo(homeAddress);
    +
    +    // cast to SimpleFirestoreReactiveRepository for method be reachable with Spring Boot 2.4
    +    SimpleFirestoreReactiveRepository repository =
    +        AopTestUtils.getTargetObject(this.userRepository);
    +    StepVerifier.create(
    +            repository
    +                .deleteAllById(Arrays.asList("Alice", "Bob"))
    +                .then(this.userRepository.count()))
    +        .expectNext(0L)
    +        .verifyComplete();
    +  }
    +}
    +
    +
    +
    +
    +

    Repositories allow you to define custom Query Methods (detailed in the following sections) for retrieving and counting based on filtering and paging parameters.

    +
    +
    + + + + + +
    + + +Custom queries with @Query annotation are not supported since there is no query language in Cloud Firestore +
    +
    +
    +
    +

    16.4. Firestore Operations & Template

    +
    +

    FirestoreOperations and its implementation, FirestoreTemplate, provides the Template pattern familiar to Spring developers.

    +
    +
    +

    Using the auto-configuration provided by Spring Data Cloud Firestore, your Spring application context will contain a fully configured FirestoreTemplate object that you can autowire in your application:

    +
    +
    +
    +
    @SpringBootApplication
    +public class FirestoreTemplateExample {
    +
    +    @Autowired
    +    FirestoreOperations firestoreOperations;
    +
    +    public Mono<User> createUsers() {
    +        return this.firestoreOperations.save(new User("Alice", 29))
    +            .then(this.firestoreOperations.save(new User("Bob", 60)));
    +    }
    +
    +    public Flux<User> findUsers() {
    +        return this.firestoreOperations.findAll(User.class);
    +    }
    +
    +    public Mono<Long> removeAllUsers() {
    +        return this.firestoreOperations.deleteAll(User.class);
    +    }
    +}
    +
    +
    +
    +
    +

    The Template API provides support for:

    +
    +
    + +
    +
    +
    +

    16.5. Query methods by convention

    +
    +
    +
    public class MyApplication {
    +  void partTreeRepositoryMethodTest() {
    +    User u1 = new User("Cloud", 22, null, null, new Address("1 First st., NYC", "USA"));
    +    u1.favoriteDrink = "tea";
    +    User u2 =
    +        new User(
    +            "Squall",
    +            17,
    +            Arrays.asList("cat", "dog"),
    +            null,
    +            new Address("2 Second st., London", "UK"));
    +    u2.favoriteDrink = "wine";
    +    Flux<User> users = Flux.fromArray(new User[] {u1, u2});
    +
    +    this.userRepository.saveAll(users).blockLast();
    +
    +    assertThat(this.userRepository.count().block()).isEqualTo(2);
    +    assertThat(this.userRepository.findBy(PageRequest.of(0, 10)).collectList().block())
    +        .containsExactly(u1, u2);
    +    assertThat(this.userRepository.findByAge(22).collectList().block()).containsExactly(u1);
    +    assertThat(this.userRepository.findByAgeNot(22).collectList().block()).containsExactly(u2);
    +    assertThat(this.userRepository.findByHomeAddressCountry("USA").collectList().block())
    +        .containsExactly(u1);
    +    assertThat(this.userRepository.findByFavoriteDrink("wine").collectList().block())
    +        .containsExactly(u2);
    +    assertThat(this.userRepository.findByAgeGreaterThanAndAgeLessThan(20, 30).collectList().block())
    +        .containsExactly(u1);
    +    assertThat(this.userRepository.findByAgeGreaterThan(10).collectList().block())
    +        .containsExactlyInAnyOrder(u1, u2);
    +    assertThat(this.userRepository.findByNameAndAge("Cloud", 22).collectList().block())
    +        .containsExactly(u1);
    +    assertThat(
    +            this.userRepository
    +                .findByNameAndPetsContains("Squall", Collections.singletonList("cat"))
    +                .collectList()
    +                .block())
    +        .containsExactly(u2);
    +  }
    +}
    +
    +
    +
    +
    +

    In the example above the query method implementations in UserRepository are generated based on the name of the methods using the Spring Data Query creation naming convention.

    +
    +
    +

    Cloud Firestore only supports filter components joined by AND, and the following operations:

    +
    +
    +
      +
    • +

      equals

      +
    • +
    • +

      is not equal

      +
    • +
    • +

      greater than or equals

      +
    • +
    • +

      greater than

      +
    • +
    • +

      less than or equals

      +
    • +
    • +

      less than

      +
    • +
    • +

      is null

      +
    • +
    • +

      contains (accepts a List with up to 10 elements, or a singular value)

      +
    • +
    • +

      in (accepts a List with up to 10 elements)

      +
    • +
    • +

      not in (accepts a List with up to 10 elements)

      +
    • +
    +
    +
    + + + + + +
    + + +If in operation is used in combination with contains operation, the argument to contains operation has to be a singular value. +
    +
    +
    +

    After writing a custom repository interface specifying just the signatures of these methods, implementations are generated for you and can be used with an auto-wired instance of the repository.

    +
    +
    +
    +

    16.6. Transactions

    +
    +

    Read-only and read-write transactions are provided by TransactionalOperator (see this blog post on reactive transactions for details). +In order to use it, you would need to autowire ReactiveFirestoreTransactionManager like this:

    +
    +
    +
    +
    public class MyApplication {
    +  @Autowired ReactiveFirestoreTransactionManager txManager;
    +}
    +
    +
    +
    +
    +

    After that you will be able to use it to create an instance of TransactionalOperator. +Note that you can switch between read-only and read-write transactions using TransactionDefinition object:

    +
    +
    +
    +
    DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
    +transactionDefinition.setReadOnly(false);
    +TransactionalOperator operator =
    +    TransactionalOperator.create(this.txManager, transactionDefinition);
    +
    +
    +
    +
    +

    When you have an instance of TransactionalOperator, you can invoke a sequence of Firestore operations in a transaction by using operator::transactional:

    +
    +
    +
    +
    User alice = new User("Alice", 29);
    +User bob = new User("Bob", 60);
    +
    +this.userRepository
    +    .save(alice)
    +    .then(this.userRepository.save(bob))
    +    .as(operator::transactional)
    +    .block();
    +
    +this.userRepository
    +    .findAll()
    +    .flatMap(
    +        a -> {
    +          a.setAge(a.getAge() - 1);
    +          return this.userRepository.save(a);
    +        })
    +    .as(operator::transactional)
    +    .collectList()
    +    .block();
    +
    +assertThat(this.userRepository.findAll().map(User::getAge).collectList().block())
    +    .containsExactlyInAnyOrder(28, 59);
    +
    +
    +
    +
    + + + + + +
    + + +Read operations in a transaction can only happen before write operations. +All write operations are applied atomically. +Read documents are locked until the transaction finishes with a commit or a rollback, which are handled by Spring Data. +If an Exception is thrown within a transaction, the rollback operation is performed. +Otherwise, the commit operation is performed. +
    +
    +
    +

    16.6.1. Declarative Transactions with @Transactional Annotation

    +
    +

    This feature requires a bean of SpannerTransactionManager, which is provided when using spring-cloud-gcp-starter-data-firestore.

    +
    +
    +

    FirestoreTemplate and FirestoreReactiveRepository support running methods with the @Transactional annotation as transactions. +If a method annotated with @Transactional calls another method also annotated, then both methods will work within the same transaction.

    +
    +
    +

    One way to use this feature is illustrated here. You would need to do the following:

    +
    +
    +
      +
    1. +

      Annotate your configuration class with the @EnableTransactionManagement annotation.

      +
    2. +
    3. +

      Create a service class that has methods annotated with @Transactional:

      +
    4. +
    +
    +
    +
    +
    class UserService {
    +  @Autowired private UserRepository userRepository;
    +
    +  @Transactional
    +  public Mono<Void> updateUsers() {
    +    return this.userRepository
    +        .findAll()
    +        .flatMap(
    +            a -> {
    +              a.setAge(a.getAge() - 1);
    +              return this.userRepository.save(a);
    +            })
    +        .then();
    +  }
    +}
    +
    +
    +
    +
    +
      +
    1. +

      Make a Spring Bean provider that creates an instance of that class:

      +
    2. +
    +
    +
    +
    +
    @Bean
    +public UserService userService() {
    +  return new UserService();
    +}
    +
    +
    +
    +
    +

    After that, you can autowire your service like so:

    +
    +
    +
    +
    public class MyApplication {
    +  @Autowired UserService userService;
    +}
    +
    +
    +
    +
    +

    Now when you call the methods annotated with @Transactional on your service object, a transaction will be automatically started. +If an error occurs during the execution of a method annotated with @Transactional, the transaction will be rolled back. +If no error occurs, the transaction will be committed.

    +
    +
    +
    +
    +

    16.7. Subcollections

    +
    +

    A subcollection is a collection associated with a specific entity. +Documents in subcollections can contain subcollections as well, allowing you to further nest data. You can nest data up to 100 levels deep.

    +
    +
    + + + + + +
    + + +Deleting a document does not delete its subcollections! +
    +
    +
    +

    To use subcollections you will need to create a FirestoreReactiveOperations object with a parent entity using FirestoreReactiveOperations.withParent call. +You can use this object to save, query and remove entities associated with this parent. +The parent doesn’t have to exist in Firestore, but should have a non-empty id field.

    +
    +
    +

    Autowire FirestoreReactiveOperations:

    +
    +
    +
    +
    @Autowired
    +FirestoreReactiveOperations firestoreTemplate;
    +
    +
    +
    +
    +

    Then you can use this object to create a FirestoreReactiveOperations object with a custom parent:

    +
    +
    +
    +
    FirestoreReactiveOperations bobTemplate =
    +    this.firestoreTemplate.withParent(new User("Bob", 60));
    +
    +PhoneNumber phoneNumber = new PhoneNumber("111-222-333");
    +bobTemplate.save(phoneNumber).block();
    +assertThat(bobTemplate.findAll(PhoneNumber.class).collectList().block())
    +    .containsExactly(phoneNumber);
    +bobTemplate.deleteAll(PhoneNumber.class).block();
    +assertThat(bobTemplate.findAll(PhoneNumber.class).collectList().block()).isEmpty();
    +
    +
    +
    +
    +
    +

    16.8. Update Time and Optimistic Locking

    +
    +

    Firestore stores update time for every document. +If you would like to retrieve it, you can add a field of com.google.cloud.Timestamp type to your entity and annotate it with @UpdateTime annotation.

    +
    +
    +
    +
    @UpdateTime
    +Timestamp updateTime;
    +
    +
    +
    +
    +
    Using update time for optimistic locking
    +
    +

    A field annotated with @UpdateTime can be used for optimistic locking. +To enable that, you need to set version parameter to true:

    +
    +
    +
    +
    @UpdateTime(version = true)
    +Timestamp updateTime;
    +
    +
    +
    +
    +

    When you enable optimistic locking, a precondition will be automatically added to the write request to ensure that the document you are updating was not changed since your last read. +It uses this field’s value as a document version and checks that the version of the document you write is the same as the one you’ve read.

    +
    +
    +

    If the field is empty, a precondition would check that the document with the same id does not exist to ensure you don’t overwrite existing documents unintentionally.

    +
    +
    +
    +
    +

    16.9. Cloud Firestore Spring Boot Starter

    +
    +

    If you prefer using Firestore client only, Spring Framework on Google Cloud provides a convenience starter which automatically configures authentication settings and client objects needed to begin using Google Cloud Firestore in native mode.

    +
    +
    +

    See documentation to learn more about Cloud Firestore.

    +
    +
    +

    To begin using this library, add the spring-cloud-gcp-starter-firestore artifact to your project.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-firestore</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +  implementation("com.google.cloud:spring-cloud-gcp-starter-firestore")
    +}
    +
    +
    +
    +

    16.9.1. Using Cloud Firestore

    +
    +

    The starter automatically configures and registers a Firestore bean in the Spring application context. To start using it, simply use the @Autowired annotation.

    +
    +
    +
    +
    @Autowired
    +Firestore firestore;
    +
    + void writeDocumentFromObject() throws ExecutionException, InterruptedException {
    +   // Add document data with id "joe" using a custom User class
    +   User data =
    +       new User(
    +           "Joe",
    +           Arrays.asList(new Phone(12345, PhoneType.CELL), new Phone(54321, PhoneType.WORK)));
    +
    +   // .get() blocks on response
    +   WriteResult writeResult = this.firestore.document("users/joe").set(data).get();
    +
    +   LOGGER.info("Update time: " + writeResult.getUpdateTime());
    + }
    +
    + User readDocumentToObject() throws ExecutionException, InterruptedException {
    +   ApiFuture<DocumentSnapshot> documentFuture = this.firestore.document("users/joe").get();
    +
    +   User user = documentFuture.get().toObject(User.class);
    +
    +   LOGGER.info("read: " + user);
    +
    +   return user;
    + }
    +
    +
    +
    +
    +
    +
    +

    16.10. Emulator Usage

    +
    +

    The Google Cloud Firebase SDK provides a local, in-memory emulator for Cloud Firestore, which you can use to develop and test your application.

    +
    +
    +

    First follow the Firebase emulator installation steps to install, configure, and run the emulator.

    +
    +
    + + + + + +
    + + +By default, the emulator is configured to run on port 8080; you will need to ensure that the emulator does not run on the same port as your Spring application. +
    +
    +
    +

    Once the Firestore emulator is running, ensure that the following properties are set in your application.properties of your Spring application:

    +
    +
    +
    +
    spring.cloud.gcp.firestore.emulator.enabled=true
    +spring.cloud.gcp.firestore.host-port=${EMULATOR_HOSTPORT}
    +
    +
    +
    +

    From this point onward, your application will connect to your locally running emulator instance instead of the real Firestore service.

    +
    +
    +
    +

    16.11. Samples

    +
    +

    Spring Framework on Google Cloud provides Firestore sample applications to demonstrate API usage:

    +
    + +
    +
    +

    16.12. Test

    +
    +

    Testcontainers provides a gcloud module which offers FirestoreEmulatorContainer. See more at the docs

    +
    +
    +
    +
    +
    +

    17. Cloud Memorystore for Redis

    +
    +
    +

    17.1. Spring Caching

    +
    +

    Cloud Memorystore for Redis provides a fully managed in-memory data store service. +Cloud Memorystore is compatible with the Redis protocol, allowing easy integration with Spring Caching.

    +
    +
    +

    All you have to do is create a Cloud Memorystore instance and use its IP address in application.properties file as spring.redis.host property value. +Everything else is exactly the same as setting up redis-backed Spring caching.

    +
    +
    + + + + + +
    + + +
    +

    Memorystore instances and your application instances have to be located in the same region.

    +
    +
    +
    +
    +

    In short, the following dependencies are needed:

    +
    +
    +
    +
    <dependency>
    +    <groupId>org.springframework.boot</groupId>
    +    <artifactId>spring-boot-starter-cache</artifactId>
    +</dependency>
    +<dependency>
    +    <groupId>org.springframework.boot</groupId>
    +    <artifactId>spring-boot-starter-data-redis</artifactId>
    +</dependency>
    +
    +
    +
    +

    For reactive applications, you can also use spring-boot-starter-data-redis-reactive instead.

    +
    +
    +

    And then you can use org.springframework.cache.annotation.Cacheable annotation for methods you’d like to be cached.

    +
    +
    +
    +
    @Cacheable("cache1")
    +public String hello(@PathVariable String name) {
    +    ....
    +}
    +
    +
    +
    +
    +

    If you are interested in a detailed how-to guide, please check Spring Boot Caching using Cloud Memorystore codelab.

    +
    +
    +

    Cloud Memorystore documentation can be found here.

    +
    +
    +
    +
    +
    +

    18. BigQuery

    +
    +
    +

    Google Cloud BigQuery is a fully managed, petabyte scale, low cost analytics data warehouse.

    +
    +
    +

    Spring Framework on Google Cloud provides:

    +
    +
    +
      +
    • +

      A convenience starter which provides autoconfiguration for the BigQuery client objects with credentials needed to interface with BigQuery.

      +
    • +
    • +

      A Spring Integration message handler for loading data into BigQuery tables in your Spring integration pipelines.

      +
    • +
    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-bigquery</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-bigquery")
    +}
    +
    +
    +
    +

    18.1. Configuration

    +
    +

    The following application properties may be configured with Spring Framework on Google Cloud BigQuery libraries.

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.bigquery.datasetName

    The BigQuery dataset that the BigQueryTemplate and BigQueryFileMessageHandler is scoped to.

    Yes

    spring.cloud.gcp.bigquery.enabled

    Enables or disables Spring Framework on Google Cloud BigQuery autoconfiguration.

    No

    true

    spring.cloud.gcp.bigquery.project-id

    Google Cloud project ID of the project using BigQuery APIs, if different from the one in the Spring Framework on Google Cloud Core Module.

    No

    Project ID is typically inferred from gcloud configuration.

    spring.cloud.gcp.bigquery.credentials.location

    Credentials file location for authenticating with the Google Cloud BigQuery APIs, if different from the ones in the Spring Framework on Google Cloud Core Module

    No

    Inferred from Application Default Credentials, typically set by gcloud.

    spring.cloud.gcp.bigquery.jsonWriterBatchSize

    Batch size which will be used by BigQueryJsonDataWriter while using BigQuery Storage Write API. Note too large or too low values might impact performance.

    No

    1000

    spring.cloud.gcp.bigquery.threadPoolSize

    The size of thread pool of ThreadPoolTaskScheduler which is used by BigQueryTemplate

    No

    4

    +
    +

    18.1.1. BigQuery Client Object

    +
    +

    The GcpBigQueryAutoConfiguration class configures an instance of BigQuery for you by inferring your credentials and Project ID from the machine’s environment.

    +
    +
    +

    Example usage:

    +
    +
    +
    +
    // BigQuery client object provided by our autoconfiguration.
    +@Autowired
    +BigQuery bigquery;
    +
    +public void runQuery() throws InterruptedException {
    +  String query = "SELECT column FROM table;";
    +  QueryJobConfiguration queryConfig =
    +      QueryJobConfiguration.newBuilder(query).build();
    +
    +  // Run the query using the BigQuery object
    +  for (FieldValueList row : bigquery.query(queryConfig).iterateAll()) {
    +    for (FieldValue val : row) {
    +      System.out.println(val);
    +    }
    +  }
    +}
    +
    +
    +
    +
    +

    This object is used to interface with all BigQuery services. +For more information, see the BigQuery Client Library usage examples.

    +
    +
    +
    +

    18.1.2. BigQueryTemplate

    +
    +

    The BigQueryTemplate class is a wrapper over the BigQuery client object and makes it easier to load data into BigQuery tables. +A BigQueryTemplate is scoped to a single dataset. +The autoconfigured BigQueryTemplate instance will use the dataset provided through the property spring.cloud.gcp.bigquery.datasetName.

    +
    +
    +

    Below is a code snippet of how to load a CSV data InputStream to a BigQuery table.

    +
    +
    +
    +
    // BigQuery client object provided by our autoconfiguration.
    +@Autowired
    +BigQueryTemplate bigQueryTemplate;
    +
    +public void loadData(InputStream dataInputStream, String tableName) {
    +  ListenableFuture<Job> bigQueryJobFuture =
    +      bigQueryTemplate.writeDataToTable(
    +          tableName,
    +          dataFile.getInputStream(),
    +          FormatOptions.csv());
    +
    +  // After the future is complete, the data is successfully loaded.
    +  Job job = bigQueryJobFuture.get();
    +}
    +
    +
    +
    +
    +

    Below is a code snippet of how to load a newline-delimited JSON data InputStream to a BigQuery table. This implementation uses the BigQuery Storage Write API. +Here is a sample newline-delimited JSON file which can be used for testing this functionality.

    +
    +
    +
    +
    // BigQuery client object provided by our autoconfiguration.
    +@Autowired
    +BigQueryTemplate bigQueryTemplate;
    +
    +  /**
    +   * This method loads the InputStream of the newline-delimited JSON records to be written in the given table.
    +   * @param tableName name of the table where the data is expected to be written
    +   * @param jsonInputStream InputStream of the newline-delimited JSON records to be written in the given table
    +   */
    +  public void loadJsonStream(String tableName, InputStream jsonInputStream)
    +      throws ExecutionException, InterruptedException {
    +    ListenableFuture<WriteApiResponse> writeApFuture =
    +        bigQueryTemplate.writeJsonStream(tableName, jsonInputStream);
    +    WriteApiResponse apiRes = writeApFuture.get();//get the WriteApiResponse
    +    if (!apiRes.isSuccessful()){
    +      List<StorageError> errors = apiRes.getErrors();
    +      // TODO(developer): process the List of StorageError
    +    }
    +    // else the write process has been successful
    +  }
    +
    +
    +
    +
    +

    Below is a code snippet of how to create table and then load a newline-delimited JSON data InputStream to a BigQuery table. This implementation uses the BigQuery Storage Write API. +Here is a sample newline-delimited JSON file which can be used for testing this functionality.

    +
    +
    +
    +
    // BigQuery client object provided by our autoconfiguration.
    +@Autowired
    +BigQueryTemplate bigQueryTemplate;
    +
    +  /**
    +   * This method created a table with the given name and schema and then loads the InputStream of the newline-delimited JSON records in it.
    +   * @param tableName name of the table where the data is expected to be written
    +   * @param jsonInputStream InputStream of the newline-delimited JSON records to be written in the given table
    +   * @param tableSchema Schema of the table which is required to be created
    +   */
    +  public void createTableAndloadJsonStream(String tableName, InputStream jsonInputStream, Schema tableSchema)
    +      throws ExecutionException, InterruptedException {
    +    ListenableFuture<WriteApiResponse> 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()){
    +      List<StorageError> errors = apiRes.getErrors();
    +      // TODO(developer): process the List of StorageError
    +    }
    +    // else the write process has been successful
    +  }
    +
    +
    +
    +
    +
    +
    +

    18.2. Spring Integration

    +
    +

    Spring Framework on Google Cloud BigQuery also provides a Spring Integration message handler BigQueryFileMessageHandler. +This is useful for incorporating BigQuery data loading operations in a Spring Integration pipeline.

    +
    +
    +

    Below is an example configuring a ServiceActivator bean using the BigQueryFileMessageHandler.

    +
    +
    +
    +
    @Bean
    +public DirectChannel bigQueryWriteDataChannel() {
    +  return new DirectChannel();
    +}
    +
    +@Bean
    +public DirectChannel bigQueryJobReplyChannel() {
    +  return new DirectChannel();
    +}
    +
    +@Bean
    +@ServiceActivator(inputChannel = "bigQueryWriteDataChannel")
    +public MessageHandler messageSender(BigQueryTemplate bigQueryTemplate) {
    +  BigQueryFileMessageHandler messageHandler = new BigQueryFileMessageHandler(bigQueryTemplate);
    +  messageHandler.setFormatOptions(FormatOptions.csv());
    +  messageHandler.setOutputChannel(bigQueryJobReplyChannel());
    +  return messageHandler;
    +}
    +
    +
    +
    +
    +

    18.2.1. BigQuery Message Handling

    +
    +

    The BigQueryFileMessageHandler accepts the following message payload types for loading into BigQuery: java.io.File, byte[], org.springframework.core.io.Resource, and java.io.InputStream. +The message payload will be streamed and written to the BigQuery table you specify.

    +
    +
    +

    By default, the BigQueryFileMessageHandler is configured to read the headers of the messages it receives to determine how to load the data. +The headers are specified by the class BigQuerySpringMessageHeaders and summarized below.

    +
    + ++++ + + + + + + + + + + + + + + +

    Header

    Description

    BigQuerySpringMessageHeaders.TABLE_NAME

    Specifies the BigQuery table within your dataset to write to.

    BigQuerySpringMessageHeaders.FORMAT_OPTIONS

    Describes the data format of your data to load (i.e. CSV, JSON, etc.).

    +
    +

    Alternatively, you may omit these headers and explicitly set the table name or format options by calling setTableName(…​) and setFormatOptions(…​).

    +
    +
    +
    +

    18.2.2. BigQuery Message Reply

    +
    +

    After the BigQueryFileMessageHandler processes a message to load data to your BigQuery table, it will respond with a Job on the reply channel. +The 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<Job> 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.

    +
    +
    + + + + + +
    + + +If you decide to use Spring Integration Gateways and you wish to receive ListenableFuture<Job> 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. +
    +
    +
    +
    +
    +

    18.3. Sample

    +
    +

    A BigQuery sample application is available.

    +
    +
    +
    +
    +
    +

    19. Cloud IAP

    +
    +
    +

    Cloud Identity-Aware Proxy (IAP) provides a security layer over applications deployed to Google Cloud.

    +
    +
    +

    The IAP starter uses Spring Security OAuth 2.0 Resource Server functionality to automatically extract user identity from the proxy-injected x-goog-iap-jwt-assertion HTTP header.

    +
    +
    +

    The following claims are validated automatically:

    +
    +
    +
      +
    • +

      Issue time

      +
    • +
    • +

      Expiration time

      +
    • +
    • +

      Issuer

      +
    • +
    • +

      Audience

      +
    • +
    +
    +
    +

    The audience ("aud" claim) validation string is automatically determined when the application is running on App Engine Standard or App Engine Flexible. +This functionality relies on Cloud Resource Manager API to retrieve project details, so the following setup is needed:

    +
    +
    +
      +
    • +

      Enable Cloud Resource Manager API in Google Cloud Console.

      +
    • +
    • +

      Make sure your application has resourcemanager.projects.get permission.

      +
    • +
    +
    +
    +

    App Engine automatic audience determination can be overridden by using spring.cloud.gcp.security.iap.audience property. It supports multiple allowable audiences by providing a comma-delimited list.

    +
    +
    +

    For Compute Engine or Kubernetes Engine spring.cloud.gcp.security.iap.audience property must be provided, as the audience string depends on the specific Backend Services setup and cannot be inferred automatically. +To determine the audience value, follow directions in IAP Verify the JWT payload guide. +If spring.cloud.gcp.security.iap.audience is not provided, the application will fail to start the following message:

    +
    +
    +
    +
    No qualifying bean of type 'com.google.cloud.spring.security.iap.AudienceProvider' available.
    +
    +
    +
    + + + + + +
    + + +If you create a custom WebSecurityConfigurerAdapter, enable extracting user identity by adding .oauth2ResourceServer().jwt() configuration to the HttpSecurity object. + If no custom WebSecurityConfigurerAdapter is present, nothing needs to be done because Spring Boot will add this customization by default. +
    +
    +
    +

    Starter Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-security-iap</artifactId>
    +</dependency>
    +
    +
    +
    +

    Starter Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-security-iap")
    +}
    +
    +
    +
    +

    19.1. Configuration

    +
    +

    The following properties are available.

    +
    +
    + + + + + +
    + + +Modifying registry, algorithm, and header properties might be useful for testing, but the defaults should not be changed in production. +
    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameDescriptionRequiredDefault

    spring.cloud.gcp.security.iap.registry

    Link to JWK public key registry.

    true

    www.gstatic.com/iap/verify/public_key-jwk

    spring.cloud.gcp.security.iap.algorithm

    Encryption algorithm used to sign the JWK token.

    true

    ES256

    spring.cloud.gcp.security.iap.header

    Header from which to extract the JWK key.

    true

    x-goog-iap-jwt-assertion

    spring.cloud.gcp.security.iap.issuer

    JWK issuer to verify.

    true

    cloud.google.com/iap

    spring.cloud.gcp.security.iap.audience

    Custom JWK audience to verify.

    false on App Engine; true on GCE/GKE

    +
    +
    +

    19.2. Sample

    +
    +

    A sample application is available.

    +
    +
    +
    +
    +
    +

    20. Cloud Vision

    +
    +
    +

    The Google Cloud Vision API allows users to leverage machine learning algorithms for processing images and documents including: image classification, face detection, text extraction, optical character recognition, and others.

    +
    +
    +

    Spring Framework on Google Cloud provides:

    +
    +
    +
      +
    • +

      A convenience starter which automatically configures authentication settings and client objects needed to begin using the Google Cloud Vision API.

      +
    • +
    • +

      CloudVisionTemplate which simplifies interactions with the Cloud Vision API.

      +
      +
        +
      • +

        Allows you to easily send images, PDF, TIFF and GIF documents to the API as Spring Resources.

        +
      • +
      • +

        Offers convenience methods for common operations, such as classifying content of an image.

        +
      • +
      +
      +
    • +
    • +

      DocumentOcrTemplate which offers convenient methods for running optical character recognition (OCR) on PDF and TIFF documents.

      +
    • +
    +
    +
    +

    20.1. Dependency Setup

    +
    +

    To begin using this library, add the spring-cloud-gcp-starter-vision artifact to your project.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +  <groupId>com.google.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-starter-vision</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +  implementation("com.google.cloud:spring-cloud-gcp-starter-vision")
    +}
    +
    +
    +
    +
    +

    20.2. Configuration

    +
    +

    The following options may be configured with Spring Framework on Google Cloud Vision libraries.

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.vision.enabled

    Enables or disables Cloud Vision autoconfiguration

    No

    true

    spring.cloud.gcp.vision.executors-threads-count

    Number of threads used during document OCR processing for waiting on long-running OCR operations

    No

    1

    spring.cloud.gcp.vision.json-output-batch-size

    Number of document pages to include in each OCR output file.

    No

    20

    +
    +

    20.2.1. Cloud Vision OCR Dependencies

    +
    +

    If you are interested in applying optical character recognition (OCR) on documents for your project, you’ll need to add both spring-cloud-gcp-starter-vision and spring-cloud-gcp-starter-storage to your dependencies. +The storage starter is necessary because the Cloud Vision API will process your documents and write OCR output files all within your Google Cloud Storage buckets.

    +
    +
    +

    Maven coordinates using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +  <groupId>com.google.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-starter-vision</artifactId>
    +</dependency>
    +<dependency>
    +  <groupId>com.google.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-starter-storage</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +  implementation("com.google.cloud:spring-cloud-gcp-starter-vision")
    +  implementation("com.google.cloud:spring-cloud-gcp-starter-storage")
    +}
    +
    +
    +
    +
    +
    +

    20.3. Image Analysis

    +
    +

    The CloudVisionTemplate allows you to easily analyze images; it provides the following method for interfacing with Cloud Vision:

    +
    +
    +

    public AnnotateImageResponse analyzeImage(Resource imageResource, Feature.Type…​ featureTypes)

    +
    +
    +

    Parameters:

    +
    +
    +
      +
    • +

      Resource imageResource refers to the Spring Resource of the image object you wish to analyze. +The Google Cloud Vision documentation provides a list of the image types that they support.

      +
    • +
    • +

      Feature.Type…​ featureTypes refers to a var-arg array of Cloud Vision Features to extract from the image. +A feature refers to a kind of image analysis one wishes to perform on an image, such as label detection, OCR recognition, facial detection, etc. +One may specify multiple features to analyze within one request. +A full list of Cloud Vision Features is provided in the Cloud Vision Feature docs.

      +
    • +
    +
    +
    +

    Returns:

    +
    +
    +
      +
    • +

      AnnotateImageResponse contains the results of all the feature analyses that were specified in the request. +For each feature type that you provide in the request, AnnotateImageResponse provides a getter method to get the result of that feature analysis. +For example, if you analyzed an image using the LABEL_DETECTION feature, you would retrieve the results from the response using annotateImageResponse.getLabelAnnotationsList().

      +
      +

      AnnotateImageResponse is provided by the Google Cloud Vision libraries; please consult the RPC reference or Javadoc for more details. +Additionally, you may consult the Cloud Vision docs to familiarize yourself with the concepts and features of the API.

      +
      +
    • +
    +
    +
    +

    20.3.1. Detect Image Labels Example

    +
    +

    Image labeling refers to producing labels that describe the contents of an image. +Below is a code sample of how this is done using the Cloud Vision Spring Template.

    +
    +
    +
    +
    @Autowired
    +private ResourceLoader resourceLoader;
    +
    +@Autowired
    +private CloudVisionTemplate cloudVisionTemplate;
    +
    +public void processImage() {
    +  Resource imageResource = this.resourceLoader.getResource("my_image.jpg");
    +  AnnotateImageResponse response = this.cloudVisionTemplate.analyzeImage(
    +      imageResource, Type.LABEL_DETECTION);
    +  System.out.println("Image Classification results: " + response.getLabelAnnotationsList());
    +}
    +
    +
    +
    +
    +
    +
    +

    20.4. File Analysis

    +
    +

    The CloudVisionTemplate allows you to easily analyze PDF, TIFF and GIF documents; it provides the following method for interfacing with Cloud Vision:

    +
    +
    +

    public AnnotateFileResponse analyzeFile(Resource fileResource, String mimeType, Feature.Type…​ featureTypes)

    +
    +
    +

    Parameters:

    +
    +
    +
      +
    • +

      Resource fileResource refers to the Spring Resource of the PDF, TIFF or GIF object you wish to analyze. +Documents with more than 5 pages are not supported.

      +
    • +
    • +

      String mimeType is the mime type of the fileResource. +Currently, only application/pdf, image/tiff and image/gif are supported.

      +
    • +
    • +

      Feature.Type…​ featureTypes refers to a var-arg array of Cloud Vision Features to extract from the document. +A feature refers to a kind of image analysis one wishes to perform on a document, such as label detection, OCR recognition, facial detection, etc. +One may specify multiple features to analyze within one request. +A full list of Cloud Vision Features is provided in the Cloud Vision Feature docs.

      +
    • +
    +
    +
    +

    Returns:

    +
    +
    +
      +
    • +

      AnnotateFileResponse contains the results of all the feature analyses that were specified in the request. +For each page of the analysed document the response will contain an AnnotateImageResponse object which you can retrieve using annotateFileResponse.getResponsesList(). +For each feature type that you provide in the request, AnnotateImageResponse provides a getter method to get the result of that feature analysis. +For example, if you analysed an PDF using the DOCUMENT_TEXT_DETECTION feature, you would retrieve the results from the response using annotateImageResponse.getFullTextAnnotation().getText().

      +
      +

      AnnotateFileResponse is provided by the Google Cloud Vision libraries; please consult the RPC reference or Javadoc for more details. +Additionally, you may consult the Cloud Vision docs to familiarize yourself with the concepts and features of the API.

      +
      +
    • +
    +
    +
    +

    20.4.1. Running Text Detection Example

    +
    +

    Detect text in files refers to extracting text from small document such as PDF or TIFF. +Below is a code sample of how this is done using the Cloud Vision Spring Template.

    +
    +
    +
    +
    @Autowired
    +private ResourceLoader resourceLoader;
    +
    +@Autowired
    +private CloudVisionTemplate cloudVisionTemplate;
    +
    +public void processPdf() {
    +  Resource imageResource = this.resourceLoader.getResource("my_file.pdf");
    +  AnnotateFileResponse response =
    +    this.cloudVisionTemplate.analyzeFile(
    +        imageResource, "application/pdf", Type.DOCUMENT_TEXT_DETECTION);
    +
    +  response
    +    .getResponsesList()
    +    .forEach(
    +        annotateImageResponse ->
    +            System.out.println(annotateImageResponse.getFullTextAnnotation().getText()));
    +}
    +
    +
    +
    +
    +
    +
    +

    20.5. Document OCR Template

    +
    +

    The DocumentOcrTemplate allows you to easily run optical character recognition (OCR) on your PDF and TIFF documents stored in your Google Storage bucket.

    +
    +
    +

    First, you will need to create a bucket in Google Cloud Storage and upload the documents you wish to process into the bucket.

    +
    +
    +

    20.5.1. Running OCR on a Document

    +
    +

    When OCR is run on a document, the Cloud Vision APIs will output a collection of OCR output files in JSON which describe the text content, bounding rectangles of words and letters, and other information about the document.

    +
    +
    +

    The DocumentOcrTemplate provides the following method for running OCR on a document saved in Google Cloud Storage:

    +
    +
    +

    ListenableFuture<DocumentOcrResultSet> 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.

    +
    +
    + + + + + +
    + + +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. +
    +
    +
    +
    +

    20.5.2. Running OCR Example

    +
    +

    Below is a code snippet of how to run OCR on a document stored in a Google Storage bucket and read the text in the first page of the document.

    +
    +
    +
    +
    @Autowired
    +private DocumentOcrTemplate documentOcrTemplate;
    +
    +public void runOcrOnDocument() {
    +    GoogleStorageLocation document = GoogleStorageLocation.forFile(
    +            "your-bucket", "test.pdf");
    +    GoogleStorageLocation outputLocationPrefix = GoogleStorageLocation.forFolder(
    +            "your-bucket", "output_folder/test.pdf/");
    +
    +    ListenableFuture<DocumentOcrResultSet> result =
    +        this.documentOcrTemplate.runOcrForDocument(
    +            document, outputLocationPrefix);
    +
    +    DocumentOcrResultSet ocrPages = result.get(5, TimeUnit.MINUTES);
    +
    +    String page1Text = ocrPages.getPage(1).getText();
    +    System.out.println(page1Text);
    +}
    +
    +
    +
    +
    +

    20.5.3. Reading OCR Output Files

    +
    +

    In some use-cases, you may need to directly read OCR output files stored in Google Cloud Storage.

    +
    +
    +

    DocumentOcrTemplate offers the following methods for reading and processing OCR output files:

    +
    +
    +
      +
    • +

      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.

      +
    • +
    • +

      readOcrOutputFile(GoogleStorageLocation jsonFile): +Reads a single OCR output file and returns the parsed contents.

      +
    • +
    +
    +
    +
    +

    20.5.4. Reading OCR Output Files Example

    +
    +

    The code snippet below describes how to read the OCR output files of a single document.

    +
    +
    +
    +
    @Autowired
    +private DocumentOcrTemplate documentOcrTemplate;
    +
    +// Parses the OCR output files corresponding to a single document in a directory
    +public void parseOutputFileSet() {
    +  GoogleStorageLocation ocrOutputPrefix = GoogleStorageLocation.forFolder(
    +      "your-bucket", "json_output_set/");
    +
    +  DocumentOcrResultSet result = this.documentOcrTemplate.readOcrOutputFileSet(ocrOutputPrefix);
    +  System.out.println("Page 2 text: " + result.getPage(2).getText());
    +}
    +
    +// Parses a single OCR output file
    +public void parseSingleOutputFile() {
    +  GoogleStorageLocation ocrOutputFile = GoogleStorageLocation.forFile(
    +      "your-bucket", "json_output_set/test_output-2-to-2.json");
    +
    +  DocumentOcrResultSet result = this.documentOcrTemplate.readOcrOutputFile(ocrOutputFile);
    +  System.out.println("Page 2 text: " + result.getPage(2).getText());
    +}
    +
    +
    +
    +
    +
    +

    20.6. Sample

    +
    +

    Samples are provided to show example usages of Spring Framework on Google Cloud with Google Cloud Vision.

    +
    +
    +
      +
    • +

      The Image Labeling Sample shows you how to use image labelling in your Spring application. +The application generates labels describing the content inside the images you specify in the application.

      +
    • +
    • +

      The Document OCR demo shows how you can apply OCR processing on your PDF/TIFF documents in order to extract their text contents.

      +
    • +
    +
    +
    +
    +
    +
    +

    21. Secret Manager

    +
    +
    +

    Google Cloud Secret Manager is a secure and convenient method for storing API keys, passwords, certificates, and other sensitive data. +A detailed summary of its features can be found in the Secret Manager documentation.

    +
    +
    +

    Spring Framework on Google Cloud provides:

    +
    +
    +
      +
    • +

      A property source which allows you to specify and load the secrets of your Google Cloud project into your application context as a Bootstrap Property Source.

      +
    • +
    • +

      A SecretManagerTemplate which allows you to read, write, and update secrets in Secret Manager.

      +
    • +
    +
    +
    +

    21.1. Dependency Setup

    +
    +

    To begin using this library, add the spring-cloud-gcp-starter-secretmanager artifact to your project.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +  <groupId>com.google.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-starter-secretmanager</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +  implementation("com.google.cloud:spring-cloud-gcp-starter-secretmanager")
    +}
    +
    +
    +
    +

    21.1.1. Configuration

    +
    +

    By default, Spring Framework on Google Cloud Secret Manager will authenticate using Application Default Credentials. +This can be overridden using the authentication properties.

    +
    +
    + + + + + +
    + + +All the below settings must be specified in a 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 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 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 Application Default Credentials.

    spring.cloud.gcp.secretmanager.allow-default-secret

    Define the behavior when accessing a non-existent secret string/bytes.
    +If set to true, null will be returned when accessing a non-existent secret; otherwise throwing an exception.

    No

    false

    +
    +
    +
    +

    21.2. Secret Manager Property Source

    +
    +

    The Spring Framework on Google Cloud integration for Google Cloud Secret Manager enables you to use Secret Manager as a bootstrap property source.

    +
    +
    +

    This allows you to specify and load secrets from Google Cloud Secret Manager as properties into the application context during the Bootstrap Phase, which refers to the initial phase when a Spring application is being loaded.

    +
    +
    +

    The Secret Manager property source uses the following syntax to specify secrets:

    +
    +
    +
    +
    # 1. Long form - specify the project ID, secret ID, and version
    +sm://projects/<project-id>/secrets/<secret-id>/versions/<version-id>}
    +
    +# 2.  Long form - specify project ID, secret ID, and use latest version
    +sm://projects/<project-id>/secrets/<secret-id>
    +
    +# 3. Short form - specify project ID, secret ID, and version
    +sm://<project-id>/<secret-id>/<version-id>
    +
    +# 4. Short form - default project; specify secret + version
    +#
    +# The project is inferred from the spring.cloud.gcp.secretmanager.project-id setting
    +# in your bootstrap.properties (see Configuration) or from application-default credentials if
    +# this is not set.
    +sm://<secret-id>/<version>
    +
    +# 5. Shortest form - specify secret ID, use default project and latest version.
    +sm://<secret-id>
    +
    +
    +
    +

    You can use this syntax in the following places:

    +
    +
    +
      +
    1. +

      In your application.properties or bootstrap.properties files:

      +
      +
      +
      # Example of the project-secret long-form syntax.
      +spring.datasource.password=${sm://projects/my-gcp-project/secrets/my-secret}
      +
      +
      +
    2. +
    3. +

      Access the value using the @Value annotation.

      +
      +
      +
      // Example of using shortest form syntax.
      +@Value("${sm://my-secret}")
      +
      +
      +
    4. +
    +
    +
    +
    +

    21.3. Secret Manager Template

    +
    +

    The SecretManagerTemplate class simplifies operations of creating, updating, and reading secrets.

    +
    +
    +

    To begin using this class, you may inject an instance of the class using @Autowired after adding the starter dependency to your project.

    +
    +
    +
    +
    @Autowired
    +private SecretManagerTemplate secretManagerTemplate;
    +
    +
    +
    +
    +

    Please consult SecretManagerOperations for information on what operations are available for the Secret Manager template.

    +
    +
    +
    +

    21.4. Refresh secrets without restarting the application

    +
    +
      +
    1. +

      Before running your application, change the project’s configuration files as follows:

      +
      +

      import the actuator starter dependency to your project,

      +
      +
      +
      +
      <dependency>
      +    <groupId>org.springframework.boot</groupId>
      +    <artifactId>spring-boot-starter-actuator</artifactId>
      +</dependency>
      +
      +
      +
      +

      add the following properties to your project’s application.properties. The latter is used to enable Spring Boot’s Config Data API.

      +
      +
      +
      +
      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.

      +
      +
      +
      +
      spring.cloud.gcp.secretmanager.legacy=false
      +
      +
      +
    2. +
    3. +

      After running the application, update your secret stored in the Secret Manager.

      +
    4. +
    5. +

      To refresh the secret, send the following command to your application sever:

      +
      +
      +
      curl -X POST http://[host]:[port]/actuator/refresh
      +
      +
      +
      +

      Note that only @ConfigurationProperties annotated with @RefreshScope support updating secrets without restarting the application.

      +
      +
    6. +
    +
    +
    +
    +

    21.5. Allow default secret

    +
    +

    By default, when accessing a non-existent secret, the Secret Manager will throw an exception.

    +
    +
    +

    However, if your want to use a default value in such a scenario, you can add the following property to project’s properties.

    +
    +
    +
    +
    `spring.cloud.gcp.secretmanager.allow-default-secret=true`
    +
    +
    +
    +

    Therefore, a variable annotated with @Value("${${sm://application-fake}:DEFAULT}") will be resolved as DEFAULT when there is no application-fake in Secret Manager and application-fake is NOT a valid application property.

    +
    +
    +
    +

    21.6. Sample

    +
    +

    A Secret Manager Sample Application is provided which demonstrates basic property source loading and usage of the template class.

    +
    +
    +
    +
    +
    +

    22. Google Cloud Key Management Service

    +
    +
    +

    The Google Cloud Key Management Service (KMS) allows you to create, import, and manage cryptographic keys and perform cryptographic operations in a single centralized cloud service.

    +
    +
    +

    Spring Framework on Google Cloud offers a utility template class KmsTemplate which allows you to conveniently encrypt and decrypt binary or text data.

    +
    +
    +

    22.1. Dependency Setup

    +
    +

    To begin using this library, add the spring-cloud-gcp-starter-kms artifact to your project.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +  <groupId>com.google.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-starter-kms</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +  implementation("com.google.cloud:spring-cloud-gcp-starter-kms")
    +}
    +
    +
    +
    +
    +

    22.2. Configuration

    +
    +

    The following options may be configured with Spring Framework on Google Cloud KMS libraries.

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.kms.enabled

    Enables or disables Google Cloud KMS autoconfiguration

    No

    true

    spring.cloud.gcp.kms.project-id

    Google Cloud project ID of the project using Cloud KMS APIs, if different from the one in the Spring Framework on Google Cloud Core Module.

    No

    Project ID is typically inferred from gcloud configuration.

    spring.cloud.gcp.kms.credentials.location

    Credentials file location for authenticating with the Cloud KMS APIs, if different from the ones in the Spring Framework on Google Cloud Core Module

    No

    Inferred from Application Default Credentials, typically set by gcloud.

    +
    +
    +

    22.3. Basic Usage

    +
    +

    Once you have added the spring-cloud-gcp-starter-kms to your project, the autoconfiguration class com.google.cloud.spring.autoconfigure.kms.GcpKmsAutoConfiguration will be activated for your project.

    +
    +
    +

    The com.google.cloud.spring.kms.KmsTemplate bean provided by the autoconfiguration is the entrypoint to using Spring Framework on Google Cloud support for Google KMS. This class allows you to specify a Cloud KMS key in your project via a URI string (format described below) and perform encryption/decryption with it.

    +
    +
    +

    The template class automatically validates the CRC32 checksums received responses from Cloud KMS APIs to verify correctness of the response.

    +
    +
    +

    22.3.1. Cloud KMS Key ID format

    +
    +

    Spring Framework on Google Cloud offers the following key syntax to specify Cloud KMS keys in your project:

    +
    +
    +
    +
     1. Shortest form - specify the key by key ring ID, and key ID.
    + The project is inferred from the spring.cloud.gcp.kms.project-id if set, otherwise
    + the default Google Cloud project (such as using application-default-credentials) is used.
    + The location is assumed to be `global`.
    +
    + {key-ring-id}/{key-id}
    +
    + 2. Short form - specify the key by location ID, key ring ID, and key ID.
    + The project is inferred from the spring.cloud.gcp.kms.project-id if set, otherwise
    + the default Google Cloud project (such as using application-default-credentials) is used.
    +
    + {location-id}/{key-ring-id}/{key-id}
    +
    + 3. Full form - specify project ID, location ID, key ring ID, and key ID
    +
    + {project-id}/{location-id}/{key-ring-id}/{key-id}
    +
    + 4. Long form - specify project ID, location ID, key ring ID, and key ID.
    + This format is equivalent to the fully-qualified resource name of a Cloud KMS key.
    +
    + projects/{project-id}/locations/{location-id}/keyRings/{key-ring-id}/cryptoKeys/{key-id}
    +
    +
    +
    +
    +
    +

    22.4. Sample

    +
    +

    A Cloud KMS Sample Application is provided which demonstrates basic encryption and decryption operations.

    +
    +
    +
    +
    +
    +

    23. Cloud Runtime Configuration API

    +
    +
    + + + + + +
    + + +The Google Cloud Runtime Configuration service is in Beta status, and is only available in snapshot and milestone versions of the project. It’s also not available in the Spring Framework on Google Cloud BOM, unlike other modules. +
    +
    +
    +

    Spring Framework on Google Cloud makes it possible to use the Google Runtime Configuration API as a Spring Cloud Config server to remotely store your application configuration data.

    +
    +
    +

    The Spring Framework on Google Cloud Config support is provided via its own Spring Boot starter. +It enables the use of the Google Runtime Configuration API as a source for Spring Boot configuration properties.

    +
    +
    +

    Maven coordinates:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-config</artifactId>
    +    <version>3.8.13</version>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    compile group: 'org.springframework.cloud',
    +    name: 'spring-cloud-gcp-starter-config',
    +    version: '3.8.13'
    +}
    +
    +
    +
    +

    23.1. Configuration

    +
    +

    The following parameters are configurable in Spring Framework on Google Cloud Config:

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.config.enabled

    Enables the Config client

    No

    false

    spring.cloud.gcp.config.name

    Name of your application

    No

    Value of the spring.application.name property. +If none, application

    spring.cloud.gcp.config.profile

    Active profile

    No

    Value of the spring.profiles.active property. +If more than a single profile, last one is chosen

    spring.cloud.gcp.config.timeout-millis

    Timeout in milliseconds for connecting to the Google Runtime Configuration API

    No

    60000

    spring.cloud.gcp.config.project-id

    Google Cloud project ID where the Google Runtime Configuration API is hosted

    No

    spring.cloud.gcp.config.credentials.location

    OAuth2 credentials for authenticating with the Google Runtime Configuration API

    No

    spring.cloud.gcp.config.credentials.encoded-key

    Base64-encoded OAuth2 credentials for authenticating with the Google Runtime Configuration API

    No

    spring.cloud.gcp.config.credentials.scopes

    OAuth2 scope for Spring Framework on Google Cloud Config credentials

    No

    www.googleapis.com/auth/cloudruntimeconfig

    +
    + + + + + +
    + + +These properties should be specified in a bootstrap.yml/bootstrap.properties file, rather than the usual applications.yml/application.properties. +
    +
    +
    + + + + + +
    + + +Core properties, as described in Spring Framework on Google Cloud Core Module, do not apply to Spring Framework on Google Cloud Config. +
    +
    +
    +
    +

    23.2. Quick start

    +
    +
      +
    1. +

      Create a configuration in the Google Runtime Configuration API that is called ${spring.application.name}_${spring.profiles.active}. +In other words, if spring.application.name is myapp and spring.profiles.active is prod, the configuration should be called myapp_prod.

      +
      +

      In order to do that, you should have the Google Cloud SDK installed, own a Google Cloud Project and run the following command:

      +
      +
      +
      +
      gcloud init # if this is your first Google Cloud SDK run.
      +gcloud beta runtime-config configs create myapp_prod
      +gcloud beta runtime-config configs variables set myapp.queue-size 25 --config-name myapp_prod
      +
      +
      +
    2. +
    3. +

      Configure your bootstrap.properties file with your application’s configuration data:

      +
      +
      +
      spring.application.name=myapp
      +spring.profiles.active=prod
      +
      +
      +
    4. +
    5. +

      Add the @ConfigurationProperties annotation to a Spring-managed bean:

      +
      +
      +
      @Component
      +@ConfigurationProperties("myapp")
      +public class SampleConfig {
      +
      +  private int queueSize;
      +
      +  public int getQueueSize() {
      +    return this.queueSize;
      +  }
      +
      +  public void setQueueSize(int queueSize) {
      +    this.queueSize = queueSize;
      +  }
      +}
      +
      +
      +
    6. +
    +
    +
    +

    When your Spring application starts, the queueSize field value will be set to 25 for the above SampleConfig bean.

    +
    +
    +
    +

    23.3. Refreshing the configuration at runtime

    +
    +

    Spring Cloud provides support to have configuration parameters be reloadable with the POST request to /actuator/refresh endpoint.

    +
    +
    +
      +
    1. +

      Add the Spring Boot Actuator dependency:

      +
      +

      Maven coordinates:

      +
      +
      +
      +
      <dependency>
      +    <groupId>org.springframework.boot</groupId>
      +    <artifactId>spring-boot-starter-actuator</artifactId>
      +</dependency>
      +
      +
      +
      +

      Gradle coordinates:

      +
      +
      +
      +
      dependencies {
      +    implementation("org.springframework.boot:spring-boot-starter-actuator")
      +}
      +
      +
      +
    2. +
    3. +

      Add @RefreshScope to your Spring configuration class to have parameters be reloadable at runtime.

      +
    4. +
    5. +

      Add management.endpoints.web.exposure.include=refresh to your application.properties to allow unrestricted access to /actuator/refresh.

      +
    6. +
    7. +

      Update a property with gcloud:

      +
      +
      +
      $ gcloud beta runtime-config configs variables set \
      +  myapp.queue_size 200 \
      +  --config-name myapp_prod
      +
      +
      +
    8. +
    9. +

      Send a POST request to the refresh endpoint:

      +
      +
      +
      $ curl -XPOST https://myapp.host.com/actuator/refresh
      +
      +
      +
    10. +
    +
    +
    +
    +

    23.4. Sample

    +
    +

    A sample application and a codelab are available.

    +
    +
    +
    +
    +
    +

    24. Cloud Foundry

    +
    +
    +

    Spring Framework on Google Cloud provides support for Cloud Foundry’s GCP Service Broker. +Our Pub/Sub, Cloud Spanner, Storage, Cloud Trace and Cloud SQL MySQL and PostgreSQL starters are Cloud Foundry aware and retrieve properties like project ID, credentials, etc., that are used in auto configuration from the Cloud Foundry environment.

    +
    +
    +

    In order to take advantage of the Cloud Foundry support make sure the following dependency is added:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-cloudfoundry</artifactId>
    +</dependency>
    +
    +
    +
    +

    In cases like Pub/Sub’s topic and subscription, or Storage’s bucket name, where those parameters are not used in auto configuration, you can fetch them using the VCAP mapping provided by Spring Boot. +For example, to retrieve the provisioned Pub/Sub topic, you can use the vcap.services.mypubsub.credentials.topic_name property from the application environment.

    +
    +
    + + + + + +
    + + +If the same service is bound to the same application more than once, the auto configuration will not be able to choose among bindings and will not be activated for that service. +This includes both MySQL and PostgreSQL bindings to the same app. +
    +
    +
    + + + + + +
    + + +In order for the Cloud SQL integration to work in Cloud Foundry, auto-reconfiguration must be disabled. +You can do so using the cf set-env <APP> JBP_CONFIG_SPRING_AUTO_RECONFIGURATION '{enabled: false}' command. +Otherwise, Cloud Foundry will produce a DataSource with an invalid JDBC URL (i.e., jdbc:mysql://null/null). +
    +
    +
    +

    24.1. User-Provided Services

    +
    +

    User-provided services enable developers to use services that are not available in the marketplace with their apps running on Cloud Foundry. +For example, you may want to use a user-provided service that points to a shared Google Service (like Cloud Spanner) used across your organization.

    +
    +
    +

    In order for Spring Framework on Google Cloud to detect your user-provided service as a Google Cloud Service, you must add an instance tag indicating the Google Cloud Service it uses. +The tag should simply be the Cloud Foundry name for the Google Service.

    +
    +
    +

    For example, if you create a user-provided service using Cloud Spanner, you might run:

    +
    +
    +
    +
    $ cf create-user-provided-service user-spanner-service -t "google-spanner" ...
    +
    +
    +
    +

    This allows Spring Framework on Google Cloud to retrieve the correct service properties from Cloud Foundry and use them in the auto configuration for your application.

    +
    +
    +

    A mapping of Google service names to Cloud Foundry names are provided below:

    +
    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Google Cloud Service

    Cloud Foundry Name (add this as a tag)

    Google Cloud Pub/Sub

    google-pubsub

    Google Cloud Storage

    google-storage

    Google Cloud Spanner

    google-spanner

    Datastore

    google-datastore

    Firestore

    google-firestore

    BigQuery

    google-bigquery

    Cloud Trace

    google-stackdriver-trace

    Cloud Sql (MySQL)

    google-cloudsql-mysql

    Cloud Sql (PostgreSQL)

    google-cloudsql-postgres

    +
    +
    +
    +
    +

    25. Kotlin Support

    +
    +
    +

    The latest version of the Spring Framework provides first-class support for Kotlin. +For Kotlin users of Spring, the Spring Framework on Google Cloud libraries work out-of-the-box and are fully interoperable with Kotlin applications.

    +
    +
    +

    For more information on building a Spring application in Kotlin, please consult the Spring Kotlin documentation.

    +
    +
    +

    25.1. Prerequisites

    +
    +

    Ensure that your Kotlin application is properly set up. +Based on your build system, you will need to include the correct Kotlin build plugin in your project:

    +
    + +
    +

    Depending on your application’s needs, you may need to augment your build configuration with compiler plugins:

    +
    +
    + +
    +
    +

    Once your Kotlin project is properly configured, the Spring Framework on Google Cloud libraries will work within your application without any additional setup.

    +
    +
    +
    +

    25.2. Sample

    +
    +

    A Kotlin sample application is provided to demonstrate a working Maven setup and various Spring Framework on Google Cloud integrations from within Kotlin.

    +
    +
    +
    +
    +
    +

    26. Configuration properties

    +
    +
    +

    To see the list of all Google Cloud related configuration properties please check the Appendix page.

    +
    +
    +
    +
    +

    27. Migration Guide from Spring Framework on Google Cloud 1.x to 2.x

    +
    +
    +

    27.1. Maven Group ID Change

    +
    +

    Spring Cloud unbundled Spring Framework on Google Cloud and other cloud providers from their release train. +To use the newly unbundled libraries, add the spring-cloud-gcp-dependencies bill of materials (BOM) and change the spring-cloud-gcp group IDs in your pom.xml files:

    +
    +
    +
    Before (pom.xml)
    +
    +
    <dependencyManagement>
    +  <dependencies>
    +    <dependency>
    +      <groupId>org.springframework.cloud</groupId>
    +      <artifactId>spring-cloud-dependencies</artifactId>
    +      <version>${spring-cloud.version}</version>
    +      <type>pom</type>
    +      <scope>import</scope>
    +    </dependency>
    +  </dependencies>
    +</dependencyManagement>
    +
    +<dependencies>
    +  <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter</artifactId>
    +  </dependency>
    +</dependencies>
    +
    +
    +
    +
    After (pom.xml)
    +
    +
    <dependencyManagement>
    +  <dependencies>
    +    <dependency>
    +      <groupId>com.google.cloud</groupId> (2)
    +      <artifactId>spring-cloud-gcp-dependencies</artifactId>
    +      <version>3.4.0</version>
    +      <type>pom</type>
    +      <scope>import</scope>
    +    </dependency>
    +  </dependencies>
    +</dependencyManagement>
    +
    +<dependencies>
    +  <dependency>
    +    <groupId>com.google.cloud</groupId> (3)
    +    <artifactId>spring-cloud-gcp-starter</artifactId>
    +  </dependency>
    +  ... (4)
    +</dependencies>
    +
    +
    +
    + + + + + + + + + + + + + + + + + +
    1Upgrade to Spring Cloud Ilford release
    2Explicitly add spring-cloud-gcp-dependencies to import the BOM
    3Change Group IDs to com.google.cloud
    4Add other starters as desired (i.e., spring-cloud-gcp-starter-pubsub or spring-cloud-gcp-starter-storage)
    +
    +
    +
    +

    27.2. Java Package Name Change

    +
    +

    All code in Spring Framework on Google Cloud has been moved from org.springframework.cloud.gcp over to the com.google.cloud.spring package.

    +
    +
    +
    +

    27.3. Deprecated Items Removed

    +
    +
    +
    Property spring.cloud.gcp.datastore.emulatorHost
    +
    +

    Use spring.cloud.gcp.datastore.host instead

    +
    +
    GcpPubSubHeaders.ACKNOWLEDGEMENT
    +
    +

    Use GcpPubSubHeaders.ORIGINAL_MESSAGE, which is of type BasicAcknowledgeablePubsubMessage

    +
    +
    SpannerQueryOptions.getQueryOptions()
    +
    +

    Use getOptions()

    +
    +
    PubSubTemplate.subscribe(String, MessageReceiver)
    +
    +

    Use subscribe(String, Consumer<BasicAcknowledgeablePubsubMessage>)

    +
    +
    SpannerReadOptions.getReadOptions()
    +
    +

    Use getOptions()

    +
    +
    Cloud Logging
    +
    +
    +
    +
    org.springframework.cloud.gcp.autoconfigure.logging package
    +
    +

    Use com.google.cloud.spring.logging from the spring-cloud-gcp-logging module.

    +
    +
    Cloud Logging Logback Appenders
    +
    +

    Replace org/springframework/cloud/gcp/autoconfigure/logging/logback[-json]-appender.xml with com/google/cloud/spring/logging/logback[-json]-appender.xml from the spring-cloud-gcp-logging module.

    +
    +
    logback-spring.xml
    +
    +
    <configuration>
    +  <include resource="com/google/cloud/spring/logging/logback-appender.xml" />
    +  ...
    +</configuration>
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/spring-integration-pubsub.html b/3.8.13/reference/html/spring-integration-pubsub.html new file mode 100644 index 0000000000..f42de6b242 --- /dev/null +++ b/3.8.13/reference/html/spring-integration-pubsub.html @@ -0,0 +1,692 @@ + + + + + + + +Channel Adapters for Cloud Pub/Sub + + + + + + + + + +
    +
    +
    + +
    +
    +

    Channel Adapters for Cloud Pub/Sub

    +
    +

    The channel adapters for Google Cloud Pub/Sub connect your Spring 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.

    +
    +
    +

    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.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-pubsub</artifactId>
    +</dependency>
    +<dependency>
    +    <groupId>org.springframework.integration</groupId>
    +    <artifactId>spring-integration-core</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-pubsub")
    +    implementation("org.springframework.integration:spring-integration-core")
    +}
    +
    +
    +
    +

    Inbound channel adapter (using Pub/Sub Streaming Pull)

    +
    +

    PubSubInboundChannelAdapter is the inbound channel adapter for Spring Framework on Google Cloud Pub/Sub that listens to a Spring Framework on Google Cloud Pub/Sub subscription for new messages. +It converts new messages to an internal Spring Message and then sends it to the bound output channel.

    +
    +
    +

    Google Pub/Sub treats message payloads as byte arrays. +So, by default, the inbound channel adapter will construct the Spring Message with byte[] as the payload. +However, you can change the desired payload type by setting the payloadType property of the PubSubInboundChannelAdapter. +The PubSubInboundChannelAdapter delegates the conversion to the desired payload type to the PubSubMessageConverter configured in the PubSubTemplate.

    +
    +
    +

    To use the inbound channel adapter, a PubSubInboundChannelAdapter must be provided and configured on the user application side.

    +
    +
    + + + + + +
    + + +The subscription name could either be a short subscription name within the current project, or the fully-qualified name referring to a subscription in a different project using the projects/[project_name]/subscriptions/[subscription_name] format. +
    +
    +
    +
    +
    @Bean
    +public MessageChannel pubsubInputChannel() {
    +    return new PublishSubscribeChannel();
    +}
    +
    +@Bean
    +public PubSubInboundChannelAdapter messageChannelAdapter(
    +    @Qualifier("pubsubInputChannel") MessageChannel inputChannel,
    +    PubSubTemplate pubsubTemplate) {
    +    PubSubInboundChannelAdapter adapter =
    +        new PubSubInboundChannelAdapter(pubsubTemplate, "subscriptionName");
    +    adapter.setOutputChannel(inputChannel);
    +    adapter.setAckMode(AckMode.MANUAL);
    +
    +    return adapter;
    +}
    +
    +
    +
    +
    +

    In the example, we first specify the MessageChannel where the adapter is going to write incoming messages to. +The MessageChannel implementation isn’t important here. +Depending on your use case, you might want to use a MessageChannel other than PublishSubscribeChannel.

    +
    +
    +

    Then, we declare a PubSubInboundChannelAdapter bean. +It requires the channel we just created and a SubscriberFactory, which creates Subscriber objects from the Google Cloud Java Client for Pub/Sub. +The Spring Boot starter for Spring Framework on Google Cloud Pub/Sub provides a configured PubSubSubscriberOperations object.

    +
    +
    +
    Acknowledging messages and handling failures
    +
    +

    When working with Cloud Pub/Sub, it is important to understand the concept of ackDeadline — the amount of time Cloud Pub/Sub will wait until attempting redelivery of an outstanding message. +Each subscription has a default ackDeadline applied to all messages sent to it. +Additionally, the Cloud Pub/Sub client library can extend each streamed message’s ackDeadline until the message processing completes, fails or until the maximum extension period elapses.

    +
    +
    + + + + + +
    + + +In the Pub/Sub client library, default maximum extension period is an hour. However, Spring Framework on Google Cloud disables this auto-extension behavior. +Use the spring.cloud.gcp.pubsub.subscriber.max-ack-extension-period property to re-enable it. +
    +
    +
    +

    Acknowledging (acking) a message removes it from Pub/Sub’s known outstanding messages. Nacking a message resets its acknowledgement deadline to 0, forcing immediate redelivery. +This could be useful in a load balanced architecture, where one of the subscribers is having issues but others are available to process messages.

    +
    +
    +

    The PubSubInboundChannelAdapter supports three acknowledgement modes: the default AckMode.AUTO (automatic acking on processing success and nacking on exception), as well as two modes for additional manual control: AckMode.AUTO_ACK (automatic acking on success but no action on exception) and AckMode.MANUAL (no automatic actions at all; both acking and nacking have to be done manually).

    +
    + + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Table 1. Acknowledgement mode behavior
    AUTOAUTO_ACKMANUAL

    Message processing completes successfully

    ack, no redelivery

    ack, no redelivery

    <no action>*

    Message processing fails, but error handler completes successfully**

    ack, no redelivery

    ack, no redelivery

    <no action>*

    Message processing fails; no error handler present

    nack, immediate redelivery

    <no action>*

    <no action>*

    Message processing fails, and error handler throws an exception

    nack, immediate redelivery

    <no action>*

    <no action>*

    +
    +

    * <no action> means that the message will be neither acked nor nacked. +Cloud Pub/Sub will attempt redelivery according to subscription ackDeadline setting and the max-ack-extension-period client library setting.

    +
    +
    +

    ** For the adapter, "success" means the Spring Integration flow processed without raising an exception, so successful message processing and the successful completion of an error handler both result in the same behavior (message will be acknowledged). +To trigger default error behavior (nacking in AUTO mode; neither acking nor nacking in AUTO_ACK mode), propagate the error back to the adapter by throwing an exception from the Error Handling flow.

    +
    +
    +
    Manual acking/nacking
    +
    +

    The adapter attaches a BasicAcknowledgeablePubsubMessage object to the Message headers. +Users can extract the BasicAcknowledgeablePubsubMessage using the GcpPubSubHeaders.ORIGINAL_MESSAGE key and use it to ack (or nack) a message.

    +
    +
    +
    +
    @Bean
    +@ServiceActivator(inputChannel = "pubsubInputChannel")
    +public MessageHandler messageReceiver() {
    +    return message -> {
    +        LOGGER.info("Message arrived! Payload: " + new String((byte[]) message.getPayload()));
    +        BasicAcknowledgeablePubsubMessage originalMessage =
    +              message.getHeaders().get(GcpPubSubHeaders.ORIGINAL_MESSAGE, BasicAcknowledgeablePubsubMessage.class);
    +        originalMessage.ack();
    +    };
    +}
    +
    +
    +
    +
    +
    +
    Error Handling
    +
    +

    If you want to have more control over message processing in case of an error, you need to associate the PubSubInboundChannelAdapter with a Spring Integration error channel and specify the behavior to be invoked with @ServiceActivator.

    +
    +
    + + + + + +
    + + +In order to activate the default behavior (nacking in AUTO mode; neither acking nor nacking in AUTO_ACK mode), your error handler has to throw an exception. +Otherwise, the adapter will assume that processing completed successfully and will ack the message. +
    +
    +
    +
    +
    @Bean
    +public MessageChannel pubsubInputChannel() {
    +    return new PublishSubscribeChannel();
    +}
    +
    +@Bean
    +public PubSubInboundChannelAdapter messageChannelAdapter(
    +    @Qualifier("pubsubInputChannel") MessageChannel inputChannel,
    +    PubSubTemplate pubsubTemplate) {
    +    PubSubInboundChannelAdapter adapter =
    +        new PubSubInboundChannelAdapter(pubsubTemplate, "subscriptionName");
    +    adapter.setOutputChannel(inputChannel);
    +    adapter.setAckMode(AckMode.AUTO_ACK);
    +    adapter.setErrorChannelName("pubsubErrors");
    +
    +    return adapter;
    +}
    +
    +@ServiceActivator(inputChannel =  "pubsubErrors")
    +public void pubsubErrorHandler(Message<MessagingException> message) {
    +	LOGGER.warn("This message will be automatically acked because error handler completes successfully");
    +}
    +
    +
    +
    +
    +

    If you would prefer to manually ack or nack the message, you can do it by retrieving the header of the exception payload:

    +
    +
    +
    +
    @ServiceActivator(inputChannel =  "pubsubErrors")
    +public void pubsubErrorHandler(Message<MessagingException> exceptionMessage) {
    +
    +	BasicAcknowledgeablePubsubMessage originalMessage =
    +	  (BasicAcknowledgeablePubsubMessage)exceptionMessage.getPayload().getFailedMessage()
    +	    .getHeaders().get(GcpPubSubHeaders.ORIGINAL_MESSAGE);
    +
    +	originalMessage.nack();
    +}
    +
    +
    +
    +
    +
    +
    +
    +

    Pollable Message Source (using Pub/Sub Synchronous Pull)

    +
    +

    While PubSubInboundChannelAdapter, through the underlying Asynchronous Pull Pub/Sub mechanism, provides the best performance for high-volume applications that receive a steady flow of messages, it can create load balancing anomalies due to message caching. +This behavior is most obvious when publishing a large batch of small messages that take a long time to process individually. +It manifests as one subscriber taking up most messages, even if multiple subscribers are available to take on the work. +For a more detailed explanation of this scenario, see Spring Framework on Google Cloud Pub/Sub documentation.

    +
    +
    +

    In such a scenario, a PubSubMessageSource can help spread the load between different subscribers more evenly.

    +
    +
    +

    As with the Inbound Channel Adapter, the message source has a configurable acknowledgement mode, payload type, and header mapping.

    +
    +
    +

    The default behavior is to return from the synchronous pull operation immediately if no messages are present. +This can be overridden by using setBlockOnPull() method to wait for at least one message to arrive.

    +
    +
    +

    By default, PubSubMessageSource pulls from the subscription one message at a time. +To pull a batch of messages on each request, use the setMaxFetchSize() method to set the batch size.

    +
    +
    + + + + + +
    + + +The subscription name could either be a short subscription name within the current project, or the fully-qualified name referring to a subscription in a different project using the projects/[project_name]/subscriptions/[subscription_name] format. +
    +
    +
    +
    +
    @Bean
    +@InboundChannelAdapter(channel = "pubsubInputChannel", poller = @Poller(fixedDelay = "100"))
    +public MessageSource<Object> pubsubAdapter(PubSubTemplate pubSubTemplate) {
    +	PubSubMessageSource messageSource = new PubSubMessageSource(pubSubTemplate,  "exampleSubscription");
    +	messageSource.setAckMode(AckMode.MANUAL);
    +	messageSource.setPayloadType(String.class);
    +	messageSource.setBlockOnPull(true);
    +	messageSource.setMaxFetchSize(100);
    +	return messageSource;
    +}
    +
    +
    +
    +
    +

    The @InboundChannelAdapter annotation above ensures that the configured MessageSource is polled for messages, which are then available for manipulation with any Spring Integration mechanism on the pubsubInputChannel message channel. +For example, messages can be retrieved in a method annotated with @ServiceActivator, as seen below.

    +
    +
    +

    For additional flexibility, PubSubMessageSource attaches an AcknowledgeablePubSubMessage object to the GcpPubSubHeaders.ORIGINAL_MESSAGE message header. +The object can be used for manually (n)acking the message.

    +
    +
    +
    +
    @ServiceActivator(inputChannel = "pubsubInputChannel")
    +public void messageReceiver(String payload,
    +        @Header(GcpPubSubHeaders.ORIGINAL_MESSAGE) AcknowledgeablePubsubMessage message)
    +            throws InterruptedException {
    +    LOGGER.info("Message arrived by Synchronous Pull! Payload: " + payload);
    +    message.ack();
    +}
    +
    +
    +
    +
    + + + + + +
    + + +AcknowledgeablePubSubMessage objects acquired by synchronous pull are aware of their own acknowledgement IDs. +Streaming pull does not expose this information due to limitations of the underlying API, and returns BasicAcknowledgeablePubsubMessage objects that allow acking/nacking individual messages, but not extracting acknowledgement IDs for future processing. +
    +
    +
    +
    +

    Outbound channel adapter

    +
    +

    PubSubMessageHandler is the outbound channel adapter for Spring Framework on Google Cloud Pub/Sub that listens for new messages on a Spring MessageChannel. +It uses PubSubTemplate to post them to a Spring Framework on Google Cloud Pub/Sub topic.

    +
    +
    +

    To construct a Pub/Sub representation of the message, the outbound channel adapter needs to convert the Spring Message payload to a byte array representation expected by Pub/Sub. +It delegates this conversion to the PubSubTemplate. +To customize the conversion, you can specify a PubSubMessageConverter in the PubSubTemplate that should convert the Object payload and headers of the Spring Message to a PubsubMessage.

    +
    +
    +

    To use the outbound channel adapter, a PubSubMessageHandler bean must be provided and configured on the user application side.

    +
    +
    + + + + + +
    + + +The topic name could either be a short topic name within the current project, or the fully-qualified name referring to a topic in a different project using the projects/[project_name]/topics/[topic_name] format. +
    +
    +
    +
    +
    @Bean
    +@ServiceActivator(inputChannel = "pubsubOutputChannel")
    +public MessageHandler messageSender(PubSubTemplate pubsubTemplate) {
    +    return new PubSubMessageHandler(pubsubTemplate, "topicName");
    +}
    +
    +
    +
    +
    +

    The provided PubSubTemplate contains all the necessary configuration to publish messages to a Spring Framework on Google Cloud Pub/Sub topic.

    +
    +
    +

    PubSubMessageHandler publishes messages asynchronously by default. +A publish timeout can be configured for synchronous publishing. +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.

    +
    +
    +
    +
    adapter.setPublishCallback(
    +    new ListenableFutureCallback<String>() {
    +      @Override
    +      public void onFailure(Throwable ex) {}
    +
    +      @Override
    +      public void onSuccess(String result) {}
    +    });
    +
    +
    +
    +
    +

    To override the default topic you can use the GcpPubSubHeaders.TOPIC header.

    +
    +
    +
    +
    @Autowired
    +private MessageChannel pubsubOutputChannel;
    +
    +public void handleMessage(Message<?> msg) throws MessagingException {
    +    final Message<?> message = MessageBuilder
    +        .withPayload(msg.getPayload())
    +        .setHeader(GcpPubSubHeaders.TOPIC, "customTopic").build();
    +    pubsubOutputChannel.send(message);
    +}
    +
    +
    +
    +
    +

    It is also possible to set an SpEL expression for the topic with the setTopicExpression() or setTopicExpressionString() methods.

    +
    +
    +
    +
    PubSubMessageHandler adapter = new PubSubMessageHandler(pubSubTemplate, "myDefaultTopic");
    +adapter.setTopicExpressionString("headers['sendToTopic']");
    +
    +
    +
    +
    +
    +

    Header mapping

    +
    +

    These channel adapters contain header mappers that allow you to map, or filter out, headers from Spring to Google Cloud Pub/Sub messages, and vice-versa. +By default, the inbound channel adapter maps every header on the Google Cloud Pub/Sub messages to the Spring messages produced by the adapter. +The outbound channel adapter maps every header from Spring messages into Google Cloud Pub/Sub ones, except the ones added by Spring and some special headers, like headers with key "id", "timestamp", "gcp_pubsub_acknowledgement", and "gcp_pubsub_ordering_key". +In the process, the outbound mapper also converts the value of the headers into string.

    +
    +
    +

    Note that you can provide the GcpPubSubHeaders.ORDERING_KEY ("gcp_pubsub_ordering_key") header, which will be automatically mapped to PubsubMessage.orderingKey property, and excluded from the headers in the published message. +Remember to set spring.cloud.gcp.pubsub.publisher.enable-message-ordering to true, if you are publishing messages with this header.

    +
    +
    +

    Each adapter declares a setHeaderMapper() method to let you further customize which headers you want to map from Spring to Google Cloud Pub/Sub, and vice-versa.

    +
    +
    +

    For example, to filter out headers "foo", "bar" and all headers starting with the prefix "prefix_", you can use setHeaderMapper() along with the PubSubHeaderMapper implementation provided by this module.

    +
    +
    +
    +
    PubSubMessageHandler adapter = ...
    +...
    +PubSubHeaderMapper headerMapper = new PubSubHeaderMapper();
    +headerMapper.setOutboundHeaderPatterns("!foo", "!bar", "!prefix_*", "*");
    +adapter.setHeaderMapper(headerMapper);
    +
    +
    +
    +
    + + + + + +
    + + +The order in which the patterns are declared in PubSubHeaderMapper.setOutboundHeaderPatterns() and PubSubHeaderMapper.setInboundHeaderPatterns() matters. +The first patterns have precedence over the following ones. +
    +
    +
    +

    In the previous example, the "*" pattern means every header is mapped. +However, because it comes last in the list, the previous patterns take precedence.

    +
    +
    + +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/spring-integration-storage.html b/3.8.13/reference/html/spring-integration-storage.html new file mode 100644 index 0000000000..4c334ebc5a --- /dev/null +++ b/3.8.13/reference/html/spring-integration-storage.html @@ -0,0 +1,316 @@ + + + + + + + +Channel Adapters for Google Cloud Storage + + + + + + + + + +
    +
    +
    + +
    +
    +

    Channel Adapters for Google Cloud Storage

    +
    +

    The channel adapters for Google Cloud Storage allow you to read and write files to Google Cloud Storage through MessageChannels.

    +
    +
    +

    Spring Framework on Google Cloud provides two inbound adapters, GcsInboundFileSynchronizingMessageSource and GcsStreamingMessageSource, and one outbound adapter, GcsMessageHandler.

    +
    +
    +

    The Spring Integration Channel Adapters for Google Cloud Storage are included in the spring-cloud-gcp-storage module.

    +
    +
    +

    To use the Storage portion of Spring Integration for Spring Framework on Google Cloud, you must also provide the spring-integration-file dependency, since it isn’t pulled transitively.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-storage</artifactId>
    +</dependency>
    +<dependency>
    +    <groupId>org.springframework.integration</groupId>
    +    <artifactId>spring-integration-file</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-storage")
    +    implementation("org.springframework.integration:spring-integration-file")
    +}
    +
    +
    +
    +

    Inbound channel adapter

    +
    +

    The Google Cloud Storage inbound channel adapter polls a Google Cloud Storage bucket for new files and sends each of them in a Message payload to the MessageChannel specified in the @InboundChannelAdapter annotation. +The files are temporarily stored in a folder in the local file system.

    +
    +
    +

    Here is an example of how to configure a Google Cloud Storage inbound channel adapter.

    +
    +
    +
    +
    @Bean
    +@InboundChannelAdapter(channel = "new-file-channel", poller = @Poller(fixedDelay = "5000"))
    +public MessageSource<File> synchronizerAdapter(Storage gcs) {
    +  GcsInboundFileSynchronizer synchronizer = new GcsInboundFileSynchronizer(gcs);
    +  synchronizer.setRemoteDirectory("your-gcs-bucket");
    +
    +  GcsInboundFileSynchronizingMessageSource synchAdapter =
    +          new GcsInboundFileSynchronizingMessageSource(synchronizer);
    +  synchAdapter.setLocalDirectory(new File("local-directory"));
    +
    +  return synchAdapter;
    +}
    +
    +
    +
    +
    +
    +

    Inbound streaming channel adapter

    +
    +

    The inbound streaming channel adapter is similar to the normal inbound channel adapter, except it does not require files to be stored in the file system.

    +
    +
    +

    Here is an example of how to configure a Google Cloud Storage inbound streaming channel adapter.

    +
    +
    +
    +
    @Bean
    +@InboundChannelAdapter(channel = "streaming-channel", poller = @Poller(fixedDelay = "5000"))
    +public MessageSource<InputStream> streamingAdapter(Storage gcs) {
    +  GcsStreamingMessageSource adapter =
    +          new GcsStreamingMessageSource(new GcsRemoteFileTemplate(new GcsSessionFactory(gcs)));
    +  adapter.setRemoteDirectory("your-gcs-bucket");
    +  return adapter;
    +}
    +
    +
    +
    +
    +

    If you would like to process the files in your bucket in a specific order, you may pass in a Comparator<BlobInfo> to the constructor GcsStreamingMessageSource to sort the files being processed.

    +
    +
    +
    +

    Outbound channel adapter

    +
    +

    The outbound channel adapter allows files to be written to Google Cloud Storage. +When it receives a Message containing a payload of type File, it writes that file to the Google Cloud Storage bucket specified in the adapter.

    +
    +
    +

    Here is an example of how to configure a Google Cloud Storage outbound channel adapter.

    +
    +
    +
    +
    @Bean
    +@ServiceActivator(inputChannel = "writeFiles")
    +public MessageHandler outboundChannelAdapter(Storage gcs) {
    +  GcsMessageHandler outboundChannelAdapter = new GcsMessageHandler(new GcsSessionFactory(gcs));
    +  outboundChannelAdapter.setRemoteDirectoryExpression(new ValueExpression<>("your-gcs-bucket"));
    +
    +  return outboundChannelAdapter;
    +}
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/spring-integration.html b/3.8.13/reference/html/spring-integration.html new file mode 100644 index 0000000000..4de99dcb04 --- /dev/null +++ b/3.8.13/reference/html/spring-integration.html @@ -0,0 +1,827 @@ + + + + + + + +Spring Integration + + + + + + + + + +
    +
    +
    + +
    +
    +

    Spring Integration

    +
    +
    +

    Spring Framework on Google Cloud provides Spring Integration adapters that allow your applications to use Enterprise Integration Patterns backed up by Google Cloud services.

    +
    +
    +

    Channel Adapters for Cloud Pub/Sub

    +
    +

    The channel adapters for Google Cloud Pub/Sub connect your Spring 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.

    +
    +
    +

    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.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-pubsub</artifactId>
    +</dependency>
    +<dependency>
    +    <groupId>org.springframework.integration</groupId>
    +    <artifactId>spring-integration-core</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-pubsub")
    +    implementation("org.springframework.integration:spring-integration-core")
    +}
    +
    +
    +
    +

    Inbound channel adapter (using Pub/Sub Streaming Pull)

    +
    +

    PubSubInboundChannelAdapter is the inbound channel adapter for Spring Framework on Google Cloud Pub/Sub that listens to a Spring Framework on Google Cloud Pub/Sub subscription for new messages. +It converts new messages to an internal Spring Message and then sends it to the bound output channel.

    +
    +
    +

    Google Pub/Sub treats message payloads as byte arrays. +So, by default, the inbound channel adapter will construct the Spring Message with byte[] as the payload. +However, you can change the desired payload type by setting the payloadType property of the PubSubInboundChannelAdapter. +The PubSubInboundChannelAdapter delegates the conversion to the desired payload type to the PubSubMessageConverter configured in the PubSubTemplate.

    +
    +
    +

    To use the inbound channel adapter, a PubSubInboundChannelAdapter must be provided and configured on the user application side.

    +
    +
    + + + + + +
    + + +The subscription name could either be a short subscription name within the current project, or the fully-qualified name referring to a subscription in a different project using the projects/[project_name]/subscriptions/[subscription_name] format. +
    +
    +
    +
    +
    @Bean
    +public MessageChannel pubsubInputChannel() {
    +    return new PublishSubscribeChannel();
    +}
    +
    +@Bean
    +public PubSubInboundChannelAdapter messageChannelAdapter(
    +    @Qualifier("pubsubInputChannel") MessageChannel inputChannel,
    +    PubSubTemplate pubsubTemplate) {
    +    PubSubInboundChannelAdapter adapter =
    +        new PubSubInboundChannelAdapter(pubsubTemplate, "subscriptionName");
    +    adapter.setOutputChannel(inputChannel);
    +    adapter.setAckMode(AckMode.MANUAL);
    +
    +    return adapter;
    +}
    +
    +
    +
    +
    +

    In the example, we first specify the MessageChannel where the adapter is going to write incoming messages to. +The MessageChannel implementation isn’t important here. +Depending on your use case, you might want to use a MessageChannel other than PublishSubscribeChannel.

    +
    +
    +

    Then, we declare a PubSubInboundChannelAdapter bean. +It requires the channel we just created and a SubscriberFactory, which creates Subscriber objects from the Google Cloud Java Client for Pub/Sub. +The Spring Boot starter for Spring Framework on Google Cloud Pub/Sub provides a configured PubSubSubscriberOperations object.

    +
    +
    +
    Acknowledging messages and handling failures
    +
    +

    When working with Cloud Pub/Sub, it is important to understand the concept of ackDeadline — the amount of time Cloud Pub/Sub will wait until attempting redelivery of an outstanding message. +Each subscription has a default ackDeadline applied to all messages sent to it. +Additionally, the Cloud Pub/Sub client library can extend each streamed message’s ackDeadline until the message processing completes, fails or until the maximum extension period elapses.

    +
    +
    + + + + + +
    + + +In the Pub/Sub client library, default maximum extension period is an hour. However, Spring Framework on Google Cloud disables this auto-extension behavior. +Use the spring.cloud.gcp.pubsub.subscriber.max-ack-extension-period property to re-enable it. +
    +
    +
    +

    Acknowledging (acking) a message removes it from Pub/Sub’s known outstanding messages. Nacking a message resets its acknowledgement deadline to 0, forcing immediate redelivery. +This could be useful in a load balanced architecture, where one of the subscribers is having issues but others are available to process messages.

    +
    +
    +

    The PubSubInboundChannelAdapter supports three acknowledgement modes: the default AckMode.AUTO (automatic acking on processing success and nacking on exception), as well as two modes for additional manual control: AckMode.AUTO_ACK (automatic acking on success but no action on exception) and AckMode.MANUAL (no automatic actions at all; both acking and nacking have to be done manually).

    +
    + + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Table 1. Acknowledgement mode behavior
    AUTOAUTO_ACKMANUAL

    Message processing completes successfully

    ack, no redelivery

    ack, no redelivery

    <no action>*

    Message processing fails, but error handler completes successfully**

    ack, no redelivery

    ack, no redelivery

    <no action>*

    Message processing fails; no error handler present

    nack, immediate redelivery

    <no action>*

    <no action>*

    Message processing fails, and error handler throws an exception

    nack, immediate redelivery

    <no action>*

    <no action>*

    +
    +

    * <no action> means that the message will be neither acked nor nacked. +Cloud Pub/Sub will attempt redelivery according to subscription ackDeadline setting and the max-ack-extension-period client library setting.

    +
    +
    +

    ** For the adapter, "success" means the Spring Integration flow processed without raising an exception, so successful message processing and the successful completion of an error handler both result in the same behavior (message will be acknowledged). +To trigger default error behavior (nacking in AUTO mode; neither acking nor nacking in AUTO_ACK mode), propagate the error back to the adapter by throwing an exception from the Error Handling flow.

    +
    +
    +
    Manual acking/nacking
    +
    +

    The adapter attaches a BasicAcknowledgeablePubsubMessage object to the Message headers. +Users can extract the BasicAcknowledgeablePubsubMessage using the GcpPubSubHeaders.ORIGINAL_MESSAGE key and use it to ack (or nack) a message.

    +
    +
    +
    +
    @Bean
    +@ServiceActivator(inputChannel = "pubsubInputChannel")
    +public MessageHandler messageReceiver() {
    +    return message -> {
    +        LOGGER.info("Message arrived! Payload: " + new String((byte[]) message.getPayload()));
    +        BasicAcknowledgeablePubsubMessage originalMessage =
    +              message.getHeaders().get(GcpPubSubHeaders.ORIGINAL_MESSAGE, BasicAcknowledgeablePubsubMessage.class);
    +        originalMessage.ack();
    +    };
    +}
    +
    +
    +
    +
    +
    +
    Error Handling
    +
    +

    If you want to have more control over message processing in case of an error, you need to associate the PubSubInboundChannelAdapter with a Spring Integration error channel and specify the behavior to be invoked with @ServiceActivator.

    +
    +
    + + + + + +
    + + +In order to activate the default behavior (nacking in AUTO mode; neither acking nor nacking in AUTO_ACK mode), your error handler has to throw an exception. +Otherwise, the adapter will assume that processing completed successfully and will ack the message. +
    +
    +
    +
    +
    @Bean
    +public MessageChannel pubsubInputChannel() {
    +    return new PublishSubscribeChannel();
    +}
    +
    +@Bean
    +public PubSubInboundChannelAdapter messageChannelAdapter(
    +    @Qualifier("pubsubInputChannel") MessageChannel inputChannel,
    +    PubSubTemplate pubsubTemplate) {
    +    PubSubInboundChannelAdapter adapter =
    +        new PubSubInboundChannelAdapter(pubsubTemplate, "subscriptionName");
    +    adapter.setOutputChannel(inputChannel);
    +    adapter.setAckMode(AckMode.AUTO_ACK);
    +    adapter.setErrorChannelName("pubsubErrors");
    +
    +    return adapter;
    +}
    +
    +@ServiceActivator(inputChannel =  "pubsubErrors")
    +public void pubsubErrorHandler(Message<MessagingException> message) {
    +	LOGGER.warn("This message will be automatically acked because error handler completes successfully");
    +}
    +
    +
    +
    +
    +

    If you would prefer to manually ack or nack the message, you can do it by retrieving the header of the exception payload:

    +
    +
    +
    +
    @ServiceActivator(inputChannel =  "pubsubErrors")
    +public void pubsubErrorHandler(Message<MessagingException> exceptionMessage) {
    +
    +	BasicAcknowledgeablePubsubMessage originalMessage =
    +	  (BasicAcknowledgeablePubsubMessage)exceptionMessage.getPayload().getFailedMessage()
    +	    .getHeaders().get(GcpPubSubHeaders.ORIGINAL_MESSAGE);
    +
    +	originalMessage.nack();
    +}
    +
    +
    +
    +
    +
    +
    +
    +

    Pollable Message Source (using Pub/Sub Synchronous Pull)

    +
    +

    While PubSubInboundChannelAdapter, through the underlying Asynchronous Pull Pub/Sub mechanism, provides the best performance for high-volume applications that receive a steady flow of messages, it can create load balancing anomalies due to message caching. +This behavior is most obvious when publishing a large batch of small messages that take a long time to process individually. +It manifests as one subscriber taking up most messages, even if multiple subscribers are available to take on the work. +For a more detailed explanation of this scenario, see Spring Framework on Google Cloud Pub/Sub documentation.

    +
    +
    +

    In such a scenario, a PubSubMessageSource can help spread the load between different subscribers more evenly.

    +
    +
    +

    As with the Inbound Channel Adapter, the message source has a configurable acknowledgement mode, payload type, and header mapping.

    +
    +
    +

    The default behavior is to return from the synchronous pull operation immediately if no messages are present. +This can be overridden by using setBlockOnPull() method to wait for at least one message to arrive.

    +
    +
    +

    By default, PubSubMessageSource pulls from the subscription one message at a time. +To pull a batch of messages on each request, use the setMaxFetchSize() method to set the batch size.

    +
    +
    + + + + + +
    + + +The subscription name could either be a short subscription name within the current project, or the fully-qualified name referring to a subscription in a different project using the projects/[project_name]/subscriptions/[subscription_name] format. +
    +
    +
    +
    +
    @Bean
    +@InboundChannelAdapter(channel = "pubsubInputChannel", poller = @Poller(fixedDelay = "100"))
    +public MessageSource<Object> pubsubAdapter(PubSubTemplate pubSubTemplate) {
    +	PubSubMessageSource messageSource = new PubSubMessageSource(pubSubTemplate,  "exampleSubscription");
    +	messageSource.setAckMode(AckMode.MANUAL);
    +	messageSource.setPayloadType(String.class);
    +	messageSource.setBlockOnPull(true);
    +	messageSource.setMaxFetchSize(100);
    +	return messageSource;
    +}
    +
    +
    +
    +
    +

    The @InboundChannelAdapter annotation above ensures that the configured MessageSource is polled for messages, which are then available for manipulation with any Spring Integration mechanism on the pubsubInputChannel message channel. +For example, messages can be retrieved in a method annotated with @ServiceActivator, as seen below.

    +
    +
    +

    For additional flexibility, PubSubMessageSource attaches an AcknowledgeablePubSubMessage object to the GcpPubSubHeaders.ORIGINAL_MESSAGE message header. +The object can be used for manually (n)acking the message.

    +
    +
    +
    +
    @ServiceActivator(inputChannel = "pubsubInputChannel")
    +public void messageReceiver(String payload,
    +        @Header(GcpPubSubHeaders.ORIGINAL_MESSAGE) AcknowledgeablePubsubMessage message)
    +            throws InterruptedException {
    +    LOGGER.info("Message arrived by Synchronous Pull! Payload: " + payload);
    +    message.ack();
    +}
    +
    +
    +
    +
    + + + + + +
    + + +AcknowledgeablePubSubMessage objects acquired by synchronous pull are aware of their own acknowledgement IDs. +Streaming pull does not expose this information due to limitations of the underlying API, and returns BasicAcknowledgeablePubsubMessage objects that allow acking/nacking individual messages, but not extracting acknowledgement IDs for future processing. +
    +
    +
    +
    +

    Outbound channel adapter

    +
    +

    PubSubMessageHandler is the outbound channel adapter for Spring Framework on Google Cloud Pub/Sub that listens for new messages on a Spring MessageChannel. +It uses PubSubTemplate to post them to a Spring Framework on Google Cloud Pub/Sub topic.

    +
    +
    +

    To construct a Pub/Sub representation of the message, the outbound channel adapter needs to convert the Spring Message payload to a byte array representation expected by Pub/Sub. +It delegates this conversion to the PubSubTemplate. +To customize the conversion, you can specify a PubSubMessageConverter in the PubSubTemplate that should convert the Object payload and headers of the Spring Message to a PubsubMessage.

    +
    +
    +

    To use the outbound channel adapter, a PubSubMessageHandler bean must be provided and configured on the user application side.

    +
    +
    + + + + + +
    + + +The topic name could either be a short topic name within the current project, or the fully-qualified name referring to a topic in a different project using the projects/[project_name]/topics/[topic_name] format. +
    +
    +
    +
    +
    @Bean
    +@ServiceActivator(inputChannel = "pubsubOutputChannel")
    +public MessageHandler messageSender(PubSubTemplate pubsubTemplate) {
    +    return new PubSubMessageHandler(pubsubTemplate, "topicName");
    +}
    +
    +
    +
    +
    +

    The provided PubSubTemplate contains all the necessary configuration to publish messages to a Spring Framework on Google Cloud Pub/Sub topic.

    +
    +
    +

    PubSubMessageHandler publishes messages asynchronously by default. +A publish timeout can be configured for synchronous publishing. +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.

    +
    +
    +
    +
    adapter.setPublishCallback(
    +    new ListenableFutureCallback<String>() {
    +      @Override
    +      public void onFailure(Throwable ex) {}
    +
    +      @Override
    +      public void onSuccess(String result) {}
    +    });
    +
    +
    +
    +
    +

    To override the default topic you can use the GcpPubSubHeaders.TOPIC header.

    +
    +
    +
    +
    @Autowired
    +private MessageChannel pubsubOutputChannel;
    +
    +public void handleMessage(Message<?> msg) throws MessagingException {
    +    final Message<?> message = MessageBuilder
    +        .withPayload(msg.getPayload())
    +        .setHeader(GcpPubSubHeaders.TOPIC, "customTopic").build();
    +    pubsubOutputChannel.send(message);
    +}
    +
    +
    +
    +
    +

    It is also possible to set an SpEL expression for the topic with the setTopicExpression() or setTopicExpressionString() methods.

    +
    +
    +
    +
    PubSubMessageHandler adapter = new PubSubMessageHandler(pubSubTemplate, "myDefaultTopic");
    +adapter.setTopicExpressionString("headers['sendToTopic']");
    +
    +
    +
    +
    +
    +

    Header mapping

    +
    +

    These channel adapters contain header mappers that allow you to map, or filter out, headers from Spring to Google Cloud Pub/Sub messages, and vice-versa. +By default, the inbound channel adapter maps every header on the Google Cloud Pub/Sub messages to the Spring messages produced by the adapter. +The outbound channel adapter maps every header from Spring messages into Google Cloud Pub/Sub ones, except the ones added by Spring and some special headers, like headers with key "id", "timestamp", "gcp_pubsub_acknowledgement", and "gcp_pubsub_ordering_key". +In the process, the outbound mapper also converts the value of the headers into string.

    +
    +
    +

    Note that you can provide the GcpPubSubHeaders.ORDERING_KEY ("gcp_pubsub_ordering_key") header, which will be automatically mapped to PubsubMessage.orderingKey property, and excluded from the headers in the published message. +Remember to set spring.cloud.gcp.pubsub.publisher.enable-message-ordering to true, if you are publishing messages with this header.

    +
    +
    +

    Each adapter declares a setHeaderMapper() method to let you further customize which headers you want to map from Spring to Google Cloud Pub/Sub, and vice-versa.

    +
    +
    +

    For example, to filter out headers "foo", "bar" and all headers starting with the prefix "prefix_", you can use setHeaderMapper() along with the PubSubHeaderMapper implementation provided by this module.

    +
    +
    +
    +
    PubSubMessageHandler adapter = ...
    +...
    +PubSubHeaderMapper headerMapper = new PubSubHeaderMapper();
    +headerMapper.setOutboundHeaderPatterns("!foo", "!bar", "!prefix_*", "*");
    +adapter.setHeaderMapper(headerMapper);
    +
    +
    +
    +
    + + + + + +
    + + +The order in which the patterns are declared in PubSubHeaderMapper.setOutboundHeaderPatterns() and PubSubHeaderMapper.setInboundHeaderPatterns() matters. +The first patterns have precedence over the following ones. +
    +
    +
    +

    In the previous example, the "*" pattern means every header is mapped. +However, because it comes last in the list, the previous patterns take precedence.

    +
    +
    + +
    +
    +

    Channel Adapters for Google Cloud Storage

    +
    +

    The channel adapters for Google Cloud Storage allow you to read and write files to Google Cloud Storage through MessageChannels.

    +
    +
    +

    Spring Framework on Google Cloud provides two inbound adapters, GcsInboundFileSynchronizingMessageSource and GcsStreamingMessageSource, and one outbound adapter, GcsMessageHandler.

    +
    +
    +

    The Spring Integration Channel Adapters for Google Cloud Storage are included in the spring-cloud-gcp-storage module.

    +
    +
    +

    To use the Storage portion of Spring Integration for Spring Framework on Google Cloud, you must also provide the spring-integration-file dependency, since it isn’t pulled transitively.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-storage</artifactId>
    +</dependency>
    +<dependency>
    +    <groupId>org.springframework.integration</groupId>
    +    <artifactId>spring-integration-file</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-storage")
    +    implementation("org.springframework.integration:spring-integration-file")
    +}
    +
    +
    +
    +

    Inbound channel adapter

    +
    +

    The Google Cloud Storage inbound channel adapter polls a Google Cloud Storage bucket for new files and sends each of them in a Message payload to the MessageChannel specified in the @InboundChannelAdapter annotation. +The files are temporarily stored in a folder in the local file system.

    +
    +
    +

    Here is an example of how to configure a Google Cloud Storage inbound channel adapter.

    +
    +
    +
    +
    @Bean
    +@InboundChannelAdapter(channel = "new-file-channel", poller = @Poller(fixedDelay = "5000"))
    +public MessageSource<File> synchronizerAdapter(Storage gcs) {
    +  GcsInboundFileSynchronizer synchronizer = new GcsInboundFileSynchronizer(gcs);
    +  synchronizer.setRemoteDirectory("your-gcs-bucket");
    +
    +  GcsInboundFileSynchronizingMessageSource synchAdapter =
    +          new GcsInboundFileSynchronizingMessageSource(synchronizer);
    +  synchAdapter.setLocalDirectory(new File("local-directory"));
    +
    +  return synchAdapter;
    +}
    +
    +
    +
    +
    +
    +

    Inbound streaming channel adapter

    +
    +

    The inbound streaming channel adapter is similar to the normal inbound channel adapter, except it does not require files to be stored in the file system.

    +
    +
    +

    Here is an example of how to configure a Google Cloud Storage inbound streaming channel adapter.

    +
    +
    +
    +
    @Bean
    +@InboundChannelAdapter(channel = "streaming-channel", poller = @Poller(fixedDelay = "5000"))
    +public MessageSource<InputStream> streamingAdapter(Storage gcs) {
    +  GcsStreamingMessageSource adapter =
    +          new GcsStreamingMessageSource(new GcsRemoteFileTemplate(new GcsSessionFactory(gcs)));
    +  adapter.setRemoteDirectory("your-gcs-bucket");
    +  return adapter;
    +}
    +
    +
    +
    +
    +

    If you would like to process the files in your bucket in a specific order, you may pass in a Comparator<BlobInfo> to the constructor GcsStreamingMessageSource to sort the files being processed.

    +
    +
    +
    +

    Outbound channel adapter

    +
    +

    The outbound channel adapter allows files to be written to Google Cloud Storage. +When it receives a Message containing a payload of type File, it writes that file to the Google Cloud Storage bucket specified in the adapter.

    +
    +
    +

    Here is an example of how to configure a Google Cloud Storage outbound channel adapter.

    +
    +
    +
    +
    @Bean
    +@ServiceActivator(inputChannel = "writeFiles")
    +public MessageHandler outboundChannelAdapter(Storage gcs) {
    +  GcsMessageHandler outboundChannelAdapter = new GcsMessageHandler(new GcsSessionFactory(gcs));
    +  outboundChannelAdapter.setRemoteDirectoryExpression(new ValueExpression<>("your-gcs-bucket"));
    +
    +  return outboundChannelAdapter;
    +}
    +
    +
    +
    +
    + +
    +
    +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/spring-stream.html b/3.8.13/reference/html/spring-stream.html new file mode 100644 index 0000000000..97ea27f669 --- /dev/null +++ b/3.8.13/reference/html/spring-stream.html @@ -0,0 +1,571 @@ + + + + + + + +Spring Cloud Stream + + + + + + + + + +
    +
    +
    + +
    +
    +

    Spring Cloud Stream

    +
    +
    +

    Spring Framework on Google Cloud provides a Spring Cloud Stream binder to Google Cloud Pub/Sub.

    +
    + +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-pubsub-stream-binder</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-pubsub-stream-binder")
    +}
    +
    +
    +
    +

    Overview

    +
    +

    This binder binds producers to Google Cloud Pub/Sub topics and consumers to subscriptions.

    +
    +
    + + + + + +
    + + +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.<channelName>.<consumer|producer>.auto-create-resources property, which is turned ON by default.

    +
    +
    +

    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 configuration section for other Pub/Sub parameters.

    +
    +
    + + + + + +
    + + +To use this binder with a running emulator, configure its host and port via spring.cloud.gcp.pubsub.emulator-host. +
    +
    +
    +

    Producer Synchronous Sending Configuration

    +
    +

    By default, this binder will send messages to Cloud Pub/Sub asynchronously. +If synchronous sending is preferred (for example, to allow propagating errors back to the sender), set spring.cloud.stream.gcp.pubsub.default.producer.sync property to true.

    +
    +
    +
    +

    Producer Destination Configuration

    +
    +

    If automatic resource creation is turned ON and the topic corresponding to the destination name does not exist, it will be created.

    +
    +
    +

    For example, for the following configuration, a topic called myEvents would be created.

    +
    +
    +
    application.properties
    +
    +
    spring.cloud.stream.bindings.events.destination=myEvents
    +spring.cloud.stream.gcp.pubsub.bindings.events.producer.auto-create-resources=true
    +
    +
    +
    +
    +

    Consumer Destination Configuration

    +
    +

    A PubSubInboundChannelAdapter will be configured for your consumer endpoint. +You may adjust the ack mode of the consumer endpoint using the ack-mode property. +The ack mode controls how messages will be acknowledged when they are successfully received. +The three possible options are: AUTO (default), AUTO_ACK, and MANUAL. +These options are described in detail in the Pub/Sub channel adapter documentation.

    +
    +
    +
    application.properties
    +
    +
    # How to set the ACK mode of the consumer endpoint.
    +spring.cloud.stream.gcp.pubsub.bindings.{CONSUMER_NAME}.consumer.ack-mode=AUTO_ACK
    +
    +
    +
    +

    With automatic resource creation turned ON for a consumer, the library creates a topic and/or a subscription if they do not exist. +The topic name becomes the same as the destination name, and the subscription name follows these rules (in order of precedence):

    +
    +
    +
      +
    • +

      A user-defined, pre-existing subscription (use spring.cloud.stream.gcp.pubsub.bindings.{CONSUMER_NAME}.consumer.subscriptionName)

      +
    • +
    • +

      A consumer group using the topic name (use spring.cloud.stream.bindings.events.group to create a subscription named <topicName>.<group>)

      +
    • +
    • +

      If neither of the above are specified, the library creates an anonymous subscription with the name anonymous.<destinationName>.<randomUUID>. +Then when the binder shuts down, the library automatically cleans up all Pub/Sub subscriptions created for anonymous consumer groups.

      +
    • +
    +
    +
    +

    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
    +
    +
    +
    +

    Only an anonymous subscription named anonymous.myEvents.a6d83782-c5a3-4861-ac38-e6e2af15a7be is created and later cleaned up.

    +
    +
    +

    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
    +
    +# specify consumer group, and avoid anonymous consumer group generation
    +spring.cloud.stream.bindings.events.group=consumerGroup1
    +
    +
    +
    +

    These resources will be created:

    +
    +
    +
      +
    • +

      A topic named myEvents

      +
    • +
    • +

      A subscription named myEvents.consumerGroup1

      +
    • +
    +
    +
    +
    +

    Header Mapping

    +
    +

    You can filter incoming and outgoing message headers with allowHeaders property. +For example, for a consumer to allow only two headers, provide a comma separated list like this:

    +
    +
    +
    application.properties
    +
    +
    spring.cloud.stream.gcp.pubsub.bindings.<consumerFunction>-in-0.consumer.allowedHeaders=allowed1, allowed2
    +
    +
    +
    +

    Where <consumerFunction> 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.

    +
    +
    +

    A similar style is applicable for producers as well. For example:

    +
    +
    +
    application.properties
    +
    +
    spring.cloud.stream.gcp.pubsub.bindings.<producerFunction>-out-0.producer.allowedHeaders=allowed3,allowed4
    +
    +
    +
    +

    Where <producerFunction> 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.

    +
    +
    +
    +

    Endpoint Customization

    +
    +

    You may customize channel routing by defining a ConsumerEndpointCustomizer in your autoconfiguration. This is useful if you want to customize the default configurations provided by the Pub/Sub Spring Cloud Stream Binder.

    +
    +
    +

    The example below demonstrates how to use a ConsumerEndpointCustomizer to override the default error channel configured by the binder.

    +
    +
    +
    +
    @Bean
    +public ConsumerEndpointCustomizer<PubSubInboundChannelAdapter> messageChannelAdapter() {
    +    return (endpoint, destinationName, group) -> {
    +        NamedComponent namedComponent = (NamedComponent) endpoint.getOutputChannel();
    +        String channelName = namedComponent.getBeanName();
    +        endpoint.setErrorChannelName(channelName + ".errors");
    +    };
    +}
    +
    +
    +
    +
    +
    +
    +

    Binding with Functions

    +
    +

    Since version 3.0, Spring Cloud Stream supports a functional programming model natively. +This means that the only requirement for turning your application into a sink is presence of a java.util.function.Consumer bean in the application context.

    +
    +
    +
    +
    @Bean
    +public Consumer<UserMessage> logUserMessage() {
    +  return userMessage -> {
    +    // process message
    +  }
    +};
    +
    +
    +
    +

    A source application is one where a Supplier bean is present. +It can return an object, in which case Spring Cloud Stream will invoke the supplier repeatedly. +Alternatively, the function can return a reactive stream, which will be used as is.

    +
    +
    +
    +
    @Bean
    +Supplier<Flux<UserMessage>> generateUserMessages() {
    +  return () -> /* flux creation logic */;
    +}
    +
    +
    +
    +

    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.

    +
    +
    +
    +
    public interface PollableSink {
    +
    +	@Input("input")
    +	PollableMessageSource input();
    +}
    +
    +
    +
    +
    +

    The PollableMessageSource can then be injected and queried, as needed.

    +
    +
    +
    +
    @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:

    +
    + +
    +
    +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/sql.html b/3.8.13/reference/html/sql.html new file mode 100644 index 0000000000..d53cc41a51 --- /dev/null +++ b/3.8.13/reference/html/sql.html @@ -0,0 +1,683 @@ + + + + + + + +Cloud SQL + + + + + + + + + +
    +
    +
    + +
    +
    +

    Cloud SQL

    +
    +
    +

    Spring Framework on Google Cloud adds integrations with +Spring JDBC and Spring R2DBC so you can run your MySQL or PostgreSQL databases in 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.

    +
    +
    +

    JDBC Support

    +
    +

    Maven and Gradle coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +

    To use MySQL:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-sql-mysql</artifactId>
    +</dependency>
    +
    +
    +
    +
    +
    dependencies {
    +implementation("com.google.cloud:spring-cloud-gcp-starter-sql-mysql")
    +}
    +
    +
    +
    +

    To use PostgreSQL:

    +
    +
    +
    +
    <dependency>
    +<groupId>com.google.cloud</groupId>
    +<artifactId>spring-cloud-gcp-starter-sql-postgresql</artifactId>
    +</dependency>
    +
    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-sql-postgresql")
    +}
    +
    +
    +
    +

    Prerequisites

    +
    +

    In order to use the Spring Boot Starters for Google Cloud SQL, the Google Cloud SQL API must be enabled in your Google Cloud project.

    +
    +
    +

    To do that, go to the API library page of the Google Cloud Console, search for "Cloud SQL API" and enable the option that is called "Cloud SQL" .

    +
    +
    +
    +

    Spring Boot Starter for Google Cloud SQL

    +
    +

    The Spring Boot Starters for Google Cloud SQL provide an auto-configured DataSource object. +Coupled with Spring JDBC, it provides a +JdbcTemplate object bean that allows for operations such as querying and modifying a database.

    +
    +
    +
    +
    public List<Map<String, Object>> listUsers() {
    +    return jdbcTemplate.queryForList("SELECT * FROM user;");
    +}
    +
    +
    +
    +
    +

    You can rely on +Spring Boot data source auto-configuration 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).

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Property name

    Description

    Required

    Default value

    spring.datasource.username

    Database username

    No

    MySQL: root; PostgreSQL: postgres

    spring.datasource.password

    Database password

    No

    null

    spring.datasource.driver-class-name

    JDBC driver to use.

    No

    MySQL: com.mysql.cj.jdbc.Driver; PostgreSQL: org.postgresql.Driver

    +
    + + + + + +
    + + +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. +
    +
    +
    +
    DataSource creation flow
    +
    +

    Spring Boot starter for Google Cloud SQL registers a CloudSqlEnvironmentPostProcessor that provides a correctly formatted spring.datasource.url property to the environment based on the properties mentioned above. +It also provides defaults for spring.datasource.username and spring.datasource.driver-class-name, which can be overridden. +The starter also configures credentials for the JDBC connection based on the properties below.

    +
    +
    +

    The user properties and the properties provided by the CloudSqlEnvironmentPostProcessor are then used by Spring Boot to create the DataSource. +You can select the type of connection pool (e.g., Tomcat, HikariCP, etc.) by adding their dependency to the classpath.

    +
    +
    +

    Using the created DataSource in conjunction with Spring JDBC provides you with a fully configured and operational JdbcTemplate object that you can use to interact with your SQL database. +You can connect to your database with as little as a database and instance names.

    +
    +
    +
    +
    +
    +

    R2DBC Support

    +
    +

    Maven and Gradle coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +

    To use MySQL:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-sql-mysql-r2dbc</artifactId>
    +</dependency>
    +
    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-sql-mysql-r2dbc")
    +}
    +
    +
    +
    +

    To use PostgreSQL with Spring Boot 2.6:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-sql-postgres-r2dbc</artifactId>
    +</dependency>
    +
    +
    +
    +
    +
    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):

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-sql-postgres-r2dbc</artifactId>
    +    <exclusions>
    +        <exclusion>
    +            <groupId>io.r2dbc</groupId>
    +            <artifactId>r2dbc-postgresql</artifactId>
    +        </exclusion>
    +    </exclusions>
    +</dependency>
    +
    +<dependency>
    +    <groupId>org.postgresql</groupId>
    +    <artifactId>r2dbc-postgresql</artifactId>
    +    <version>0.9.1.RELEASE</version>
    +</dependency>
    +
    +
    +
    +

    Prerequisites

    +
    +

    In order to use the Spring Boot Starters for Google Cloud SQL, the Google Cloud SQL API must be enabled in your Google Cloud project.

    +
    +
    +

    To do that, go to the API library page of the Google Cloud Console, search for "Cloud SQL API" and enable the option that is called "Cloud SQL".

    +
    +
    +
    +

    Spring Boot Starter for Google Cloud SQL

    +
    +

    The Cloud SQL R2DBC starter provides a customized io.r2dbc.spi.ConnectionFactory bean for connecting to Cloud SQL with the help of the Cloud SQL Socket Factory. +Similar to the JDBC support, you can connect to your database with as little as a database and instance names.

    +
    +
    +

    A higher level convenience object +R2dbcEntityTemplate is also provided for operations such as querying and modifying a database.

    +
    +
    +
    +
    @Autowired R2dbcEntityTemplate template;
    +
    +public Flux<String> listUsers() {
    +  return template.select(User.class).all().map(user -> user.toString());
    +}
    +
    +
    +
    +
    +

    Standard R2DBC properties like the SQL username, spring.r2dbc.username, and password, spring.r2dbc.password can be 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

    +
    + + + + + +
    + + +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 . +
    +
    +
    +
    ConnectionFactory creation flow
    +
    +

    Spring Framework on Google Cloud starter for Google Cloud SQL registers a R2dbcCloudSqlEnvironmentPostProcessor that provides a correctly formatted spring.r2dbc.url property to the environment based on the properties mentioned above. +It also provides a default value for spring.r2dbc.username, which can be overridden. +The starter also configures credentials for the R2DBC connection based on the properties below.

    +
    +
    +

    The user properties and the properties provided by the R2dbcCloudSqlEnvironmentPostProcessor are then used by Spring Boot to create the ConnectionFactory.

    +
    +
    +

    The customized ConnectionFactory is then ready to connect to Cloud SQL. The rest of Spring Data R2DBC objects built on it ( R2dbcEntityTemplate, DatabaseClient) are automatically configured and operational, ready to interact with your SQL database.

    +
    +
    +
    +
    +
    +

    Cloud SQL IAM database authentication

    +
    +

    Currently, Cloud SQL only supports IAM database authentication for PostgreSQL. +It allows you to connect to the database using an IAM account, rather than a predefined database username and password. +You will need to do the following to enable it:

    +
    +
    +
      +
    1. +

      In your database instance settings, turn on the cloudsql.iam_authentication flag.

      +
    2. +
    3. +

      Add the IAM user or service account to the list of database users.

      +
    4. +
    5. +

      In the application settings, set spring.cloud.gcp.sql.enableIamAuth to true. Note that this will also set the database protocol sslmode to disabled, as it’s required for IAM authentication to work. +However, it doesn’t compromise the security of the communication because the connection is always encrypted.

      +
    6. +
    7. +

      Set spring.datasource.username to the IAM user or service account created in step 2. Note that IAM user or service account still needs to be granted permissions before modifying or querying the database.

      +
    8. +
    +
    +
    +
    +

    Cloud SQL Configuration Properties

    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Property name

    Description

    Required

    Default value

    spring.cloud.gcp.sql.enabled

    Enables or disables Cloud SQL auto configuration

    No

    true

    spring.cloud.gcp.sql.jdbc.enabled

    Enables or disables Cloud SQL auto-configuration for JDBC

    No

    true

    spring.cloud.gcp.sql.r2dbc.enabled

    Enables or disables Cloud SQL auto-configuration for R2DBC

    No

    true

    spring.cloud.gcp.sql.database-name

    Name of the database to connect to.

    Yes

    spring.cloud.gcp.sql.instance-connection-name

    A string containing a Google Cloud SQL instance’s project ID, region and name, each separated by a colon.

    Yes

    For example, my-project-id:my-region:my-instance-name.

    spring.cloud.gcp.sql.ip-types

    Allows you to specify a comma delimited list of preferred IP types for connecting to a Cloud SQL instance. Left unconfigured Cloud SQL Socket Factory will default it to PUBLIC,PRIVATE. See Cloud SQL Socket Factory - Specifying IP Types

    No

    PUBLIC,PRIVATE

    spring.cloud.gcp.sql.credentials.location

    File system path to the Google OAuth2 credentials private key file. +Used to authenticate and authorize new connections to a Google Cloud SQL instance.

    No

    Default credentials provided by the Spring Framework on Google Cloud Core Starter

    spring.cloud.gcp.sql.credentials.encoded-key

    Base64-encoded contents of OAuth2 account private key in JSON format. +Used to authenticate and authorize new connections to a Google Cloud SQL instance.

    No

    Default credentials provided by the Spring Framework on Google Cloud Core Starter

    spring.cloud.gcp.sql.enableIamAuth

    Specifies whether to enable IAM database authentication (PostgreSQL only).

    No

    False

    +
    +
    +

    Troubleshooting tips

    +
    +

    Connection issues

    +
    +

    If you’re not able to connect to a database and see an endless loop of Connecting to Cloud SQL instance […​] on IP […​], it’s likely that exceptions are being thrown and logged at a level lower than your logger’s level. +This may be the case with HikariCP, if your logger is set to INFO or higher level.

    +
    +
    +

    To see what’s going on in the background, you should add a logback.xml file to your application resources folder, that looks like this:

    +
    +
    +
    +
    <?xml version="1.0" encoding="UTF-8"?>
    +<configuration>
    +  <include resource="org/springframework/boot/logging/logback/base.xml"/>
    +  <logger name="com.zaxxer.hikari.pool" level="DEBUG"/>
    +</configuration>
    +
    +
    +
    +
    +

    Errors like c.g.cloud.sql.core.SslSocketFactory : Re-throwing cached exception due to attempt to refresh instance information too soon after error

    +
    +

    If you see a lot of errors like this in a loop and can’t connect to your database, this is usually a symptom that something isn’t right with the permissions of your credentials or the Google Cloud SQL API is not enabled. +Verify that the Google Cloud SQL API is enabled in the Cloud Console and that your service account has the necessary IAM roles.

    +
    +
    +

    To find out what’s causing the issue, you can enable DEBUG logging level as mentioned above.

    +
    +
    +
    +

    PostgreSQL: java.net.SocketException: already connected issue

    +
    +

    We found this exception to be common if your Maven project’s parent is spring-boot version 1.5.x, or in any other circumstance that would cause the version of the org.postgresql:postgresql dependency to be an older one (e.g., 9.4.1212.jre7).

    +
    +
    +

    To fix this, re-declare the dependency in its correct version. +For example, in Maven:

    +
    +
    +
    +
    <dependency>
    +  <groupId>org.postgresql</groupId>
    +  <artifactId>postgresql</artifactId>
    +  <version>42.1.1</version>
    +</dependency>
    +
    +
    +
    +
    + +
    +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/storage.html b/3.8.13/reference/html/storage.html new file mode 100644 index 0000000000..17f16c1112 --- /dev/null +++ b/3.8.13/reference/html/storage.html @@ -0,0 +1,398 @@ + + + + + + + +Cloud Storage + + + + + + + + + +
    +
    +
    + +
    +
    +

    Cloud Storage

    +
    +
    +

    Google Cloud Storage allows storing any types of files in single or multiple regions. +A Spring Boot starter is provided to auto-configure the various Storage components.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-storage</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-storage")
    +}
    +
    +
    +
    +

    This starter is also available from Spring Initializr through the GCP Storage entry.

    +
    +
    +

    Using Cloud Storage

    +
    +

    The starter automatically configures and registers a Storage bean in the Spring application context. +The Storage bean (Javadoc) can be used to list/create/update/delete buckets (a group of objects with similar permissions and resiliency requirements) and objects.

    +
    +
    +
    +
    @Autowired
    +private Storage storage;
    +
    +public void createFile() {
    +    Bucket bucket = storage.create(BucketInfo.of("my-app-storage-bucket"));
    +
    +    storage.create(
    +        BlobInfo.newBuilder("my-app-storage-bucket", "subdirectory/my-file").build(),
    +            "file contents".getBytes()
    +    );
    +}
    +
    +
    +
    +
    +
    +

    Cloud Storage Objects As Spring Resources

    +
    +

    Spring Resources are an abstraction for a number of low-level resources, such as file system files, classpath files, servlet context-relative files, etc. +Spring Framework on Google Cloud adds a new resource type: a Google Cloud Storage (GCS) object.

    +
    +
    +

    The Spring Resource Abstraction for Google Cloud Storage allows GCS objects to be accessed by their GCS URL using the @Value annotation:

    +
    +
    +
    +
    @Value("gs://[YOUR_GCS_BUCKET]/[GCS_FILE_NAME]")
    +private Resource gcsResource;
    +
    +
    +
    +
    +

    …​or the Spring application context

    +
    +
    +
    +
    SpringApplication.run(...).getResource("gs://[YOUR_GCS_BUCKET]/[GCS_FILE_NAME]");
    +
    +
    +
    +
    +

    This creates a Resource object that can be used to read the object, among other possible operations.

    +
    +
    +

    It is also possible to write to a Resource, although a WriteableResource is required.

    +
    +
    +
    +
    @Value("gs://[YOUR_GCS_BUCKET]/[GCS_FILE_NAME]")
    +private Resource gcsResource;
    +...
    +try (OutputStream os = ((WritableResource) gcsResource).getOutputStream()) {
    +  os.write("foo".getBytes());
    +}
    +
    +
    +
    +
    +

    To work with the Resource as a Google Cloud Storage resource, cast it to GoogleStorageResource.

    +
    +
    +

    If the resource path refers to an object on Google Cloud Storage (as opposed to a bucket), then the getBlob method can be called to obtain a Blob. +This type represents a GCS file, which has associated metadata, such as content-type, that can be set. +The createSignedUrl method can also be used to obtain signed URLs for GCS objects. +However, creating signed URLs requires that the resource was created using service account credentials.

    +
    +
    + + + + + +
    + + +
    +

    As of v2.0.2+, the GoogleStorageResource.getURL() method returns the Bucket or Blob 's selfLink value, rather than attempting to convert the URI a URL object that nearly-always threw a MalformedURLException. +This value is notably different from GoogleStorageResource.getURI(), which returns the more commonly used gs://my-bucket/my-object identifier. +Returning a valid URL is necessary to support some features in the Spring ecosystem, such as spring.resources.static-locations.

    +
    +
    +
    +
    +

    The Spring Boot Starter for Google Cloud Storage auto-configures the Storage bean required by the spring-cloud-gcp-storage module, based on the CredentialsProvider provided by the Spring Framework on Google Cloud Starter.

    +
    +
    +

    Setting the Content Type

    +
    +

    You can set the content-type of Google Cloud Storage files from their corresponding Resource objects:

    +
    +
    +
    +
    ((GoogleStorageResource)gcsResource).getBlob().toBuilder().setContentType("text/html").build().update();
    +
    +
    +
    +
    +
    +
    +

    Configuration

    +
    +

    The Spring Boot Starter for Google Cloud Storage provides the following configuration options:

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.storage.enabled

    Enables the Google Cloud storage APIs.

    No

    true

    spring.cloud.gcp.storage.auto-create-files

    Creates files and buckets on Google Cloud Storage when writes are made to non-existent files

    No

    true

    spring.cloud.gcp.storage.credentials.location

    OAuth2 credentials for authenticating with the Google Cloud Storage API, if different from the ones in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.storage.credentials.encoded-key

    Base64-encoded contents of OAuth2 account private key for authenticating with the Google Cloud Storage API, if different from the ones in the Spring Framework on Google Cloud Core Module

    No

    spring.cloud.gcp.storage.credentials.scopes

    OAuth2 scope for Spring Framework on Google Cloud Storage credentials

    No

    https://www.googleapis.com/auth/devstorage.read_write

    +
    +
    +

    Sample

    +
    +

    A sample application and a codelab are available.

    +
    +
    +
    +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/trace.html b/3.8.13/reference/html/trace.html new file mode 100644 index 0000000000..4ef95b5791 --- /dev/null +++ b/3.8.13/reference/html/trace.html @@ -0,0 +1,553 @@ + + + + + + + +Cloud Trace + + + + + + + + + +
    +
    +
    + +
    +
    +

    Cloud Trace

    +
    +
    +

    Google Cloud provides a managed distributed tracing service called Cloud Trace, and Spring Cloud Sleuth 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 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.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +    <groupId>com.google.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-trace</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +    implementation("com.google.cloud:spring-cloud-gcp-starter-trace")
    +}
    +
    +
    +
    +

    You must enable Cloud Trace API from the Google Cloud Console in order to capture traces. +Navigate to the Cloud Trace API for your project and make sure it’s enabled.

    +
    +
    + + + + + +
    + + +
    +

    If you are already using a Zipkin server capturing trace information from multiple platform/frameworks, you can also use a Stackdriver Zipkin proxy to forward those traces to Cloud Trace without modifying existing applications.

    +
    +
    +
    +
    +

    Tracing

    +
    +

    Spring Cloud Sleuth uses the Brave tracer to generate traces. +This integration enables Brave to use the 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. +A canonical example of the propagation usage is a web server that receives an HTTP request, which triggers other HTTP requests from the server before returning an HTTP response to the original caller.

    +
    +
    +

    In the case of StackdriverTracePropagation, first it looks for trace context in the 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 CUSTOM propagation type.

    +
    +
    + + + + + +
    + + +
    +

    The value of the x-cloud-trace-context key can be formatted in three different ways:

    +
    +
    +
      +
    • +

      x-cloud-trace-context: TRACE_ID

      +
    • +
    • +

      x-cloud-trace-context: TRACE_ID/SPAN_ID

      +
    • +
    • +

      x-cloud-trace-context: TRACE_ID/SPAN_ID;o=TRACE_TRUE

      +
    • +
    +
    +
    +

    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 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.

    +
    +
    +
    +
    +
    +

    Spring Boot Starter for Cloud Trace

    +
    +

    Spring Boot Starter for Cloud Trace uses Spring Cloud Sleuth and auto-configures a StackdriverSender that sends the Sleuth’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.project-id

    Overrides the project ID from the Spring Framework on Google Cloud Module

    No

    spring.cloud.gcp.trace.credentials.location

    Overrides the credentials location from the Spring Framework on Google Cloud Module

    No

    spring.cloud.gcp.trace.credentials.encoded-key

    Overrides the credentials encoded key from the Spring Framework on Google Cloud Module

    No

    spring.cloud.gcp.trace.credentials.scopes

    Overrides the credentials scopes from the Spring Framework on Google Cloud Module

    No

    spring.cloud.gcp.trace.num-executor-threads

    Number of threads used by the Trace executor

    No

    4

    spring.cloud.gcp.trace.authority

    HTTP/2 authority the channel claims to be connecting to.

    No

    spring.cloud.gcp.trace.compression

    Name of the compression to use in Trace calls

    No

    spring.cloud.gcp.trace.deadline-ms

    Call deadline in milliseconds

    No

    spring.cloud.gcp.trace.max-inbound-size

    Maximum size for inbound messages

    No

    spring.cloud.gcp.trace.max-outbound-size

    Maximum size for outbound messages

    No

    spring.cloud.gcp.trace.wait-for-ready

    Waits for the channel to be ready in case of a transient failure

    No

    false

    spring.cloud.gcp.trace.messageTimeout

    Timeout in seconds before pending spans will be sent in batches to Google Cloud Trace. (previously spring.zipkin.messageTimeout)

    No

    1

    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 for more information on Sleuth 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
    +
    +
    +
    + + + + + +
    + + +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:

    +
    +
    +
      +
    • +

      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<Span> 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.

    +
    +
    +
    +

    Customizing spans

    +
    +

    You can add additional tags and annotations to spans by using the brave.SpanCustomizer, which is available in the application context.

    +
    +
    +

    Here’s an example that uses WebMvcConfigurer to configure an MVC interceptor that adds two extra tags to all web controller spans.

    +
    +
    +
    +
    @SpringBootApplication
    +public class Application implements WebMvcConfigurer {
    +
    +	public static void main(String[] args) {
    +		SpringApplication.run(Application.class, args);
    +	}
    +
    +	@Autowired
    +	private SpanCustomizer spanCustomizer;
    +
    +	@Override
    +	public void addInterceptors(InterceptorRegistry registry) {
    +		registry.addInterceptor(new HandlerInterceptor() {
    +			@Override
    +			public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    +				spanCustomizer.tag("session-id", request.getSession().getId());
    +				spanCustomizer.tag("environment", "QA");
    +
    +				return true;
    +			}
    +		});
    +	}
    +}
    +
    +
    +
    +
    +

    You can then search and filter traces based on these additional tags in the Cloud Trace service.

    +
    +
    +
    +

    Integration with Logging

    +
    +

    Integration with Cloud Logging is available through the Cloud Logging Support. +If the Trace integration is used together with the Logging one, the request logs will be associated to the corresponding traces. +The trace logs can be viewed by going to the Google Cloud Console Trace List, selecting a trace and pressing the Logs → View link in the Details section.

    +
    +
    +
    +

    Pub/Sub Trace Instrumentation (Experimental)

    +
    +

    You can enable trace instrumentation and propagation for Pub/Sub messages by using the spring.cloud.gcp.trace.pubsub.enabled=true property. +It’s set to false by default, but when set to true, trace spans will be created and propagated to Cloud Trace whenever the application sends or receives messages through PubSubTemplate or any other integration that builds on top of PubSubTemplate, such as the 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

    +
    +

    A sample application and a codelab are available.

    +
    +
    +
    +
    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/3.8.13/reference/html/vision.html b/3.8.13/reference/html/vision.html new file mode 100644 index 0000000000..dcb6cab356 --- /dev/null +++ b/3.8.13/reference/html/vision.html @@ -0,0 +1,615 @@ + + + + + + + +Cloud Vision + + + + + + + + + +
    +
    +
    + +
    +
    +

    Cloud Vision

    +
    +
    +

    The Google Cloud Vision API allows users to leverage machine learning algorithms for processing images and documents including: image classification, face detection, text extraction, optical character recognition, and others.

    +
    +
    +

    Spring Framework on Google Cloud provides:

    +
    +
    +
      +
    • +

      A convenience starter which automatically configures authentication settings and client objects needed to begin using the Google Cloud Vision API.

      +
    • +
    • +

      CloudVisionTemplate which simplifies interactions with the Cloud Vision API.

      +
      +
        +
      • +

        Allows you to easily send images, PDF, TIFF and GIF documents to the API as Spring Resources.

        +
      • +
      • +

        Offers convenience methods for common operations, such as classifying content of an image.

        +
      • +
      +
      +
    • +
    • +

      DocumentOcrTemplate which offers convenient methods for running optical character recognition (OCR) on PDF and TIFF documents.

      +
    • +
    +
    +
    +

    Dependency Setup

    +
    +

    To begin using this library, add the spring-cloud-gcp-starter-vision artifact to your project.

    +
    +
    +

    Maven coordinates, using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +  <groupId>com.google.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-starter-vision</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +  implementation("com.google.cloud:spring-cloud-gcp-starter-vision")
    +}
    +
    +
    +
    +
    +

    Configuration

    +
    +

    The following options may be configured with Spring Framework on Google Cloud Vision libraries.

    +
    + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.vision.enabled

    Enables or disables Cloud Vision autoconfiguration

    No

    true

    spring.cloud.gcp.vision.executors-threads-count

    Number of threads used during document OCR processing for waiting on long-running OCR operations

    No

    1

    spring.cloud.gcp.vision.json-output-batch-size

    Number of document pages to include in each OCR output file.

    No

    20

    +
    +

    Cloud Vision OCR Dependencies

    +
    +

    If you are interested in applying optical character recognition (OCR) on documents for your project, you’ll need to add both spring-cloud-gcp-starter-vision and spring-cloud-gcp-starter-storage to your dependencies. +The storage starter is necessary because the Cloud Vision API will process your documents and write OCR output files all within your Google Cloud Storage buckets.

    +
    +
    +

    Maven coordinates using Spring Framework on Google Cloud BOM:

    +
    +
    +
    +
    <dependency>
    +  <groupId>com.google.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-starter-vision</artifactId>
    +</dependency>
    +<dependency>
    +  <groupId>com.google.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-starter-storage</artifactId>
    +</dependency>
    +
    +
    +
    +

    Gradle coordinates:

    +
    +
    +
    +
    dependencies {
    +  implementation("com.google.cloud:spring-cloud-gcp-starter-vision")
    +  implementation("com.google.cloud:spring-cloud-gcp-starter-storage")
    +}
    +
    +
    +
    +
    +
    +

    Image Analysis

    +
    +

    The CloudVisionTemplate allows you to easily analyze images; it provides the following method for interfacing with Cloud Vision:

    +
    +
    +

    public AnnotateImageResponse analyzeImage(Resource imageResource, Feature.Type…​ featureTypes)

    +
    +
    +

    Parameters:

    +
    +
    +
      +
    • +

      Resource imageResource refers to the Spring Resource of the image object you wish to analyze. +The Google Cloud Vision documentation provides a list of the image types that they support.

      +
    • +
    • +

      Feature.Type…​ featureTypes refers to a var-arg array of Cloud Vision Features to extract from the image. +A feature refers to a kind of image analysis one wishes to perform on an image, such as label detection, OCR recognition, facial detection, etc. +One may specify multiple features to analyze within one request. +A full list of Cloud Vision Features is provided in the Cloud Vision Feature docs.

      +
    • +
    +
    +
    +

    Returns:

    +
    +
    +
      +
    • +

      AnnotateImageResponse contains the results of all the feature analyses that were specified in the request. +For each feature type that you provide in the request, AnnotateImageResponse provides a getter method to get the result of that feature analysis. +For example, if you analyzed an image using the LABEL_DETECTION feature, you would retrieve the results from the response using annotateImageResponse.getLabelAnnotationsList().

      +
      +

      AnnotateImageResponse is provided by the Google Cloud Vision libraries; please consult the RPC reference or Javadoc for more details. +Additionally, you may consult the Cloud Vision docs to familiarize yourself with the concepts and features of the API.

      +
      +
    • +
    +
    +
    +

    Detect Image Labels Example

    +
    +

    Image labeling refers to producing labels that describe the contents of an image. +Below is a code sample of how this is done using the Cloud Vision Spring Template.

    +
    +
    +
    +
    @Autowired
    +private ResourceLoader resourceLoader;
    +
    +@Autowired
    +private CloudVisionTemplate cloudVisionTemplate;
    +
    +public void processImage() {
    +  Resource imageResource = this.resourceLoader.getResource("my_image.jpg");
    +  AnnotateImageResponse response = this.cloudVisionTemplate.analyzeImage(
    +      imageResource, Type.LABEL_DETECTION);
    +  System.out.println("Image Classification results: " + response.getLabelAnnotationsList());
    +}
    +
    +
    +
    +
    +
    +
    +

    File Analysis

    +
    +

    The CloudVisionTemplate allows you to easily analyze PDF, TIFF and GIF documents; it provides the following method for interfacing with Cloud Vision:

    +
    +
    +

    public AnnotateFileResponse analyzeFile(Resource fileResource, String mimeType, Feature.Type…​ featureTypes)

    +
    +
    +

    Parameters:

    +
    +
    +
      +
    • +

      Resource fileResource refers to the Spring Resource of the PDF, TIFF or GIF object you wish to analyze. +Documents with more than 5 pages are not supported.

      +
    • +
    • +

      String mimeType is the mime type of the fileResource. +Currently, only application/pdf, image/tiff and image/gif are supported.

      +
    • +
    • +

      Feature.Type…​ featureTypes refers to a var-arg array of Cloud Vision Features to extract from the document. +A feature refers to a kind of image analysis one wishes to perform on a document, such as label detection, OCR recognition, facial detection, etc. +One may specify multiple features to analyze within one request. +A full list of Cloud Vision Features is provided in the Cloud Vision Feature docs.

      +
    • +
    +
    +
    +

    Returns:

    +
    +
    +
      +
    • +

      AnnotateFileResponse contains the results of all the feature analyses that were specified in the request. +For each page of the analysed document the response will contain an AnnotateImageResponse object which you can retrieve using annotateFileResponse.getResponsesList(). +For each feature type that you provide in the request, AnnotateImageResponse provides a getter method to get the result of that feature analysis. +For example, if you analysed an PDF using the DOCUMENT_TEXT_DETECTION feature, you would retrieve the results from the response using annotateImageResponse.getFullTextAnnotation().getText().

      +
      +

      AnnotateFileResponse is provided by the Google Cloud Vision libraries; please consult the RPC reference or Javadoc for more details. +Additionally, you may consult the Cloud Vision docs to familiarize yourself with the concepts and features of the API.

      +
      +
    • +
    +
    +
    +

    Running Text Detection Example

    +
    +

    Detect text in files refers to extracting text from small document such as PDF or TIFF. +Below is a code sample of how this is done using the Cloud Vision Spring Template.

    +
    +
    +
    +
    @Autowired
    +private ResourceLoader resourceLoader;
    +
    +@Autowired
    +private CloudVisionTemplate cloudVisionTemplate;
    +
    +public void processPdf() {
    +  Resource imageResource = this.resourceLoader.getResource("my_file.pdf");
    +  AnnotateFileResponse response =
    +    this.cloudVisionTemplate.analyzeFile(
    +        imageResource, "application/pdf", Type.DOCUMENT_TEXT_DETECTION);
    +
    +  response
    +    .getResponsesList()
    +    .forEach(
    +        annotateImageResponse ->
    +            System.out.println(annotateImageResponse.getFullTextAnnotation().getText()));
    +}
    +
    +
    +
    +
    +
    +
    +

    Document OCR Template

    +
    +

    The DocumentOcrTemplate allows you to easily run optical character recognition (OCR) on your PDF and TIFF documents stored in your Google Storage bucket.

    +
    +
    +

    First, you will need to create a bucket in Google Cloud Storage and upload the documents you wish to process into the bucket.

    +
    +
    +

    Running OCR on a Document

    +
    +

    When OCR is run on a document, the Cloud Vision APIs will output a collection of OCR output files in JSON which describe the text content, bounding rectangles of words and letters, and other information about the document.

    +
    +
    +

    The DocumentOcrTemplate provides the following method for running OCR on a document saved in Google Cloud Storage:

    +
    +
    +

    ListenableFuture<DocumentOcrResultSet> 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.

    +
    +
    + + + + + +
    + + +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. +
    +
    +
    +
    +

    Running OCR Example

    +
    +

    Below is a code snippet of how to run OCR on a document stored in a Google Storage bucket and read the text in the first page of the document.

    +
    +
    +
    +
    @Autowired
    +private DocumentOcrTemplate documentOcrTemplate;
    +
    +public void runOcrOnDocument() {
    +    GoogleStorageLocation document = GoogleStorageLocation.forFile(
    +            "your-bucket", "test.pdf");
    +    GoogleStorageLocation outputLocationPrefix = GoogleStorageLocation.forFolder(
    +            "your-bucket", "output_folder/test.pdf/");
    +
    +    ListenableFuture<DocumentOcrResultSet> result =
    +        this.documentOcrTemplate.runOcrForDocument(
    +            document, outputLocationPrefix);
    +
    +    DocumentOcrResultSet ocrPages = result.get(5, TimeUnit.MINUTES);
    +
    +    String page1Text = ocrPages.getPage(1).getText();
    +    System.out.println(page1Text);
    +}
    +
    +
    +
    +
    +

    Reading OCR Output Files

    +
    +

    In some use-cases, you may need to directly read OCR output files stored in Google Cloud Storage.

    +
    +
    +

    DocumentOcrTemplate offers the following methods for reading and processing OCR output files:

    +
    +
    +
      +
    • +

      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.

      +
    • +
    • +

      readOcrOutputFile(GoogleStorageLocation jsonFile): +Reads a single OCR output file and returns the parsed contents.

      +
    • +
    +
    +
    +
    +

    Reading OCR Output Files Example

    +
    +

    The code snippet below describes how to read the OCR output files of a single document.

    +
    +
    +
    +
    @Autowired
    +private DocumentOcrTemplate documentOcrTemplate;
    +
    +// Parses the OCR output files corresponding to a single document in a directory
    +public void parseOutputFileSet() {
    +  GoogleStorageLocation ocrOutputPrefix = GoogleStorageLocation.forFolder(
    +      "your-bucket", "json_output_set/");
    +
    +  DocumentOcrResultSet result = this.documentOcrTemplate.readOcrOutputFileSet(ocrOutputPrefix);
    +  System.out.println("Page 2 text: " + result.getPage(2).getText());
    +}
    +
    +// Parses a single OCR output file
    +public void parseSingleOutputFile() {
    +  GoogleStorageLocation ocrOutputFile = GoogleStorageLocation.forFile(
    +      "your-bucket", "json_output_set/test_output-2-to-2.json");
    +
    +  DocumentOcrResultSet result = this.documentOcrTemplate.readOcrOutputFile(ocrOutputFile);
    +  System.out.println("Page 2 text: " + result.getPage(2).getText());
    +}
    +
    +
    +
    +
    +
    +

    Sample

    +
    +

    Samples are provided to show example usages of Spring Framework on Google Cloud with Google Cloud Vision.

    +
    +
    +
      +
    • +

      The Image Labeling Sample shows you how to use image labelling in your Spring application. +The application generates labels describing the content inside the images you specify in the application.

      +
    • +
    • +

      The Document OCR demo shows how you can apply OCR processing on your PDF/TIFF documents in order to extract their text contents.

      +
    • +
    +
    +
    +
    +
    +
    + + +
    +
    +
    + + \ No newline at end of file