From f053f5979642ad42dcd2ac1d2d20e6e2bd247818 Mon Sep 17 00:00:00 2001 From: buildmaster Date: Fri, 31 Jan 2020 22:59:00 +0000 Subject: [PATCH] Sync docs from v1.1.5.RELEASE to gh-pages --- spring-cloud-gcp/1.1.5.RELEASE/Guardfile | 20 + .../1.1.5.RELEASE/css/highlight.css | 35 + .../1.1.5.RELEASE/css/manual-multipage.css | 9 + .../1.1.5.RELEASE/css/manual-singlepage.css | 6 + spring-cloud-gcp/1.1.5.RELEASE/css/manual.css | 342 ++ spring-cloud-gcp/1.1.5.RELEASE/ghpages.sh | 330 ++ .../images/GCP on Spring Initializr.png | Bin 0 -> 189333 bytes .../1.1.5.RELEASE/images/Stackdrive trace.png | Bin 0 -> 125360 bytes .../1.1.5.RELEASE/images/background.png | Bin 0 -> 18255 bytes .../1.1.5.RELEASE/images/callouts/1.png | Bin 0 -> 329 bytes .../1.1.5.RELEASE/images/callouts/2.png | Bin 0 -> 353 bytes .../1.1.5.RELEASE/images/callouts/3.png | Bin 0 -> 350 bytes .../1.1.5.RELEASE/images/caution.png | Bin 0 -> 2099 bytes .../1.1.5.RELEASE/images/important.png | Bin 0 -> 2085 bytes .../1.1.5.RELEASE/images/logo.png | Bin 0 -> 4387 bytes .../1.1.5.RELEASE/images/note.png | Bin 0 -> 2257 bytes spring-cloud-gcp/1.1.5.RELEASE/images/tip.png | Bin 0 -> 931 bytes .../1.1.5.RELEASE/images/warning.png | Bin 0 -> 2130 bytes .../1.1.5.RELEASE/multi/css/highlight.css | 35 + .../multi/css/manual-multipage.css | 9 + .../multi/css/manual-singlepage.css | 6 + .../1.1.5.RELEASE/multi/css/manual.css | 342 ++ .../1.1.5.RELEASE/multi/images/background.png | Bin 0 -> 18255 bytes .../1.1.5.RELEASE/multi/images/callouts/1.png | Bin 0 -> 329 bytes .../1.1.5.RELEASE/multi/images/callouts/2.png | Bin 0 -> 353 bytes .../1.1.5.RELEASE/multi/images/callouts/3.png | Bin 0 -> 350 bytes .../1.1.5.RELEASE/multi/images/caution.png | Bin 0 -> 2099 bytes .../1.1.5.RELEASE/multi/images/important.png | Bin 0 -> 2085 bytes .../1.1.5.RELEASE/multi/images/logo.png | Bin 0 -> 4387 bytes .../1.1.5.RELEASE/multi/images/note.png | Bin 0 -> 2257 bytes .../1.1.5.RELEASE/multi/images/tip.png | Bin 0 -> 931 bytes .../1.1.5.RELEASE/multi/images/warning.png | Bin 0 -> 2130 bytes .../multi/multi__cloud_foundry.html | 8 + ...entity_aware_proxy_iap_authentication.html | 13 + .../multi__cloud_memorystore_for_redis.html | 15 + .../multi/multi__dependency_management.html | 15 + .../multi/multi__getting_started.html | 5 + .../multi/multi__google_cloud_pubsub.html | 76 + .../multi/multi__google_cloud_vision.html | 27 + .../multi/multi__introduction.html | 3 + .../multi/multi__kotlin_support.html | 5 + .../1.1.5.RELEASE/multi/multi__sample_13.html | 3 + .../multi/multi__spring_cloud_config.html | 35 + .../multi/multi__spring_cloud_sleuth.html | 27 + .../multi/multi__spring_cloud_stream.html | 21 + .../multi__spring_data_cloud_datastore.html | 467 ++ .../multi__spring_data_cloud_spanner.html | 465 ++ .../multi/multi__spring_integration.html | 113 + .../multi/multi__spring_jdbc.html | 42 + .../multi/multi__spring_resources.html | 18 + .../multi/multi__stackdriver_logging.html | 60 + .../multi/multi_spring-cloud-gcp-core.html | 23 + .../multi/multi_spring-cloud-gcp.html | 3 + .../1.1.5.RELEASE/single/css/highlight.css | 35 + .../single/css/manual-multipage.css | 9 + .../single/css/manual-singlepage.css | 6 + .../1.1.5.RELEASE/single/css/manual.css | 342 ++ .../single/images/background.png | Bin 0 -> 18255 bytes .../single/images/callouts/1.png | Bin 0 -> 329 bytes .../single/images/callouts/2.png | Bin 0 -> 353 bytes .../single/images/callouts/3.png | Bin 0 -> 350 bytes .../1.1.5.RELEASE/single/images/caution.png | Bin 0 -> 2099 bytes .../1.1.5.RELEASE/single/images/important.png | Bin 0 -> 2085 bytes .../1.1.5.RELEASE/single/images/logo.png | Bin 0 -> 4387 bytes .../1.1.5.RELEASE/single/images/note.png | Bin 0 -> 2257 bytes .../1.1.5.RELEASE/single/images/tip.png | Bin 0 -> 931 bytes .../1.1.5.RELEASE/single/images/warning.png | Bin 0 -> 2130 bytes .../single/spring-cloud-gcp.html | 1384 ++++++ .../1.1.5.RELEASE/spring-cloud-gcp.html | 117 + .../1.1.5.RELEASE/spring-cloud-gcp.xml | 4290 +++++++++++++++++ 70 files changed, 8761 insertions(+) create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/Guardfile create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/css/highlight.css create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/css/manual-multipage.css create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/css/manual-singlepage.css create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/css/manual.css create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/ghpages.sh create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/images/GCP on Spring Initializr.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/images/Stackdrive trace.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/images/background.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/images/callouts/1.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/images/callouts/2.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/images/callouts/3.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/images/caution.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/images/important.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/images/logo.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/images/note.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/images/tip.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/images/warning.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/css/highlight.css create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/css/manual-multipage.css create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/css/manual-singlepage.css create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/css/manual.css create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/images/background.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/images/callouts/1.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/images/callouts/2.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/images/callouts/3.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/images/caution.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/images/important.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/images/logo.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/images/note.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/images/tip.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/images/warning.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/multi__cloud_foundry.html create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/multi__cloud_identity_aware_proxy_iap_authentication.html create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/multi__cloud_memorystore_for_redis.html create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/multi__dependency_management.html create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/multi__getting_started.html create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/multi__google_cloud_pubsub.html create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/multi__google_cloud_vision.html create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/multi__introduction.html create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/multi__kotlin_support.html create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/multi__sample_13.html create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_cloud_config.html create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_cloud_sleuth.html create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_cloud_stream.html create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_data_cloud_datastore.html create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_data_cloud_spanner.html create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_integration.html create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_jdbc.html create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_resources.html create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/multi__stackdriver_logging.html create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/multi_spring-cloud-gcp-core.html create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/multi/multi_spring-cloud-gcp.html create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/single/css/highlight.css create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/single/css/manual-multipage.css create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/single/css/manual-singlepage.css create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/single/css/manual.css create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/single/images/background.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/single/images/callouts/1.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/single/images/callouts/2.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/single/images/callouts/3.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/single/images/caution.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/single/images/important.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/single/images/logo.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/single/images/note.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/single/images/tip.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/single/images/warning.png create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/single/spring-cloud-gcp.html create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/spring-cloud-gcp.html create mode 100644 spring-cloud-gcp/1.1.5.RELEASE/spring-cloud-gcp.xml diff --git a/spring-cloud-gcp/1.1.5.RELEASE/Guardfile b/spring-cloud-gcp/1.1.5.RELEASE/Guardfile new file mode 100644 index 00000000..bdd4d729 --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/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/spring-cloud-gcp/1.1.5.RELEASE/css/highlight.css b/spring-cloud-gcp/1.1.5.RELEASE/css/highlight.css new file mode 100644 index 00000000..3850f8b9 --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/css/highlight.css @@ -0,0 +1,35 @@ +/* + code highlight CSS resemblign the Eclipse IDE default color schema + @author Costin Leau +*/ + +.hl-keyword { + color: #7F0055; + font-weight: bold; +} + +.hl-comment { + color: #3F5F5F; + font-style: italic; +} + +.hl-multiline-comment { + color: #3F5FBF; + font-style: italic; +} + +.hl-tag { + color: #3F7F7F; +} + +.hl-attribute { + color: #7F007F; +} + +.hl-value { + color: #2A00FF; +} + +.hl-string { + color: #2A00FF; +} \ No newline at end of file diff --git a/spring-cloud-gcp/1.1.5.RELEASE/css/manual-multipage.css b/spring-cloud-gcp/1.1.5.RELEASE/css/manual-multipage.css new file mode 100644 index 00000000..b790654b --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/css/manual-multipage.css @@ -0,0 +1,9 @@ +@IMPORT url("manual.css"); + +body.firstpage { + background: url("../images/background.png") no-repeat center top; +} + +div.part h1 { + border-top: none; +} diff --git a/spring-cloud-gcp/1.1.5.RELEASE/css/manual-singlepage.css b/spring-cloud-gcp/1.1.5.RELEASE/css/manual-singlepage.css new file mode 100644 index 00000000..303192a8 --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/css/manual-singlepage.css @@ -0,0 +1,6 @@ +@IMPORT url("manual.css"); + +body { + background: url("../images/background.png") no-repeat center top; +} + diff --git a/spring-cloud-gcp/1.1.5.RELEASE/css/manual.css b/spring-cloud-gcp/1.1.5.RELEASE/css/manual.css new file mode 100644 index 00000000..20cf07da --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/css/manual.css @@ -0,0 +1,342 @@ +@IMPORT url("highlight.css"); + +html { + padding: 0pt; + margin: 0pt; +} + +body { + color: #333333; + margin: 15px 30px; + font-family: Helvetica, Arial, Freesans, Clean, Sans-serif; + line-height: 1.6; + -webkit-font-smoothing: antialiased; +} + +code { + font-size: 16px; + font-family: Consolas, "Liberation Mono", Courier, monospace; +} + +:not(a) > code { + color: #6D180B; +} + +:not(pre) > code { + background-color: #F2F2F2; + border: 1px solid #CCCCCC; + border-radius: 4px; + padding: 1px 3px 0; + text-shadow: none; + white-space: nowrap; +} + +body > *:first-child { + margin-top: 0 !important; +} + +div { + margin: 0pt; +} + +hr { + border: 1px solid #CCCCCC; + background: #CCCCCC; +} + +h1, h2, h3, h4, h5, h6 { + color: #000000; + cursor: text; + font-weight: bold; + margin: 30px 0 10px; + padding: 0; +} + +h1, h2, h3 { + margin: 40px 0 10px; +} + +h1 { + margin: 70px 0 30px; + padding-top: 20px; +} + +div.part h1 { + border-top: 1px dotted #CCCCCC; +} + +h1, h1 code { + font-size: 32px; +} + +h2, h2 code { + font-size: 24px; +} + +h3, h3 code { + font-size: 20px; +} + +h4, h1 code, h5, h5 code, h6, h6 code { + font-size: 18px; +} + +div.book, div.chapter, div.appendix, div.part, div.preface { + min-width: 300px; + max-width: 1200px; + margin: 0 auto; +} + +p.releaseinfo { + font-weight: bold; + margin-bottom: 40px; + margin-top: 40px; +} + +div.authorgroup { + line-height: 1; +} + +p.copyright { + line-height: 1; + margin-bottom: -5px; +} + +.legalnotice p { + font-style: italic; + font-size: 14px; + line-height: 1; +} + +div.titlepage + p, div.titlepage + p { + margin-top: 0; +} + +pre { + line-height: 1.0; + color: black; +} + +a { + color: #4183C4; + text-decoration: none; +} + +p { + margin: 15px 0; + text-align: left; +} + +ul, ol { + padding-left: 30px; +} + +li p { + margin: 0; +} + +div.table { + margin: 1em; + padding: 0.5em; + text-align: center; +} + +div.table table, div.informaltable table { + display: table; + width: 100%; +} + +div.table td { + padding-left: 7px; + padding-right: 7px; +} + +.sidebar { + line-height: 1.4; + padding: 0 20px; + background-color: #F8F8F8; + border: 1px solid #CCCCCC; + border-radius: 3px 3px 3px 3px; +} + +.sidebar p.title { + color: #6D180B; +} + +pre.programlisting, pre.screen { + font-size: 15px; + padding: 6px 10px; + background-color: #F8F8F8; + border: 1px solid #CCCCCC; + border-radius: 3px 3px 3px 3px; + clear: both; + overflow: auto; + line-height: 1.4; + font-family: Consolas, "Liberation Mono", Courier, monospace; +} + +table { + border-collapse: collapse; + border-spacing: 0; + border: 1px solid #DDDDDD !important; + border-radius: 4px !important; + border-collapse: separate !important; + line-height: 1.6; +} + +table thead { + background: #F5F5F5; +} + +table tr { + border: none; + border-bottom: none; +} + +table th { + font-weight: bold; +} + +table th, table td { + border: none !important; + padding: 6px 13px; +} + +table tr:nth-child(2n) { + background-color: #F8F8F8; +} + +td p { + margin: 0 0 15px 0; +} + +div.table-contents td p { + margin: 0; +} + +div.important *, div.note *, div.tip *, div.warning *, div.navheader *, div.navfooter *, div.calloutlist * { + border: none !important; + background: none !important; + margin: 0; +} + +div.important p, div.note p, div.tip p, div.warning p { + color: #6F6F6F; + line-height: 1.6; +} + +div.important code, div.note code, div.tip code, div.warning code { + background-color: #F2F2F2 !important; + border: 1px solid #CCCCCC !important; + border-radius: 4px !important; + padding: 1px 3px 0 !important; + text-shadow: none !important; + white-space: nowrap !important; +} + +.note th, .tip th, .warning th { + display: none; +} + +.note tr:first-child td, .tip tr:first-child td, .warning tr:first-child td { + border-right: 1px solid #CCCCCC !important; + padding-top: 10px; +} + +div.calloutlist p, div.calloutlist td { + padding: 0; + margin: 0; +} + +div.calloutlist > table > tbody > tr > td:first-child { + padding-left: 10px; + width: 30px !important; +} + +div.important, div.note, div.tip, div.warning { + margin-left: 0px !important; + margin-right: 20px !important; + margin-top: 20px; + margin-bottom: 20px; + padding-top: 10px; + padding-bottom: 10px; +} + +div.toc { + line-height: 1.2; +} + +dl, dt { + margin-top: 1px; + margin-bottom: 0; +} + +div.toc > dl > dt { + font-size: 32px; + font-weight: bold; + margin: 30px 0 10px 0; + display: block; +} + +div.toc > dl > dd > dl > dt { + font-size: 24px; + font-weight: bold; + margin: 20px 0 10px 0; + display: block; +} + +div.toc > dl > dd > dl > dd > dl > dt { + font-weight: bold; + font-size: 20px; + margin: 10px 0 0 0; +} + +tbody.footnotes * { + border: none !important; +} + +div.footnote p { + margin: 0; + line-height: 1; +} + +div.footnote p sup { + margin-right: 6px; + vertical-align: middle; +} + +div.navheader { + border-bottom: 1px solid #CCCCCC; +} + +div.navfooter { + border-top: 1px solid #CCCCCC; +} + +.title { + margin-left: -1em; + padding-left: 1em; +} + +.title > a { + position: absolute; + visibility: hidden; + display: block; + font-size: 0.85em; + margin-top: 0.05em; + margin-left: -1em; + vertical-align: text-top; + color: black; +} + +.title > a:before { + content: "\00A7"; +} + +.title:hover > a, .title > a:hover, .title:hover > a:hover { + visibility: visible; +} + +.title:focus > a, .title > a:focus, .title:focus > a:focus { + outline: 0; +} diff --git a/spring-cloud-gcp/1.1.5.RELEASE/ghpages.sh b/spring-cloud-gcp/1.1.5.RELEASE/ghpages.sh new file mode 100644 index 00000000..55e76be1 --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/ghpages.sh @@ -0,0 +1,330 @@ +#!/bin/bash -x + +set -e + +# Set default props like MAVEN_PATH, ROOT_FOLDER etc. +function set_default_props() { + # The script should be executed from the root folder + ROOT_FOLDER=`pwd` + echo "Current folder is ${ROOT_FOLDER}" + + if [[ ! -e "${ROOT_FOLDER}/.git" ]]; then + echo "You're not in the root folder of the project!" + exit 1 + fi + + # Prop that will let commit the changes + COMMIT_CHANGES="no" + MAVEN_PATH=${MAVEN_PATH:-} + echo "Path to Maven is [${MAVEN_PATH}]" + REPO_NAME=${PWD##*/} + echo "Repo name is [${REPO_NAME}]" + SPRING_CLOUD_STATIC_REPO=${SPRING_CLOUD_STATIC_REPO:-git@github.com:spring-cloud/spring-cloud-static.git} + echo "Spring Cloud Static repo is [${SPRING_CLOUD_STATIC_REPO}" +} + +# Check if gh-pages exists and docs have been built +function check_if_anything_to_sync() { + git remote set-url --push origin `git config remote.origin.url | sed -e 's/^git:/https:/'` + + if ! (git remote set-branches --add origin gh-pages && git fetch -q); then + echo "No gh-pages, so not syncing" + exit 0 + fi + + if ! [ -d docs/target/generated-docs ] && ! [ "${BUILD}" == "yes" ]; then + echo "No gh-pages sources in docs/target/generated-docs, so not syncing" + exit 0 + fi +} + +function retrieve_current_branch() { + # Code getting the name of the current branch. For master we want to publish as we did until now + # https://stackoverflow.com/questions/1593051/how-to-programmatically-determine-the-current-checked-out-git-branch + # If there is a branch already passed will reuse it - otherwise will try to find it + CURRENT_BRANCH=${BRANCH} + if [[ -z "${CURRENT_BRANCH}" ]] ; then + CURRENT_BRANCH=$(git symbolic-ref -q HEAD) + CURRENT_BRANCH=${CURRENT_BRANCH##refs/heads/} + CURRENT_BRANCH=${CURRENT_BRANCH:-HEAD} + fi + echo "Current branch is [${CURRENT_BRANCH}]" + git checkout ${CURRENT_BRANCH} || echo "Failed to check the branch... continuing with the script" +} + +# Switches to the provided value of the release version. We always prefix it with `v` +function switch_to_tag() { + git checkout v${VERSION} +} + +# Build the docs if switch is on +function build_docs_if_applicable() { + if [[ "${BUILD}" == "yes" ]] ; then + ./mvnw clean install -P docs -pl docs -DskipTests + fi +} + +# Get the name of the `docs.main` property +# Get whitelisted branches - assumes that a `docs` module is available under `docs` profile +function retrieve_doc_properties() { + MAIN_ADOC_VALUE=$("${MAVEN_PATH}"mvn -q \ + -Dexec.executable="echo" \ + -Dexec.args='${docs.main}' \ + --non-recursive \ + org.codehaus.mojo:exec-maven-plugin:1.3.1:exec) + echo "Extracted 'main.adoc' from Maven build [${MAIN_ADOC_VALUE}]" + + + WHITELIST_PROPERTY=${WHITELIST_PROPERTY:-"docs.whitelisted.branches"} + WHITELISTED_BRANCHES_VALUE=$("${MAVEN_PATH}"mvn -q \ + -Dexec.executable="echo" \ + -Dexec.args="\${${WHITELIST_PROPERTY}}" \ + org.codehaus.mojo:exec-maven-plugin:1.3.1:exec \ + -P docs \ + -pl docs) + echo "Extracted '${WHITELIST_PROPERTY}' from Maven build [${WHITELISTED_BRANCHES_VALUE}]" +} + +# Stash any outstanding changes +function stash_changes() { + git diff-index --quiet HEAD && dirty=$? || (echo "Failed to check if the current repo is dirty. Assuming that it is." && dirty="1") + if [ "$dirty" != "0" ]; then git stash; fi +} + +# Switch to gh-pages branch to sync it with current branch +function add_docs_from_target() { + local DESTINATION_REPO_FOLDER + if [[ -z "${DESTINATION}" && -z "${CLONE}" ]] ; then + DESTINATION_REPO_FOLDER=${ROOT_FOLDER} + elif [[ "${CLONE}" == "yes" ]]; then + mkdir -p ${ROOT_FOLDER}/target + local clonedStatic=${ROOT_FOLDER}/target/spring-cloud-static + if [[ ! -e "${clonedStatic}/.git" ]]; then + echo "Cloning Spring Cloud Static to target" + git clone ${SPRING_CLOUD_STATIC_REPO} ${clonedStatic} && git checkout gh-pages + else + echo "Spring Cloud Static already cloned - will pull changes" + cd ${clonedStatic} && git checkout gh-pages && git pull origin gh-pages + fi + DESTINATION_REPO_FOLDER=${clonedStatic}/${REPO_NAME} + mkdir -p ${DESTINATION_REPO_FOLDER} + else + if [[ ! -e "${DESTINATION}/.git" ]]; then + echo "[${DESTINATION}] is not a git repository" + exit 1 + fi + DESTINATION_REPO_FOLDER=${DESTINATION}/${REPO_NAME} + mkdir -p ${DESTINATION_REPO_FOLDER} + echo "Destination was provided [${DESTINATION}]" + fi + cd ${DESTINATION_REPO_FOLDER} + git checkout gh-pages + git pull origin gh-pages + + # Add git branches + ################################################################### + if [[ -z "${VERSION}" ]] ; then + copy_docs_for_current_version + else + copy_docs_for_provided_version + fi + commit_changes_if_applicable +} + + +# Copies the docs by using the retrieved properties from Maven build +function copy_docs_for_current_version() { + if [[ "${CURRENT_BRANCH}" == "master" ]] ; then + echo -e "Current branch is master - will copy the current docs only to the root folder" + for f in docs/target/generated-docs/*; do + file=${f#docs/target/generated-docs/*} + if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then + # Not ignored... + cp -rf $f ${ROOT_FOLDER}/ + git add -A ${ROOT_FOLDER}/$file + fi + done + COMMIT_CHANGES="yes" + else + echo -e "Current branch is [${CURRENT_BRANCH}]" + # https://stackoverflow.com/questions/29300806/a-bash-script-to-check-if-a-string-is-present-in-a-comma-separated-list-of-strin + if [[ ",${WHITELISTED_BRANCHES_VALUE}," = *",${CURRENT_BRANCH},"* ]] ; then + mkdir -p ${ROOT_FOLDER}/${CURRENT_BRANCH} + echo -e "Branch [${CURRENT_BRANCH}] is whitelisted! Will copy the current docs to the [${CURRENT_BRANCH}] folder" + for f in docs/target/generated-docs/*; do + file=${f#docs/target/generated-docs/*} + if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then + # Not ignored... + # We want users to access 1.0.0.RELEASE/ instead of 1.0.0.RELEASE/spring-cloud.sleuth.html + if [[ "${file}" == "${MAIN_ADOC_VALUE}.html" ]] ; then + # We don't want to copy the spring-cloud-sleuth.html + # we want it to be converted to index.html + cp -rf $f ${ROOT_FOLDER}/${CURRENT_BRANCH}/index.html + git add -A ${ROOT_FOLDER}/${CURRENT_BRANCH}/index.html + else + cp -rf $f ${ROOT_FOLDER}/${CURRENT_BRANCH} + git add -A ${ROOT_FOLDER}/${CURRENT_BRANCH}/$file + fi + fi + done + COMMIT_CHANGES="yes" + else + echo -e "Branch [${CURRENT_BRANCH}] is not on the white list! Check out the Maven [${WHITELIST_PROPERTY}] property in + [docs] module available under [docs] profile. Won't commit any changes to gh-pages for this branch." + fi + fi +} + +# Copies the docs by using the explicitly provided version +function copy_docs_for_provided_version() { + local FOLDER=${DESTINATION_REPO_FOLDER}/${VERSION} + mkdir -p ${FOLDER} + echo -e "Current tag is [v${VERSION}] Will copy the current docs to the [${FOLDER}] folder" + for f in ${ROOT_FOLDER}/docs/target/generated-docs/*; do + file=${f#${ROOT_FOLDER}/docs/target/generated-docs/*} + copy_docs_for_branch ${file} ${FOLDER} + done + COMMIT_CHANGES="yes" + CURRENT_BRANCH="v${VERSION}" +} + +# Copies the docs from target to the provided destination +# Params: +# $1 - file from target +# $2 - destination to which copy the files +function copy_docs_for_branch() { + local file=$1 + local destination=$2 + if ! git ls-files -i -o --exclude-standard --directory | grep -q ^${file}$; then + # Not ignored... + # We want users to access 1.0.0.RELEASE/ instead of 1.0.0.RELEASE/spring-cloud.sleuth.html + if [[ ("${file}" == "${MAIN_ADOC_VALUE}.html") || ("${file}" == "${REPO_NAME}.html") ]] ; then + # We don't want to copy the spring-cloud-sleuth.html + # we want it to be converted to index.html + cp -rf $f ${destination}/index.html + git add -A ${destination}/index.html + else + cp -rf $f ${destination} + git add -A ${destination}/$file + fi + fi +} + +function commit_changes_if_applicable() { + if [[ "${COMMIT_CHANGES}" == "yes" ]] ; then + COMMIT_SUCCESSFUL="no" + git commit -a -m "Sync docs from ${CURRENT_BRANCH} to gh-pages" && COMMIT_SUCCESSFUL="yes" || echo "Failed to commit changes" + + # Uncomment the following push if you want to auto push to + # the gh-pages branch whenever you commit to master locally. + # This is a little extreme. Use with care! + ################################################################### + if [[ "${COMMIT_SUCCESSFUL}" == "yes" ]] ; then + git push origin gh-pages + fi + fi +} + +# Switch back to the previous branch and exit block +function checkout_previous_branch() { + # If -version was provided we need to come back to root project + cd ${ROOT_FOLDER} + git checkout ${CURRENT_BRANCH} || echo "Failed to check the branch... continuing with the script" + if [ "$dirty" != "0" ]; then git stash pop; fi + exit 0 +} + +# Assert if properties have been properly passed +function assert_properties() { +echo "VERSION [${VERSION}], DESTINATION [${DESTINATION}], CLONE [${CLONE}]" +if [[ "${VERSION}" != "" && (-z "${DESTINATION}" && -z "${CLONE}") ]] ; then echo "Version was set but destination / clone was not!"; exit 1;fi +if [[ ("${DESTINATION}" != "" && "${CLONE}" != "") && -z "${VERSION}" ]] ; then echo "Destination / clone was set but version was not!"; exit 1;fi +if [[ "${DESTINATION}" != "" && "${CLONE}" == "yes" ]] ; then echo "Destination and clone was set. Pick one!"; exit 1;fi +} + +# Prints the usage +function print_usage() { +cat </` +- if the destination switch is passed (-d) then the script will check if the provided dir is a git repo and then will + switch to gh-pages of that repo and copy the generated docs to `docs//` + +USAGE: + +You can use the following options: + +-v|--version - the script will apply the whole procedure for a particular library version +-d|--destination - the root of destination folder where the docs should be copied. You have to use the full path. + E.g. point to spring-cloud-static folder. Can't be used with (-c) +-b|--build - will run the standard build process after checking out the branch +-c|--clone - will automatically clone the spring-cloud-static repo instead of providing the destination. + Obviously can't be used with (-d) + +EOF +} + + +# ========================================== +# ____ ____ _____ _____ _____ _______ +# / ____|/ ____| __ \|_ _| __ \__ __| +# | (___ | | | |__) | | | | |__) | | | +# \___ \| | | _ / | | | ___/ | | +# ____) | |____| | \ \ _| |_| | | | +# |_____/ \_____|_| \_\_____|_| |_| +# +# ========================================== + +while [[ $# > 0 ]] +do +key="$1" +case ${key} in + -v|--version) + VERSION="$2" + shift # past argument + ;; + -d|--destination) + DESTINATION="$2" + shift # past argument + ;; + -b|--build) + BUILD="yes" + ;; + -c|--clone) + CLONE="yes" + ;; + -h|--help) + print_usage + exit 0 + ;; + *) + echo "Invalid option: [$1]" + print_usage + exit 1 + ;; +esac +shift # past argument or value +done + +assert_properties +set_default_props +check_if_anything_to_sync +if [[ -z "${VERSION}" ]] ; then + retrieve_current_branch +else + switch_to_tag +fi +build_docs_if_applicable +retrieve_doc_properties +stash_changes +add_docs_from_target +checkout_previous_branch \ No newline at end of file diff --git a/spring-cloud-gcp/1.1.5.RELEASE/images/GCP on Spring Initializr.png b/spring-cloud-gcp/1.1.5.RELEASE/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/spring-cloud-gcp/1.1.5.RELEASE/images/Stackdrive trace.png b/spring-cloud-gcp/1.1.5.RELEASE/images/Stackdrive trace.png new file mode 100644 index 0000000000000000000000000000000000000000..b024ed922f4174f5949933bbde4421bca0a32045 GIT binary patch literal 125360 zcmeFZRdgK9vMwlQW|qZF7FujEGc#I}#mvxRvdFTSSr)XInHeo+W@aAwocr&)_qFF~ z)~tD$(@(uBDzmb_h>VQL{G!4Xp`gdg*S6H|c;vjU+;{kTVHF{p z=v#K4M&a)*B~rgceNC`=okj__WR2m06ua<7`O!3U$l8@oV9DPh!D(j-JDE{r0Spt+ z1<_>ECH>L;X5RhcZ8_Ftu+C&SDt&&|b?oB8e0MC}Q@~Y>NnpiOm4%s^8H5od3j~r{ z5EM?q5A5InQnLc2hf3Q@8UH4HZ$b|W)#pa?e>D1EUB%~vfE$}G9G9~Fy)n3P3)Ej8 z`QCa+$q!IjW#yul@*jiwU;Qfh0a*0F|9zDI5`dY~baZHFa`4c}{_!fmZ|dm40sd7W z2&lp^Fes%@P5)8wS0Iw4!Tt|y&@s7Oj&W8!&-juGK26fQ5&YL%jxYm6Bd|s*(6E^p z!Hs*Z8$$l$6>x;t*E<95_r(RmA>pwiV;wxsd>(cZ?Yj{(>XiD8m;yZDqX zJ{|j=^1t1cbqoOo1@%QV-AtNK%rerUXv?bP&FX9Qf=c4^kyYI+U3*luyZ4YsB!w64 z`>*f*`vh9LMGNI7KfcLKKoLt@75*SI7yxDS@jlA<>;Hl9f2$ZUh@Di`k+g``5k<%Y z^;XBm09}EBc;v9;atzaHnfWXw>l_tFE*xtrvBeu33zKUt48sD|*GI$Ya;eR+kK(KI zkBueqgXzEYce}l;uuc~W(ht^PF>itLqDaXdeN0em!01A@rCqyw_CUbr5Xt%x%uc5? z+GELCw=1=biEAbwtvRWct4@mR0Rjm8eGCecyX0&_VXJw|S)hUGd@jUFE|~r;G!gBVFagWN(9Y zJGHpLqoZeO?a&cpZ}r{i_%;m(;`)!wmgrihG<+FN8eM-=b=6MKxqLhNpCF~&-{R&j z_@B=U`!Bj*%(7BDHYt8+tTdpks$MAs>3MlhD;{eqMr5ujr+?}P@ws>rcH17M^)lNc z<@)%r=5eNErS*e_XC>m>8h1FKC_n3ruRZMR#x5wiQ9p`DC9!QL2(9IA&g(87?{?g! z7j#K4CY*wS7(9*yMI5sU4*`dJ1*I5mR9<$+X~pN@C=*^tVYQ8ll18TDNQ_NM@SiK7 zsvOx==n|%DXVPdUnt+_23c=nbriXY+DM-pK&g7uomigv{-BNP5!w$)a>&GNMaA zd$d=0t%1Om3G8w8sCO(gqodk3y*I4!U~cf~R3~()ocdh%@_cTz5mTYOv}dJU+WwB+ z(eKzZbb~#C+{?_E0ZR)w?07Md8*%Y)EWKy22uiT*OOTxumT@A$%)tx3HGZl`m9H~Lh#VYy?8-}5Gj#x{b`3srt$vOol|LgvY_2) z_dJn&XZ(qs?Q#$y!=V$sg@q}t$IB0w`)gTKA?Y5}h&w?STajT=&((?8Pzwd!D4KRx z`E>4}wdY+xxm-=d=P+ zhct^nu9p-^K5ZaBtyc}kkYjhv@?fKD(St-!Ly0DQ7=$}!B`*g?NJFE8pi zswBJjxU--y)efzD?@~b*^410aerf*P&cLG3g!X-2CBWd|DUnJEERxbxESyvK}DtAGs(J$$)BdoXJ5wy}orsYrFc1WMtrk|L zh$V@W{T>plHJ`7xXsXy)hdVTGj$zAE2o@;R+{i6@YeSW9*qzSKp|*^Sl^90v|Wk zFNELroi>{SmZYxCG@QB-6RkO8jBn@bd#(6)&aoe{zhpdUsD8+NmhHd8zdtNjp698D zyK7|X73Li8>`aW>DrpXG`vPv9YBK&NAxu&6_U=r36v;iV#$7;XZ(ulhTL@WX`zLWd z5Rp}q{y1^{5^UT^T0d$vUvsd?fZZ$bD`a+@4&u^)f9!d6R zIVq=G31xeT8lcmMu#WlMAfPj?gW3!cg;;BwSE=XLoNU7;hp9YbZ3I1q0_@H2EuZ$q zwV)GworB(CA-Vu38e6|esL1Th z+A^5GgEpk$$Ec*NTVHhsljn4z%yNBaMzg;!H7^YdT!Y%6$;f`k~_UXPiL$8HIHOJbSyF#_LlzWcu z&`BU;bvnAcD)6k~sxCrsK^2W)K~IBh?0dT*lA5mmx200sREl3M(;>jOIg1t_Jd}X+ zUL|JW^vk>_N|8<_2=a`&Hs2%x9b@OkS!UqtDz1U(?z;V3YQT}2$`-f%axCk782>fX z&mQZNDjTzlktH)g%GYJrJ+Ni+sj{CUqX)VB+&)myipi(moTpcpAn#MD4_*Q-QU$Kb zig*&4fR2Wm2tsfnsVF+Ts$XbKqyc%BD(Bj9*f_Z-)da zTIh$2j~^D&G@8qC>qhvpsJJI4lLd$3vlde;U96k>15lU35usBmjIEpCgJ&B-Pc$MJ zMrq?tkh7+1z>CSZXRS5P238eNCh9Y)ZRdm=klQ9u!5eou?U`7qne3==1w#o7Q;4~ z6t+I%rw)&b-{^Q`7*$YWsd058DF&|^4V$eUoi!`!Vjs39tc}X8nd#JnMU+L<&|~)l zr5E>JQ4*;o!@u+~#=FeW`K2I6X-?C+ ztbeJpl{NXaHNlk+fnj7oJa?wO&WA9=JO;#Yjbc&lSM7umDdtktEu*3U{&6?}lqQ`B zZZ-^28?ok81ODhe0$$%hf<3A_(cNcDw(cF~^~?XJeZJBM z^((xJ(N^TUH9DGY*v$rR!GzCvVx>hr(!yk*$5BL%RkHHF+xllKOw$Bw^TlW1itaK& z+c$dtN>7H>=eaK>dpcjI3oit2M%|o4(7zb*%hZSAjZIMPji=+Imy2<=w(M^{Wo9!zjxum$&v>HLx zC2<14&72QJCiLju70;w28E$Gj@GL&9xm5Yg2SUD=xN`}@0mSU;{wPRZ?5f6gb3Xo zW%a9HPu@;!eJx+!ss)}{kJ_Zk(cG>^Im=c}H{m5yM}vvq?)$myHzAsun=iuTy};)5 zeffT8f+gxHFbTT$y_okSX8gvGTOwcN1zu`??B2}j_b*hNZP;OyLa&WoN!ZM|iQQCFFcc6Qth`8Q%-m)nSVz&kN=d*&?~;BXaX1>FFxd@SqM? zpy%Ni5}#iSCMw2_b;J0a39YA(QYiu>yD#C(7fGhF1qFT3kMY(ab* z++3r~GGhXdur3%KroK1Uy?2%pt!+8B@sPp4?LBFkZg=qwexhcd-sRQpp0l>`^19U> zZX6G@2-uDxOXpWFdR95z_yReH#o$-(gS6^%0g>VUgpuLn3^K<_-*eI(ED5rAK6IKp z?{0_G<}{vf7dbEB*EZeQ?!Q;Xft2+&^65IAKLmO0q_^4@8AN*5$lWE>oN_d@Wmnbc z<|Z-A_YIa8jVDMwDh+&B(rvwMxZ)te3Z>*;ld8=O$)>3hjh)Kl;%-eKO25S$$~!4w z=~OGy_kPL?s+SUJn2d&b3zB?tSehe7YUid8uU74&3Cr>%vq5VBGeS}UgeETDYZXyy zu!NcyYU}t%zEP_Ml6dB9{EA>Xq=$KH$R!yZwebL@DHA!mV4rZ3Y_0MSO$nzf9BxCK z6lt@ba!KGQpjAx#xBMC2#lOm>^IP@gTQ<$khj{k}e&*Z@_>A`?>9}b_dC4Ub7lV$3wL>N+7vB79o$)n*mzyn@=*Z=`E2@@ODZSb7hZ;e9 zE57++C}DG*p?7*zR9WSeSOpH$cHg&oLr5hyC+FkfUoD8uF4o;z>|wWKgGrfXZrTev zM-yq^l)~4?EupNOMT3uV@gjee%~u&i&n`GSIa7Yj9Hd-~^0SaA(ywPA1YtKaKVzG$ zp_ulRJrnFV8G)xjbcbT{k<8WxSeyOpV=!wV+N0H@gdnIn22nGvXXhC+fwz&6qbU`$2Vylpr z2MPlIXIht8=dDh}?<_<=?{5;3vuBG@d~!6tR6(Z{XZPbVP?k#*)^VeHu*I6&ibqd{ zTQ|EH7zY5xZbw=ROo77oFEv84b;kG8VaZngE|Yyn{Yc?EiDmO1#fex>gytF{gzxvK z%L@0;A`xKTy_8#MD!)Y4;Q^ZD-`mltD~{o2m^9Y{rW~&giN4 zb#Lm38FJh|IOsOCAX3v8z`o%OH;A&h;iq$3?fizRQQHJnG8x_Pn%+~wjz)Ap5<~st z$L0O2@%(*BwZ^Oh{8?3O{YggQTKAzivgs?oh?Cm`^tdbD+5-JlTS&nfp|E^yp!jK- z>i&eK%%ECV86J)uFYl2merr7|_FU#46a?lrkZT z%uHReaJh>#jL?XC(Z}iN@1#SYepF;pwZ}$59gd)UjfTD?L)b)8Cz*@M=y@G^4jW{w`2znxK6)X z?exnydX>4I+;VADFESMTb4f7NNn6aj(PD& z8DUIhV~7-B zE0DLzCDr_~PpbsFz{FC_()$#zuIx#jw|dmnc^T#A=XazHzXr+~F}7m9{qTqfmgdQP z_AJew-kuw(WG*Mc08rd027!KQ9-aJz+m>m`TkfJo%UNVz^CzcyU6=hQkq(J&%ck&m zr2|gm!}};xo$IN`DCe)eQK~=YH=+9p^Ja$roS&HgAY5~>eyK%OS9fHN(pv2x6VP1f zBwCl-nIf#>U#1t7$C1?uCox8%xaxH(?jl%(lUp#4cP1@@g_J&X+ z5!w|}J5`luWyaN7XbBBhu zYM!!At=xWi7blEk*_U{*oTDe=^3h>iCA-?TCJ`xW>RWBW==0T2^oP3e_S5GL^A~zv zd*YNx`n4c$>pw?Q6$BEEy|+l0Ik^b(z{1`3bnKEN+O?`t>a&rv&oa9JqN)wf^MPHx zS1Irzj;I7GcF!M0KCno^xr@l6+K^!ln13?*MDUGtE~-wq*& z-jV_8k)rd?W|fV_OR?$t@|9U6OE5v|NHZ>T+)1B_ zSI$DBxc|z7Z@VLo7y;35$A{Mpq+hhdYUA8blX2@&4EK%!zr#_0FP!*htowv6xXo9? z>qW@QVxYBwNsqK`9WQ@MDd?u{^EgyTRpG~RP0;EgnGP!>%4Cpr>YKp~8}8@vm5`h{ z>{4WjfSszgq*XKP@yH4MWp-0KEo^Q!p4r-P*4-NvyraOz@QxbVIBb;=f31ZM(Abp* z{%~q@y?U&4UBdZuKO00IYa8oe9CrShh~&ebSw>jdBi&ve5De;&do81>MMW2*6>fAw zUG0l=7nZJ4*s?NZfn>MYSvJ`Lx(K$UB)zI?LPAMYHZ;B!8R}$Y;Ma7*{dwrJY%IP4 z#Lvv6SnJmH^hxN;@d*7Nti865?(`rU=~Ut^B6FU<9j^TO2gddMj754Ua1 zJ>WvvFcI;O0J3}%2h!P+&gK}VzmPjo_}GYjKnRnqWT`c5@^}{Z2W8y^A)ZTnA&!%= zt&$}AHfNuAzt8Y4RLflZp1Mc@zd17@3do<{Z+MFF6Ui^eitp8_sH%RJX%mPweYo72 zt;`9TJD4u4{5nH>Wb3Ba+S)1%_5~9Yb9U=Hl{}JnD++6bTAo0=zJp}b_b|6KfoJD^ zy1R2$-CzC5pE~`y3064*!CWkjJk3dJmNAL6sy3!M5LQw>Rc8DcChPna^&eVDH9n>R zxY%sLCN(fBH3(4G29{+11_O2d zchpr4Rh=nX-Y5Gawp`W`AMD*OB0f#hTyy_Ly})Tel?Upp%?yD~T`AsO9fHAGZvVv9 zp@qQ=MNzEZ>(*XrhRy*p#-5!9#0Kk{9Ws-oWVC$h{6%#C#bH&@{a8BMPZ3}n&))j^ z`>K8MLb)n76SHbVu`?)wk=4P5C4rocLd2Yas;{!J)bp?WuT1B?iAjOP61X{cVy{5c0#CsOr7NyG*k)>3Y*W7pS@=L4D` z$@K1!43O!LhEI`}TGd&|1d}Q5csw$YaII_%78hyR2OX`jJ)sg}y zIBA^rivK(ml(4o4^Ga8;@e?4Jh$>1}9!yv1q0|uzSt}fQxao^gcgl4P%TeRkBXF*% z7ThCz$8j>!bBmpiT+uOZY2~h})|htNFdooQA3^T}CJ;aAMvs`7=9DgMP2Wh)bq{ei z%Ok82X@oVO`C_<6#L}flZ(K~+p0C^(kEiOy1vzIpC5MFWufVvp1OAFT3a_h z>KfGH3BY%xQ&mM9X*rtwS}F5zy|OfEzwNs|IeMpEIo5LvJ(R`c-X%kwTFWQ`d9%j{b`CAR&YuZu-)5T-=Tmt70Zq@ARanEO;Ez)bH_`ua0b;Wq zqc!IiJ5Zg<-BBw0GB?zDM9scKwACG^9^B%CB)mn7QJ)abn_Op=TTuNh{fIvSb$GEq z#^;6sg|_x6{X7GX+HwyZHSTkbn4&oP zd?`((){pw(c`MNH@!Gg8>TtZ!CYhNcD}5ev1TO7))v}&icZ$0Y)ZWR+t@Xiqqd%;c zEhDfPx?W$NLI|jK!Jlq`zKHcph^Se#Uno5s!ie;KZ=aMQ$&B+qmEK6lC+a3VElQct zd&F93ohDL>_>fXl?jqx#eSB8FcZ<&Ky0#DOdiu~P917KH$3u9a52z}Rxdb;fibB2C z?8Xbm7JIIWzptg4Y}HWgk+l(0O1I+{f7QG?M;g!S>EA3|WoJ1HITVcG^s{3FWw-%n z%fgFP#;CKV(bp{YMAAhd(HbOf0Akufx@u%>! z7JffdI&$i>RQLXy4FkQpkhQ_Jxrm--VYsAW2fH$`5}VBE$40Y@Ya2%iT)5rZJT$2@ zTqn_JqyC74OYvN)GW~O%Hkp>sz@8JjT@c%JktKQgO>E%xW^yLp{6o||v*I^P=y7<2 z(KVTI^@Mugfx{MejoN*J+gk#0?dN#aH~r^Jt{iAk)DHZovhkSZ`}bq}q+77$Z2#ck zyaH|8U?&EPn~As|&qP=cn6cT57zP^5qf&;Wn3Qc4b7dJ@Dan$SCn^TYrlCXz4|{0c zA5BFW&ihvVDBE7J+j$uZ+pV#5Z^{Jd)F?iq0_YTP^OCIeAvQ%;NzW5rd!;8$t&Z6q zoY6zVd3#YxTlCurQcU{zv=meU>?M}xOh<|FC>Y=hLq|85s`t-7I`H}W&+qtJC0ZQd z%?-Fg12`{5%d2gyf0(rukak%J*lFu?hP%u*!dS zuBd}~Zc$xGty((0#lE)8*CNP@W#`vg*-fOG+x{_j}h)xx@F{^mx$) z{M%m+EY^QMG^oq8=!Z)dv@Bc++zscxpC;+GFi5)<1wKY9uE4h~sp&%>pNe!nGX4_G zvE)=(Gmtf6oEz!@5Lt#|M3Oh%<+n4Z9}6wc@dMI%T)(`wMX04l2HWzo&{K0fYEpQD zA0oZzJB1H+h!!TCG9G`&oA|*0b6{A}vL%@4!qL7UO61P6H^ezr(#hwD#VOq05Jdoq zl>dXN_*|r3FzgxocXMAa&Uj#{FziL4e_?Ws%de*Md1Jzgf**0@zlMwqc7n96Zs-i1 z&gAqRjpCI*-r=9A0R<-m3t)wh#Ku8cffiOp`W9KuPatZu@?yT^=ueBpJ!`xPGIMM5 z-^RQ@9+P=gFHFo+XCGaZkMO3h>@Ox_vKkV|-eIuHf)|$;PNZ|b;aIumDjo{;pK`Nl z>sI#kAKLI%Ju+P|Sn6@&4oxj~0lSsw$-k~R5bQ66EON7`DEY7GMPy_MYh)Bd!eACgQT|@Svx> z29}rior5eI^yQXrw~gUZOo}d;rc~@Qk7N!o^F*r?PqlB8z)|vSxTs~*VF~@R+wD`v zXSvsP{cv~>TN8@!QV%~|Z!6A>5=3=8*19oc3?t@;wMMNA=m(L2O1r;`@n;x>cwX4! zoB<8d3X;1BO!~x6SkDIE=1RPUvCIbv27Pyvvo}Iv-qZ@kxpPyeRa=3l`{oSh>G8PE zz^vl4@l*QZ=IEW(0sI1Yb4$bPh|*A(4VBqWdG_t93U2wLr2Bp^QEtO|@7c*H7dh2+ zr_~}^J8U$XaSRcWNNL=0zWJ<%y>Kw>jXMhXrv84&cFx}darcGe4RFk3bU&Kbk$Co! zKWwU}kzZsh0!FiulFbIV6XUDetO>4k3INx85a7kz6Je6QOxoNc&;zc4RZ9zR~sb^|ke=Jdzu{B9Q8sju+7u>`^idZVFS_5BOl_59nlDR$?0qx$cX zU|t%qUmW)gX1>8s`s{&~+__0oCf_kHT+RIo)%P#&yz5~yik^|Qt>B$D@0|?~IkoBk z9?ch8*hApu?7BsAQQ~M+$+eFh%S{cA>U3phs3qeaXecuPgx86Y37p#k1@ z`K|9d(K`-&RCYy;&{eq7=cdxU9eNN@i%i&YTi6y1-8-G86#`sdQ*gq$H>t^6y%Bb@ zRDfsD@MBWb=B=e{RBDwN%O?qvhM})#O=@$dD9+bXozxo%?@;FWj6c?zfOCP zb7x1Rk!_Ag+WHahuwEd@*87B}#QR8%cA|8==HN0cE3#!=W;{R@#ex?fv*y$b47hUF zeqK4u#xh3y4fzF~$T@PP`*vSoBb(_B)8=)aEM~@|l;D`5EbC@zP4I5*8Iu2&0oHkR z)V#P-P<}<@r zLNzI<<7DE%n8Ra*e2gl47QxS3iy;Oz0ztqPDxW52io-J-(Ol}VGR!sex^Jdv#;}!LZt6Hi_&3gCC;8wYW9`n1YqP>dj?AH*t%UE#(-X>hx+ycJ#^!hcQ z<{9B*Xn}jso{6Do?_=Xxpk%m?sGp3?7?Y31<&-oNA=(I9yAX{>f+#5}9W|v-tdh(! z6BdhE(mWNIsqbTK&q7rprhC((SRQQd4s)9=|@{~W)JQ2tGi!5BH zA?MdA+kV;4xI9YsZe^+R+qq;@M*V#0lxiIA>n;+^n~EZvcq486MMrm}m=efo9N$5? z((z-IQr2&p4297q6>xN$2$weB$p9u$Mnurdgm}Nim|7TIBs{=k6>wk&jbFk|J5i&x zP>`0onU{Q`O(+jBuLc^(HoN3UV3u==ic!*{pkre2H*RV>7$Y+G5lMa}`&&zc!TJ`Y z*)#omDC~?5n^OFA0I>*|Ic}PhLNFbH z`0^x_FEdy>CxoP$p(`{hvKzGTri&1wiLnsQR8;SZio7&mBmkii4-3U*k1 z@<5K!z;r+*to_x8fB1}Fg8g0&rAnKxlyvT>rtcZ{MzTz3XaoTZ9}EunS6h*#k5UV2 zT-QvLk{@k&Z&ff;d|iA#R}RvnU*&X|!0S{H8GqNc+ZF#0=Bkbt=%ld~$2EaOzEX7# zQLZlPjzYiD4Yo7(aBs7kvfE{6fA)g*#kZ#;X*?I|A$pIC`*Vce9M>m|>N9>F2oJ)rzz%wyR>(ksnD6nbueu#Gh7d`HK4xd>oZ2Np)?XMvD zDCETe5hTpte1BXT*`25Toszpl+CgNzGc{F8_3t#`0zre-7I!Y^LyX<=@n^U@jI;nv zr7Rst{sY-J^A4*b-+SNjV66(IYDGWC$k9GE)AWC6oWHrMb^wTXGHCGKswZ-Rge4eV_#V}W&m?zw$DLA?8iBm*{s7MtbQp^o`d3n{h0ztUh0c)_Zvo_RC0 zSQ-9(g#UV;;KDmiR_!(s1oU5*{tX{Ncertr0zw=`Mp~wQNu-|FL$4sDLf8$64 zE`O7bd6&{_|4KSGWqKzwFX)3o{FR=&^PB4IUdLnkSE}=n-fs$ZI|R(1obLDDhLqo> zmsYN`|G^;sM}P6Hzlqzvuqgk$#eW!HeBrxvXW_Jz{cmVdd%iQmhhEV>{2N67`!BWp zZ)x?4rsY3Ve+wB`R_Mn9Yf;a&^<}}f&5j< z`&&BhVpc};7d-wSDmdxsa+oQR5A3hz@s728mXU#hLDuunQM|vI2q;{9Gf0S1!V_l4 zhX^H(F;VSG1(Mt#syfxoIl`8VJ-8=BUCh4l0{r2q5ae~j(_mry4) zb!bc>Yl}kCmI}aD9r{tF1-egWD~Ce)hgt-|<&YgZc2P>lm_Q==@+#{Qk`zMBBvv4O z+%$yfg2f4{TIcwSVL5=kXRr&X5<48plUNc;@Ht;FY-sR#2HtD~+RfsinRg;THKNO#$VQOhgnmedHScMKBX>2~WC1ilhi{8Hk%Gn@eEl?1#C2uPlDsJ{ zyOU829~q{kK4bXeOVHC9K-1&((MS*3f3Ubzj>f$Z#lljU{4AJ&mliirAg>!$>O*oU z$#$hM-g2_`B>Lx?ARL_GFuQMmtOLxTpo2jbl$%ht8&qY|iZd0Ep2*?-ur)K$RpS|- z<}THqx61;!?$yvxkx?uxaMAoy1;fOMazs(B;K|ry1!o7A3v7maYv>ouKGcqg%s_8&HWUWvxS7qFv*M008?m8r+4IuNSJ{Y z=rk!U1K{~sqo0K3(A8#0{OCaxJc=rjs2$yXR`vOv2G79>W^XS82igoVL*kLO5OZLa z;3`zE^pDN|%eN2d3gSiL52%e};D%$0$B-D-cx!UvhW`{)w6=Q`Stbb+sk2n)2-{CD z;M#C06sRwe6A=0=&#jj=zws@+C_sbF5+j*bESAQlCNPd!6q!Mt2kH8xk1rg4qcacH zjS!Ap63~rbyNTr&W>Q4KG#d+FkUWU8T<3()v%sO6dmwg&XRTlGNZmOlLvm~{82eGI z6CzSb%maZxaa4+`MDCt`E0!fWr!qrmZxG8d?oHm^fL>dV4@(q`)`|mGqAt4^m`f4o zp8(T=b=O5z&d3Lr$pp86Elx}0ufIUYm?{l;%iZ3&quPKuVve(}pa_8JBZN5;`0S|4 zpq0svV!yXj_=pk>?7sXdc08p7!wr9f39e#GFv#Fs^m8p(r3)F~Nu-4#v>23}4N)-~ z;8%}$@Hx?sK`*fP!s0*L^xH~pB#)f4x%w1-TS+aJs*)U8k|aP z$DVt)-{Ch>T-$(@SV*J=k!(|&EI79uJEFJAVf-yvP;Ewh`?Vp2a6Ko>=!vo;2V?qfF}^>{RRGbaz!vgoFJ%~6NGvt zh4FCs&!->+lU!^-e>bIII$E(YNxvnePi#(3S^zZcH78UCie_{dRzJUSu8peN+pTjMFoAxfuCP>ADmfCDgg8)I&prSQc@&LOf!gH!II!f&tf&$Hmf(m|(L{Qdn ztVVN((?7$#_;f!~FvSG{(>Q)MCfmr@;_i+cG%pJa*+0!k?z`l?zsMtB14Mnkn7Td$#lTG zHCki$Sd176T7frwAlyMlvWcug*kQ#89k0@p(xEr^9Y*{6WvX8&uateDFzmhuSeQw9 zW*YslAZ=7b0SRt2$rBH{=#|vHp*o+$JTDBy(t4b-M1hEuL4wf{5#j3fk5+@G=#%NTik)T!nfa0HC_krMd+_J|&| zaGCwl4WalNpbX$C1;#bZ{-ZmAaWG^!=>6g-kp&-&uzp&FOxt|G!m0#A9}MIuUsXmg zo4k+4U;2pYBxEo8q4@;{N-OKDq(*isi5Mk9H;9OzqBIHK=t%LG4a9GQ7!fxZeB<|Y z?7OJSL^#rkg8tQIh>Uy?z8V4B2p{mA1d+M2e=#bhIM2F8CCP?k7e4+;9HWhyV||B3 z@VX0htUsJB7Z{L0I6^mQ1wCN9LY7NN_qlXg_+PI1UqNz*3hcZ$YM{1X%o5bhhPafy zkj!@^Pk60kyu~yk5r-F7F%01zDpF8Pd-+{AaTwIhB)erOTWT23E^yU4&C1ZQ+pqZc zQ}8XFyRaR=zI8S7fpY5jI*=A4w__AC+|#-bX-&jP&G;n3!C)(RHr%|F%Jj3!*SyLK zYvF+RAI*TPQkBfTl^vz`#HOCfUVUnOWHRjE;?Au1m|7=`$k_Bu=C8rLAi#o>xy&-2iBkcXa%}*c#B7 zPerIdBpiaY@p89Dy!Kh=&5+=P)rBQDKoKATH`VB3l>rk)~5&}ZJgU{c3GQffl)ssUAN3=HB zV2LGi+WGoP5tw}#L@cXmPWMzMxxLkLE-EjtqdaOqt{VPg zm9Ak27fIQX9iMw(NBqYKvJfD~GWa6Y^}R9ATCTAuh|BQ*g06HvztzRHJcSn-JRaNcZIpEs9 zM!NK*f|OG-Cq^H<`boW>H+p_$6wo^qnih|abZ8VS$^7R=&F?HVDG1Ey-o#};59cGz z>+5-lfQIivf6gN5Xb71l4&T zzU}(MUM7=<-}~6G>Q_B1JH3sqF&x89u~Es_1EvMOVrx7vaQ@W05)vGS?X0j>lyVY7 zP^`ftECRAWqA#x|1(q;1u<4N2^5g6KZ*Zv2jlUSl2?x>K1x(5q-D-s}RQ7HF$8V!E zBZ3$MocS9=VJ>!v5>`7y~!qmr> zrBXgGJ)B};5;AwAxk5~yR@PjJbEB9M{Pc@EtsG~ud$>Wg#_!Ea`Gxbr3kBxx%*`HCZ8I}x2)DUOn5tk^Yq>UI7T&2gXB@h5jQ zs%l?fH?W(h4538+1GCf>+$o3Uli`l}hC#PKeUX6+>^mpWLBei?86l0!-l!t({n&m= zrnjoum~(sgy%K0#$D(f$3Q^$r*JZtfHv*_u6sU@UYj4NC_cyo>+?qb%r^xAS7;45nKN{B zboS5p7gw}*Ry-PRFo_q8|K*95yb6!iX?lSgUgw?A>+^F;DFBOq1p)ukg)&ox$spR1 zI%VOjOe5>{|6W>1JMw=XHUt>|cVFf8H1Lr>931|(4*a%_j$X8+s_b~|5+PouKS23! zvr--5!R_`m5P$@zU7X|##bH0aMU{`O%1)B10){ts&ZV=ez~7ysYRlB8Pzi!i5D zasRvp0^#@g>G?gQ|Ne%^Ke6Y3JA(Jf+JcF>4BS|31lslktRouEs0s|fKEfmUb}#*y z{0E}w+`_`bZJGDaMhHJ|3A`E)9*k3Mw0XZ5H&u^EC5sjx^o^LO!LGV(A->nDJP&h4 zr=)EQJS~i$H?O_eIDAa?*J0dt{s`HdiK?j=X=%_hj{O~S@W`TyJb8Q@CP@NsOr z-^?rzC4yq;+*_W6j*z7t`ZWnDuj{<7Cd>3blQVFS>@{{tTWcMb(ttE5hSN2z|43My z8p^sS_1E=-ww71uds&@2?Y5|N1a`LJQb+;VhzeL`V1mz^WgqJKJ?f`u&I*qnWcd&L zG-rh&iO$F?#Ro-te zoryJR3``P&uW_z(`KZY48ye7C@80Yj{eIa(+E!!(S@K*oErv2XCaxRVwg&RG2;8uq z7xvS~h)D>!G%nB!z-_wg>St3^3bj%VfV}VHbP}uCSWLIiO^JT?&!2Fa&)ZZXR(k;v zOa?emulM}|o9}0*4fjFG+i1VDiQgZifz!-zABRM5G^1Qhux)>-qiDBUsFJh+z$dPV z9QkZW4X`R2ghO0I7@-nYO@o?#T$PYjWMOivtHXlmW&mLmSpt(-d@*+{QDY&8y=UbF zA&c@8fa}Hf>!&;?mzWhdg>{h!;V(p(ey{#TAVE<>eXmn&QJ;yG$eS>#HvO3-!b{V~ z82QpxK8ujma-jkFRKSR2q|B6ZtAZY0(0k;5VZ;Xm8L zNY)eI9yoZ9b~&W+xFQKW49Zt{Jvg}W5~Y1|glcv@6C;i7Fyp^UCSk|=oVd|hMFRU} zJ^%m!eb4?zDwMDpPh-i%KY&SKb2Gb;Ac(eN(Q7c1fgArf+|Wn_6^v2I1URYYEAVTW z`L?YnbM^=MhC`YR1_dav!Km^FepUg~Tfz}&LHgI+%z!LT5d8t<5ORLDAe5-W_YX7V z_Cf=dKhqwI3RZUm$k9^nBX>ko`)wdiMpPg=?FdLMdryPsj{Gt1x{2$_P|N{Fl&F_M z1|I(Bva9%YG&YQ|q#%@FDr%up2_&l%X4sahSODf&x#Qu}!RPV3#P<#AF>166a)fy? zuNYMkL3&j2DdXx@#}p&oy3T~TknvweFd;c8gasI>=tTPP@gg9v75fZW!9YZqdr?pX z^GT5BK@5aJsQNYJ#G64)aZ&;?@>mPydTb(f?xX@LA(&wF2?|V(1(eEGi`s!Mjkv5P zoeFV(2@5~VuL6VeC6Cq2)AAiM@A?kI(ngzfLn~M@T;b@=Bnw&f<*0||pPTs5NJMw* zW$^(Oj)dMLSPG6H*>g@J5LJc?)!gwjYUf>yn+<0Vy2xYW3fwQ``tOD-@|*4}7UJaD z!XqN&r%yuT4=8+G!gy9cVHr$?>KZq08ndKw>c1jn+hIr^?exG#*F(DCN{D*FkYmov zAQsSps1JoGOhLq1pJyX>CM4q{;Fb^e=fdadGgSj~(M%*;w8DEuNYO-8Fy{gSlh&de zj{WkevXVFPT!rPtO7(){MiJZBe)z16_#)@dZ``_V(7L5Z>y;1A^T>i5)AVfmvBtCx z#+f_Tb=uKPc+mYbkh$3h@101^aId_vqJa!F=)#XqD3GA^uN){(@x2SCrj zhHMMdAB3dGRt`4!!0l#R;Qh{qwQXCSnI*`s-(DRMgdT+uBx#j`9=r zx+0j`U@o#Rw}{^weu%$HdU$$HE-b)?bb~If*H;{1^hXodgW0`*odSS)e!slOA2Q6? zj9?`3tNmrEU9)voL{KE{EQ;r}V<%DHhVXuA?G-DBU{kDpGo0>Hp3|gNgPW!0zjCae z%(sV3?VZQyw|nGJ!{{8&Wrq4?|D*EeF)1<+VCaYJ`wGUoFrrWT_g&bf4!fD98oHO$ zp*QM!?!9;kwEod^dY%k6%_kfq={(p*MSH$)cfS7@UvC)@SF~h}26qbt2^Ju@ySoLq z;10pv-QC?Cg1ZH$ae}+MyEN{+&dinEnQz|DKHa^mww!(H)LON+FeIgDHLs|`C6*5m zOXZ?trdZB!BAXZW?CUZ0bi@~9ZS6I^JA>ap57WL*pD#BeSj<<{-ocF!zHGv9xZjk_ z@gX50%{x!g<$LvW29?*)&*Gj0Vd)4kr8O)I+$?fA9VaT&DXfEPyYaoeyyk%*q7QdC z4&UQ$LV$C=cRu0*NYO@b$>;S%R}-vb>nu|S6*(mC_R|^ZvGypVN8|(j@WY7io4suU zgN>zS!?H)iPQ)2Ha{g<72XR#vb#?pnvGU`p!-RTgzAW+kmf!q~hnSWJh&Ww$g|?8~ zY*J?xPiva@3*&s7C%=QP<3{lWdy3#OqFMI+PyQf7Hk$`7WyGj5XM*+1xy4k1TS9<|?o%4=27>(aMr>Yw@;eA)1%5J0M9anJ>bdcjiP~G`NwLg(# zqkU19?wy62ytcN6+Mz6}-N*X%ynAb*mO{9er;nFtn7qovj{lRUh@7N?%GN_%jAx%Y z`4qGBgF|c9`y%>X;_^l$GP6JksGlz@x1g?R7-JztAWluC^aiBw+j0B^#f?e_xDM9M4!6&LF1e-ze z=?U9>pIC44K6mrsbNg(lKN^NVldxX1rEzfD`*qb1llWLf zW(Eeo$NB5gA0`)#3wurRryU!&UK>}Odb9ayFZ&vUo*5ot=+f*!?mLYyCo;8&r-|YWpFt75h}j0|sFD`J*~;^#{#+M)moAL*#Aw0=D=h zmOyL*{vC#pAD3w(0i-2#pRhM%0NF#wopgk2^a@PI5^MnpY|}{xDu}G}YODGCaS6SFq&vME<=$kKPmv7q*Bt2PP*6@4*4nnD`VfG`wIrYVoD~z^W z4O81~1!4I+)%!@}g(&W1>Y}OF>SAP3gyTmRc+*{bUwCXN`Cz;SBjE;1B#=3|zI6|; zw>p)7lznu|ZT|h>TyHX=;-FbQf`rRz&%gbI&n72@AZKMdXWb@0oOJ_mf z@!m)Zweu6FCn;g}2){PQ&Rg#zqCD!(1q2}E1@H1&O0c@LRKm-LGIk<^UCv8arCKLz z1FunLDl-T7Y8(5|P|kUR=Q{G^@Bl%MyBvF-U5)G_i4&H$)+;;-R_=BU( zqC=CSdhSXL?Ygk*EihR889QqeGOD5`BbHjx;5i*@yqNj}4{i*+trxLi3cPp$C8ifK z)v`vN0=_UdlvH4@7~F{w+mh}90t|9C2g~wliyR48Oe1Gb;lwDd<>P<|wXCapPMg)sz6lwqbA zakgTzCtGw@AtthvG2tNHP;I@Y9usi=e)Bw||BOHXqVa2wj%siLKUjU8Jx@NX2 zkyI9G#eQx}m=0lYFKr+dOd+=QLVtC||IPOMN5Gnae{D~N9D?geMmVuvoti#Vx?XL% z9)p_R-a$x?7s&~ADP61+{gowndd-zC-C?nT6@W(*?Pp~aN85Hq{MJt|r2 zC(eEQV}pCs7;o7f?wACa?SFi~afC1Z2_3N%CO->Cdx2odf)gjHK#87EOLFVC6F^K` z*qUydWj}ypvP%a{##?xG;|XWlp>+Vq>dcpOno-nh9`M#I?^d&FDd9Btp?9U6%kgn(19K3N9^C zs^KkPPuwqI-oK&QfZJ5NP}3%fJLOqwZeeNS!%4x1es7?IfL%fknl~Ax^OeVl<2dI5 zMTDTn%^vyHPf{v?E=KZDwAYpmawVbJ<8P6OY5RPG7A8}Pfr%9BH3`fgikTey7(`A3 zyD9Wn`|p@FblwxU6SVyAmRN(W%p1V%*1Zl?3Q0YIn{@bR@ylhQ)p@}F#1!`);Yf!W@J z;P*D)$+n7jc9r#sk)kQl2@HX2ZZ7Qk1lGVJTR2XZ#2$7sSBe3)`k{Y(wzy5sTj#2U zINnn9wg~(8a6*iPRIWd3vwu(V+nAiE?m2U24!utsvzKuO#g9E7VSo=KIeS500K6()2VRIddOIVED9s=dTZ|Dv3N zYX)qAs1b$w^CEW`98s}dr;t|O(Wj?zrWYrg8%K?@#qIk!y z`})H|!l=}+$Vf{oAgmT{3|b9IP=o|d&GhVZ7(}whr#?WU+QcBxkt?Xd_}n)BOHP9& zf}aq$u^O!agq(f383wv!7^gcyr=q|YlQnzl_r*NRdY*X+ ztX~Fpz09WPI5heMnC6P4%XNGtlWH}X`PlH?KpPa^>NqvkZvYjIhCA0~1S0kZ z$CwJ(HbZ8PglJHMC!}1sp%djyw1{wahVeufv3wafgT}V}xIYPTYzktNsh!eY;-*Bf zDMZfe1*{~9eA$ISBKm&$dU_m%bB_z5NVbBe^H|ZWFu3d?Oor_|{lNpc4cUNs&4Gp( zwCMyPR{yE-JQC5kZdorZ5bY9oOR4)_>VBnYz{n@@FS~x! zKm3D6;xRS-66xwR@6uapgt-E1CIWnoeV5Eb2DN*pp?v$*5z5H}`7OGDzt0qF;!p+Ilh70&fgA*))!DM(LDsG^_J;S#?w5F-=f8F^pd zGRHgdxqiR&D%J>vf=1WM)_@cObA8CLsqH-*ui$XaWia;bw+ot3sVsNb8SU`bq4-Lm%3&hW}VZaOnxMtx*X>&z!Y* zkD5-5D3k!yQVS5-v_^c;jyRMY(SgdSbb86NBIdRp z-x394_&O<>mL?^c6jV0N|NVnC0rl;d^`*{OblP2PhbWpug5k3O(z_sFhN;!SgRG;; zY-P^X1S!g0P?b$#>Y$9c}I&9$% zp3Y>$l7p=l3_NV{D6ia-1p1H1kHZ~T*CKJjJJagXG&#w>kw--ck~DxncZ5onmAHY= zo0@$)$2uPUKx)AW13yH+;PVo4wm4YCybOLN!3ro4fpUOXaTMO;-2Gkm=(TTvfKhx1 zKFXmr%5JsX0FL*%vT?d zijSHKa(wHQ|LS#+VRDBV8qJ-vd&%3Kb#3XNjyZgwr#PTyiK_mgg zfJ$NLcTL@qmtr*;#|se#muS**3U6tsuyM60t&*<1FuszoGHw){B~A|?#a zTaQX9aZ-lz^VH=QwR}cO) z?dHgf&UDt|SxCWQn}z*D+%x>7=(OsUW}>0K~E5Z|KqYaGnTuYPo$tM!UjNAUi7LU6uEzTZhP03NXYg~N{;Sy_sZ#64$rQy`U z2;|gh_+|Lz(KAoII=Kg#mj0;o_l2W)DRT~3c-qqi_+WIhp(QT*h{-8iRq)Wo78I72 zt$-88GET}xUI>9=4?Cc(Rg*zgd+!m$m|Qlqgb#}*xA_7;shh8+TKc6uS&}A0XtfuG zzoL(BfVF@_ld2Z6N@z$__#ti24MU3iDf&9~E0O#ZWF4ItbQ=uQgxVAdX%srRP;N2Y z?+YWj7~ffXNG_RNIZcyMb1rSId!yNWg4y}Gd^)`gHU}=+eNn5$=vLo?mEqG)0x5|O zzYhTGRy`4g!xX>^X;tUbaj!kpDOJ-8+vVyYp^p|E1_phM<8XCGGJgZ&^EMV>vC!P- zI%9)^{g=JJP6~X&sE`0GEEv%j$Ut9IRPg`)Fj)?VA?8u_1p~SbtI848&$W$YcZ-!i zT8{xLMO>t|cZb4+5E)D(o)McJidZ92VU;uUK`mq&v(;g0F48LZ1V?+&u{=wdvbz3Y zk~9X>s{#6l7K4V~)RS2Jzij+v&@Y=3vymXokzg{C}9n@U=jhdYv#aJ3Il4yzk z-Q0f~^3##bH3+ILQby4OE+45~ZuYu9Y_&0#Z>g#^nJ1F(V%TKQ_d3?E@}iY7O6_lx)isYo%+Xu_O0Z z%#hfKZDQ94hUW}zf$@~sqa8B`hC_j5jg?ZSGD!6Lz%hW(%)&(U;R=0eCC!BN_IV6oOY6*dyw){=zE%ou|C-KbZmWENRH;WOFSLzmpE zf)8pA_me^m*pf$2TPfxj7h&HdK7#+V&1hP1;b26t_pfmOv10t9VZT=}6tTCO?ZDGq zy@CZ)ccNoq9T}LKLpxXYuaKM%#D~cBS1Cu>*4t2$@tk&Je^^ni7MmG!(R&*fRcml0 z^WVstCS{ilH_-fLIh8?N{ZE5|U5o;HvtIn7J3YRzhJ#&d66k*|vdI}pDh=$FTW9Ph z`bdN6D3_hKnQLu|`bB7z!Oe6Dj*A|pq)k?XCreLwsoGL7No-te09w;ndUSey^*|Xe zrUCHgi*;I6CLQ;f*Xd#{HoLV}@in0Z&O(KHfZ0qDzQtJbopYRTqyv^8e!-GaX3Uh| z2?#OHL_t0c2r!Am`2(GH#=sOq=c41wjVDG9?PX=&8$TP{wlhnuq0 z>ln?fOb{tB!zDN(RThyM5*eolY3|GQl!-#aw@*Ib_EakR7nd(DE}!PLU7v$BUg%a>XZocs zH`~$FS}v(7(zQdljgbdNac%mZf)H#)o8d~*0wZi%pp-uR_*v&nMq<1k`l!H;Nw482 z;WFl5`WkEb*UHGhozZn@Ek4|?{11)~sFU3ouj zh5V5>R?yat`a>URd2P4zem-kL80J1}n6bTEGQm3KPF}C!|3k|~goXfR$sMILQ^SUU z-7tiDb7jgfjz&F!a1VzCF&G;V$l!lqLYM@J^Dk1hLmT(I5i4WmhQp zaaQ441A@w_yoT6scN!#YmP;Y!ojmYf&v{hMr!PEWTXPf$Rqqkh2{&h7Pl*MGXy z0(&r7P4D$|)BOkFEVZcAxdag=qn%FX^(S)$AFfgW6&G}m!*^>=3)*E~k2tP`moYvs z5W26&s@E`$wf&+|H+6gm0HHPF}w>DtYH3{oScmu(?;R zvEQ~SO)bvHPILHY2=sjV97mrY9Y;q%l`*MwomerSPI3LxTzG=hpq_W5{@SLga=LJB z+Wp!tI&o!WSkhozXT0{3R1M}U)JbvW6ikn%3y)Lje1UoTL-C}G0Di+EX4_4H2TwkE z)a}tT-S?*j|2o$l?+SQB(6Jmj?uS#{B07JN5O;xZWC_Rmg8W5*@l-}(Ufav=^6&Zb z6g_H`Oj#Q)C$EHC^cK$BDeSU+CPj#f^TKc9N|L{y&H*v!o`Or6xct^ao50Fu=W)}9 zMzDyt*d7V;S-o{tF{hS_XxVQ?>6fedgX_<)AY8VT?fUZ^jy{d59h5ak zmTddovc)mq9v0w1)X#TOX{=IBzoOXpTgPAfMF|Ud49`_)t3)S=R>SCeskaJ>G9_0j zNh5(pY3y_TN1*%fd;Ve*;MHo<{*>P5F(REbhu^2ueNyl3!t;RJoGoLepIBZ~Govh_ z+)MF$d3Cia3Is^rPlfsR`R=T@k;y6#z-g>MT!ud1)KsqZh>6RManVItsvbji(|`09Na z5IVF3>+)bI_F`pQ$H_O3Zp&(veW=>Ib3%Ki^!I1>;3>hwp*;%?Hcd!a=G1v z_s`GicGIbEi_?>VGXuDnE%i()A{fOwj|X&GH;-Ek-%Vx{lFD7muHKxMDo+?V(xgqDyTojSnd-quYKpPlx?<-(TSnqA&1%XMYo1 z=QB}lC1P)pVDJ)DA3~vUU3Ou1wnWLyPISbK7nnXELCo9O z(4aS!FDh9LW_O+J0t)DgIC08ljYpQ*sJTlN8!Cey*phhxN~sk;hzeMXWwdn+?A=zK2#aH zd7SXk!7pYQ$S@&WpFFN^?JC^3DTDAWE1#`+06 zf0Xtu*)nui(LDBKcxOJTYUL3`HjlyN`@CJ}w7l69Lh-Dxrbwn2m9Alo4DV4oWuID* z3rI2WdL5J(T;9Q?+dJ%bn!MFQJf~{{@7bAq)LuGx8KMqDXm7{V+Ptg1)7=YMu_pi* zr7>vQE1^UX2`iJl-N(X? zyrPpv@;z8WhB&t&w!Q7 zkoVM|fqjaj6WJ`N)g5i(tO+~X%21=}**GK}XO$qC(@LYK@{0sjqF6a!bO!#HEtELP zLJ%wS$S;|R^N?vmBt@`bDilN{(r~o14+h5}H|5Cdvmhil=a>?~@_2RWEGv_Q6Lt4$ z55DC3wiT=o6OLXzh~e+5okt;(GTP#BfC;Or)gD8a1LDi6B*={RTCi4^^8gBNKr+K+ zy-$rH2U9)A^*OHJY#1TOVSX6eh{QLGTiN5_OcMrgwB!&TRAn%&M zH9Ay7&f86K8UO0UP|=DFKzl7kzJVH9PQV#s0Gq^WZ#bzXpP~y0((9w0UfPgE9e}_1 zX)lwE^!M9c(d?O|z+{EUjGm>dm!A{sZ=eVN zcNA!zqK(w!d>ot5$H}c*_atTYx5pkw+eNN_B4+Co#LyF!N$D)wO$U8}x^85o1USSq7iQ3cCwI96CKc0u*P{71HC%@m-z0ZGvo_>>OrrR` zSKNquN0l-=1lp;qeQZC9Ay@Y3>RNs8$DHKzU?%I-(IdNhUFotu~oa7WZDJS zHIFw5>~LaSQS?lG4o^^T*aj)x@+JzF+TVI*E*fwF`yyi}JbeFf%~TUO-Ln;|L3L%( zt#9_zA#|yh8QF)y5)rK70R(bnI&(SiFD9gxz~$c$>AO7~rqYpZ$vLjNf^ip`8&ApR z2)$hLm)qcA?c`fuKc39?^JGg=Gw$nqSa&`eE0bxfy!1jq^L|C4F?cNk78;MqT3|M;My2u^bhpnUNSlw1#*qV!=q( z%B^_0{Hs7zJ=tz!`JO4qFM`kJy8)gydI3<3v;p`Kn)hm(=Eb42QG+G-%3G;L)sj;? zvF_?aA7ARl*~HhXsUbJ!N%y3B-`yN{hWWKIhndRGYAM`3J9>EnWe{teATnWKb~9+f zMSqUFE$_Z%mJwcF+9Vx@BF^kp{Q2@c_kNAd|ba{aaIiWdtp@lc1HSuMT_JTu#V(@2DK6{s=JLL=-*xp zw^^fmuq&&NrUxYww)-A3F>EM$fS*;Vrk*0gj3%ivgmBi#oPbe-*x`DMHTuKpmZPbR zR5u^`IW#;@76uiK9CEDvWGqgrzOLCvFPLbUIOK;XPfNFp%}_<6i#N3HKw{DM4}}wB zL(%9|bL#gw&x|qgPZ38GV?US65d%Hm%t-T?de%~2X{)+=<;FVN&g&Aqrkv~Ti8zN(IG zBe{WCh=R2dD6pOr4+MV-wUQ`Ax#ECb#c3Kk|p(qHEKTAxt!L0B(7`sRUum3>G&g(>6N zJhCIN0qVQb;Jqrak-(#BGD>lxIzsV4mH_r`>WYT{Xv%w8!>^35OV|e_gl@rnp5;!F zJKoUVLYg3jrpWeRcb=^7FKm^3mH^)Zv%poDNH zU88n4IIQ`8CJ}*^mC*^aDaLx9C8cVNRNsY)Os}ZA9y&XNel>Xv6E9uBIl}rjgJB800?skfi zMkn(wmgGT<1Ga|GrniN<6Swvh#&Lwt<1MACSmW#Me`FmoP&W~2MH!*ni>U($CR5U|QTBj?gsSy5?Mp^F8WMIYsbR!e zqD&g`QwHG&S46(-KSzbp%s?7m=4P(@#d}yvrMTKoxraG#da-~EJ5i2^w?BkmDl+&O zgVJL*so|jtzXq6Dy6F)f^F_9opPkb^3QyhQ59rnb~-RuTZdWrvXhgYM0&RczK zj_5vp^mg#t7mV3+Px51??z5x3w7G!wQ&Hv@g68-Q-_hH$F3XbbCCk zSm03CD|WnGCXkVaXYu@kp6WWHP$N~sDhXdMO<}f^CM&r=!DXrnsW#d9Ih9mjVEFXd zUu|_WMkDEX`(mnD^xoiIka2K|(~J7Nhp|}HpgDb1oMfPOuC}wTLIYo%Xtb((o?U^- z^T4s@kEOd&s3m>tetX^W?ZZsQg$d2|Ky(S+NG7;(X&=)))^TEC-4GvRB2=u(8zFe{QG2p2L8IeQ~Hx6c{ z6`F?3+)yPGp8L#_-|{_(wW*!_D5{&|GoBh#5~Q>x+_mJr6s53=)S#;YmNShDIuY^| z-)&`n?s14xYuDtbzb%T450O znozSN&xqa!!|^M{$$Z?eHi|eg5!8%-GF`*zkwz{JvtHiMQgDXtL;IFba^6?yx3)bm zjyoKmkEnGY!^7kP;1M%A^0<)ZkKkRirB!o2Oo?n>JbXxn{6aAoeuukOG zw3T7g!MUGNj;->GJiFZw?r^;zK>{Uty1zTPsV)gZl!5X*i7Q3V*(jImA!$(9ra)Hv zZ~Jj!))Cj3q`;%0AR&wsLqx`^_Q$tmGSApySbHoV@8*xE!}!!vPXgMAfCoc(A6IK` zOl^<+W88_T&iT;30i;Q&uA+*)&oM~`B@3#aqwdEaULL@ZY&rm{mFGhbrqbjY=J3b( zsZJ?BP+zGrJ99LpyMgq*vm8kV-t&|YNggFS?7HuuteE%13Ch;XHteeBQtFFv9W7+5 zw69(LVdwoHPItpgp%X?g_@sLzHOGwk3FuT`G!QlZS7twEb&ys^Jm6U?DlJU#nX^SC z;=mw+vP0p?JP{<8Yd0=>4O%Bk^C~=IjM8!#nH2U4o5LH`g!K1igP4Ygfu3lkTHap# zP_#N8l>Spxnbvvk)BWTFXnXK;825|f@F5szZZQJo<{C*e)2$ppPmMhV>>Wt_1Z(X9 zzALR?DVVPA1|F_sq$w|kh-+OU-SKC?T?FSQ&cak{u}|QR6^sAMW3>thU^#Q_NTEr; z8%}z@O)-VD=Qc!7zdN#jgjZbcJSxhV9iz7~cVl)3#nL;Vx`JP=oNwJI-nr3?Z?MppO`%@%Ov1I%>3CmLvbvYC*mk^StuJMWds zQ3QYw&mk<9s+RIGntWT)-syTUiDWcROSS9GFJb$kr>k|lr(hm5(6KZ}e0%*cyl4n` zSQq(|9hkeuUY` zMDB15j?*N?I4ecXXN>nfsDDAGuXG1Vt=<|1diF*cft=x&hIhx)<0t^QL-wgYiaOX{ zFwlOK5+1V{l#2bXE6YIOTES7>js*@wA}|$Z{p2{bbHQ89{^!`B-O_U-d- zf}9Gbb9=EK4o`}kP4gDCk&(E9balcYrS!V}2yHm}_0{sIYk~Xwq8M4UD(O!?tHPp3 zsyw4llfh#d%p)8px|#5%icRc(WI0b*c`FDh;MBn&?4Td9AI&N>@*1sQlQC$7gV2+L z@8Qm8Xsc=%4;a5&mJ zx(>q*dnZRuu`OzTcdUegdsV+{sl5xUXD)-qsoHVdr@@srU`Ic05(2gA)M<1ds!UD? zpI;f(W`Do9iC^@&X;UH*Wb{|RKQF4Z1oc0L;tRITe=nMA#ijQsbL!V<`QUg&V(w#% zW49$*r)c4YmMg-fu5n!Fk0}5P?%>DJ}O^Fjy&$j!yWRc_g~Q&9x>SXs*c-U zl6GetZ{P7Q3(@2bq^aF6uGBD+!2uC(m1cL170_Us$IA6q;U_Ye=sGBYX0vK(TaU$UuQ0^jvPM-f_%N z&F@7c(x28f%(78A+#xBL7+<=l)M(S_@*P>TK>1L6?lVdHmfQ~$h7>RZr$6VgcT$fL zh!d$JtdlX`xo}4sv^dvVbhRkCb1AD5@w?)L2Qk3xI=G60PhxSYcRYU|Mwg<4Fd{y! zj-jNhO_r}Ae{DN~0^*Q;1{EY+_R{|gn*Q<+h;_4>^h?j89e2Yt<6q^O|3N17m4+PTx?-u^cAr5P}PzL8! z2^cLgvB0 zkZ>TT#1rC?#4f?5VNl@suTFQ>eiI0+HME=kIrcB>0?}QDwZ?tx7Tq{I<$5{OD8=JE zFc*ayN#A}QPCQ>O4ZYePnUpy^8VUdBR33tQIwSGB{9o?cj-t6R@0q>srCxzoZM2J*cKff4%T{K;{-aHH zbX5I{3_p+r)ITS>D~|kVuAV8QWKZp_bw}~h9BNg(D~1$KG_EQ?eaAs z(&hTS4UX`J^IMVO!{zT^Y@U+`9Os*iJZx)AjyMPBKh;}|hB#Eu7adJTeq{Qo+z8I4 z@%Nh*p?}h7m32gWBS{QpN=qSQ3N15-6mN_;&EPmX^CFz zJz3Wd87h|HbLN&W+LL*H)_d-xio~IyV-|62k9J7gx@c@O%JdkV9N{n-a6545y;Km! zC5t`|*a~>kdp-x1o5V0r*;@|kp!K^CvWvP?{`>y*8%HbA)2{$4Pp0du&e!Z_{TZC& z#cJJVL?+$;>i!I{>qi=ylF#C0FeC84*NIFguO37?IoPVz93RK~#v2mE{tn-k>9P~O zgg$iywIauh^g0+tU3J^+w(;{!_`TGGfc642;Dyo?mWuc0Pw3*eK|_mJD=sN0WfMEn7gkpNrRw)1 zRSGGq|C)VYR1OVDmY*}w*iqu;odwD$&rsM=!3E0t#V4hU*oHFY1YO&WGqs!DA;8OC z7ekfa>KaqgUNC$MH2?uC7Hu~OOGddmae8NV&yegA5OiuXZyKXuy7Z=B8a#+X#1KrnX70Q~K5jN!*Qk{E_klnVPw_EjJCtLWbikip}p2 zs=py{r$Zk)V96WW%Rto(FrI=~G%X#UK~j(YM;egr$ao{@a~j0{@*MuI@k%pfYL;}( zS?W4rsH>f@@C2NQD$Nj$-(0;yG4XzPF)n+-wysF*#SV$3z*>5ggH0GR(50ofsM33H z6!v^L)oj9X$iWYse9^}S-^?mz+suw5-(FA;iK|LpxPH9fiNZQo;D3Z%M6pc(lLtV! z(5;0hWoY>6L)Z?j0-L?f_wB4#G$~i_4w2hyA+S01VXf2Ik)1yw8S}4)dO~i5^Da)W zVi}*9uDb2nrY1GXh!57W-2Jqhk-OJBj~UcYKhK-F&ix715$l^Q`2{oTOL|@RNo}L< z`?Ou4KT%)&XFZmJ`8~g0`^`B8DN!`|V6}?IT_2k5V_Z#AvyObpRiM7@@Pml}divL+p zm0*JHdOy3t`Y77BO101oFDb^O?h6Bk2`g9R2y2}%3S+~eQDOA{UzSVYZ}<~tW9d7F z*%r4jBsEPf?U#)Xng~zU^ma`iJSf&W8__9t<0tW!$zQ(gXn_ygHA@X}GM6+Ke?So< z&-1xO^<35pnvflT`GoSAet*9c^KLq@ZG%L?=NQ?(fQkZz%Ib~^tD*Nzbn6~U!2~(6 zQ*hn@b`ChM3-Dm;*+S}xThOFE4xu+2+1n(dMHAD;D=FcgYiDzDXa3~Jj~cXeCfa2V zJ`Fbu1%4^+*#h%1pal~QaP5nVf2W6St&)h?iwn8>$$4kp4Ej^Lom4v*(ai*$VgE(j z{HaQXn-RTi4x?C@L6=U2XE}U*wN1{K3k|oV*fuDe_8NQ}WPGT$(ueF{P;&h7JsITD zgERb}LkE-zx0a1G8LhTlGeg8yn=G*gbU5Jv*~>5%1X2dwBxcfW-JwLde8CqsR<^IE zO#6sy5gr$hsGTM-#Jh(mC5FBygFL@b@<83Y?-2;3JIo1~YIY|7B~3t?uNEC@c>@W? zx`Yl^QN*ICvGrAs218`}RJ6+i`lH~3zkVvjV*;P0-A5-suH5;U+%Z^Mf1T3Vrd=@}^QG&#bcxe3bq<2 zn?mxRr&_;OG@e&nrU?8-7wNI|V@$Hfg1BCBsIba7wN33PiJ0p#90{21eJxSbmp+(o zu!F*UeDm#A-3W__aAJ(?lAh2`O&jB}OGZE7ZNM&P2Y3E|ajhN~4VIWdwb>DNgLaXO zd|EOYY%+sh(tys!Vw_&?LoizCBAJX?!RVd)Ng&Jk-~|P~_r#A(l$@4=P=KOJ6jpDamMuL9bV|i^xU61OVzteTF^$= zu^Bw9=Xu?QKUX8-!!Iy=;D(3Ex9eqlNS@nCdSZ*rWWZR^HtVUVxD=5p=O$S5V{K^Q zGQzg*SPr~a8_4c(-JTkI7}FeERMm!f76loZqQl-ZH?>riIFVE7BJ6Jgo*!irGe&5OF` zhF8mHN}_pTIn=_Z6{g-MBb2i@y|$_oe#RI@MQ9WCL_YJ@lsBH+@7kHgV9d9>ncISw z?aZGD`#C|Zeaf=0p;Bhi;B)$?WoZ5Jme-mWVKLkDnBCTnMOG)4{m8E~V9mRp5JKg5 zxj_Fn_1apfDdn+LHV^r#wmKq6cYoiheZfg<`JR+EIslYJAG-0PcA5nW4Kk?4$LNzW zV=?O8Q=Bu@<}YkL=Vtv{BRUc2@=yDE7?em1g)zq zq*Y1O0udo-u%fY((luB9%+tkoiq*?ikB4JA=iUWi!#!%L)dh3`Gsh*~iQ!Pu*zDcPRL7r-WMQtR&p zWZViYtKKBm8LaqN8}_UNB3K4Ms#XI@E*&_A-`?U9x=9rUJpX)61Q9Ril)Y^4_aGSD zzY+30c%sv_xu96L-ScX-{%CUNA78|Z*hdxsltovgC9PSYpIT$p-Cs-gg%^+2B-(xw z%ty{^z^7~T7S_r)3FGRELf-Xo_(@FE=T@-MZRQ*NeHk)^J~Va%Oaub9Z`Af#7}ou4 z^0+6oyv;k@5)C^OEKmrndCU&7*J$?F$1(19?azusEYivO(WwfuXY)x+t5oKJH?~5e=p(cT1FkaqM(j^6EGb=glg;&#xvKz*Vo} zt@HZ(t!!PdNS9QjLFd&*OZHI*@~u7H%t+0_Qx#%$5)I&h5+PAwoa-(YCJ*m~FFV0X z_$QC6;W6v;l@^p+@|gY|00+{2_Eh8s%|@8m-hNuo?T9AB!PCs;6+PKDuE(#feQ_6k zLf?To%GE|QhU6Cm`0VE?1{PX5q6RYtOd6J*R`ll{zKidxQstF-!ggb;ajAlN$gHW1 zey^)RR%4ZmcpG8t!N^_e!eFPXF#~?D%g=ndpM}#k6*drW_}x&+ik%IN%=z!mmZFLm z&Ii+!n89@oy%3;LOq&w0nHmV6E-Y2fRM%E&(WzaKdrp;*Pn%BBz{Qh%^IShtXtoW6 zkw74}gkh0H4zSF&qe<7atuCFU!?FE;?0scaoIw&Gt^q;<1cyL?;O_3heSkrOyL*7( zB)Gc;XK)55cyM=z5Zr=8@SE&zWOuo{fA{X*yFc@KhL7s1>gw{ZyNQB;Oh}XlU$ox} zmY*68ryyl=>?T(J)u-fA3UOxAKsPqD&?SV_p#V2F-B&Jn`ZZc=SKNYmVp#UEL@pD; z#>}M2V&g{Cg-zNs=NBM#`XGHK^jKaW#ZkmyB}IwCi?YO zeYaSIuLv@>n6YqxrOD9hP!(geTDY;vkPptgpI#!Gfscr(fU;qxiy9`Da2vE^OEx?fy; z?>IpdT5FUjVGeJDm>^JDyE|B}es~_^Mk8}g?AD_UGiNrTPt*=4rP8=S_x+9K!zX{{ z%Uft%CdAdQt+t0#vrQ+)j&Hkuo}GaM4Yx0_VM=e*)(~jLceW` zAK3IF11|CYc3ijGVuEdtbP6F0WlM(*pIqJ6Z|&Ok$QEP2vwA?jO>>v349%0x!j)`D z9B6f8kt`VGxac9s z3;!%LC03~un1b5!FZi&m(=K4km%Fa1iUGmXR>t;77iE>(XYv8tyBM?Aj1WG9>TjC12RU z=8p|vrD}LRp8OUJbIb)_jP=Z3PSXKLmAFl8}(w-W$rVy_yXTFzv5FeB}Q z1a$E8+Cel}s~U7%11J6=${b9Dkd*XMlyjAcGs_#S_UN*r(2a7m2lR5=rNBDd!mh)r ztrP}bKg6x@w*El%w^RE(i_DH>3@uk$8t*UB>c72&>Ky5ssxt=U(bXo9FrP{cMmAQu zjiJakT2A}w`7zWgRH+mDe$2Tx=y&^i5Q!ev_WnXj?W_I2UcIopea`jCc)@<5PxY0L z{ye(vN1>zs-GP0-$Zq8g8x9qv$-Gj#4ZlpY4}oMfS+>-$)#$<}Qn~9BAEX+90ed1x zjVeqU`*w|K@W%iiy2}dmlMBMpN!Sn274?`;nta`!UVZ1>FbE?8OgT81!vvSeU3T_< zvYxHsBoD*Jl9$P!6so5*{eEy!y5P(HJglCYDy?SAX0eh4S2G?vvAz|5^V7}o<}iTm zyA^cMrX~=LAb^|o%rW)&OBKCFvPGA`zND8Uw)XX>+Zn(6^C2W>Gts*Yj!)rvKTKd)74^HuqYo@~~?Y*VA63WMTcheB%tG8@=! zHuq_Gt_s>T(h*_)BUS9HD?&gC_CmkO}==)aclbLx~>}o8hUv{*e#t;c6{2>Fwlmy9A2^@6RV8L12@x zp%CRj9bA?Y6pbd5xB>G!csB9|Fba9_MORd_TH7$r22ZaV{n%oFD@RScr!+QzVIfR? zm-UL;)nMS`?u{xEqngaus2x(QzEAovcuP2h2`Hvqp^y2Q<)vQ-EOa8^z|%w zaTZT8zo#?dp;EdkZbvM+dB;M(c<`AvZ;L7-w_hE-JifgtO%k`aHD%p}4l~4DEc;%t zE8Xpy)6E0zf-O1d!@Y&k=B2<+<~{}$MmYDPNx(|U0JroE-?bJ-w3wA4xrj*P)nKparJ_I*Y&rku)5o+A!nlF^iEeWHQh==XeTa`xv6d z=O8yDaZ}S37J$aHoj8o)edh$kCqcO59PfOEjA|Erh2WSCQK)4~Q*iq=W#SpM57=L` zmJj!P9>C+xt~yN{G0Xnk1;Rz;KYh=qz(vi4WhnyAG*=;(Bvv z-9P3DSAtdw5ZcO0I)Krh#h8bMxO#GK-sj$fNzppSE+Y&ji)>JiRnvxD)_4xxOtQtc zZC=F})C1+N8Kpg^un5KVnxezFAz)>+A45El4idMuA`JUe6g0WnNC`D{f|Luk)pYf7 z4KSivuBAbNvYssOdnA0lD_n(wi(>s71=fos=lYP3wO4bIM+bRmZ&(m!eY~Z&A-4d| zAl?-(u)xHF3XpBTT+%3Ez*^dkGojI{gGq$e^4T&J)C^f(U?wrav8E6l-*8!X11mP? zBqkMza~J7)yPg!5#+7NQ zLcJRyO2M4xnKJEr1qt4umY`KE3_hq6(i8WUASGivseBATsZdZCF4IkJV6eyD#8oYk zr^w2~M1_m4Zdf(P*mT!7#if0M{-fhg*G>*Y%T!$*Up;}7TLoii`i4-}$T!#Ve z*f5`~WcW9OIOtne7(xwGmKbA@@Aufo8R8dALa)-CspQB3FQBb1TvqAh#pcZ>jlnTG zJoCJ?(+2xy{!!O|^^_snh)KG7Ltue0-R3>V9hlvQ#E~*WqgCM;Z%ZC~V4gK$jz3}2Og~z0Uxz`gm;}TzO&64w((eUi?6p`K zAzLkf*0c^MCbL0(m&?51? zVNqp#^3|=yd+WNa>+9tgrFUF7x(yvhqDW`2e0AzigSD8yBgRT2W{+@KI~u{_=<|u7 z^E}ji-Ju}SU)qwFMmb+0)AtCvmz&(4(xI3upcEFun zzoQc>&alNKQdMT{{8H_!6J@K6Ue`c)leLw<)wwLZ_yrTC0Kw+vJ*mA!BZ{;{Wg( z!K{+!%b@%uAnLWm>u=JUKgOb5|1LQS!P z?17_CzhSzde0l~KRo(cNUxO|vrY7la9l_I+F`Z{jK#NuCN&}bGA}O)wIQ2RSIWKfS zRX`iBd}IRPgcJVf`_f;xpW=qW3lexbfntFFP(9ePZTc>4@j7zXz3dY(u8NUB53`yi zE37GP9j>`2J<*36FXksQ)U>G=Gy&!6FiR8r^vAA&Mk z{^DG%k>;=ZXUMAx>xJ47t3)>GziQ#}V)~PXN=wUxnB9#;MOd%@`iC-1u4kO?I+nic zIR9`;p?`{<(BUi6vbbi)#k`rngyG+oYWw_W+rv2tG&JP-f8+G8FTTrCkkaom_xEwj zpD=&@{ErF#_2tY1()4hC9q9x0Z?OOR@)MH|jYC4$otppIvU%|TtbgB03PJsQf5)GR zvRol;93`$1O=rNrnDTFwHD^HHjc&dtCxv?KpL@ zg9z}yc$+UN%3r>C*`aaC8vpE9UH+f=36;O9_@<7meIp_>a^WQjTh?qto;@U)g?D9a zEB*Gnb!z1%k}A14WUq~CA)CDkxJBy55XF-b|9T!2UBVS${>2YEfVTYYwz_>uuJFGR z5eQfPAH_>1taWK}Oms3VIWlNtoYrYd3IgKNvib4)}T@u6We@wxWZmP`qPLwEC-1 z{)9vmhH%{mE}3Go+|mUAHls&ho25pAz2b+Lx>mF8lF6W#%Ttll;I~LVt{L+_?<6ul z=w&WV#n4bZcz-??;oqgo^oh}Hzb2}8+yRsuqgBce*-yaKp8JPnGQ76-=H2Weq7_4+3cV zcp*A`VK!s`8AHp~`@A(Ic+n3O>}_Ter30|Rc}Kg28hP1B2zx9u@UieNiHP!{&_xw5 zmXXR!52Pk!3(!HN&3tQAQi;u#CI3~Meh(fO>`V9XDX}+G(0y7)oZpGqiPB{d4dhUE{Vv)xX3p=^ zr>HzEEkidMk`mBDr0D0*hPmjQ1YbDq>FIXGT<6}OUBA6^AE$5tHlpuy7tMI5OlFpU z7Ae153*@OifW5m5MRXP&!fw>;IqQ6p>O7|dKC_)kE2z_z+8)cycj}}8Gk71iuHP(~ ztKF{=(q~R)n`YjJdbTzz3eUhRyJV_f35I)7VR?F#dzR}Jetp4UhR8kbri!>JOYwS| zp`+ssQZ88y2wlcSM!L^wRq=CmcDv8BX^X4kQe{3;JdEK76Zkrk0fMAq&Z~u6prh^I7zV zu+fD%SYc}M-RD(J%~x%D+U+2pZ-CUnK2=Gkimb*Qo3U?k1HjVjLM^ojm|RQ&Oc+D* zMJTLDC>SJCAIL|VCTU(Ap+nOte+C^ocj-%U@Trs2qAKMCCHIoNCOEakVU`}m=^=}^ znklACT`Ve*)?3eB#~bF=ZQUq!tRcc->df%Q$feg9mB#4Fs$I<;Fuho3@=$GdJ&qbv ziO)P{NR7IVJzw#4w%Gi3*QQ$hKuWVcr-v21-+2fH1$d}Px?J*NcrN`a4zXh92TY8E zDu~6kN&^vzLx>q+8Gy)E zCox+*{?f9H63Oqi+a~0ZYxLJ$GB@XIu0Ur4k&kJ5jUp}ZM9?`G&wyGr$6c3a+qKVm zIIcvNTrZuL>}!;I6yn_Od_2*5s`u*0RQ8_nyQn8`;y0H870lLTNVd(_l8`BY6@z3e zFXZ>;*hvakN2$geml_{r)OR#*nD6c`BQ57r@PGmhYE9_j6KDg{VBfaTgIK2}ncz4s znkCQmq~#P15Y=gOkIj#cuEu@6h#iGelO;Q*9M}Y@E1?B(p!b>^`z;0e4h)4$P2p9F zOpPZ2X;OeqtLX;q&|I5nZnz%udKN7`rcRxY6^q8|K4^m*AK z6xI%PTjsF^(~PmyMHS^71loJYTzJq8QMc_C-^@ezyAf>~>7OJ5_y`p%1Q^JxO`JW{ zjL)(-yrDpHMJ!*3;4i-ktHH~GcCY;vSwQ*~x?@%tR!w|r%NJR1X>j5MlbR4KcuS=v z(`x{byghLP%T$Ep{btW7rHz7!^#`tYW$0E5a}FY3H; z%4AeOfC*T0huHK^jyAcfaQua0#WaQZ?TKXR$ut6Xp$m-;i89^0nM_45Cc4<3a{W=p za`t?o*VA<>M0TL+n2&LEwY7|JS5N3&xEElIVHy0N3{&cV(Qf^`TV8YZUKdnUiE$40 zT3i2Y8j+#HB%DZJuS{@AKk@#s+GZHhK%Sb_G38!;h_HDxy-+G9QUN5Vc zI(Iy^G0Q7ibGqAil_4u}Sh?0{)t>7q0ME_`>0U(5b!ulR=6EjK<)7?fG~Y8vI>+plEqJ#J3(u}5c;_ouzGOI_smom$-h+BF#F)Y5 zArfq&o1gjcwfubF6nMTCl*2>0^QA2JyDeAI#^poJ@@@MQuY(_wz&mBI{2QKH{-VPJ z+9_7Sq=KObF3seiT)!LXbF*-@OC9%?w9CP1uta&ORP0$eZ@z^uW}(VQ99hm`uqH{3 zclKRn&h9MqL7CW0JVsxnfwEZ?lsHLe5? z!>sw)8y;4mVQ|Oq#2-bB4s?cyb)?=;i1r$b)0^C}*CvA2f{wryr?OtwMY5nOFSo-t zN?Agmt{XMU3^~o zR}Gr%*tBPFI(0W3 zYjoeeYHV|e#*yXEgOy+`6lm_YY-gv$dz%_so&jRi8V}iLiQw0pwq%%IL|tIv&f}~z zaq9F#1)EQKXcM@`n_aPAEgM?*g^{WnO54iQPt*-Cj>6*?G?dAluK-h~jxwCAW|*6e zsV6ug7UH?I(z0+-3amL&c^}A zjqTpPPHZ`%*J8HK-m(;^_tJV90`e*P<0sfAI)uF0bzz7wVS)-4)@o$#$}cn+#$wsk zi>eCDgDK;-5pKlic;2izohC9&O&;8=H@+{p&)ibj4#Iw)w^w!Qoj&Z{x`Qj`p1AvIhe9=S3Tz^q=bC-j3c(o zWWdV=dzvF%@%7W#skQIV;9_E;D`H#<<^_0AF$Nf@$U}0i9p+zwml!NvW{tK~B^b@$ zv1FUoCOQDe?6h$TL^tI{g?LFOY7ikC)-tLdl zZEkKY%zrw;P`(COZ-VYna??Nponl8jZf|PNDI1D)4jj$!Vy_SkIMfg=j9DqL(XO$P z7SQB5*hWO*C0(HsvXGn8#(r??Ch|uNEfX8HySKv$n5ODcp7VT zO7rwYy3z+Fc;$?vXEqBEIOqVA>Ctg@n=wwfA>h0wikCj;mEWB%8xjjrd- z3CNA^J8b+XfnYCs-cO0sUa#@^S8#N)o;RUeoXgzQ9Ix&GL8nzKw(~H+@f~e6sy98$ z+*y(<%sU@bl7URjIdY8m;*s~eWX)%JA8GJbY9a&d%OqCV)|^G64+=Iu7x#pIR`fB3 zlhDpOJjPxt`T*T!t$T$+1wD`b8)81X0gIktUcjKxqrD2OgwO5y88DY)RsXJKI(NTROBa!&m92ns6o2; zITGJuCgKS=461rpt7)*qsVNL+rGQ0#nbzTYCA>?cF0|`b1mn{ENizdf@7v0yWwd>T zoRT0fj8_4N=3c(^ddPOFJ$?Joi&Sm#pj6~!a9K`Eg7>^b&v4p#E+lE~7i(Q)2nNiPZ`*fH~i>nBglX7`;y`Gk-ZvMmh z54yW=#{=v8FSn;NjOzkV+0HvoonC8>QtXW)6Q$U-9#u8* zoqBK*z&0Z|6XjjpJ6k$}OiO+>x>C0az&T|kh+ijYfuKFlOoJKRV=`=hfH0{wE&d{;t8ES>)R|G z*}09kFy019L0%xe4aLy>OpQ@Pa>FZoS|Ahrx{9{mL3OYlX%H2jhH?A6A_C zrcBfeH>IUns?t(x-Z?`rtpi2|E%p|d60BZ5xIE9FKzqnaje*1qE0z2amX zk4ZJf%I&4+u)T@)b~@tVuP_2$X6VbMs?{}0q&k85LU*XA(IU})$rH^#+V zzdcavB>PgYRKz}~ViB!MY16Kmj-jrkGRiMntZdRrdCaV%KJ>7`f5A>I%t4i9mCqQ+ z1EZuflLrv5^`qokj~zIqS5RXxoc(xxa|=7OZ6BwVm$DOwQelk-I)-Aop=ret$isSM6a4 z#7iHqw9+hy=gt@#E~RdE*5)yJBDX)W=ebm|Z9ueet>;1SPQyEQZ`r+QctJhKJ$CVM z0ecteEGLcY=p#p19ttKJ2+OeF+VMj!aD)a~apT=lPsgC{>S$yaQx?>iH`x=9S9($3 z32+NLh6N+ZwrZdPBX>-hb4F&Rn$H&udDynH_n8I;@)bruxhl^EqZYE~^iJdSIruWf zI$g2906D3v)T*Zewa!9kCQ{xFiO;oZ7+8WC>g{6~4kxZ+kbI+`F$KX=Vk7r}A6gZ9 zAO$LsO+nb$V>cT=FT;u~Oj7Aa)s33VBh4t~S7}j0Qxh$>4S0@$eH41Ha}^f^4ZmHB zi7Bmda|SZ5z2x33^gE~K)^5HOqUHOd!I50bLJ@14Ap+VH?rt1n&|+DC6_@|BvV=}Jf$v46Q7U0=99eXLRRr;_CetEwI>)9uE9KD$;@2>i=c~7-UciG( zObO>O=9E=MZ!UA`vFi#WzN0M2i_HPbCXVmQFKUO#uP&{;TqLV(=BilVae{^~tE3OT zMe3ZxL)mS|=Fdq6PJbB0$6M_IW;z$@Xu;R^OHD=c&U-ktM?>yQ{-#S2yPsFvd|_Gc z(+A2SH(T#%3-AxqGYauWhS$Ly$nr*_`Lbr_F;k5;oU;(G5CizADLyN~pHP`rsex+e zOQIkVX)AF0I*ROLu5692_&k)Xi~F6L1J~yhN*EMXH5%$zI*vNfNhkBru(Vn?Ea*Ha zE`j1E=|`DwP3`{m+01%W9nH)471GPO%zb72ygT*_7+qfdhNK?{(bcYU6`8U?`pV|9 z;(#UyhitXh0c%@RxqY!O0_%ZsEY{i+fLlF<`Wfw2*FIi)RUavq4< zeB+(lZ(FnNLuuAWq;BEv^38n>IU3PvKO%G;OgNRlmgiyQ!SG;sNT_5mJbC--c4=VJ z2<&-jw0>hQ5~>%MFW;)MqI<61;0~r#7&CNDA6;W?DNc=ilQ@)2wxSaY_WiU$n{T!k z`mUyWm2ZeTGnXLWG>$pQ89#REa@Dy#&D_Ib&YS=K?4jVW2H!DPC!6Z<_0WYyYSTW7 z*IL)z^^NUaVuT!S`5umTeu|ZTxfm*rP9e#IQyopD7Irn~UQwdJbfT$l-bSp9^TU$y zNpsTZx6BMcUm%4;S-rO200NY>^8>pR-llT{m92n-!gpVU7mSVCkn2A;PP?27 z&-rvXOsg{LEgi~Sm(FC)zl>v?ZC|=>?3z8$@>lla`c7oeEaMdzU%HZnPXwz_A;WaO z){ki(FRmBN0`*rLRrVj0_9N;)3xR`eC{PCGpadF7zb@PD*KhHr)YKN3@(82i491q? zUFR2_VKB`0itU`iTx51&=3TE2#EPKYV9BR6d!M}F1XR3`*1lX!SgmEN)Upf3MgA${GX|>~n-Z+qr#S98Q`r!2mwS z8oDS{+w!F4XTGv5#$whqa-B6|&x=bfZF9>K*1q>qkH4aNTkARrRs;1vSIn9&)}MbB zx|9RzkAP9OU#tA|JsTw~-Szrx=a%#g#)nMPY8i9fkR5Z#<14Zk`18%8LGn!T_vD!8ZHI;6xUfaV(D)bIt9%Hc;uLj}Iv-3ql9KaxlR zwc3wSAQ)w}(t-t1G@h+Q)!7${{d=*Pv2+RQW9WIRRl<=|Rw@mL$u`23vMS1Qctu9X zYoPM-kq51uE|vcC(V)X3FYo)97;OyZ0cC!tLptkXtzvq)E)fO(e`Tbhe0`B~4VCiJ z;5(lqBoIlGE1tn5HkAz{c7=f=W=_=Lx)7Sj%35R@F|i1o04!ozj4&LLlACYdR9>t` zcyaHlIL{uHn{(s;GQ1p5AnZ8OdLO#Wyoc#A`;h`Q`mhOBA0+n;$)7bx;w@jNqfnL=?LH*&tuTk497G$Y87Sh2If5`kb z>i!G?%KU%>_3MYfsK!l&h(WhWljHO+P``=rX-j$m0lK*Y1N9%I|NLsG0^$9vmFw{D zrTRk-?&(jUw^~q8kMQ0K1;YFPvkR*D#kZ2uTI(_k08lN=&K9|xu?AEHbM)`qC=T7x z);MfPmwJwt(_e}8=`ELIJBJfVzTzy-HwYv)pDmW{0VMpzuir#=7YdCbbh9jE%*=N? z@LzWzSkE5iCUEcQgDuJ zHx#9C7TXvH0@&lWYmHBNJObFQ0ZY61*5gAbbd;~BS&4t=UH%f{N@~019mp-PFHVb0 zFKkrP4~=*B6wHPU2JnjKt4rmJXXHm=8CA`Jop;yQj=|2SyNGP>RnqZI_{-WjQT7%cS6b)PiN<;6a&09j31_zDnEP_X0l)fG(`Pt( z31Ykn97g71qhNkx0p(jF^qA$Gla%2oKHlKrZ>8X*em_zB0bgdzTvw~7Dkf&JLi&&G z^i)nE`2+0tNIwj+Jo+Wf)w;GMi=WPm7aPx~wZIR6vIY7gsZBSpC z&AgBhlu{1?@{ycNGhK{u;2&E@GuP?O@$xZkNbj3XB+cEri+z_5+UnN0m@KcMYb`dq zU@1keE_KeWTf8XI-O(5l7;-pXD>t(*ur7II&m)DY`DPz5a{veeP9M=TPjQJMDzS4- zmioVS2^5StrI3C}FMh!8V}PGTLR8eMeLmK&)&3*7P`+f(NtpmfXd=#!?ZY5Rh#orP zQbGA|h3O*{jrdLv(yU%Mq%dqve?_9l5L(5A=taKa zIKMx-^7ly52%`H!N`wp1A3>-tI2Y~b%%|13GgBj}Fomd0>S4;PwE&?%_CVa&Hus`RH~w?1(Pa+#({ z%Wa`KAFuzeIRCXAOiy6WaQFPuxT%{jdSZFpavd3&4VPwZ_O~9L=^A2>oXtC}57U+R ziWmq^i@RM61sNY>*cim*mB43%v4%%g0w6B zH%s+NAyS5r-BbA&=-&&O1AuIaLTetHh{xpPDTH&dV{01!4HU{(i59XYCOL?(ALBXh z2O&(v?v!flC4Zo%(kF-#TGFf2>?0m#12+k?UzSfouHmgY5pJzre@^kM++ezq48NBF{~8p)l;mF(S zEd}1ogLE<8%OUeZm2}htrb&sv#MRCQ38E_L{#i$u$zik_4MJ0}FrRP)EzC}e^Z$Rt z`+u|HomdHFY^g68vJ(C0ruH?!DLf7xIWn*z-3|IBtyqyl#|YgqXdWddsr_Cfy|Jlg zB|7vIajK3*;>Hw17M>0AhY5T+?5G2i)%4ljB^GaSDYo|RjL?EH~F8PEo&$$ zBv9XlRMu=QSL!dSIvOsmwmV) z^qy|>r_6|=#?yNXT@G7L&NqfjUsPoNswezW#Xr4dnXK(CTmr43_PIy3L88#u1ug*t zRR3cJ;eYR&r!2z^@t%<%`dgUo7CN0q=Xw?7&Q zC{u*o0pP%2H-rM!G1-yGxhQ3onYNk-m*^Iml)*I{$3>e2v3BX5N;0Stzj zbZ0EkmTx61SlvX+AHQIw(0qZQx|F^wLBVw_?%BZyD_W$agX#Q(p~mfOVito5Joup8 z^z*W10gWbo;-O?DY`ste>c&{5QtG%9kn%{3EaX&Lr*x0bDi1*ad^6f}(SsyuoFHp7MX z?-aa-Q)-M=a`G7))MfHHL2qSN>+IJ?Q`s#g#lo$bo0c%yr3=mu0B!y{HX|^M3xlYYEfKWG2>-M7}g&`%osuoQpzi@Dt(!6#{ zir>+;z0QPU>PRDyhS*(YqP1EU9NwmJQXx&RG9Lj*HV#3j!l)~+V`LMb9OaT3KEVUl z8zs2h(qzlujWf_BIxaVbtdkoyk%w1t*(PAxu1%5nelj=pqLWK*6}$Ab_2=MoEYBCW zPz5(Q+OcZM%r^*zv-?!TT5aVIfl#Gf1u7JY)EQ?^q4149pcAFIYa;eFt{yv!ff!?-NTO0`=WrhXlU4)0>3!#!M=pV8CB zN)IHO=~fB1?z62|scsBw&SNrfk}Q7F1r6Tu1gH|JIR;PU+oGYhS$^;Ry&^qsQGcX!g632;S%_DCRpS2JG}1XYIb zwRtXotapz5Pn&I7<%ja(>?x%D?h%T&fD(f{R&KP0b6~bdYNYvk>#W(sRZqg0HH_GR zq^^L&BT6n=X%?|-wnjZ#?vMt&#$BpiDMAYnC?HDq3b_T@$upDdZw{grum!0C)0SGR z>k>OeTHz69hlhAMKyS4c2uRgEq@-qSH>(|67mO<~wqMSQByh8YDMQkonrr3$6 zH&d|Zg7xh9FwG}UI129^l8_HD6uG$-5-#EB6x*iI*a082-me)Dq%q?Zf z$)VP4#;c!6FSH)sl}gvLnaQlax~14~7`B)^6||TPqku$ko-V`DHTNh_D$=K_b@nnb zsBbv>_NN&+iB%iGc1fko)IW1tLS*x!daX3kjJprCzL}Vk!~%}6+bqzrj^CQ%1s4t z%NnRL+FodMZw)jtR~0Rg{1$K4;+PPFNbkV1=iT)6E8UZlbeR{+#ldCo)tzZ+RVz=B zo15&yzaOwo#=8)wx!{{Icv)`g%B*^PkI%1nfjr%i5n9Y%g!bF0wQaxPIy{2}<&qEP zb_jn&>#xz7f)BCb-6`24*(IMgZZaYLeM0j$@_%CPKb|)vH3!#yy(Mn(dw2cj8I-T1 z7({rv$D`5^ek1c+-}?wXKq8m(kyFY`swU)t$+ z2r!HZQNLSnL>=%SgL?-F8K(sql^;nOBBhZ-NEwa}T4#GKp1h`lsK58RxsQ0{Pq`$2 z24UoAedfOj_-k?qbK5i?KEL^yH~i1P+eJVEKVC`vn2Fy6{5AP@CWH(3?$M8=(uBSe zLV`_~2)0)RzK_5i74&_H8YW&`!jdd&`eQ4Kk%5{BkT*o*$~4!S@;+r!sl3D_`1T{@ z&`*oGQu(WJk0X&kYx;YHs{*qYylZu!_A+p3NBd$Y$%21Mx*irq6?iJ1VhqL$1#qX! z=UHuj0!bfU0SO&=C{pt-U+q8Uvv6_32hlN^RPn4^h5AaNBNVVljX;vwOb zV60_N(Z3(%4}(|6`fo1z2vs6InN#d<$<%M=GXZP2tA39P&_f5yseOt9X}f3+nP^(c zyNlZAsbF65qifQeB$v806nF0OL9rHnXjPU^6*mE^xwzJJq(65m10;TmFN)GKam*7L z^Px%S&>S?;JRrWxxyrD;%9%Kh0xc3b7eDzj$t}KRpz$wv^p8wKMy~~Y$sm#&5TjVZ z^V;14`w9Gd4F&dDqp_$-r8WF$>*1q?+TFSTLkPpTJ186o2)xAn(Agh`hgV|(3&fK>a_Y7JM(npAp zjpF8T8qd9N-c>SnGBZOHH?p^XUe!kcN@NuLHent@vmf#LSz=mkb%#J^HcmO&xxifb zU>bVz8b`w|bQi7HE|22b{zmj)Nw430f%G4TRQDuFnaD7_P7r6-GGb0Ps#$Olu3VHz z9#ZCs-(yY)3BXPiC01{Lb#D?(SX*r{rm4BvIq zFrV&1jj2*ndJ0^TA*XmmBHECUvCJbY?=4@cQ0GlBs{v&}`wsPU92%@Xd2!Nt@khMI zC$7|A))mJg`I;{qjk)^Rzmfed-$Gd*;D(dilIDyO`u3HrXQ?Eq^kZccIvDiRT`nBE zUd7J6YU0fm%F8>gqy(4vtI2MqX+9CMnTDW*J1r#dn6@$<( z=QpZTRY?^34P1-J*%AOe)-<@A+*u-Azl$D!ZG3%PxDcm^N(KAP3yZ5ob9jCRwP3Q4 z?rapq;`Qpe4eR&0>jk}a6E98NJ#{s6)Ev~V)kwmraf%XG)}p3}6suH#fWNfIZ@?j% zU((=1yn3}p z{jHu3kkdYz1MnZqz&!DXXv?=k`ACo9{OxD1(9>EZ{4s=rgdui{iBtvd z5kV6={+TOm;rJx+7($(-5Kj^}iu$oVk@e$eu23&kk^C`)-aduo3ZL|1J*K@CJt3AT zq+G5L^AUvFn11F85sX6qV)$=~ek}muUe^reB+?_&4iPV;Ff>>e@&B|!KSNWjpSI?k z<#UUGN6ZN%0SMO}1f5Lw823W{pIc6XPaIO>o+tI%4h@SS6bgw#9K+mMF(I2&IhQ6K zmnP8+q2Z=2jyJTM<#7vU1g?27m1nrZ_#qRrw3ui`wd3S^)uaz%%^<$+cPkVM&KiBU z4D&f{W}`UoEMdz&3X=0yrk_);;S@3Mw>%>;KW?FvO%h@8E z3;88IXPS^ro>_#z{FWs@V2-+Au6Kf=fc;aFJ!7w>8C(o?Urg8c-qu8pH$8^Z3q)18 z)YkHIG0bCZL{@j2EKL^JCFlLd?0u8o`r2awYVyIAU9W;Cvc>3Lju9V-_&1C?65v)! zmwjw^5>U}KQLh1MH#|IOSOJo&1pVxY()z3_F3%Scv=RLeJo9g58Cw0+F05jlcqo$T z=0*a==%`a!>z2OR^!a9noJtUhhs zR-$ZPTw!_4z-mvrfef9SOfX8+}RpS`R|AOCndY(UMx%Xh}w zWi;v+j->J5>tDq5(ZwpW2sqrFax-g8k(2yOG;o_BlF@Cm5Sh$c$5o7V6pR%ip=uf0 zyTuvAa!i8{IqBX=TqV!3nN0h6=Ss&%#dyC>8nvx&21~SU&ZA34QFTt%ux0+j2m)Fz zCW#iEN?m>&Q!_y*L9>|$ZB~eD07K=P)-pv?as51q|f!J4$p-?!@G>W32_NaJ3Lma<8v)=0bC z&@N1O&N{PRnxMHWOxIBPeOaV{x?jNW)nbIO!owGDd#2*c15dxwnwM__zuUu%r9@K2BF>gYIJA8&&frIg|6lYusw-m|to=Ow;$+ z@M2K%?8^ZNRNJq$qbx)@M|0;eMr+;+{Gz0m_oha&Y`)sazJF^Z+<3PVtt1}g;#EP~kvd;)d0!6`Jwqv)ZT9)cDhRJAa+IlkNU?~ zLTucS8#ow4Q+Mzac#pTy-EtF(iAoy8155B`|AV@>jEW=O+CT?_1`EMMfCP7UC%C(7 zg1fr~NN{&|w?={o8Yj5B2d8Pg@rFw>bLKneo;mCOzH8O$)jzu5dTYzG<=IsgbWGXP z{*@Eo?(BGH5@A(7*c-)`dFAKoZ~vI1NuFpdtn7=D&QIat{=O2Z-pU?lPLXh}XeuvhmQvb`Ff5rK~Hry$N5=nCK zG7@tBbnx_$RG3^l#@xN9NEqqxLMk84A}9_I|MraoQKTB-_x&tx6>(L@|NAS_uPK>L zgj|pRmQXGLmQJ(!$a>0Py8XF5+kOnP9Z+9iUtg{kv|qo7Nx9|$J5ZQV?>I=S{u2-S zd|%6iab;s;d9UU%LDJt4dw+3Q4ndQoF-B z*T$|yr;P*1vA)%qIaF?#XRPQWZ7b0+kko^GBsK1tG)VlN6+Sas&#C8-n~(k^$CJE2 z?Sptt>7n)!gpRgSq(aVYKH)BgBgHc4gzwH_!igv>a%!pVm=!uO_ueCjYTokVLTDy# zE{+>8scE*$q<9E%1(bVUs%O3V<#v0y!g^gxrI~_;O`GwyRMt*v!`9`R)XB^7p#!FM zOQzPc?;>n8B3_?;nF22_;i|k;U)sKcSJyp=?=eeQ)h=6-QW=!0qSbd>v~lC1^9{%L z6QerZsc;#3zMXQ{VVE$Kb~c&fudS@dV01$$=SGZ`#Wlq~jdiw@t(j_q6_#srl3%E&>b>|~y zc;BrT1u8s4w7n1@pI!ydyH4|gxv4aHfrcoGjwO#5MXu=&Nul&u2=ePc|-9-^zz9$^O4mChKuXc2a;iTNw&0N+Jbc$6F#EsoGQOI;qgW+lFAsQ_!Io`{E71oyLE_Y?UL$PSlw|y^eu}NVvXLdix*D|Vp zCEpr&6)EW4(4rb0RIB1N>h215OUc(2Dq&*8KSJ?iwvr-#cEV2l*P*i&Yun15*+P42 zasgI3A8HfjI7`ZX1Y0U8dGvAN$s7mmRC$x)2Ul({x=c6+Q&UHfA{$(}>C_@a$eiZh zw2x1P!31p9C<)yvA~%1YO15F{fZu`$XThbm~DwL~y- z2)|a-|588MduIHLuTf&{5mj9KG+U%`&`|hfCNt)=SnQz%r zP4U6G9|QIM44I?G(}4LSl<@AN!zX?&$jVZ?IK%pS_(JV?eaf^QzOQhEkHhL= zoDsNsoh%6=nYnpLnA&@O=Is#-!;@};TP1)X-yqOTzxI-D$i~Mi^!!9@==Uhd^Hk;Q z$y7P?kjh1NWcZ45?1p>n=wS6EFh%nHTNdpqnH_t$dR?z@zpT8s6>T!=3K^9eZ7b@; zi>C4QOH?VVv=yrqfPp+0{$#@HGzHo?=HXqZA-mciyOk=oom5R#IbXB$1dsl#Y>f)x z_gOfn7$&T5sa$TN?DyxF4b@dadZW!yaRwa+rDY#?%j>)#UdVTOhT{{f^Alo5t@To- zF;8^>{F+i4MG?j|Sbev9{Gi_bNvl3enijoky1#_Wa;yhnvAgZlbz(b#m|!>Ga0}U8 zu%3D#p=(N?3IXaD?VtD+kTE2<9t83-eE0S2Xp&WgWMqtf+sQhy7_K0^+*G#wIpQC? zbv#RJtCtrAc$7Gpo4>SPCuTVJ)l73`oAo%yJ%FxTv7G5`~?+uXaWF|8Zg z1;lMd11d)L+Y4Nn?x(zjm{s{GHA=UkXfnj|?c_(Th~!4d zCCwl9wliZX8VBQ1ZHZ~zl58E{3{dR9%fz#&U4CpBNh{vHb}xVSTC zw?Xv;c!>seykE9TW7cF#<|iA#YfZLBCCe-mvTo@odI*YsD{o!5MZj=3^;(|2+-;x3 z*87Za3HnGJRZl;DjlEPL1O;g}Tx^IOgm6*_J=NLi>)?O!)a9E4Of|O^+*M8*AN>H` zO^-VX6tP>znXNzC-yLzZlrENSS1etrL|r}ZM}Zdu>3plrE#T3n&S5R~v8ToL z(wRERLD6A|xM3EffT*WHj^gI1i;g@H%4h2bqGY(6b9X)XQjhQE33QubtioslR)<|Y z@$69>A6J^Tx>>Hxp5tf(9-^;^I1v0@9!*z~@_m(_5)~L)B~$EH7PCz+4Bg6H9-Wf( zsSmmz&Jb0nT7S4;n$wGx7RRHDU4LZV5%(eF@04k+}E?eZO>Ddk$>qWkyK zbDQOo*~>ewZ#(P$1!Pq%*4CRuhVv#{OLZ%x!(ZAGJrr9n53feFfA<{l%>qnF-`A7j z;i}S?8Vyx_o=4IxN>s=&vCME7F^Q+qpCGFoK4CPkYAoAuUicwvMvyhKWU#n;;9^Hz zJV;}oByRvvB`%RgFHvP|(mf8mv>iD6lWGZ=q!o^-&9`mIvf}N~i+J4m+~KqL)_37l zl%hdurz3P%F{e1qeE`xY-sG;$704SOUCe%9&o!i9FDgrf2pNEYTzVG-A-KL*(X(~m z;QPpZZ{`ToeYO~GnXdUDkGhoFQGdi1s-E~r0)QW<9_)dDaDQ#U@O72SH(!I&>_c-n zQ@?x$&{M9Z`^m(7cJ#gu6u8t_t6YR)Fqv}FpPgM zF5RzDrfvCrzmKaP%D0rPSKYTRR0;3}5u0*WO^sb;``X^V@t?MuWYGCWS&S1SSTNQ9 zjHZ13`e0({UIa#&FNF~9o3ZPu)+EML&V>+7U)-LF5_+3`1lG)JMGsX=7 zl)W#0tWWlTs!3jVY>x+>OpS^5cVsQKJn~t!oSFLG9$Z8E2(R~J0Cqpdm|V@)8BN~` zl!oOjm*Q9-x*QM;gTOw#=hk6WQy^eQ@%UpqCU(M@*}^S!DK5rwjf9xJ>}aUb&iR7l zI@_Fsjc;WEANLlKh~K5Xc=YHEHrtvm$8!vLnZ#GkG@_IMpwX1ws<=D#&?$)*n(x?# zq`R3u-fVf#Oq~Ze&MsUIy=i;gnw|H`DycYd$DrXUQJ6Pa+|d=Y_moQoq(vleznGvQ zTD&Wd)@ymbM0&&71n(JszlM$*CdBOy-{^QIKXpDzU(I1)j`!1dL*RRNFo{mdKCp!2 zWOJo~AG#rAxqQ{gwWp`g3Jy(woN8PP3-~=q8}RY9RLL~!9+}#ilw2L(g>wi+lAN}Q zF+e@l}$X?Z=_$7q&o@EDEu4RYy}r3R}WT}%&|ebyV%a?>ed}!Q^5P|rr?&?+52mcoyV$lS=H?;C)jV~r7flNH2G95R z8BYH6VczWNU$^+V_t<16$I&sdUO2w;7)jZf`2%lB7iy<`+tXCa?mePjHG zd$c*DVV^K$_YgkBI0BC(sv}I< zId5@jVQiWy?NyG`1$xCXW?ecH3DEAebv58r59au58)kBI&G-CZ&ORmLAUvq+XJcm& zvT?%@V@4Sewq!n+@$cFLM8k_A#iCdn%2&yRoy z5TD4+ZzXGh$=&{}bjUR!pBHoXU=8TLLA4$IaEmN~qfGLoP|)MxBZvM) z_q_g;jy#6xe4WTNQ3W^q%ZbZe@r5kHLazTGhZ7Kt5=h zD`ozMC%t2%SWnl#-vfhW(q%xNHtk)Ko^yoE6BKN7uaGRgOR0Zd^;FRU z7TA+Z=k~1AK_{5ETt;@QrnUZE9Evg-v8E-m2I@$V)S9T^TI*WSRhbOY>-updg$|os zn9!N9>=K~#V{1yjVn~)6G8@0tES4fybEo5OE2E9&Z}5iN?ZRd4D9SKtYEK&pf$WzwA2IK&zO>WNpH!&;mRv; zA8RJg9yUodWc5>Yqc;7SJBdSImApkMe`ru^6KnUAoMw9J7H`pJ3U)o7(7&>809eq! z_arg7R87*5F<5kP0i-$#911*Y#C?Tb$;dItHbT5#DG<@ z152^^;#?%AI4Hh)s(A}I*=X*cUtB;{I2Xh_b(eJHqu0LN1pm^T1}e7$@e;J;psNb9 zx0$%b+H-r^m-*Y&oTH|AU8l%#I`Ha|r>vGdQt~BFCh?;Fu-*%D*@lEbx{{vCi~7Ht zfZO4n!vr5@^_Msf2%xK~!|F@tgtjOvMM4Hx>bGwb*j-x$y}h3~&>gM2biZ1tt0}H; z`@7h*nPW}=(y-y~*8GystJ>$(&~6EJpZ8U}3!+O8541_QewB2sS%$hqGk(Wgt%WRJ zdRB)5Ixgli{wp``<%Ud2qy9=eP3|+UD;^0hb?=yPf5Td^e&?qKT7nqay5W^a6X^;S zh=_PRO^w4FcWsi|YjPU-U<+nw)omdn+8ON?e1V*pz`NiTRYonRFCbh57hkPvHq_VdeMdxSO)&3|shorb&p{a^wKSrygaNSwe`O2f5_`6ZeGimth z58jNUo)pCED^O}rFU|s2=CCWyz$?>Zi<*MnBYplqpPId4NbH-_8inQ(!CPQSZ?{SI zPuP*E!qbvOF2v=y-xjV1I0X-vs*g|hTzlch9xXISc{oX2T_)JQ=-+z9&HG)EoBNx# zvvC@>SEYY-LG6Gx%%4Ck9%d`id^nhs!B4GUR+FrcmTMmUs<4o*;>O1_q7hr%{#j=Njt^Uv z7acAi@LeSd#^=|DRuNL=_5|3-Jn_Fc^Vu>1bu%xDm;r&JX@-0)6 z-hw6(;?ly|U4=q%%FrA9gImh}8m>iGdN!ijZ}P_P4Il1dFCHB^@Q!X_fWBqk0u5tN zrjB#wR=Zl)4)JK1uV64=1z^43IQN`9=TWm5A&Pv>YpF!5zbUV;)6dwqB^pS-rnNrSLG_%Lgxf{{W$ z|2k0Ly$h&?`Rv8_0DQWBqD&d)38(=+f`EdRQXKdiz5GO!BgwC`|=Ss)0sCwL7HjDr)e!siG?(9lGaBFz>%U z;?LhSNU%31B-rb3)7G1ISIXKesFD`6;NteZC;EYs%30PF8v^pqje7EGT0Kyem)&WC zHn^{)9GrNKarUzv9wx~_N7%O60KAZEOD%UG@f=3~h2|dkCzE&U$@rK%aJYESXvjVJ z<7IMfpSsUu-ct;GjN0S<9#!+k5#i0xCw-&CC;Z{kNyJ6yUBu3PgE3Z-Z5#gW`T(DN z#YkJ#IRBTj4@ckhnN%F zELX%IJ~_O1A(tbVRXryNBG+3AJ_~(d_cq+Sl+`iS5 zO?M;KpX-6sI(s`w-JqnMPZtjk5{5i@TlGD)&ptM`H(Hws*&{ivI4Rqh#F5Kv3l5{r zlYmdYJ8$1~d^B@KA?%p%mh7Q4q6WySQCTgk2S`QLFUmdUc`rg!lq1tRUZ3n(J;5=y znG)P<`e!wyp{FxGu=+bM*A3y%>dOrPEOjqX@z&b1r3GLqu~J0n@+EyZE>N zYsN}r2Y@X1{TAl&p=WuKmtuVHp83e9{mQQ@Tle0Ic{l#-Vc=x_%kXTgGuX!84u|9@ zVEwVj@N8LTvR<$)67UGsYJ7y!GTxLxIuieG}p5{&pBAqJ07Z^IJRsJ$e*Gu~?< zc2B=A*NX13z$d_hy9Iqg34bRZKts{gw`7+XMA1`IJnTEiV)Hr&2qroQb#yKjj%xS2 zFt*!LY~3GO%NO~67XZa0-u}e1+v9-P-^TWr%HnQj8Tf@)dNFt1`*#GQJdPh94EU;% zwTTPEi3!6+S9}&>CZ-^HiOFg#CfZT$+IOS^Gkv-dZ$CTW3Rmd#3!%AZ;S}b+{Xl+t zcr25cwf7f-gz3DK?RfLr!rIz+C~*xp!M^lO$ti<0)z^|Utb5)by)ht_`({)b6~Yz7 zv{ry81UTaLwd_fhq5Fvx2%ThWJ|;3kE9fZa17F#b0zHuTR-i8vydRZuqkl9By~-e} z5FBnx0d|eZ#1;XkB;AhG4I&yG=4?HS4LnTDGj)m%kLNphf#x_s>BcjzYr!dR=31|{ z0IJ(jXhzFr&bFT>d6xb97~pX)rNwV^g*f1hHsyPGXWF|OUOfZ*-1hb?WGr&;bCc-X z19k*SUk0~0IaX<2-P~>dk19P5w@7`T;1I0Cw+G*vSnB?gk=RBmJObG}H+E10nuKsA z)En9+hZrT(mS*GG`oMea<+(I`zoNGB9<#H)zWUatoz*gmiOI2! zm|(UmeAe*bEBN_UfUoP-cX+vWCn7-mOn>U4D!Q%(&}&K3_GwH~Q=^`u!h3vTUKDUn zLg?mrz(r2&7Q6=IwD^n^qRPU~SfS)dV!@yG3KjoE4^fc~i$I;A$rEE_D>RAwfeLhO z<4O^2;QDx}a-EcGkd4ZN2lA12U+}%|e)A1qwg{iFJW1e>bjJh_p-gU z8f^%2?q*8f@Yi4e3=&MLyEZcc{1t&ig-L$>5F<6A=H5d-jvG0p`@n^Psz-4N`_H8U zyaKi$_3FhcbFvsU?LN2XFfuW=278XoZ{mjBo1b9$gkgZM|eP$g?W0vq>Lr|T1d|4j$+Eu1|Gz;lS|hE?HU z*92AJ@pKUX2Y#jx%t64vF6;MNnI15M_|_9X%t4>fI2ro>^r&A3^PdNTvM{VM+NYs6 z#%O`k|6|Yi`LM6?tsRWWpOwc|H!(#IiN5r;Kmub45p0d>!)K@g;Nsha#L>O$p-VWwFOg=Ak=3M)XFS-#6b|Ht^7VngvnKh+IpNy{_Lf*6gH!jduR4q&NS4c zJjEzsc)K$t_bpzRBkjm-vMlP832R98tl zZ=#8XibGwfc(k!q9AB5wdt#x+#2B$lG*89&%!pENy~m!VDRhdir+P(!J2%+-v+#mK z$v1`~)Pv3!z^ZNthCR>eoO1MpihW{vP@sBAj({IzMFJ~AZE~K-e2QS)TOc^} zktRWpUq+9po*gx2y%yceOLqQEv%oA^g;Rk(?!95%guVgnB)4)-fVQ(e&QHM=WTC+i zu!ayFI>6WF93vUB_|aEx)%E`7HPqz8*RoyTCRYstC%G~0-8V)@sKO|wwo!_Wk1@nU z;RTW+`&!XU55gr~h2D1g>ROg3zQhdOV+pimWtoj3!w>nCY%>`oH0g^t2hrnv1_A{b zm^7Kijdxk0xmrQ`@&DmIC0~WJUFP};QL!#RuwYRn*E0KHs;~gD*jXM}RLh$+641-ZIHgPq?<5pLUJK4O-p=COJq!Ih8^~kSH!JI*=x%{wPSL zd=niHV3aEUV7`b7PbN&g-NSrAP9~kHK5)c8Y4k-+nhIa`6-Em)8%wuB$;y{_=?@CA zW3t?7q(8yLa%?GiCO62p?f4qeq2(pHn<$cDH)!EyNL^C6RMiOB2_FN`P>boL$lr9^ zno7fox`Kpu22D7=cfEp%d8r_d4EG5eO=z=*B<0YXUHRDCU6b~No^|t8ZDbf9HWe|Q zt{5q@7d^}DIDR7CPy$!G*lXl`zbqeGIr6ge#-aIF3OFo*j<3&{?bt-=-@ey1)<2ND zmK6do-csjD4YhPgu?H$L#l8&5^zLqHA~o3ab3oiGRQcHYvuL$}xkY87oFAYf|4_sKR z;Y(9dyP0e$jhIn;J+vzbfkAIffJmX86Iij&2s*GWW*W({!0;c4>@bAl5k`*1UB3wX zhQcs2f5_;i$ElYd_pK}=#K3eFzx}8}jA-Tkr?}4v;ycdT*idBFG+5;r#MPX4pc7=Qj|01kf!skojOtnhAuz~Z}L)bLw8`@_VM5gWc~Gn{N&iVM7^ntf-7`Ics0x z=^-w|+01|Je8EOs>8%dv)A8=8Af`nZLOL?GPsZR#*rJ)uEKVre0QGiByE#AlE`?ES0Z; zSEI-&XJz(tDeup&y-DeEqw=D_vi9q|+WWkx`?;~?f?@*F{R9I?6%-4_k>%REtF!XGWU#p0AG`+3}D za>+In2{j!_OJ3#1T870Z;Pb`y*-0h+^i8;!**9J-=~#$wLOxK+4uT|qe)5fpeT(@Y zM#72ISCuu}hlG$sgjY%g+OwoqU>ZUfP5DMmNNPQ!R`T~v z_qipNwo#jL+mh1ZsB{=@C)_bWrk)fgas{NriWUU2W!b9~-$j-<_141=p4*G;toFNB zNm(w?P^OEku&htjr1NeWCeUPz!P=n4d8x3Qr217mR|!_$R1TtI-qThITP{6{M|pIy z!Ah&D2cqK*WO0Zu$-Efy?cM3l730LD%7*vnKI9jBN9c;1Nv7|-3K=b%`OerzY<%2Z z6&Oo9T%ps+7>g7VW_D*d4yxfS=RG%dq=ks@weWLl6-OYoDzjUR!KR#`@1}5P||Tl zduKG!1~N{PN<%5Lm4X#Eb0xt$v{{rSzpy`Z_0BY^T*5J?ja$9=FPNnGmqu?J0o^Jx zm{GzLFIrsUNf|#RMKaS7zQ=DuB3J#gO1Wr5oFb&6JzeJ4HgrUW?Gh*7hb$=%YZqZd zE53G>k|V=2vCfRf#ptx1pd6o!NS8uc^oeyWUwelW)qKb#z=a<^7sD^P2?4tl*e(gult_~Fum({dc%_NCHkF{xxFFgS^ZHj9uLs-0 zN{l^RxfoxZFTyFO=#&ac@~Udz0pED2xI)+vc8br*NtG)<6Vhck;TBzs!8Wrn%M#3_ zTTsx+O_(NVqBxfssq7Ru$2_V{7u{Kole~;Ys(^pk`tra_F>7EzrRMkD1fXfRmk(!5 z;S=UEIxSlvj_B0rNcd)(mHZQeV1X28lCuIM;tP(4xOam_w=n2Mp9%R=D79x!B}8|; zCr4hPQ`81YtN0e;{iRj@$Zk9`+#gtDy`j5x8P66>IqZUjMH&=cJ_&p93pYM@o1}fnsEA6VU=M$eLT3SVw|%)ThDW~C0lK{Z^aXe zuY1;of2s&91NdOc2_bTcZpScu`wx%O~R5kutg~9Up^(+6pAkwTf&ff)zsAMP7 zD|F9^2yK-#pIe&9cCe~|M#M@26{cc*y#U-#>HMU$_(t20+8UlE<#~0nYl0b`;2?pC>bH-eI~a7e;i`FlZ*5I6s0Q+9f-$fCy7%lPuWng zN4soPtRm7d4i2#(4Ifu0O^}LZY(YX8O0y|SkNFCX5^2HVGQcsC>&>dXgRBp{3Z%xoC^HocMe4NXWtQ3aWhVH5EHcc91X=>P837Hnz%o?s z;mXSOIZ3MM4L|PldyO8vh)piJx>{DzAlPx&joZQFKo`;c7mcH zk87*W;xSLNO4e@GOV52jx6!4^|821e)8FBAvd?q>1D66%vQ0EnD zZhD3YwOR_k77g1UbNG7tgY@}5o^%9Lo4*v%>Eai#uQx}C3+43YhuE2^iMubf@X90V zdfqj{yR6(<yaH#pd5%W$jCLGbV$7>UT*k{Ww+0UO-K1iotjq-QnwWM=&I)n#r zZhm}w`l38!S3OI?*ukl~Nce-mqGCks=$AIU+!O2O2<>>!Ea4xz1`F+eZGa0Ce<9cx zF(QJ8?-S2njh755njDoh24JAAtaZAg>gB(>#*`xvtFye5W+!aUpU(dx{yxhs`x8N_ ze$~^rX3iEQO#CI-0g!pcb5_EAOeJJo8FW3j+8f{5LXRp()=*o=+1c19hDUGa4e`>k zFQCvHO{R@E6pRv|uW+v3rUL0K#`Nua_n&sQ9xlOBGBreQZbmiu9|l4R?ell;trt&b zbhlgV^?xXGYCeWvUy&Wr&l=YrytO;MfjtfSzHl1Ll@6_uFfY(HBep=3TUfwlH zO@2aQmd@!}mRVjGU^%;`Ufq}NKx1~cYx8A~(-7HI{?_WTrBT}m?Ya$06`@Rt;SuK+ z=Pn-uFXI>EzW%K4WBe(}r?%Vf8VU>b0VTRa_a~LZdeeZ`xC<0(8e%h@<<#$gw==l6 z;8*;*XLD1#OVfK;sWeQH&GNeeQ>yr= zN+attacQgbXv;NxV!hig+0nBr05$A=jC$&1a$AuaW)j+_!dM>*C^`AL z+jp+kgJEa_iZ&~r6Ck|J95!RIyQe!+l0RDM)h+JxuBsY?J!xxda@?=eF7;`;&N1jb z1Z&oxt_yB&4kR<%Kw&lPs}Z_vczH9lZ?q*8J4i9 zQ4ur1OkLd<=?fKk}S6Gvh9r_v0F{uUE!G*X@l4ZV0tK48YQz&1RG_ zK7rSbUCm%7ryu@&TQA4!g3Nf=EcWsVMuiEVJ-ly5{F+r6{_|K?&&sXPP6wWW$YUz~UfZYEbT6JT>vS}#cib(W zuPeBkrU$WuIV|5UEsr63r>8M*ne%Vd9vsg<>$4?-=y_O2gof;29Yu#bB#%&&2r-2m z6kd3M$p*`5KMM9%kgMg3CU+Z#TPbeENB%~gZJS@MRs12?cosM80@W-9?WZM8bxi|L zii_P*zN4BY|K~^ey={E@;f^1PkBoSw&Q|XwbdgC=VUhNNXFdxo^H0P(FGC-ZLS^XdRDHR;p9Q$AH8Z<=Yd-=KVSdXl%*H}#( zYWb~W>zn{1xIiVO8n{P?EqA&_F;Jok&WZgBRr4qj)mufEp`9td>`l6Y98*XZUOlxB zA=61sTit;gTTi+Tx_Y2%6er!zKlFY#7={K)jtf;l|iK)P!0z!D(36t05>6f%kA z5-Rix6XV(W27hzDf@&zs^}}$EcE^z!x&Elu5ALLd@oun*H$mi^_6Q6s;#pfB zkSBq_+|A)}O8<@hLonho^Fm#I6Qu8RTmk;1B?L8-$i@`>(|@D$!5oRhU2*WJyvY}h z38?c4G!-!%(B7vwrK~mL8?fww6#*ukKMkq2EP2x-p~1Q!XtU)`s5`9a{=swtvQf6>8cU9DuURKeRGwtqt|?9IYeXiImPn4eN?~~2^`f)7YdTohl2z9I>e|BvIZ1!n#Ft%1cc!1|5k4dMkcv8a!uNqr|&KFCdHs^}bw<;h0f^5e|z zdmo7J?>?bNX7+BkC^`2^9bx2d5&A!HQQY!yf{;O~93Q82y|2aM zqh(HwqgP4OT3nk1#~IU-W~*gRG*gPdHoz?c%caO9)KLh?8Kf#DoI-Wrjp}+P+?<@B zTQ|SxpH8J$+y+9rrnII(1>The#%P@GnGwrc_VinQ?e&(`{Ap+0TnkE&JvskS?fc|7 z&jMfw$^teQFvxo>>NU1%D_AjhQ82^)=J~0&6Qh)0{~Pwkq<;JD(J#LZk|(>57 z^u2CkxjjB{@)%okw0}f;RhhXr$yr*Bx|80Jcn=n>d0jXzs=|vmpEhjU1u1h-W#Z%}BFai_AYPfe-hKynA)_yoB}XRMG!n$}%;6r-j^#CC^1Llm7pp5<4v^ez!ly zy8NC6J1YC{Bwv84n6Q9=Y{%bCq~ONSaQ$!I=3phUX!yGL1g$3glzi{-o%q%ZTc5{R zF^+}%R+P#3ow36?d?6yK!yU3*Dl0irzn<@$=?`OMh=0|CiAIzs^VYY*_#Q zqB>6x-Y)^(FIVNovNW1q?td}QsT%XaENGq=xW_%EKg^1pdv2QgR; zMv)(T{S&UJO)0|cq6eX4i68#%l1S41Vsm%?{GVSsl2)S3m8BEIfCGVBSiCFh-f-Dp zKb=Ssejm;h+lx*)i1Tt=1yk>qxkLC(J;3=FE%FzLu%JA1%p%?IGg#&x$&%@{dan=w z<~0#dA9wobJzKdKW_cm5^R^dl3(wW%I})rIl*^AvIwn1S-P&jULs)2E24x(MYTCry z{#@^EhOl5#-gZb*moUUR$1>p^BOMiHH<{%ADKG2FlA1pMHh#fYI`AEGcASsP@# zJzrG>HQEV3Z1QfK@Vid??lR7vZ4JP_a}W@^oqK*xLltn{Ib^>pi*Vue1KqthXswGh z!=L-PjLsR(3ZT0kyi0e|ALv($ZkW`rGT!sbwrfe{GtZVC4kYwAj!L%}8iN$h)Pe@y z`c*nT-YY5^-`-)pisW68;EgMuko=IE6!nUwr?8vxr7}i(G34e%HOC2yL5*ptI5q6` zY4b$XtHndRy;*WXjGwLt`vL46ZvgEMg z;ssq8gj+%1iDyZcgE7f5Im0AaeXpJt-o)1b39@5qF!C=dRmP8)-M`2&ici3Af55Ls zx@CM9k!U!OQ7Sg-^il8lRhU@mL;ak?R*V^oECK1`(50MWtXRkSi>a|!6YXO0wddou ztDVo|Yg`e9Y7S!)lllf9%9eyYmayYL`q;hD=W@93b|K-EzH|AWha?gcaP0rZAB_ON z3sqkz9?TBYBhEeVig_;a7eXks{)U9WUv{wW>eA7ZoCEl1&hbeVxVqlbPPE*TJU8Yk zm!%}w{4`6oc#t+w)Z8IP0 z>uhtvj|+9;!VsG4&MUDe+j?97L+G3+980tIfvb%#R_5eivjEyoAZi1H)=;jjjgPkr z(ZkuFZUJr=ovX%+buNzjMzv848EEMx1nrj=vF@%NqO=->pUbO&Vzk)to^PsQ3xJ@e9&4W>EgG`4dcEYQ1(JzOIzIRqE zqg6yN!Y0G49Nn4}|Na9!Nm>3^eHkp~f-kQdNakSJBcD)E%$FaGrh|#Ft^uEIji>-Xf8k0jjqoNNi<1bQ) z{!$`Lj$cuR@v`OhnppIQ!(|_a{SduiJr3NI;rRInIA2L84wb)^82L+yF2qv=##!KR zj5*q4HaZSigpG?eU`cxDu0Hm4d-tKe!d6R^>+*7^j!;5!^6f+6jtnk5Bl#UP$>ymB zwcdZr2e+Ye8~UbXRX6+V0hd!i{kRE8B4KczV?MF8gW;?#iR)FBnNAgmVfs+-IFl-L?UsGy|30y{QGi(kMzXlW;-v{wG8)5o)?qUoxnMv2pw!~sI zw)fml+naQ^Ot60>9>tP(J65_?!0nskWNbZlW5h?7-`aJIGRNx+3{U-5-!qfOza^U) z)SpVg<*aPZ$9E0;+=c{QC!}(gq&ff5e(OfbW&5*g$+N~Duvs;Ek?KWUW6L6oyCQZN zGHsFO$>`3QIxcH(v(a**$Y@^h)t(GkZ)JD!*x#nbYmbTo$Qe1`rd!o^Akok>i_W9) zAW0K5-c|;*;(=!ssCh}AX_-m{c*^#mbA$M28>V8OU zEo?Hc5l3m>+a{nVKE+qv_o(taTa+Z z-^fkr^OZw}pBaGPnEy8DGo4stEagwyR~TN*sES+9Z$k3R5Bq~S1x4|l#E%!PLK6HN zT}5?3QW(9W5AyvD5k~yM>)`GkG)%PCTK)I0X+@WJ`U%4}=3ipd#b*gFOZg1<4fHwxm-sc0&l73;j*(=>>Y=@0Zj4$3UR5*`{0_sWY z(*UPP6@CWjFSE8RHth=C@BLLhYYtmjPyZ zL*Kk?kbCBsj3B$`R3%qAOz-MX+;E}iqQnMn^$A}gpZHuF89&YTBl~O(uJT`PJ4x8y zqNj1SzCN`sZ(3~W*-bZHz8OrA#jo8FF>qmRkg+jn{an>A%6~eH;b}C99WIM^8jr?P*Ck1jPt}Dql}eOvt5Rx7v6uv6LTH~D&_`9S=$vG zU+6UYTTCx@Bv({J7UpiWAP9`eI zXt8)Z(Gn4@kcC-Lk;0aIA*t(fRAJY16$N0n>!+u3*gfKqW!|Dm76L}`3D}=UoG=y} zj{!VyZkuj`Yr|YYLtC@ASD%QzeekA}p=mk%Et!r0#%;W+p>dOosjoIfz(B<8C$>qD zTb%YC+==Ef#yU00;HPX0ii?URthU&a{VNSv!`b#EO*VN_(EFp26D9fW!6cWFLW~rz z^kgcjI#!|SF>{)I&a}^W?`$`&1_oz+LXVZ}_1V@K@w`lR!kE(EZvqfz<<5Dq0D!h0 zH%5tX*ra)WDEk+~#U`y`d-9bZ;23boQ?Fv_wNe*fY4A(*hZXFGg(U1N1nb2mQb}^_ zWWCJaylSP{eYvVCdS|1E{#Q~X`~o&OCv>QvHNs>|@W=%TAT-;VK+}*eBv)31Av|9p z#3urF5NvRPmR9)%(_AZ0XJJ{@wW<@*s(yDuYmH7q-@0yQMt#wfA`E4(B-SXJiAPyA z+&QYz(;3YgNf-<;oa$@!+|K&53X6U9dm1d4wZB~37y>}k*unO{80BiYUi0>b+Jl!G zqSSfrB+T@=zB&hk^p^O)e)JwSC_%W=IH(D$Z@$!X?d`j4spnSnD9WU8Ghoy7seTQo z8qy>%JQsJ7S`N$KdCXwVlUr9dl)>?2Z#<0Bmg)s+ms3mA*xF@werApQGEY$$v7@DqeJNF<~$w6-Vb!!^lWLNqpOGR5WYA)$SDnxxZLr>J8P z>!gc9Q0wt!n(7fziZoJhFaS!jd{&e}4KZ|c|EA1luFan=5bZMBQx}qJAh!E>qC9je zUdYJAB1xy1WM+o-RoMP^Q*Ws@qyJDws2lCw+5{V`^Q(4^ZmhRli%qjZvy9Z-K1>L# zc-4wsH3;tK_yM`?hY1K*L*B+SrBL>&8dZXS##<4J9es3|d#+lA=Z_F@dDaGX{sl($@fv~R$BWIBDe^zR4ipji}t;23|HK*O%I#v*8q zHDA4@%P}4z>FKteUrb#iQ+cb+G`0+Wi6#9@3@d&T?P76nYG5^k~Otv$h zVy^LoJ>K(BJl@?L_wZgFrLCf98Oe&R6fPq-x^ZAr)sT;T+y5Eo<(h=>@jigA z(F^DF_>d!Hqo#_t(L3SuOJd&GOF+i+qC%LLBgA2mnnmr7^!@=FY)DIu15VIRd!LNw z6BgJflkzAp^`1_lPlLF$d>D5D;bxg7)fIs^6f479W#-?TRl&n^!5UY-j?7NrCgsG( zGO0GD^Wo^lFQeX8H$!-L#fh7nqvHP1EaU+D^K!I^`a%a`)nt54PwlA=HTGA^0HT>qf`@OXfnW1tTrN;r z=%Hj7rB{l4Y@Rn(E*Z9_xwrmOQgE@TqTb!XQZ%c)*c~uVjdhsy#RnQ|Xz>?L5luHS zH{x2KQH@p%n!sZpMCzA;#)Ph5Dt*i@UY{?jZB)|5Gk8np;KNEr{E-%&YWj6E;&)IYf-O$@BUD2k;enh*s=AK%W*#cbaSDt z>gH@~TGI*ZAQE=81{Jribhog&0(x*;dosZ~gSTggCvr=Jc`<^w=Vj~sYPSBY{aIRf ziZ3R@%pC-WsTPLKfr*&x{*lFJ4pZ4sdlPR{NAD_xP0L*TO4~9@KMU$66{u-b29i$c zW>Z1fEgiA+mP?0htWTcEhg87KQPd!61I-+(*o=hJ_{j*qEY&BmyP|1$m4i_V33h*_5-m1f!(j{$>9F4n?xeBMYN zFSX_N6s7isE`;gfdhJ76nRXk@#d01iBUD2z6oFbMxvTYMW+pdx0s6=cE9^NM^ODcJ zwUfL>Fsw_R=y%ZpN+2|QwK+nh=~Ut7L^5o8WJ#6u@$J-j%A^4W$vY;+4iIc*mH{vE zvQ*8(gx2(%h@)wC>UHvX=-WJ_FK>x#26T^2)KKYnVn>_?P9P?P`3CDcM%b)gU$ z1B5u^a#U&LXtqR!Hny}NAkE@l)nnD)cPq+&aMvMA2>ZslkYQgdMRe|lkg%8;EAdl@z^b>nyYOu_QJ_emX@jheEFJQgqtytZI3$X0inATOja_m6Er54>`Spu$k9l%F3ID3PLXuV*X(-G zm|?c)3lH0`Wl%6pX9_8mQNg@et@asia!zzlQ|B-T3(L5pGmhqS9?0CzWky#U!WPRg zFiuXK3J6G!>PMb#2&Idbhy?6fm9uY#c=|`Ofg2R*FL$)k$i^>=NEhyUsFUD~nY0wu z8)uFvpkRQBgGgF|pvkn5!Cc#QnfJo;HNxX3pf2NR1<-o)4IPHX;^o`Y0IgK7$`Go3 z%_%^@%?$r1w{321`Y@xa{@1(G`b&E%Upxx8ByE?<^D|EpJ6z&EakL~AraRS@&@zNs zTYCD|bC(-eB}?3|zp1mZ&cCaA1Lcc>dK0GRP@*HL{h=gM_yIl?D6MtcpeD(tePp-4 z)abLTTt+REV2K+M7%54d1>#U8;Ep)S||iz%{fjQ1D}wQ$s<7*-(6+-wh)@D zJ9I1$^BG}-f#R#Nltr@;cU157o$ZF1vX9Ee%lsw7Xq^}_8gXI;+-DjE?2&itx~MA5 zf;FEzrX;czFLNbXdXMDnMb(5iRtO3BFA#V>Phq{^q4Uo-z;24UQZD@{RXMf%`8l@Y z0C++3=Y;Zr1LOL}SRx*b&fW91^PoXwvb6BcM@|+2GE^Fuo&L>pw2|g>qT{i~Hg2X4 zPis1)>R%*8Ks#-|*Ha=EK>3%(9O{sYe`> z6Tr`ahLL?wr#mId$r7g6>H$E2)ZW`NY*~-(IE{+AqV5UWdhYTo z(e`2sRbG+q?^D6%-^>E}o>^MVmrOp&j%W@xMaVSl{DB4S-AxlR=ei9^?X3^gPGzb+NNrANFv*$?)c>%JZSr+ZBuSk zWg5|%0=)(n2uN&f(v6;toRy5OCs8D#__B*eR{E;2Ea0rz!yZNXFd{&p!2xXqjV!&W zgf|;TEcdN{7tMJ7`t@hA{-H@mC67=d#)geU9Xgrt;ml8?@|wLdJ#A~6b= z^PX>~93f2!N%kRmL;Q<0-mIqzH(dV&J;+zWwoyeh%)mz+q5kFvIrslcPug$g^y3*{ zSsC?*gBms|zErDj+&xfq48mKfnHk$^<2Z53U%CQbbRslvO(d6* zA-?YhF3U(CT^e%P7(!yEw0lAE>{{!sY=3s@(~WBax;h3_-{V@^=`q;K1X?t3XH~Wh zS6UN|wEG!YUs0JeD%?H+v+u~-awLv;K!K5`UkawM@pVFCl$*mk#d1!~RQobRH*#CK z()!jMn&NZXh%I9D`J`A=d*W!KIXiKzJ}+By$^8+fiN3`z&nW%H@UQa`nuAl&17Z~1 zZ!OxM7~(P$4?4Gz&NIR1yaMSQ@?Xw+V0!P{VG(rb0@FjH_s>=Kp2xU7BGnBI#D1C{ z)w~T!YuhAO`JM4ZV#EBpL=szv5w3G;g2CE`nc?HYAN(pO>F-s5k*NGjEP|6@NN7he z8Y3hb8evsZ6QxmF1L3>0_>eEmWzd6bQPRf*+*%3gf04n#nB#splGUOVJI9_OSHUO# zHGfsMLV-g~i&vTV-311HiF~^pPck_rpT8ZmNPoN$5eqaY`ULDliDj50puo8TO;U}G zuO|%s$@czLk;M7*yIG0i{};+c;v^Hecnb0#srr9cJ_CM}l_LK?`o--}>Y$Gv#jJ=t=MI@?eSL^c8#M`| zv^MFdzKA261j5Fn>8xZ+K}v(zpxi)Z@Fp;++Xm7C;&f2PCfTh=gFjP*0bN1l9x z5_#tqTV2IOQkadV`ojUsO2=Ea;-$&;P(G{c7mcv0|#?-2y6th)98kS03{>2-HRjY10U zl|)~J*J3fpTTr44(Zw)w*{jLqmvMxq4GM)~y|u}bL>1&!%#DNlS?1apD}_?O>yT!A zjbW1^3E&YWi6e;%L9zML0Ldh8#)EReqiV1-m~S5&L2d*(Ly^iC6@qFAq1HctT<>hm zF{~8F`7v*|hQJE&VI*GFh3@SbEf_?7zr{6Efo*#7>XSYvrVdM`!6Sn9?(3Re%%=4deE;Sy^NT+0WiU?gg+m zW*ekYm%! zeKj6P*7nVV&We?0ErhhE*f)*MX81I0%`;!AHWF^E?vicmo<}w)=@Tv9cKIgQ-@~+# zb3zd=1w9J8$AB+n9~ws`%pZhZ9g-U}eQIk3nI(GXrb#SD^<~{r1N|*U@}dl9cGWBM z^6va^wEDMQ+ZIebGJsT(JgDRVlDZQL_M&vO;$_4TV*&ZvRJ` z6shh_#~8_7#-~m~HOJag&;uIfd$1C#F7dy-r>+h35`=(uiYBwfm=Icue3n1T*CC=2Z#=6jX%MHuY8^_2{P+@=4A0(5@kfMmAHfeI0Cb0&S zvaYam6al^gZ&iu-h~=MB==8sE2O{PdLXL_^z3S<}dyA$xfyE=kt<^;!799e^B#u)> z$-*>6|4sb+Q5`H?++F^*D22VM$hU^Kx#onFR6Eo6FKv_EMdb+H;qYjaV#SBWvcUGz zM%Z2wXied_!U)!!pJ;Gwt48I4c*|Jigmzhtf@BBzHre?~6lhzl{1ZaHUy+!D(Wlia z=P;8Q1eVUnqCZR(^u$rcI%t+Hm?j~Bfx8Pjk+A$e>Wb*nz94l1#D|A6FJ`U^R#j!;=xVZ z!YUNZK20tz3TNEwBTZjf!U`p){0y7n!xEFcvcki}``LoV^8i1WS4u5o3?l(U6$5z+ z;FD;dP4Q>fqw4r%h<-xl0bVak4eW}{cI@6nM_^Gc@U`&b9=<$CM`rs~=(#*LD8-^c zMN-Pp5RjVW=&PH?iw_k`{QC2vYI$AKo`K_o>kfl?kX|^o?a(SW?5)eNNhhv^0lQJf`l)C>(!XwDzo%Io+bN=~Ck zqq;*>%AYErpcDYE#Xb`6+n z`2v{rjCN}T@;TQ5T5kIocm^w^ta`eydaIaMc;F~z#CrA&W!UJN5jE< zb=uZJI$jY!{Uc>WngL=Nk7TV1bcn{4U|dYbq!^KCggIF3*oAvYT9ueQs63I zu}?I4!GUP#2t=K-$dShK^83k@heO-FIT(XRp{jLa^ct_~3tB>wtKTGrZyPES9N!SY z4=Jw}U*t}sOF6&V?QAj&XK5BHG&G0<9R^)VXI9lKx~2yx4fB}0IwmM6D14w?taZx} zf41XU-3*z>&$pBV?NFH!wk&_C=M5oh)6?Co zLkx5~LKv0MTpYs)ZM#^#a(PJuv+xakuJRugJM%w|o?vZRB=fh_I(a&9vx-*WfeUjm zs&DSkOKSPdoekQa0AnC>rf49m*8s1Ff=X5l8=e!6#^rZd4Ily(BuQ9p16)FVf8vOb z^UQg@V68C6>MUsNW^%)i5?82Ua}jvgPkZ}w>h78-@L2jdufmd63#zPhr=w1|1ToTN zkXOOwU|tHpq+Y@CrrB{Q`A|A;&&7#?_5`Q9>a4V-r8S1ZT%vq7ynN<3uU=W@>FDW} zvP9bA^l(y<-=&^Cz#(L5{#o51k0}}#jwLKTbYnuXYc%kN4fj*^k9qN+@v_E1b2IA? zl6Ci|4<8**r@5|6^Hal7e(sw#fjYnCvv#}?arTmVjtJH~n`ARLs`iek6F?ArP4uvR z^vH)<2Ysf)$k*+8utR(2tHeYM?eC3@3@Nbo2n#No@4hVxuKGkCjrfsW43Om5Zb6+_ za{UCZYde4r$0jZnrLjWh2sPD~F?c^QoAM1)V9ks)6DAniF=U=jCk0g!h}aADO#&sd zS8N9#veHEu`64hlknJLvN{&=`9*f+SnwNu(e?k@ef?8k1Hx;de(KinHqqVp9EVx!i z&W8T%CL)d!CmdD6klHPWOy4KOQs?W}RpJyz(OikA>f9Blzj6Uca2|No(-|keNfTpG zrc>wauPI{CM)G@)4MxcaLH|6=_3X!iPNjn*6=5z%i4f*`fud+FQ_*N#S~Y7Ye$#Lc zP3q4p94!)STJ@DM7`f!Fo^)f_@zr#MC3a>#x|Ss5tozlpijLMW1j9|`8=ihu6&0)) z3aO^|bSd1@NaHfVu+ojDT_^ih9OI!D^Wp4=b+6qCr|H@|&sX-lG~6{2Z02iOjzf(Q zw|2WvWcI=9yrBAI>gDi1(NQ$$Q_6;v%K5-OV9OowXWbQ`8qF)^tcSp0##z%sp-@Xw; zCr+&ug~_sb17|2;SZxc5E*dCkDDXN2YNT;&{I&MHA*tt&EXeu2?U#ad>x5LwE$0^5cbJVr(RQ4{ zw$BGk0fJ05q=0Q5l1T!)jN&Z*s4eK zUzjY*iW}H!<%FuA6;jPjq@gs1tn)QvzPf6{8FfrV)FD89Qh4>3ENj#x`y;`X)mef9QxM8WI z!*|CH&YpX&K!W8LoY|4&{#>SU_AkW8F&_{ei`&;1V!3bZCYSP-7*Q7}=(eO^^u%f4 z4UZkou$o`^(wuIv@=;k96+io`LO_GG~F9`Kj-;8`7ohw6b zEI>ET65vVHB>wZT?Lny3etNpk{zC1o5B9nD1@L0d}GGV6Ov7kC> zGAm@+!uBbYaXljCLLU>ySLB&Mfj1)Kp=J9nf?{^Sn6KjECHuFe#u-7$ailvGMAPFQ z#L-}7V%5=Ck3YboETDa0R+%b8;3F(71o8VnJ>5rGO?K!Oy?5>09#x9`b6WztR^a_H z(y8Q7dg;;EGjGuJN`!N}`TG;q$4wkE68oz-A}gG>75TiLg!#4+F)dHt1MXZGyc#no zcU~&?+i-O~hK;+rPh*yvGH#c;Ei^7eXaltlbw&k|W`!8pM1n`taF1DvpW0j^M-47b zGwdkFS0Ys^Pp-O$-HrM2o^SaZs&Br`v^NYP;2a%m_TC@ku46r?Ay|WnN}v9@+lbdX z7W&=E((dg`WB2@yHVh0Geeta(xG>Vr&P>t%N&NAbsuIIttV{+| z`hj{+W+E0k!+nV4W}J`5^%7K4p24-JMC*k?{TI7z#kUTiG1a?=)ipd17^EjpjvFbT zKF9iHcOEn(3nQ5X!P9f;YX|(D{OkRbRB6Hky{Wx6p`+Od!lADQn3O(n$HzY%EVesr zgI3B)kM`3etgFn-C+r1GjvA6pbD5k{M+XIi4i6tMx}_XeAK$k{O=qvrN7V5ShJbr> z=e#g>W@*fA)ito-*~Nc4l$G17xsXJg&?|vMxpm-c5Rc3HG2)4(TO30Bu|qiiQe7fP z)2?EAq|Rv8LocPM#wYnaNVu0PA!Z{x&~fp(5eBt7pF0AHqK;!yjT#9$yhjH%$ACR2rP}am3dJ^X*u3S5OC|p0}K|kRXuZp@cXw%+42fe8#pS8fJihhCV zqdQ_=+V0V8h{h_R3f?0l?Y#v$tjW;aHl2^YuaI%vs?B zluPLAkSD33v|kJ9-{+bNIs}3>Sp>@Mj^0i(zj)V~M8iF!E1wW{v-imNV1rUm?G9P2 zIU{ZstPzt(QG{jwj9|(pPFHs8;vJN}+Rc<~f821q9o~qsg-`qNYZ}MyyUhstgUGmz$>@s6uil=-b)wmFYb>?>-7$YR64%QAMed}fS8y?L|Mrp4wJEluCSmy$ z-?*=v-=OMZDw#3kb!dLoY1)k@Lm7%CZb77@?N4}Y{s*>n^_O=X2#hzLhv^luuU8qW z&waeMCfrQU*=4G9oyh%994=Q87GjxTFpi4Go==C@xzgKih=4zRFn+qf08dJM%%rJA zq#zWYEU%9r->ok3*o+ir*A)&YE=KUl8$4QGCe6)^4$Jb)MocxV*Kuh&AF;T4uWV3U zyAVODy?)--hFX%?L69*ZF#j85g)#Gx)I(JUfJ3;35r3J9K%rLckeWB zR#sImx?ghiM6JSH{T<8uH?b;3m&9ImB-`*5-s9xp-jQmUx| zWtiivs{N~nDn8|4`AV48$ABe95qsX*lNL*|2W~~x??H!s^FQ&6OIK~n`=uLJ2v$~F zvO-n;Tl?EBtx*jr!eew-@`6b-7QU85I$#K7v~LgoiPR-FVXo>7)57*tNpC7^#3lD-NAm|3AOt{pM|6(PRMsDxMd@`;h++>^(W{wA zs9{Jk1@24&SlA;r6<5ih+piCSR3ei3hil zxX{>E?NOA8`B!rEza#yB4zg|o3bI~FFF|7qZy!K1#SJDXJ) zA4odnc{bKETbFDUsd8alTgyLIb;rI)h%;6NgIR5TAwidPm{s~D=DD>xgcm8Pw_T*d z$<>$!*UDR_%HDwtg(=naf<$+Ar2B-_Utuv%BR#r2ELg3m@5I2O0vPj;cmQscF##%z zVuESY*WGAZX9t{&J==qhNMCwpWHPOnGsirFgREAD0%7_pwqRa_uIHfW+#XNFS+`=h z>C{-GZ{n8+qKTGd3U+Y_-%|TX?7Br-=ReKSc%J()(E+!Znyfv8gLaewC4E8KcDoHH zFb22#RgPh*XLvi`q23`LXI}OIj9G;;5gP`oF+9zQC6wuNcdyhQf0msCo;I{8mH^Xf zhg(4$Dt(HYo(v`0A#X@aNSV?K*aTCF~KF|6np8849 zO*maS`_LPh6Asbe4wcw!mI+9ez8bzIjo;@1Rv~xqo}-D)3o&zTcvL1MpdEUtN%qb+ z)C+OvN+&1^WPpek--so{Z|> zOf|POSTL3ei{2Yz6xT~qJ|A@z$vVY*$}ge%kS?C+LxK0L$XxI6i>IrZx$RV= z!CPq5abg#EkCyeVFpUp*a$P#*8I$3s+=sJNM=tL}6|eRl_?pTfVVCgTNrbkUc@<28 zE6m61kJq}4a5WbIkNf=sKdzcN)ZdG~5AD~E`u3+|T2r=F3+mFx#RZUPw0k>-I^*|j z)lfmC$Q{ztor%@uWsU@m^!0s{2dix}8`vlsaF;%X=uuVrE<$E1h`X|DHcYiWE2juUwVn3WbZKr|JaId&M@pz;+ z7HOKdMFd3Ew<~kt_A2_omS19_?v>#TtygcAz~3O!AHCfXC;Ac4r@l1Z`7X!8b}!4C zZ{Gya3fv2dLZLT+Oh+!9G6KAON^$j0#KC4Y*BAXK%=-NddIX4bZ1bpQ@L~xonG6r{ zzkcI4`=pzQ`pdM#8?$-))D4M8{>A!#$JI7mXuqyoMvJV(^bxcF_lJFieWw5!4Swfv z9BD!WFtIVQag=ulE^Ip(nDyz4X8!lPbZ?Q7!B4#r0KYIam>U1xuD=KKe=Ra_zZRLk zca-3Z3+VqpuX_)T`)f6NwdqdZYjEUFC5HF!KMQWG*Kfx0^9Iul3F8;~zfbtHa5!`+ zKf}(uJ}3G-2+E8e#?S=2x6E~a;tU@aSka3omT#%eAAw=PoK99E^0a@y{r#^G4XALO z%E1DBpI6=@N4_4X-xcO2w=+(*7=#4ecFBc(EB_)z{^OjC+$-9!XR~aEnv6+a_&~lD z^L={6I!uW`vP1sIL5N5WQC7@OXEnj5R1Vc0&o*cwY#tr?X(y79e;?JdSCCG8VgMM= z$nfncqpTqZBg9^ZkQPExqx*UT{vaxdz5QsiQ14v z<88)C%uau?rA=87nP}cqqKIGT8y-7q=sCW z?rqEQ;h-;Yg< zMujC!iA(HbpG!6_R+u$Dl-5_lM@+jYt<6mUFHyNVJPA zEi4mDAvY93M;8XSb5ze5ze_B}fkvc5Jcgcg@A*}_osYP4sQzFdmg&Xb_>)U5v#?4- zQB7qCh8{w(sM@7USB^N2JWh%(KzJbxjYN<}D`v8zk`1y_(JK>xqfpa&dte*Me&;~QyMX_;&=ZSGwGsV1@e`e1G_8sk7Vr`J zDb7Zsf_h8Bluw7>x>~QHn3X+~IR)|Srq+E=ZbNXUo>u_0_oWz|V4 z>r#kDLwyr1{;1UY5C{NINY~)(ALXg2!b39E^!XPEcOHmFaa5vkdahr;9T*hS8=XDK z>Uc@q0sKjz0cQGykn70m#5y;ulkPrdT8twHT#)rK6FdA#SgWvcN~b9z;y8YG2$w!pKfNV?{Q=jcK9C-46aF2@ zXtFCh`2=LzL$7yYMNKRP&V7)u&fbtt`HJyI!FFZ`|Je7i%DO;hgUhrFBWD_c92|Q3 z=1aW8u*1On%QxF1{oBMFhx@)YOVcj%#P)4`Yi=(6y$B#UDP+C=M`EM6JVWGaq&6sq zI`-_%v&y0x_%x3gRKxd6I=DlD%n_=+-fE0U9YfCU~r zL93qG6?g0oCUFw%rs1Sw!4K6BvtZY%X$a}mNfvjb-y=|l^&NMG+L&6E{6{6#glVi$ zog^1tzi8o-KW49cA+fb;H&QcqWwqiERsK5soADv?w$b`45(c$EG)NOy!06Yzata-!7Cq=do{*TIB~;Sn zxdXQ``*tf-+xIz;p)Oou*4-+__qe-YgQC^bUD?O?q<9NUVL`0}sqx)w73*qhLwjV{ zAF-7sV?`&On+SUT1XaePyzes0<;#9K;6!NrI8sLVb)gr4zWRG@l)*UJbm`^-8P{&H z;iBDyI2JJ#&%Me}wGe3~x_WrSVem8Y>?=Zq=_1Qdw49h`DGcCzlwn1wjA(0KZ8K49 zsD_UJm-FKw%Ur*jGemtut^w5V`P%3kAEZkrIuV8@b!dg^5vhDNcP7l}OR=B$N!;kw zWdIXRf3MPo&mHd;2x2rp)F)zfmw5NKSjWNx+8vjCc{M?D5sMs?i7DpIm(SaTA;_WW zN|?ngXa4WHdZCR+Wk?hyX0*!V;lZ67nCdOXmm>J(z$nLCjX>s7Sgg;_gMZUxxLc44 zGs}X4NmAoUpV*>@M!$hUfG)N#46lqglH;r1JNyV>pc)uhCw%)emP@dCEcV>{9mOR( zOxriazA{9jCw~IXrErtA&*iNK6uZ6YChkvsq;L8;mU-lJ1U^K%0Y9CRHsQTgn9GO7 zYh6fWjlb~1o1_ler$riY^kWsf^{35(zk4SE$^IE}8Y(1k`yB%)x1`FSNeg|P`71KE zCp3PuD0?{yEGyY4k>_MA4jjZEQwQ7b*W&zDwTnvQS5VOL8nvQ+wYRG08vck^by>W{ zNRs&JTzB5QX42FRYd?SmSX^AGT^1DKJ+eBp(%*+f3->+9Ds|m@o}MCDh&dYF`PMpa zSQhpjy^md>5Fi|U3<|oIh0S~j+aEvIaP%9(#s~ko!hv^R4_et?cILjTGH$+WQ}%4j z=&!%Lh*UY-FBA`2iS+6^=(*;5*flr7uC3Ep(AH35tYhDwnT9PK6f4MRi6D?fH^p7c~orzc4 zT7oyX!HEHZlS#=lRv3f%?*yKy=9`ZD*fEp71{FrFUco7r&RMg9_Z1@)N1ctv-meDG zeprav`DOnU(BK3on2Hd$$YS9`xSu+ihcC2#M}@0pZ-|Z0J^zRQkvJLoSJX% z-ra2nweNoOB3>mdunYw=;4#^NmlpUBNfiW->V09@T(9cfn=(O@edh{teLTtAVPc)HMeX;=HUQ!|}U zHCt#@`yCR#aLl}X;UBVE>Fry!I%c3FTo5-o6)@Heiw)UzCj=d>To6xU(v^_KEx+L7 z)uoZd*V}+_y7I6C)wmQ}EJT7a{c0iC% zEc<8Z+QC6=bHrQ6#}sbl9B$Hkq2%t0AJJmZPwQD5mkuR!`EvsUJ^6&ccRi=aC0!iQ zl}sdK0Cah4KI~k)0zk|tOY1VLuGUk=baPUdOj*>X=BW)`_R5fc`1skuqcXJK)01}S zw9rM;^4Q|6%C@y|O0&P>u|L%1CMLNcZlplt#Kh4BFm#gCmF!sC$ST>{>hKQ{E-mC$ zPEp&VAprv(NI}B(7B0ZmiY}<^I8<{{0(_IgnAOG_bx>q_TGu5H-{yG4gKCs4q|%Ec z(f`)87Hx`RJiIbMVBM>X>U%wdk@}*}ob$(#>`*HSlZ7Lm2F&pDYNDs(NX+#l(! zpISg;F8%_`n4J2{4yu2z{l`_m{cMC9+z}$a)Oni zQ#Ibb@BP~JxC2p^;YiF#mZ`{_>#)lJSTio>>@cL(rhlA#s$V|GcWp>9YG3Sc zn$_NDT8^wNRRC_8N~XS`@+b4OS}W_ceBP}D*2om1d)%#4fC&lYj6>d;*>@m^618E% zF&4m8|0bYTpF58&^sWQL2Gr8L1J`6?S>~ef;m7S|a)Fji@yaNM#T-{K(bF^Rp;vo+ zWit`!gRR$TCWtCy!b6{MXa&%<5T|hWc!gyvoR<0U$^5TefX&7E+hP@*$~_(6`qL9# z#&j=U@yz?K0ZJ z;{N^|$Y&F=RZC-jl9yoBkiZpba|1e%lPS}TLIJE|hT?bC9M#vyPjr_r;T-9j!eftM zsjmL!oODcQuC5d;cKxYpH9XoOE;I0b>c{FhcLo|vhlaT{_+hP_tgY0k%%3fevZP0X zE@ko1U6CB@O!axtiiIsXR!4iFGzR1G@tZ%go1?(qEidQ-B#J!cQ-q1FQDBu1+_PRO zvkP`Q*?@EK$oW6zMYFZD5_r7-RI*T(7!rk)|CIx-a@Ue5sA`*3t)e27sm8%yv@cSv zrqWLC$k4ashxHJJNEfm4cVJ&{u#s6;S)}(^zG}%b2ZO-LKXGwKoWxqYW}^gNa@;ha zFZN@886JO-u}~!qG(7{P*VCcSe3Rp$cTC&WjD}i+&APWE6h_1uKPau<#kAXR>{JhY zzENQr)vw;&XZK}`17rCG1W8j!DrR;^_MK} z@F$t2^VeAfM|?IsH+=TsBJ7d2=47`g1s7}Qq|-6?NZ#do`_q>FvV!T_>WuZQ6=@YJ zvt}S0ZTh86H@4B4m9eCDmsRnO)C}i{c=hE@;F4BwO*imWIU}VReNB{tUj!IM=kx# zSzao9Mb!z1EqC|*d3L?VE$=){Smt8a=w(>uRD^7`>=XZi%Z zTA5JIg834`utj;vK2Nok!IV#7S%1<2cc}*r9#7Mru)!~3>-tunB79;kK5sbnmgM32 zWPJ3O1h!j9lhEPIWtg$r`)Pqz`%A5DGM`%nLEyyfO~9bED#-WLy6g6u|BR9GI^qhQ z1at{Lw=$JoRP{}gY2GNB7%B_YW5&y(c!J)8*9YC(`;1G7lc;ChY()5>EMb;5QwIgieJi7q`kYo zXur!Mwp`ztWl2+VsjU3sLa`VE1q{7H*wmiJL`Ve~vvjc(((85PhF~SU)l032HKN@e zuik#qH*&!>*ID``6I1A8g_Ib2ciFI7v2*;=TMU-qH-bXv^IPVMHkJJ=)Md!q3RQxU zW&r*B!Y_S8FZb;(>y1DM{ueYpd2PLsC(W#q8Kfqs6WyaDE(G)M^JSh*PY(-|%C?o< zr9$lnn9V9m(f7r&Qyd`?%?aXb+`ok{LX2$gbR!w74b#*0wibIS!6|FEhnYf=4BbALfDfI3@hu2u@UzC5qMppq{9Sws-F9r zSpi&84U`6SueQYFa@%(kN4~?&`n@Fu=-F3ko7iu~w9XZ*twr><=q-;#UwLisuO1tJ zQW(&@WE{E4>Dy1`b81-c5saIaFgY^CkXFq%*^npHs*iMRUma#O$H03YPe>`5SKe+D zDxCAPeipF8&Y57><8xkNkMPB0!n&t;B*~XHqi3OPTiPdkC!V;(4=hU|2fWgDFBF?;%*%Vp1>ZyD0;Me`BUz= zER-zXOs8tf2?mJn92N+2lgE6O zBa0S8E99Y#AK10@6@kvU=(z}F62NUd`Z#}s_r|(~E|1CHs7kJ!*6_=EI6n1b`z=7u z?w#*bU*%M-R^J5<_-K?8`9Rsm)Atbd3zZW9sK+qY#VEJGLs$snUzP+WKoLrT!L@Tz z$?!hK5g(=A6?-7kc(aG2P&VBQ745j;H}R}&u2JtDF9=;j!K{{u+dVNfuWV16UEKRM z*E8#11Nxd{ipkb9CKA0$Y=5BbjPu}xwz3F0rp_G7QHR(kfF0`&Y(Fa|9_P!-TaO{~9r-`kqib`ix98d~gmo9=;ENwcxae@B{=700fYj%|Hxxhx) zsjgj#+5F2{Dxu0|C%PitAMOOyL~2W31hEfQm!GG3-n84s97P&o;Ss5I?)E!aCEuVT z)fS&I2aVMO`s!>`fD7Yu%UP4plG8E!-f$Mz?MCh_c3vjOQ8SsV%OhhoRZqy0>|nHSsv@uD%6c4Iz~r42FFp3U@6k z+v3IjT8NCwf)j~%fS*J3>;V4Ij543f7_5sN6Hn#A;Ok`*Rc2>6YXEclRVyyqqs+y4 zi@54u?mUv+a!e?2V;8&_%mW=}XO~BB2v>)*c2&+oiI&C&*%{hS+j|7~>uhOg~7p<+;)u4uY8Rv{QR! zrW0d!ZBIk;;=Yf1j&+;67uaT|?AlCLs@y%^+Hstw@ZFFTF1%pNWr)mAoWC}(8pv{0 z4=LotyNSoax%gH#KQ53vq()Nsj+``b6|GOX;FE~tu-S8E_7`MFNsw{HOmVMF+8NhT zz3H%J+3T3x965c@w68hq@O@0?ViT#5joBIB=COQ%#0H{7V6&%B?U=}6Ykcmq6qWgt z9$<#kbY6B;+35KJ^gUSeWCb zT>M;Hy29li+YrTHe`*hw!xkQ5luA~Z({8H|rbIZaj_KJeeB$7H%y&wL)k`_A=*#IB z#UJixZ%2U#-ld2Wu5P~)MYoQrz1D*7JlvkvO715hN>lK22@?*SqtZfAMT4jeNleC| zI7H3yPmJlO3Ew%>W7&JE{2H0u3@TYUP7`g)d)a`Y5G0v4VIhlP$g8Tfu{adma z%u4^S`U%S`gZ}>rN8beeVx9G4@eMve{w5V*ebz>)2b=|LaY8=PwI~S)mp2tzY$dbqjCP{fgOzG@4Hk( z@kj~(^HP7ubbsA}oe%v>6~Q9~dyU^j6%e_>VB-^oRI&r%hrB^q6zrm$$RM*eeU-n< z3%{Gghzq)xZC9;K0}U=!yU07&t`!?3jOR1Q=!hCFG&}Z-LIoS~AlVmkfTC zqmG63jd-pBoZZa0DpoNRnvFPDG?SkxKoGb_kswtVBcq?#J7nC(EC(K&GP>XDSjNA! zjxiTHRX*LlP=?jK&Dzlh%mgNt&)W}B28sWyqS|`6p?z1vUdWAte~to*--hk>!) zO2bjz65}dSpOa5D@Rmq)#^L&E#W{Fnd|t`Pm@AJK*D6qCjG(=HbEX)?Q||d=0Hcpi zqyLb|#lT$;DzZ!mBUd~CbwxKe^c;5nj_e%&mX|%=zB0EVYe6JPpIcZvGK$&5R5{(S zi|kMe<@_pKZ8Z1)u=bX*b!E-AXvYx895XXBGh@um7&9|-%y!Jo%*@Po%*@Qp%=C8B z>C^q4K1c6f=}MNhe{5~7HP@WAs>T>qgKPoaLs~w#R#h+}U*s9k!xky#CfW;hhW(pq zD<7;|5?V}YM;ng&Y@GIUBhtsnll{Ut|4DoTPL%wJuHiKz<;6$nEm*%?Q{h|I{s{$~7~nBi%+Dq*3;VhJP}d*-0owE` z1Y#X9hD8kdpq+M7a{^iN^`%pkRFTsZ2Og zB?7ol9${5$z&M%gRUa)hjuM_EyL{%3vGXe&6oE{}@ZyoiW%dRhZxsI^9{}0zMt`*L zGuQ_g3_|N_PHP{vA>EajT7+j#9%e$GzSKxA!5o59 zrjc*X0li&}j0`$WC{MX&l^y~vrqVd;nI}ja8bm$( zOR<~o?f{~PA288X-Q&X1eNaMW$5bE6O-_ob7dcN#{ch%StFhX~*%ltnnoMdeTxQ#0`_=4B-=&Z!v3#yB_fURt zO1Z>60$sO4$9l6JCCYba;J2fB-4Ol{KukLBmdA}F-;PL1G>oQIoig1@|9q){m~lpsEG9GUfT>RKSsWn17n2zVsCCJv z3{yXgeYGpI9b;!I-QAGCX50ozg&MR6_gW#e=(~tx&MH3#u{h+JZPzbkd6`xm{gFh9<_`6{J=D z0+ksHs`9VH?TI9hEIYhfAoq-2*X#}yExF!iUI=}pcfxbd`u zaWqcj%~>w7KaMW&f4*3r@qWKC{^Qtte+ytiEO+OdeYGS_SdEpnvko%2fnJ<<% zB*0hcGS$C+oWFP|(aqf>z?_lU^cHIz0?cA6cO7)`5e(Zvd;IZ~#J=5!pV6S{Sgsd; zMuL7PGeW^94^99cS{#zte=eZB#w(^mxku*8k2KB?X#1q)BUzV@J;E3gz*V_ruZG+i z23YMCq%8%0((y{-?{aMG{ceD>8QJf#cf%WNyfBZFZG<99!oa$TI@!#cgSkdYIs8F7 z(wSWL-WWJgH!GCKc~ZZsok-h88HR{KauJ7E;WbJrTVD$DejUjT8N3|bze zl0##!Vq5u4WlfaYzhQ^Y`!jsGoxFW&gHHqcz~sJKR!pDZX{flM8lX@fG{tOfJ~rOg zZ{yF@MkgI#o};I{cP5obJhVAhB3D3I%Aoz4pIZ!Ot2@~_l$aYrPAMTdD?8~4+=`CI ztjscV$frveXwNKK3bn?YXk1H!SleWDsx@z~F*hVWI(flJJ&kgL5N(Pz_GqG4qM;8h z!+8IpQWGTk3QD|~RvA=qcNC&|Zf0qc0!XR16-={X)q3H>hxxsYg@rTjS%{gr10+1T z;JmPLSoHR99rRj?p~Gh6vGo-d$h1KmfaX-x{Ow+x@XydncFJr*5Mq8MdDTb9#L86g z)@=}{xHdWD)@(#mswpz9(JZAIY`bj9_27h4mekeNS0WO-m%S9@`78@gsD32ycq)u! z@ma}0RT*SWa6mo3^2#&6zcRJ|PXFJY{Yn2T{igpNLCh)Rj?KPBR?4-9R-Ad{@aNeB z)#>XEJ&N5{mH2#bp~fhIO1A??*o7)86=00AZ(Ch?(UxRi>I{nQ%vI!SDJ~AT(92TZ z^PFq;E=!Xxf(M&&3=*8O?k(USIv6ei3<6t3Yu=IztAG#Y2-`2C!)1mtAO^X zrD{pe5dAW(V4Mv9Mvh=zN?(6wu~`YUid3sX(v}QuFo9GkX4#mF9TKB(rmqy>TumRI z&vIVpFLqb)ef)6>AXgtUn7fx-b4gYXG}X*F+x?oLs5zT5iGv6Cfgy=_KcYn|AlxE{ zb9jq9I8m`vBACpR1<)9UwUDS!vrN))$114{pM zHuZE=1mc&xG2vdpfAw8xxU3;W^~wOpbDr);zD9BEHsb0PFBN{VLgoSV-7ru7Bk)5h z=3+GZU{zX}(?IVm>Yk0fPrh_hEo)fYFkjrVRgPc7!SX zxSFyM1N`G<;fo@fuL33S zyW1{tt^k{4H>pmR)I~`;zX{4fBGJ8qm{%3=B2Muawqn1fHQrnrbGb5J$gijda)+n^ zw(N2RTkPVkIENgKZ2WA z9ZRNF9y#r$;Q*8=6=lyUIXHI#3T+B!u_h84Bo=CwyUJ6ev#^GlU@0 zX9JsUUU=iTB}f^gKPgkXgk?tPvviUxa?a(ijv_9#_0IRVU&!)iJ7$5{= z+=tK|6fa7mr$2N4!3n2lcEt&szv`1gU4MQVwrjU=>6@DYubUhxNWcegv-T$dgb>ih zS-wPO@8CdZGL>86_}Zhh3DB~7qA)v%pzi(@UO0X1A&QmrMA3XQlKE5L3YD>6B*-4$ zg>cUCcsb(0jwmNOv%|3c0%%0;MB@Eg_+}9&GlYx-!xmTlFEv~u!o;T^ zrrC+f#jpZ^j+u6&{bVgB(YLsw;3mj<0UNm9vD;4~KfAD~exjsA8b2|#zqFiya@(UY z`dv3s>KtEkIA5dOA6zpR&j9t$`X7}a2)W5Qv=j^XBtRTyQ$|A56}|fMSP4owqlC}8ON(^ID^$|{cuiI6Nigi0rB-Bl}& z$E%gmav!yjQaEBQC4Bue#oQjl_TAnxoUJAYX%MOtd>s(oGO^5$!dF{?yhRN+Is zSP4DioHMe_eHLJoU(iWo^DoWmTgYE32zVf7ur<~Gb(q=EU#;oy*BkLkrK?)kxYZ9E z!8*7@Y411V<}Ri*{ub(o><)%;?2E_$$KC0CD#gEh(@hS3D@;Ogg*`^EFM5fYEhj%! zB#zgahVN2VWuj($eaTzG!QqB2&JqQvOD@kedCICvwNrA7D|Cc-`GmvpG6Ca-h}Khc(+Xbqz&bvzEI((lEIxafY`OXzaW&DKXaSYX9g&2Wh;CcFNP|x2l94#zL9!5bbU4<32iZfm> z=ZIbwDSe8y2;PuV=+W%Dkrff1j$TPyz^giFoAvhI$m{zgHQL(ZFiftP-- zm%!(GTiJk}3o3`|6dG}Qd4vFrd6J(By1$?x01C4)MEonar}&*5ZeYM!|Az(x%K`I+ zx}Gv!=&nDhK4Ux`w?zpKy1 zqdM4Q8<~DvJ+7Oe#i_H@Wo2Taug#J5VN6=3rgD6`c$}c*$X~Hcy~dK`OY!R|fHlx` zCm23!ih4lk*qh!qLM=zcbc-mHme5<&+*Yk}R}^TW$0=)xa2}O6Q}->HLpiTDXgXpD zFbFx{E{m{OcsHBlyg(2majd%ZPHL^gL7!h;ensPoiMuH1N^~?FNgv9gw7mOE3y}4i z(PYP}veG-a91owE*)?!DlDaPCv>#ZWFhBX4VY6E5B7qKU5raI%y|^XQAQx;dmgd%E zawcbq3T}abY+LY7o2RR2E*~#_s(Zg)FnzYiA*)l}aCODLo5UPIw5jgmuusjc=A_(< zVX>)cet3dl(!TQFL?905^&hE=r329`9v_VpeOtQQ>^(m*Sjro3x4Ur z2!^*FI-A^8K?c$NL7-J@su@WiM%Gknk2#G%pvItF=i2vWkT4zcbA=-9F^GA5FF%_ z#yD$RhEb{%?MXr8D755&^)~(PugLZ{vIoWR3t+?h3t(%9^BzCori(DJ!qzz*Dsq2& z3UMF8qFh?xbH2K*eYfS29(mW9ZgU&s#H>lWxOgUPI3wY{r(K?F-TJOG|CDe&(eo-) z0nFhCT?*2K!uJx?jiAG~-Na2f%XyrmG*vG&=&^#V~7F!6l5X z3JUZnz`{9h94!o_@9o`V6xBo05!m(mXZ>3T%yMZYO?T>u!1SLXknwS=D z(MW)m9Nm^DD{IQQZ@-0-e!cuuf?{I-<|r@J%K1iZRq7fA2kUgvy^OW8d`FJqxqOJu z)@VUzWj|iTiPMoPa33N)aVd0tJy{!}eUkUQpg=59DrrI9^~VnV5XDGV>Y&UG$frWf zwNHlm#0uW>Bl0&O6b{8LX+-gU&h!$~q)gkhFJgCjPIk%S0)es@>JLf+rw4IlQduebh$ahzNw_XI48~vR9tdx1kix z={7c|lNrrC-x^dh;dd6tTUY)Od43|r6v+TQ5v8(?$)Nm`IC1^YUeMd#X}T& zvllmlw7t!Z`I^#b?yn|ig-`#z$(iXg>u69RRqT1B7`4_5%uN`;IQbd+UOaLDI`q7l zYVr?gr}|3+aSUe8BuRPd$2r#YPF_eJ)30jLr^izJwX;{ZI}g}ij9(4+8tpE(Qkdd$ zlxk4q4NBqOzBFAU!&>O&Z(q9|9LjZ1X*qV-C8EK;hYk+On{~S*Qzj3!JFVHZH44|7 zq;k*oPSSjRIhl7hePvj&-C<7J+l&0EW9*R z&||{;Qxei+$LVp-b9Deraq#$dy`l=q!bPB8lmD&gjrrNpGr;N$t8q_3c3Hu2#NrE1 z5#3w;qlCGckzrw)*%#9l>t)Pxjyp6iUMm{}H_ixz^xZs*+@CTJnU!9?yXyV49tri@ zSW!L3@-_o91`@~W^K;4T3hJDp4}M`xg65%}A5$rZ!Zr{BolxgwjWNO_ z{UFq&4_wW)ynaE_0@J{PK3em>lv}eCoP6CMG`uB81$XJ~O6d*6O*~D$`|Gc>b79*r zcORh;FG2DBODyySKp{XbOslaPlw_dL#@fRH@n;HWmKV-Is6Aj7F%*w(?gTSDlWy)_ z55m%yk;mGfu5>Ld+7%un+)^`hkzn?Pmyq#wveM|e!A{$)Wc`#Gyl1pMLq}(~qO`x) zk@F@*1ybV+#Q-{v{Q~C@ZFFr{Tt3)93HSeq7LAK^@jE~bW{wltqmG_IwTu>Z4waWZ zO3wsO8(^y!mP4o{2PRnyPyZYUOk>Cu3{w?dOO$TPs+i@%n0p_z2Hkp5RAOZ48 zG};~_dlW`o7*h(HfMU*g>gw3mRO3;*(Zkk=CTEl3hvO4B=SvwzME4^F_N~{nHwSZ% zqKLYVH!EzfJLwg=YuWnxXC{Lk{tniSJUUR;jnb{&B%a+YqxY14uUAGpZMEKT*RvoGcL1Xoi+t&!vk)hr^PP zlWuFX6jf3vi@z=*U%zeyZJ?4P12vG)zlcLwEbmBQ`EFV*uFbVu8k#2cm1t^b*kwdG ztk1ehHhd;~9InFoJEL44IcU!JG)B4@HcT3H@)GPncp2#_*|YK45dWG93Yd}k*&9-N zg8*eMlGsr&?d!sidi=Vd7S)R04H2}~5_)F_1ZH6ujwNkXo=Y&qw>9TPzU^_EvV~ph zGje=s-Z`dR>Kx{dGy7#lHA!@edf(O9QNawB=HJ>gfcy98-WP=H%|jZuNxU{&fEfs%m~&_wEOpPN&#*e4VQ;D zrray<=Mt(?955EEop{_$@e;p+=R9^h7B899|rqgPE9;KoF)`9BP`T`G;fJ4?t~J~ ze$<~T78DsBlbI5q5l`z>6f8U0U?PZiWh>u%xZIAMyA+o^ddv#_k*Suy#Zst(?xJ}0 zU=toAc(3td$~*^AVqj{RZqzLQ`hqSsjhi4^>!WP5L2h{j^-hBSiE%2Ayvf#2dpKrr zuj&N?jps$uM561XT11*u;LkS2liO8cA)6r`$4gM zx##IG@lx9@&*8BbXjrTP)uBaz4Btiav&$TQvR zz)?%HIiZe0U`=RyCpgjx=Dfh`-Yhj$w3TKt2&70JBXnFtV0{YBe62pI{n0GRYs;mW zLrZUR)l|Cn+q%^g=c~Bq@Y3$Q^`S%>Sz527d_ivpShz5)CI<7_Fv^JD*L_Er6-Vgn zf{!mY_u{FkEk0+4Zk9ZD30nPMCX@68Z_Yi_FOE_sBTlnA zT>OR;ilErJ)^bjiIlQ{Mde?QvCQn-zQb)6Q$$1ekuFk%hom-viF!$m1bG}agfGEv) zPsDPo*bXNmK7Vsz7{_i*-yJUBT{KwA=i#rwnl80LA1ZBEXa^eV6CA@i9u7^Z)QNDyZnSa!==tUJ)EOa%JDQ2BMrL;m1%Da2_W9~lJ@{)BI*x>eh( zfC<^3v&yaMz%@J{cTuLT#|Kr2Xy*u8Kzdz4Vz%&U@qj*tST>~_u~2Pg>AAUlZDM+P zU`N`VL|d?-Fq`lA*k`eF`RcY^AkjX(G7!VTGu%9CmUKDg2tzVwtJrEvw-vVPme_LB z1(zf$0G#+#Bw5<@e9ZZHO7SpDm&O*tQn~kb*RJZ3qGES&%;_>tS^w4A;E7IYU6(e_ z%C3KH_T?xYG?md(*CsoHt&2Cc%HGwRF45_kfs&^+nMzx0yd{B3fAXrJ$t-wiWJ$A<_%kslkv)gw2SjV$TJkJv}fYvG^R?f%Kad^?6l<&atrII|>n|!g_d?nHq&438H zleNEIcti}q`W8i~?ij2r5}^3qpQ}~a*AIK&t(Q-QLi-%T9D0uP0naqH3KDtNaMJ@= zwYU_)(0ZC3Q?{kJPCGwL{cgZO0>Y-jmi3Hvn2zHqHsrc5}mgJtaES*142&rYSQQ&FE;o9KGoA5 zs5JC1;OONeT_9a|GN4uJrwtK@mpA4>HYKO z1y;j6*MnnsT1HjkC}xUQXN^&1=DXgkZsxY6tu;uHz zgs^w@!#hoA69*j6*v3R*$5t^@V>YFRglTfw8YBDY$vP;f$o?FUA0qWN`)!#VO^?en zj!>`k3oT$^b&L|P(O|?K^sVL6sQan5B{qA!w2{!)xvTZdPSv8r6UD1ZB3Rq$F+~OK zru4WB({pgAyJDVmQwVY)@?iM7v7@nTOOPp@{}?FEYWKl~+#te{Tu(yl&q37tHQxsJ zu^0Yu(>_=;ICjUL06TE=731=kmvX=+yZmqzt^OZ&t{X21k)c)C`x5khzYd!49NI>4`weW@>OO5TR%k(n%iE(smOls#-p6P#FK;p{JvDn)$fKkQ~|EsGgaS>oD zw`(fERrDC{e{~fl{OMvEfdFt7<@#S;MH6#@s{8-(C01zfiWxwB`Nvdnz-J=?UcenH!~*WMEBAk~ft8R0OftP?r32g$LE?Yds6KQ8N%rT@ z1aVt);S~B5@(nr~duEFDH8!=JWeR7>bm8O_rv7DNSwr^ooySD<`>FqZ9kf|MQOX5p z(4eEj*!N4$G8A&fPDdey3EyXYw9Jwub_rgDp=mJNq@$Fx6eNg9rySPDX)sy@|-=z z>&j_{UAXFl0`y`iW?3Bsu`ThwWKG>d^MtJ@O?qaGnc*M4fKvUu)&k|<5C0!ahCu$A z(QGDJz^gW#^Q`Rho2vjx+>Ba-zXLj4F@>4TDqypq9GWNz@&cV*!QxMTDGxVfr-)+DwKx#`5=SDo! zIl!=p((-hPnSR(ikHkW)w2Ir0GhznluF39;S&?(&^WIR$4G@1Q_ltnuQc#CgzMk{B z&p*G9g}yyAmWr}{_7)|=O*y}3#R41xA3d&Y(f=^!g{Az=RKhyT9`?sbtR!ASr`;86 z*tbadN80{(0e_O_aPfL6qH}mPc5j@|!t8?@=^Ffjl~P3ugo;Dc!M>aGCP$+5X^kp5 z;NU5fTe?x&Jq~7!ub)Q6qU-Dkmu-9oN;Vr6TmP`95TMTJlXLh8tiObxaz`Uo80JHwP0gret1=eHz~#;W+p2Cz4=QZXo@rfJ?0{XqlYN( zxT?W|(Ydy$FQYa^#kI@~d;sT>^sQ{YJlma`IkNSL=+(m>us3$CSfs-BuH^Wz+Ma|s755*{9GzyYd{3H9Y}o{pgdeV+ANk2zyl&36yU=@^F^HjTg`TY@e49D= zQz08tw?1~i4QW0Qxur*Jo;vRVplg5w=24KhE~aQ#kyM#q&Y!+q9#_)8dRZ+Jm5IEO zFWP5&3whd4G0^pE6jj_nBKx3w62F)g=nnh#9F@ds0d_@3=!F9AghVZng{(*+hQNo< zl3z#&6%O^|0alnn2)Fndh73Q%YJoImQL{`0k_3~>`B3X?CcYJla!ELXf)Myj;1=xF z#|F42+4lUPZesOEl&#}ukkXMKUd2Qg4zDhG;U||`UO8_pgsR6hoaUHz3@_tMj-uVt=Ddrjyo zJOsdnpkFkK>(8kEkArQ*$J--eq~?`*1P(7;s7qDwNH3yNR*OG65=rloY(pv9U%Qkf zVk3!tn=e`&!;Q>nVqDZkS$WnsBiET9WoeL}rIX8$o9)`FL1)T*5z3gvIxQB$kX}tL z0PP#A`NOgx2n`%k(;(4adF8Q`M3h8PM$8N}b;+BkLMzZdJ~xY>lbnJwY_1UYbQb9& z2p@>>cmAdYBtYd`y4KQf5jmLTOq62I{H4k=daw?KwVnXmw=1^`w)i{aqW-s-@#s;c z!f3o@$B@2HarQu@Iyq9Y=3whvmL`csgk^X|l#F45l?S97$Ag3*kA!B7F!T?N@O+60Ob^m-< z8LrMUV9@-IWvo~TG|EIJP@YD|HZ#K7!4T?XQTXwZ5GY2HH(u_JmEhm63)W|#RlekO z)A@;6@*xp_6Jdp|*~<~cCPxpxHt%%uP)a0iMO9gQBYB<724L2xV)gmJvuTCOGDVYI z`cW~57uHj9!XUYvdr!f61w16{?+dwNN{0BzcTdg0kI_p3=yprcYc(gB z_w~6n9SX>C%L*?{tz3s(i27x0M>ehexgVp;ji-=s9}NrPr}%vg;oPj-)Iox!NG(;U zY_0o%%B+0yfMzb00eUBTQbrFEPt=y)G@JhZM!M|Hxe12eO*EZi`<+cCfhm94__gqR z4u-WlI#;{`p|A8mjWPOFBmcc6V?=NXWtQDc3WT*pnV|xr-Husb1Cp~QNaKenO8mJ0rA%^ zg>`@CO3H~G>UB$E%cEDUk#M7^m^8afc6Nntal@%H#i9|5QV=IuMzgTN6r?H>4Cybt zgCmHu0uzCXCW{fv6HSzActS03u6rtcVnQW_LT41!b?UtnK3^a;QuMkZY^wGc%70DI z*D(}Fq=_8e_=5II$eOq3`QzRS{^lsO<@3yTpyPvNN*+O*EnZk>PSkjv zPh}K@I7%gOimhqtdXkM-##m#4ZO$i&#-B+NVf^12N-7wT^eGn`xa*@0DDten2iXZp*P{X|P&svYj zzBY6feA5!pmfbwn{c89^*>b@wdrBCOV7KIwR6<=KqawPBrV4F>6p$ry`Iv^JyGv-= z#T~NWlBwEvZ3%Hz9* z-hLwf4<~nGg-)4gS*GEg;V{+##w(T6BL8!_TDu)A9Et=pE`NG+85=c>mJ*My7eP~6 z1W^lqo?7sC($#tRoh!0#u@B4j+oM}{{`M3R;^Ml?$n7Z+ETm38gpSnS`9y;T(8+n6 zk3Isx6fopuU%j1ABwUbTfD+nuQpp+s`X z-Z2FVkeOyHs3s?=*!N@!HDt56G2^v9+A8`MOnsb=)DhbxmCMou3d776!~KK%1B))? zI;NF4#bfTmIck-Ii$v(I;i*eE8Gsy*V0N*`qV_V(Z4B39pUxw}ojOpn6h6^#cx6ad z1fiwyEf#}4erQOZqblsdGj!Qc15>+mC|a7YE>Xav|3xr;1#Nu)K~i$5WcwA65?D;Z zh_Dj_Sr57a3b%CLR1R-nFj#u4wN|_N4<=|yzIN|Kk!+D{tn)xI1bQbL6dJ9kPV{{{ z>)CeiSdEc;U#~)Ydq$OMA%v#%EX|N6NfRy@dhe&Wsvy^3g3WL9Pes9eYbnQWblY zWoF=crVz0k$sk{ znO-nOuy*+;(3UFyvv3)p`TJHIKZAy!7gXtSfJHQbv0OFo(}BIbsfRqCcHM-f6E<#MTywr_@SKOv#cgA|dDSL5XeW*(j~G2Hj+NW`&KuTMobC%2g!gbP z);oSMvrHXZQf#^DXf^eg=oVzLoeB63-)K@RPQ4jD77?F?{unFIo}BOSs?kjpAlY`Tbz7w> zBMo#yU0^dvgzTT+j6OwbB!bG`Y#BY$y`j0u(XnP)S#f89ZzI?~XjHjo01PQUdB?;JyA}FYXyZRfn)H`h) z%l)YjQN!isgL&t03o!EP6icbZap!(SidEjkVC@o}TK=s4T&c>)_PEQbL`h_aWlI`# zl4|K=HxTyH%S)mv&w2Y5DbMo}Qfd1ogy+M+SeC8VdXH7dGuoVIR$NhCn-}TvQJS*n zh_q6Xn_-pZDf^o<>Y6*l9s7T20nQxfUcYhxgE3dz%(B0+>#kU}y`d-2G=;fL>m0Qr z-HgJrx{K`M`#ZlrH(kZMJw7ldvKxW25Qc9sd2HYY0;LIwSM28)kEpqlD5tXyZ02-c zdTPB6^CQz7Z|8hlbD{~!f8(fdtnhukmTzUZ1D*GTa`xcDP+ARt&gm^}vte(J>n)mJ zusxTvm+TSQD=Z}5R`ye_5=~-Hwv3{l3DoFj&yxTWnBI2Nh_jrfjH)(20(LN`5G5NM zIhp}|P%QTRE+J-?abqnmNiPM;VKBwE+(d985bEb&UQ$YZ%TVn{zLSBO)9Z|Fd5w#+}1JLu#AqeeQeaz-}i+VQj>klna`~A z9DBBDvx}#)s5yb=ic~#Ari(VLX&8GuT)q5I?($&0Y_g%Tt@P}iX2R=+`Dy9a9%;vF zS?O}ed%RlBK+R&YGd8Wn+1AR;X1R+*NtVlz=GgO^naAZ}j1}_O-`6_Z3rpQ;d^=pb zeK1~|yF1ilJ@aYEIHmmyCO4u@Pfx{ljke{+lm~_j-q_65;`0Q#7BluvV24WzPI=46 z^DM=ex5^_`o^eapRPpm?*!s6KMWyleo*16>!BF*do#}gJTD`ZA%$#k(CBmU@QrbAc zZVh%Dr$Go{fcZLEW|ey-nc|(Dbb#JbjxL-qGzNz?E_V=>AP1(_W;fW)%ODmftNn=) z?($UNRMzfJ@HBBF$67@!qohm&xpBR{Zt6ImPuc-e9%TT^` zexd$G=UymqpdVWFN7B>E>sywiAUeV?UJg}p1J>&Sc)7s}=XS}6`x;HDRE6%SPF8xX zH7v!li5MndW7Y{;KK;{Ho;Nwg;j5PX-ue&4X+^RaqG7A-rvvOaVH_N7YX)V9^_M4V zt#)%zGsSOU&UWjLYnoS|P8H)ciSBF8n+P;o&NxPtZ>6;RuHquDJMCyL#-jaiI_y); z=2y{-)acH&&Q!q29hq*ApiA8rHnwfFE%%llOBPWIH^hwavG%#TUS=EvrfwUk=8t@AWce?5B(udQzA&3+{0TygbjP3fNK9M}~_5})zqw3(DzCRp>u{bLnXEgIDs zMCV(4@sz!!JBc{EI^kiQNTyD{oqiouQ*QYFUR4b!Jo&|3Fc!QF*}Uk^u)QNC#^laM z);_!QA$;18iXf>!$~6hg?S3p)iZx~H&9BoOF)xI=|72VL{PVsR;|0>=l2Uf+Q`xuM zZ|g7qjh1!-WF{L4u3=E&x0s0XarR%Fx5hQQ;pq6jg=1$c-PT)*}ga#w|iEyLIz&N@Xm zz4LKnfo=AcKYgqDj$-r{8<^e}HXOkzwi%pMLlZD*Um3jFi+7`PmKM zIn0SheIQi+=)`DU{G$iO@?^Phi*piEsijc_G>s*#-X7eV+_GC;Mc|hRYuo&;yaNr_ z#XqcypepX_oM7%4tW|MdJdrve&hjb^FlzFJ7~9=Ip97RR-zE*N6K%|4fyud%zJ%j{ z3=hzF3SPA4+@N|IK)kQ=`PRYSP-8GHukkpLCV(beFmtxa^po?CR0+%$VAJnAoK@7qcecYdU~IQ(I5C;^R% ziun$=#$Ft+U))E~x@x*!odFYKmp)Y{D{Xu!*CSZON5F$mzw?YL9ACX3eHKnwwd=*XPtDwm5cyPtOlWjA44R}NJ@ zR=0VsQztPxGJWv92|ZtG0&43?A;dK2ec)ioFeiBXL4TEKm4P zo(GbD<7$|u1GkSZ2@LNi)%~Tq=NFFxK-{duLD;*h-e1PwPb?mH4sV=}81p~VTQm9` znzC&M>22(IYrb4wjJiKn&pX>JLrRFQU>Jq8*=~yz8*r?xR79E>sk~aO zJLUt9RotP27*BbY)gctK)w0C;OWg?1CFd+wMb&|2omKo;-MpmUaCkwZ$y?e8YjYaO zyp`dZ?{g&#><@8h>GV%7*PV_}u!511AO12v=m zlu*vJQ$v; zxNcY?tRLy?;^p>-+RYwj$F!}jFP#$Y>^W$djcSaWz^5Z!7xO)^c)T~VDayn`xHTNo`jPJjS){Yh{@ zmml8P3`<6XIxPUHF~oI|awxnYvi0;hq!3mQc4g)-wUlP>76LrXIeSU3j%RW}Odyl% zh+WD3v&OQ<+N6<}$?7y)Np>RAL!8bGcK$rU`&zur>*qEw>;PEdzfLbJ7!X1{8FN`g zZw5xD>o`x<>v&n`NTuG_Apt)%pa8~BeA3K(sI&NL=vG=Ik zn%eMQJnv3t57k%dlx+PLTbDC*nyNymwA06unT}oV;{lAcD8&Qq`&q%D#4BhX&FEIj z`U_|RV9tNaUe`-pg!O4v;Nruskh$v?)1F2f?OLg);OsVLviq5LnF?5Y*L^!}2pINK z#BJxayOcq`q;{7k?2V1_H2&O*GiZwAxZz{7WBtM`#X*ZXM;^_3PewqP`u_AikL3E~ zt^2w7w5rEA3#E@8 z*()`5hnJa~6}{rVo(h=Kbs2`yWm^W0&LuFcZ5+fvTWwlTFvs@zHk9p%xLUIW6YOp+ z98Vm*@~kLPY?IKMgDB%E>u1AUv4=r@iW+h8=-~iF1aOElu9v_g>3q?T4gq%)ELEC; z`UEIe?HP{H-z}@!*t~q)^McNDw@I{#X9fOPnfe-#yu?tNR6Q8_FvN(JqVc@>p?I@U zwhx?P=L>a%#EuOl`fjebp?+Z^QK|+BgVd>}QctyA*&k~Yp8`CtQaK%q_;TY-ms~Ju z`0K0^QZ$n+dMu-W|9ZQAfJ;;WN@xYJ*yobP*qZujY|azp3W6mV*JHpehrHsYQaAM9E{lo`f>Y*h%(#%U0m|pa`$i|dCAPLt3g=|SQG#I@_nK-UI#!O)lO=k26aRgn9 zj7^qGtnK&CpgsKc+{mK-_1rdYKyIMQ_cxi26~s*NNd(B%iCEiO_(11NIm4!@tnw=% z*`g7UT{u?5Mc96C1y-k@75JpqzY)gN{BJ{u40Mtq`s@+vfa>rX@z@4?(PVB?fR2i9 zZtUx=bqFHT?{pnf^56~#06-;)Yji@Lxh)h=d ze#nu_2;5>9qMa8h+PCY#z?ZK=ax$}rBzlBK?n}asqIT}dB+agqdHhj{Ja9TS^N-aF z!`;758@IwQ#F6T6sO_)%fdRP^9y#iK62gmVpui$I7@lc7M~Ts7LG?SfJ~^Hak+0#x zO!prodCBH70}C^w_)O#dB?}vdRV8|Y-OX-?af5qDiM{OU$_1%6WQOLZ@EFHR(c^!v zKgzdXyGHPD)b!T|4kiEm_;z@X#bgS-KR(!bubwiL!K0bZ$Te#Z-A#K3NkK%ehFy@@ zM-K&`MDHIfUANWyI?D(>^KO!MMlzs`J~LF@8Xi?UNJNl)tMn*UB)bj_t6d{fKn2x< zuW4%|jy56-IV}3uQ}g;28CC$s?SBQQF?DaQMhfKXW#4`0Kirh74NqwT;F0#P2#}I6%3ey^L)`Z-sAqr<6ldt*D-+W3Df^QDT zen;Ig#q;KwyL&m2XHPUANlXf@pAWSm7uVFjMHGuO-T%-$3P` z7$`OCFGMfz@95_ZDTlk$X_xXiy*aiajO{d*@%Fr4Pqi17md0ZV?D<|M`Z<>4^(;f} z>cJD8qu%b~p(tS{=_2lyv-$`^X`MC1!EFAr-*OnMp$3m3gh^Ya6V~en(F>!LV5c)Y z?y_!{paScD;V0zv+)X*B&)qZbc((r|C||nu%7Gct>1r(K+UT7&cQd}IZmvB{uIFAIMizP#H$Ejg&=>6pE&j5i~T8_lG8g(!h4Wt6pDEFtq;O#wuJN&f86%y(Y#1mi*y zWtg`IM)hN2=`xODJtGL`B65ZKZjFzGN=tnC7Yc@K0JGtr z8M`Os&kE}?=I=g1*CXm(@#(9>?vwHt}vTQcduwcAu?ac8?qGWyAI^E_4 zAJ1`#{+#aoy@w!2zPO}6!>C`NCD8^@g5Ek+*v_v20Mvs!rOu<7*V}0gH)Aa6GgG0AwUt|FM@-GG=JnP=>H5;$oqD&z z_O(;^%f2(uy)|0~oKtQtybJ+!so=Y#dU>@VQzA?%X}m=knFs1ci6qkF2`Emn!E$w= z4BE$zhhv_%E}ku%2-{XCM37m`L!U3r8YG`IrZgL%JwuOn7z_sH!LuWUWecK*8Qeq2 zU`e$5cvhVUzwUCmPekunU==8Ew3+8&7q@4Ie5Bq@7G_^gwx`hX*@r_fJE4jDfczfH z{R#4^AlZ zDmI{I9nH~gV9REEFz-HrN-I*^=bE%$p5K*?$~TrG>c-ldtWYSM!XliIOgus#mhH6n zvyf^I^d+DT^y_MhmgcdST4Pw2?=x5%v7Aov=R9?kRua}bxci>ka%d`F)k9PejBLvG zE3BN@H?I3f8{{6kZ+6ov4@*@YC^|fd{QetYt^``x-0yLNYn9d* z-cEER?cgiDX3Ld;y)>@gxt{FieBbOQIyU)riHTBcu0QK7Ve3u`(>2Wdk^F06*9~{M zW^-guMMwVC_{Gz`-7eMr)ujoo!x)662xga=W1czIdQW?YhJZG|(Er!lTZcupeR1P} z0#c$VNP~cMcL{&L+VGK^wPZA4l0yn=C(Pmg`pSdD{jSh7HB-kE zn>_B~;h2+(kyNE{39&RAvG5$*WedoKYqPKy%ga))cXA_i8?FbB7sIRotHcTWM(LRb z?DxkBC-n$SrQXnDqtUe5JyreE;8P6z*2>1$DM+{;OTtmU)-&?ui299Ls-y;JfU{gk z?f6)~C90Wime|XA*p_r&T^Vk^<2CxE@s!aNMtlF1uWT}-JN+ETNo7bV-Hq@iE1`n z+_)aE`s03YjJVC&sH+RZLS64q6p>%sWHKKx#u?h=NOt!CynH2sL zl_3oHK5_6Szoay~zf$WZA%#-sD@z4Ci{kCHO|N69rk~O}td-z2w%%$wXBsg_xF9%= zoYvmnpGzI;0Pz@R`j_;5oI~{=Z@?_RWOaGtfsgOFVt#5=CFVVb3ns_&d=&3}V43jR zNQ8qbOmN3toMY?hSCB6P1@%@3@+D&HQXpQ7o?)+}?Wh*-X|ENgyZXKiBNe^*AF3kQ z%}(O3XzUgTCRvZJ*5HD$Pw8>Atc3Z@jbDf?^{m@4Bu3NIh33a?DH@#P>OX^j1|NfX zVLp@svZ{xq4c zGvI*8^UdO-)4-pUjS22z_;qlP4BmxDzo1R~TE@z~@JXSqZPUlCqgJ)|uO*shFM1?S z;fepEV zxjeS{nt6I}DZGJ$({!xrq@t@Lak@kCNL)JYer{O*QFBwNcS0M0cTwNt>1Qh6@PPYn z1+7dUOPpF6aKTDV7%aw|jwwPxJ~Q;pt`VUK~;>nH$Jn#mL5SKch@ahNtt#DveM! zf3HFj@8`m7LP2fpIv*$loTKz9{#dXV{%Db?(;nBYUObXV#Flt+ZDrWD?|gm3m=kAs zI_&(o%`VDGv*tZsh&xEC^qDQxLbgGL?u-0h%Y7*#5&LiS ztNHJZkJK5SQ_bKHuqHt?TPb^_csSjleQ;uXz4hE7cH`l$#gNQT>DsNZ+@QB66jVI& zH6wI)fbQCXa$u`D@g|gJP_wObM;u0n0YFOu+;{ z@roDVvh(DtI}MNh%H^2YR7-n#zt`QlhJVfPX|Hkck{TQG6@Izv2MrzIzcc-21Rrkk zT8?H|3x8G$_dFWSKT5L_Ya`p+GE)_aaQT(tZ^&Q6pXz=3_*MrEa`KOR+JQU69>@@T zikQq=eb4!fcQGJ*T@U9YmxFGtAasb{HdIHY(#9h#mxjx$Jc;d*q5hzB&9Xk)$UXxhP=$bH)#Q_Vv`wFyf8eTZ!{)Hdo+c zzL4BX0?d<4S#`V$!&R(+m+K}(^7avF%%7Bb^Z9fR^5&c$%MX^xT6D(_q8`+in7o&= zh|5fQhHxctPj%Ij(I>rFm|JunR%cfN6QX}4eX#=L97-4t< zw$z943u@!EDj4Gzm4@NiCSsqvI>i3%u#n(N@CXSPMUGskpZ-Q#)&1zxVqcrN!sR*0 zg!=Eisv0LNi0K^eYy4cx|AR_HkBxAcL~tDB;PO`m9JIl8TT4@AaqCRTkuyx7_6m-l zYSKQ{WIl&{?`!x>6OTceVBxA0KCVTN=vQ5RSkPZ91XAy0a!Z$ywTh`a+}>46SNJ14 z9mVm(dB7`khUp%{$$h54{2|xH8FvPk%QBoT`*2%7e;A9GGtr39>PnqB6lkX&UKuURn@69qZ- z%cD2alqe8TJQwIh1oZ~1%CaE{CCH&R6#DpeZ=c)jxq@Dd06jS!hz9ejhcQd?FZuxY zn9*9l-MK{~)7AL*a{=BOOMqKcfSTs3roZdq0P>yg&sZ`w+_w|Wqiw`FVL$!%seW`; zC_zLmT1Woo{o=dXwV&tI}3d|r$9iMxux zo21gYyM8J_KX_bj?sP02ZfjUt|6GQJ3BJbjmABm9t%Q-Qbjw~{{$3U!f->kdcPEMU z#p$1UVTQ|Ft~yz0>a`48D@dRh2uCFqJM}dL2?e_kTzT83l{H=XF$7z%oAtWJgQ~+x zU>z&s5&|f<_zv594bv&59e-}S z8`$+)M0vkWTb)CeuDw!y9d&RysIILBBX|#?TKa?@&h3@^QtGXObe0%h8T2fCMSuY=KfZNiT!38ie~c;-<=aLLt^UJSF`!^?}_W2Fdyk~uuUA`g>@^Wp6pwQ}f+z zc6YmR;w^#0Uik^3f**XzFjzi&A|^sk4F;W^`g*777pZVS6q+e zZY@q-;&zAY+nwwp_tj|k)m4eY^yLAw?Pfc*ialemMP_Q9glBYS`X=4*cdhwSt$kT9 zCsE+9FF$VhYbfWClM3vOl$|z?)z|x>7QCu|t0?j#bY`Z%Ygy+@9GoS0=W8O1)-SB$HfD9mLc--_eBkz7CBU*XVoY2Lnn)gFa_;PbqBSi zORDJ?SEUoxmA2@s`DJs-TxHb`ap`%f#Q8Bj0;SO!$!;K44aNi@H`tpw$6F(PJV^m= z{;4--+b$HK?JHhuh|wAQGK;^0Oe<3>!>T$PkAg?HmAS6sa2+8FVjflHn(pY41r1=c zw|NF~KA3Qm<#*m$JhMimFB9n|tz+_q)X)Gy$AlV8M2Fit`fQj$RFQ+M7)D&^%v2rz zsgSDc9OlDpT&RaGrTCnR0@U_kl^4dFefpVi}&+yphCrM()xmN92Z9fCW4zziU+PHt&{}r*G-dox=6tXw#U=im9(B5Ti#Wz|Z5`Smw<_Nh5luk0^m@qIsK z{?TJg#oxteaQ+R8#zPJPe2VVeavDzClP%BL!eLh;7A}|AGV9$1dZ?s{)q}J0(QnxW zT`r7}@Jo49OhyL%w4CRE+IsZ<4Qoz4BUl@Q>91u28cwgR%#N{7*FSH z+kdt5=idj{BtMpe693UAi^#B^FPD047E;{UqVL2m_ z+OlNAipUIDK)1)8&b9D{*=6(5j2aQP7ci-b2RW*n@tS z5M1sB0@~n5d~%};iz-A(aoMwrFYo<8=q204py2UDwN=K1kxz}<6ZTJ`fsF4Le~^_| zcXbZLfCL*iNVUMf*LX$iMC(B$2JDghNm`SGrD@ZU`k*9(RD;|sI`f;lAvLRq6|XG^ zvYYhQVo}>J7j$PZLy;n}d!DwP9>4mmzfiT6&OBjBZVt^sAaf-yf6c2a;yt(w9C8;~ z6fqKFReiTnJ6(^n#f4Fru%`?=*|XXUv#+8*&{68mw^p~rL!_Zb66PT}!)l-~h|~ zL@D;YbYhST`|e6{vGf7K3vnSS&oW~ec#Bc2oAV<$o#Ff9hG5=3 zOdh3jDr8-#92)Fsr}Ykxc}!nrc{Y9dnf8{htZcLb_^CVX%dW!y4+7D<*535_$z#~< zTGj-K&xbI(E565dS?N40(lXJyR42YCO`jme(%L8Yq@RMXgqA0fn3SFw~r$rwCO)buqMQE5s?~(k_7YePm;^Rr(GQz zUPg#Mp5l=m6N(Ppd6;HlWs<)T9fh4}Z54=cb@jE{JBTVxY}s5P0&l8owkQ+oS@X_O zFEe4OZmEmE8=eyOX78R|g|$f6iiM5cCtgz1L`U^YH%`=V$t`stuj<3dPlg#eBUbjs zZB%R%vxOg|qcyk(X(({f;L^U;NsyeTH+0R*e^Rt+t{v|e2oBMFAqKPf09rN!x#1O^ z&3&(|E%5>-dM6sQC^j%V#M68hh8hfU-w_=PFG^Ao3w>6CRMRz}Lf8Ko@@6`s{z5aB zJ$ZF`O1yokT0*-4)@Tdcm+?Arz$1mhjdeO&Pz!eAb26`BREc{5QJE_25w#Tx8v&rQ z(9sPyOX;tYnj~`eNkgkLdK#^=6-TmztlUg^B~Gf+?X``-`>At38Ms^HzPr5-t?i09 z8laBh*^%QPvx_j%-%uP$`N(@dUAn5-GME_mSik{b=c#Y${*WLqC(+c+ELN_A*+z>< z`s^o?C3%QD1o?s@h48S5eC}^QZyiu5Mk!c+e-tub`01*t;WcK|q}GJsrOzm_J^h!R zkX!9&m@MQ>fV?lQcSQhsDj8q@{8?A|D3NjaMq;s9g5UO@prdibj!ZtsP`S%g_xgU% zC0ShK@C#tmVA*=NZjPI(ZLR!N0?|M^x2{#xypC=0dqu9g1-Ap8;4W!cZH#!R(k=`G z?pEN)Rp;!GV82jZtY|56*&wq2O$6=Eq~V*Q*rTdUdbB&#Z%}?8l$B4oG!(p+V22T!yi6Q_TaAJo(hP@gf&h!jG0uyC#w%SR}HRQtLFaP zn_CMW2o>${L3j7#SuiJcKL<)lES!pX`?jBXnm=sWZ0CV4y(h6NlcKdt| zaksw$t3xUIx;dz_#m%FNoPme^o(uq;^-A(-TfHg(cl&@7)dJhW-4}?66BVnOSL9dL zIZ8=Isy^0dKdo!#J8&p=q4jN&PIVJqt4VU^WQ-h|*yMG1H7eFrK~78yYZWEB#e8;9 z%I8ncib^WsU-E}fJ2!ivwhVP5YwP2gL*2V1sJ9&+Qggb~;-UY;j60(0s2vV%p|KBF zI#s9ZY7(82dp*16q)16Y1tuOS=Z~2k=~e)Qx;5Ixps7>-;V#XNuSb`hwR9S$dz8YfXC>^v=t5%dM1p zd6-^-^$SXs6MLfd@4<;l7UY5fFAP=}RhCQlJdS4-bfq{bFFfUkUMEfpUtT=FqfoB8 z)(fpI>)3a^kl1jKrNTT@C=TV^>UUR5W{MyAs{ZgyJqRI~lw#&faT?`Nr%^Tb{o`It zQ%pl;l2U86TCMmf3?sabPP0kR+W1Oo{J_z{kNRsH=;R#=ngG@p&@MfRp9~7>?R(FD z9cF8=t1~1^;I*6XTpvn@zz$%Z_IAY#^Re|xi<(VW{^7>~-9fkPX6?F^N-ae0*FD^VE^zkP4r}w z@r*UN&QKt7tdgQu$$i`-(tp@}h$VibYLAjb6tWdGcOhyg2Ry_g;{Q>mUjQA8ZJa+T zZnr3yiZYe9AL{nJWM{O%aGP2&bEu8eAC^vPA6g%z|36ypK^IjPbS&oIxLpH0RjY%iwO?n z^YtxuumwCSN#_GINN7cEQu9;g$4i-etZ{5Mh2u zx5E`cPPyDJqfw8EO{Nov{X7d`Z3yWhz;m8QcQFqpt2Qhs^jRqIJ)BWQlm*mpSO}}L zH^@rYS$|__N@Ch3(sN_K3`Gc)%Is%Dzk#C}|Eo8e>1%bj zwF2wjQ8p&?d3w&(6*rSkKH1-1C~RpZn{h)QnPtxi=I~dt-A-QI?Pr;Z%jY-FIFGp1 zP329#?@y05r%?bbIbp65+4i7+?pI+PFI`|B49{Xid2~69hJWyiMVCqZK+gH;-^%)1 zZ3EWOC$(ORS~ZN>>()0CG|5TpH$;gT-g`Fh{%rOZ`kgReYHHa1-|GFhqKl=z5s)Xu z*L9;R>0Uw%_2=ynJTcP|Yl50fr|G@nX%`a`dL;Gt%}DVF1U`$8!^SuClrbW3Bz%Iy z>hNIO3XBu@bepA_kll*U|e|;WshJBB?uH@mt z4F&tt&r}dfK&d4(Spnnkw*Gu{@7Zi4ve`|R7yn|#?^Z3JJTqF?W@Ctv_>T@%mmr%x zlT*e2uU56Od`=22TFq|s$I}E2S+wY7LTgcdhkE-VdgIArQm&v~dsuTI&OPGSjwbAX zdlsZwZHA$pTOL@D$^CA?(+agxO{{|Rg7(HefBB(1$vMc5lkA0LGz)Q=n= zrB3|izvT9--Cngf&7<1vIDwM?F+LL5Gx9A`-UhM)bLe6u#Tk5^oliOSy>u1D+Wj%HD( zHUa~He~g90?k-o&_zfiqyxLz%^H-XCz4P&iYe)BQij;;(5_2V}w10>q zmB1pPQ6sekZkYdru}5wp$|Yxh=!!FMY=74>O7!(kC7=?kqQ|r*&4|Y~b^$PlHH4u*yg;*LzwMDBc}VZxl8qk12bqwNF8=xg%quorSEN8%C!Mq* z-H^tdMloIwe`<)7U;xhpB=;w)IJT2`;kZ)6KfA2ojT~tIM;8#O0{mx=S5Y{ywgdBut}WjrWxf z;7r_kT-~C$9Auqbn_IU%DbSXT{V5wcU!trwXf=LlX%xMtaVxxOcw(o(Nj;yGmaRLX zzEri86@bYyHaZS7msX4_puBZL%IgMyhG;IfTk%ilBE`*a znN>#kiqF`CvZP-m_8pO!7-Wx@z{8|92f}M@*my!0(!jytS<;A@B|*)&PP_+Ceq?N^ zL>U&4<$kM(Asf%LeU0wasEaQ!$eIV3I5SzCR9j?Lz!{rwYwQK4?t7lhZTj&=<@!z7 ze9C2f`S8FNIHA;D?3HMJf8BXL$UZ-_@__v0hop6FpKSdk(+pDR0r&*7+ZXshv$$?d zkw`YqE>9f79M~mE1(R*KD!7al8iomnYw%xY6l;`%h`QB)QB3Rdpm4m}c=5F)3=KjqhW$B^OPObHN`AE+S3~JiXW{%%eY(!@YwY0b(g6{9j|stl6hnR zwXKCdSHj&rw!+Ck&;cK4oncmnni~S;+p z5ha4P1TSkIMLdvtFXYgj}!(1@Tak9eRZmu8mp}}77P-QspH@9W5RMQC2Sn)VSDMKcCoo|%tRb> z6+?@{YXki5^{AG415sqr-Q{5h!TN`bZ?B?2Arv)5PYYfq~F*NLuA=IH`gtC?adB z-IKt4=d1n~8)DvZer<@&Ok@;;e{2|+v+_i;!O|@;Lm>q}?CP-94pqe{h|s=0op|MY zO&xL@y)%a@`SJ4h^_DaLivtAIt@lJGNk}uIbl0{wnv5T$BR(+L&Ny=yFjiDvH@8gJ zr_TKY+9D7o8x*+E=l;^g7VqEuOsO_bxbOY?NoJ2cfUSa7o zQ0It$*ON4*%+?6J_w#OxY!Ynglt103N^2T$hyt+~UT%}yOPC5RP%H)1>_C0atWohD zt!t%F0{Y~UB1%Je^XUHZp8-p(MuAvotTd+UBj1#=dSbO6N(HoUc#qT6;P4g#CW1+8 z|H2wrvZlF;QwmF>8{>BF_UkRjMfQ#`TjBL7EB`_SAK~U+OfF+=qIZ^YVrx3M7~Ysd zc%Y#<h9W@=KU7as=ZE+Yj*6&UTT^OwkIoh7xd*8Mr`lWkr56Z|4gkLHr zFuT&`z{TRNbl`B~@YnX0DfRshy1c7ZIbCi|x~4-m6Lq_}G9Tnj5gRZH^o$aR*Mdt0 z17H`z&BK{#HFvLAss-Jq5gXvf$V{n(kO+KA?Xbtd6hLCPiq@ zrr5RB^R_;7G6O8uv?KB1l+S0#`$y==rHj%VCV`S1D^t z1j~RCV$^TOQmf4xlEHdpyUvoFlZaWFf%hzKb%`Y>gH}K()-^|C#R2cD7vy>Lh`l-H zQsf~eYi#?1>lPuu<$xH>^~e(*>5e6Q9g7trGKV92c_(#fCUA-Ob4L@;CS|}=v#WzOM91uC)8#=){(%40 z+04omMw>x{cMeD6@z2fP-qIlJiY^^kcfo{ypHp=h7ScL;-U$dktpbIW25YgM{!hTJ zOmB3{fM32RmFaM;q-m9x+M-H1)dM^_r2w|)Z7*lBY@V8mCjB)DJ+P2bxcmp_KYDxJ zF!D_kD2Ky=MhFQJRWQ(1AFJtC%;=Mc61?S~N4@c1foW2&Mzzg2FM=T<{jWqfmt48`Uc90*^jcPJTP+^l`{xK&eq2Gu$4vF|sZ> zI6WTE$M4<$wN|jJn)y{VaD5fPJGc+yot1)N*P- zM!W_s_I|&oqDw|DQs1uHj<=vV@TD~}nncE{{QXZR{uRa#k~Q`v5(7Eai9tGET#VZ? ze2N@i{N=21V*s6rq?X87vwZhf@?k*EDi~q4)#(M9|KjlI^WwGWx0lh#8?Tn-n^A6{ z!(){I1AQtc78X!`N%3@^dU~5pPR$l`S4gOR{2Zf4C7?#T!@Zm3YxF5>m5WM1>L8A7}tp&vnl3^~b!9UHTraBf0v zI=#0eBk+fjQif<|EMYeBCH=hd zFFwlCc9(;U?rbGDjW=R$zvJ=}u4zaSlFJ6_;K09ZiK2ROmY!{PDh(rMtROLT$fgVD za9$14Xds)ce(F97uz60VQZKqZP%P6l!rd3U$O3{Xbz>2!f7wxaz3xpSUQhl|`BU0U(sCS30op;D`rn@JV%b%fX$&PWm>l5kJX`Tad|q>TRrR2P0|6 zej}mM+|H-bu{E5wdMdsy<-&xW6m?^*f`gU#p zX2!Y1^qG#=Z)_l`nW}5#4L4`;=V}sjS%tNJi2y;mx ztEG-~D20=CTg7q}*GKw(Z%XNNb0M|gF8Q@~Ld&%~KT#CC<*Ao2#cth4T8_DOg*2NT zUQfF){WE~jPb8$k7a8M*)=oX=q`9m$?6T1MLr1PG@c8!>j>rVdTo=c;2@yN&9IAt0 z$OixLnBRaxLD^`ReB9Z_r`)1fmp-MUb;?6``xt=^Ht-kV!DH+toF_X@Gy)#dSl1%| zcr9`hNA}=b|k?wUmw>F+6h3;wclY>|m|w25+yOpKUu~rO$7X z5hHV+%H?f#2}Nfs28URgDark*sFi)ydn`p8v~<2K_QODyqLlY>kD|)DTgfWND*{jX zf$HNlw5H_d)+O}{^Snh0o~8OO9=n%68drfOHVIzE`wiR|?D%b|x{J~ucLhr$>j5>) q6^EfQ)H4gGKG#WAO8MC<)OY)D*j}RD8bm?2RIPIp^}n{kiY^y}jPA_v?1U#_HHA$qkYS z1Y(u>@jq=52vKeDlPn+z~j!r2!xcp@J9rZ zo~ZL*W#N2~h3F^Y#kf z79Vq?HYz92POY^z60RQgu$cgc!baLFp8`pJN$ z)TpgHDYO!o(|FCbF@nU|Z4{PyQT_pWk^4ba(@3pLy~5i|7uwlU`v1B%7(o3njiTd=qKqO7b}K-at&!f*f2n8M46&RIPn?wT2jQCY?} ze6G^KcX(b!Y*uXj(zgAp+m$yS9Gsr>(+F2nC60BdVfIQ`)cSJ{^*od zepxlPa|MUm>e9Vgly6ynJN3^PvB=>&xF()rO3xDmHI z=|xsK0?M48ABv)1&|8*aUyhO2#E8jlc2-#f51xWHc^hUwi&%dc@+wWVCpXJq!}S%S zg>L#^WBV(Qw|v9bo1MW5gc=&srYW_5F+__kX%{Z>&RZmXwCdi!gd5#fJ|%lv+{G zr|b#Ts1}Bc(CPkXaIO8<1+}HlegS6DFs7U6?N~4wR!^#(;YIbqQIOqp)Y>Db6o%1i zfzY22V-EN1GJALyq?KWSwMGbU#gV_$)SLlMlxrQPHdgnC(nU9*nIG%)UtAL8sRnL zvIO*k?9`K4fpnym;50z#ebD=+rZ~#B9dpG&=ZI-%{LqY5j8ndz5Bo^s;38&v8 z8(1+}&NV9Y(=RCMwyd1YBBL1Mc{4wI?k1TngzL8oyymA8O_M2Y5c0rtPR>#ek(4}+ zvTI`PjpdGC&F~Syy8RdkeK9)AX8N#B63UrIl;U;paq7n-;aB#n!Um^KDkm6tH=B)> z;3zLTI4#Y?2aYLOw=U)%ARIOAdmMMfhQHaQE8 zl3Cp0zQYq?6o&{k_DNXPel;f2^58wLpT=YKQSuc(*4?S`z@Dr7Qgz$FS> zi@ndTb$lk)7Z!9l#jnB&dk);SrBnVL{_rebeB*2~oq^e;zWdS~RE>Hv&Z771FSI9J z`7tfJM8x*5sOXA1eyweMto(__RVTbyU+|S5HB6d4Dgb*jRGLh3<^SP_w;CaD=Airn z>}rapX06!=({QJ<^CD>ewmorplO*#Ve>)f5@p2FXtSj8Mpa#1cVXgVCAhb)&HQZgO zfVQu&2q4IMN4mO)pTC13+M#|H5NTM8&`jguD_nAjiR*oJ9i%> zS4&QN%lZcXJT1e1N=#qGK$_eAeJ=b0Pj(!BY81~$?SW<-R5^LHJW`}xjV$cQ>zZPC zKx&lIPgkaTQ)c#4Kyjmtk6@>u&~kwQ2TO1ikDO|0e%26uY|$`ZJ&_<<=Iv{O|s*<_~}Z@laTeJVr;$B<`4hA&>B z`VsH7-~=}Ol<9at3?1V^wg6RL>j^EV032~4IaYKQnNnGs;Ssey~SyhcqT&3YZz z^xJp%0v#<&D{~;^r@WJWG&QnVUIZ8B_1fEU$761g0RP4%O(ohIte>|q%@y#fVUTSp z3>LLub23p7)|oran=&|5TltRGRS5ieG(9k&xel^Z*_B-TPiOvby+_(mUYMo9snsY?Ezus;g8M8RHQ1HQKb!kSg93n1fGkNdIc0U!-ysgq$IH3AbRuiz?4Bij zYWh9M<02o0X@!^fPTv3#RsP8U+2+zhe+uFtd;k}gJ{B&)4M?v7*+E_8dAcPbqo_^x zN&n?q>huypF8^2I>P9V?K-3j3cj~Sg3)t*kHmSFYY^Rj0R^WO+zrdA>zb*);SAsKF zzO1Jom~o%=Ys9O930x;UXCGHc@^7Y-ti47gI|()f)IYW z$3fiwh4I*B80cG~U)9X1S;3M^9XBn)VR!|^m!=!!5StHKz1RF)YLD6rKN_34G|QL0 zKgd6Bn6djN$h3Y{Ry2=JT*nJrklI3~GExg!unzW zKobvk_}QhwMzP#-rWz$TVa+W>$uZzVkVFGW1J%yZ0pL961Ci7a9i9N!$n_#r3FezE zOHZ)9o$@3746}*BvD0BoxzP%LJr&y;LV(?#7TH?rU+$3b@WTW60#_?*alt;Tj~z%X zQF(&yC_MUY`Jp#1DJnKFXT!AI5*5$5uc-3GE^)elv9tt&zAc`sIBZVPOodOd+Z*@? zWK(gmvtB75yypEXBLYk`AId00OCj~^1m}D$m@-oSre-{&gxYjaWV+lV4QFU_5@0@j zL6R!$xqlPc&SZURe|EQNpsee&g^;WLTLuD_$RMf}-Td^i%EEfQ1WR<<(6B`%X0%ul z2`V@-^T7|#v|j+;g+5$0u0cmpTQP(T{|vS69iYie+5@#L9^B-_u+ngReT=rR1OmTL zQl6CA=9<629#ARBwi?mA;yXY#kz$+8cUQK`kG*lpP;nG|&N5M6_b)@oA1%Qv7WjPI z(SmcSv8M5!NJZY3RzQr(%zQ%MSHbTc39uFT%-D5$%?=#%HU3Q6g-;4D!R_B*qE#P$ zOXwG@E2Gnc#f_HO06T_@ab6ARqIKGm&AdvT z3b1cEJCIs&T1NEg+Vvj;j6SKtPl&WCxUEL-JF0o+tDCJt++z9Q7%)PB(W5CBK^U|N zRqFH2`*n2X%fIK0V)+?+1L*OXbc59gCH6_eqEW@lBly&2dpvos9YznAH8#^U6@ecj zZafSH-QrDi-&guLMk4iH^}N&i@R3THFYO&m=+(8l!P?3O( z$7nS)&n5?siowwtBgNueMk&~^5GWa^E4}g3$+BR@{HTzgf4TL0;guS1N3q+ar7FWg z3w2gljup*1G0`4xK{n&yaD6xzy090+eA#I4cE{r{-0U$eeiUScQuH#ch1<{XFdl4R zpx2p_M!n_(s?;bBrPz(8w6LSB;n~H@Pq3E9Y0Y>}w<*=Kvv)q+o33O#RX$$;6MU@J%jgsn+3Wf)+-J@e}gPv?Yl%+nih_ZDJ&GFhYI`V zBfZ(KtL_L zSa-p-CPLUDxbB75K&bobQ*(lvj#0mb2z?5#247Q)obHkRLp2kpS0&9p(yMOap%ZaE zQk?9m-l;O_6-rt)-{&zUNJw3@*V;G6gGj3ynuWC0_uj9DyUYD2Z8w>P91szRH!K`T zNIQhRBIun-s-wd zht_q;s;7o#I1yba`Z+|)P?~N5wBXPgr->&+uafcZwDNcUR3TYV*7MX4T!%ebJu&2a zW_$_rN<{itDR*2LY_NZ1)>u)1@~*)9n77rjc}>b)CM zGkLM}d$a^bV9cYD@m(Hr^4K?e%V&%Ae&I)O6P)CnzM1FJJe);nhhGD!j}srT){J*R z9}Y5|zj#4<8Xq6bJ|Do$Zm@e4=LT!=vrRUCoZ(!q?0#J1w!~$7*_S&=Ow;q29_h!86t*aS)z{wq?JrYAmqEIT(g0mwZS8M zX0uLjWbyN=*52U9QuB`tcKls!9PYJ08NbB&#H(JK=Jj<6=8XJM`tywQS7{f|&gQl7L0A(^LH=&ZSHuG5j z)ZCE(4MRDUVp}qmH;TsDkZ$$!&7~RELTD9P-Vit?GxI%-S)(3;shT$=$fSIn)>)!4 zRQb|6f{|e1ENJ8Y@^d$HF1lkoz4R-(Hpp$RqgpP1rTJK;xJ&!EiqksWrATQ;<3VWK z@`uOV*Cc*=9#Y(QBqKif;?F+ktQf&#X%H{6D~LZ$YIJZ|2)_`_{B_w zlW=%8r3Rk7q`r-WJg!2*bHW-21*m;k*{WSs9JGOV!F}Niq^*p>`d-T~-8cFX(5huU zDt!TFB_yA3qmTSt_tMw5{$X-d8nB_ik{0fy| z&jmqt(}En(b$6z!PMk^d%Gryo!u&iK4L3*i3@tl6TT8u3z1ej>dn`fCek^gXkZg)@ z-Mwn$?h*x9@yM5uP|0b!Z*M+RpORodf8g4=I(s)KI^*)6=bW)?9J7){1WK>*R_h8N z1-ILWzEzwFJ@;WD=MI1J^Bh7{VXtS<^?L~+7@4_p)lTxvqF<@*bi)C-EmH&+FMH{bU>nG@d&KSe}Jx6fi zz3>0Ql%3Z64CWeE=M@^D@!u%D9y$x{KPVg`fD(ag#HE;59$}SH((CIf{$S z90>(#8tnaQK$(McyPi6FelH)_)EKuI{y(;Mq8O6+i8}}1D}P&9(%7Ufb4(-N#Z!aj zJGT=wkNYX5B|faCP!XliZ;O7|*7z0LTPGWLs#qRX?L>W*op=jZ68-f1A8A|9DX2?z zuHrJL;ZHwz_j)adWTO{LbQh=VAke(EQ}PeOdGDkmC7AWE{t|&k&p#Y1?Ycnl960v;WRPxkOXVp{lSKXcb#XI#GK2n zC(N7fF^ErWLq8mIV&QEudgMB2=90(bXvMmblq*5xH_PGJ$xK{RGVWK`B2sT1? zCVOeBO;7p$n?Ku6UN<2m?zfEQMNFkci*&7GF%WR!2W#$tPWA?kXwoU&aeI0I;5$Xf zSy$X2Lm}cP95R3OJ-;sC;d)Ii2*Gc;+bP<7IASI^f(Y1%W1D8@7wf$E?SR#G`3d-? zD&k6TaXSN}kM@687!l{_X=h?c|92b-YG;rHxAbzD@0enk6Eq}*r)ACLuc^(rJjP^r z_>~Y<+&>fPe`X-9va9Ckj)v$r-jfZ0cWKBufJfz>NmJ>g`Hnddrp7bu=P@#T&E`^j zsX3(Y5O+qC{AGMPs^=x7P62Dz?78^_umH(weN&5}f$&*3Fyi^!Cnt=Se3WzbboBq% z0w{|OosY;Kb4tVwNhN3@YZb>A%9_ZB!|&x*_T+&M=V^pv+p2CwrDXnIC;(qaGrsXY zfjy-P>wh411asTXAXCi0XSb}OIw)gj0yo2dBlLb}VW7e6i7%x9fd@QpXM-$6 zPGEC+&%v^XbYJ~b6hYkAi36r6M1OSfiR1Q{+^V12<+=wF^1&AB!J?wmt15|>Y(MrZ z&iB&x^O@?_hL1+vaE93%EM&UbBh7v{6pe!a3%|+Mlj&Y zYu?o%IoH4%Z&>q1F;QR0z^;<1rMlWBMp@R-d!H`kEtJf2)m>w(FM0{5yfNJ4mBf7# z*4Xb1Z6dHYU>XiXiL*n_OIdv5b;0<8>56biwqN(&7TJUgzq%X%0S3Rk??XgA10~x? zEYq_O#}K)ksqzX?c%7!YX~}u|%dPh!>H0l-cu}G0lRMyXKLaA}^ndcCn~jk9|DQ<3 zCd#Y?M;mcF+cOfK?1nTZRUH1=HK9Xc-B|lXgy`5oDM&grq7;}^$3U-gZM%{NpTFv_ zWw?xc8Z<;gem`#kOcPb+dVaMS(l`H^vTkbrs`riq=cr-cRa#(mrEOWMhP5~ylhC4N zQO}B|Y%w+5JrwOGWzn`E3TO2Ex}rKoVO18JyMf%5P44**;$cfSkB(O5^TTR{Q6YBZ zpE3ABQH)m(WDGrS8>hc}TtteQd#Mh|);282wUJ($#x4vxVX{(2xxE{boWXI31-(!JZBo_}fsThDyPlTS^^nGXF^tpP;FM~%w#G0ETr5Nh9sTIXVb{P5V0?cZsSQX6N z24!`pnOi^iR}yJwgO&7hyeeLr5(R)~)TEotk$#Q)v^0eBnEwe&G$6H36yOa8Uu5v! zxY(@9Mx~)Vy^efWnh@`E*N%?bm6yT=Gtb4ZgD%DkF7c!J-%?Qi`^JH`{K=@-7H@CpBQ`shI}ngXIP*}-3sRp^ zx|jW9%*);;7 za2c)&5Tq||1nXbOt^H!hi(4|vca)5?EU%QHo-4RH2@TlIe>moVDV9M@}G zgE#^qedD(@@I)h{$g0ru+pjzC3;`1nue1jz%|xp;v|E0m-+;p8{+nI64(jGO`XKQP zf9OnPd)Np5daB=rgGt9}!#6e%u4av;4Dd^FR3X~?R~Az^(sea-A-QPkmV|Ms>3Mt4 z=@7j~8|olEObh3@9P~FQX*Ix1axh^UAq+CYFIv&R4V0QE1=;x0!;vF=>0Y zi*d+|RAB})jTK$z6q>Btc!B1BIE$AuDk{G*d?&!#zx&LQQ}?wk#FejSPT(|J#I!;z zPlsdlTW|silt}{DE9D45a|HR0C}Y#(zp7r!P8T#8D-E|U>L;fZE=Ye9AqOa27Yw6) z4o2q+fd}X#)qxzrpRtqUcO?yHywgtLbGL!tJX#>@zGY!L+|hmed_~saTmMNrFitc5kEbUJ)b6i>a`#B<6vA@{3m6PV%sDy?)pz!AeEc_26LWhe9oh7SYcq3 zQZlx`R&|`0`CbTXjN-ZDddOg7t2E>RA)5(kc*@{iI#p&Cy|c2WvDIpT9;>feuV=CB zwTAWVJHJby!m0jNx54F5!;Xr`9KW^0>Z82qGUXRV0d}B;v0$@D%IzB|Wh$C2_=cY5 z*%u&~(4axYR;;(i7>GKRI~cU3i%;IGUhYuUTh+6K`>i(%uMHlZ_urHZgU6w{0Fk*O%9f>eXpe&GnJ+BO+ru=^X#7>_i%{{La5oqkBzq$ zherm(wRFxkcj$r)3(Uc$dJ+cT0D+-D?_2b=V$jw#i-v$|r>wXK&h4$d?{cD9b-YmL zh_S-}IQ$uEdho^52Br)!gyq@JWHZ-g{MF@3BZ`B>+&l)K{NS$nCfC=*AM=|vi@+KG zgBF9Ynm?i zjJv@it|;8(o}#i8&yu$(B`ZL4q1aO~l(_OmV>oy1IDe3ji`F7usIc>n}bCsw!jv46f?k zaPzw#e*DUQT?4HxV8lGF{Tzn^{kLFFjgp{vb+RF*VK+s)1*aE@aii}`IB&<$g7cgW z9XbBL>fmqs<@DFejOb}$!9`y+9O{hIg3CTJybR?h63m?9re|Fwn8jn~s7yUPSG6zd zk~=htz6)9sq#eenYWfiCabC0h(U%#@6UiyxB<5Hz7v;ggfaR2g!n|s`xN&lYPZ$M& zO54nh$_8=(JOJBejq&70imP_=Z%5%ws%?Uy-jS3Pdy*kH3_#HvvRRt8x?JL0LVzr% z!t1XkK7j2j0o@juepOD%8Y)RQj-Ffw)XP1Q&}4RgLS$QZD^NaoKz0Pi@ZTb}ikB;a z%&$iaN7J1=YrIn!TK~4GByMG-JC+OoHpio$;>LtgK;-*eq+-elBE52-aS|It7_^#7~pwm7ESR+U~T; z$2TlS2HAZK^Z?@O%E_I%qT<_%Bsa$h7?=#7oO7;~M6w7}M$Q?q-u0K_2mec8Odcno zk)zoCD^i4gI?$PDo2*1WsMV#TiE%6UInt^~nV$80<1%w}+b^H|S9U#e>fzvMl{Kub zsThEyupI%QGH*HNsM<*?nzGyE)En>lElv*GGxDHb-_lfNvWzMWp6PNP`r<0I!osxO zt%lG(2cX6PcQ|@}vbO(}Uq+OxixX+nr|=J|8908(2cF?L3gOyf_VDeW3Rec4Re+!}TXdq&-Y@@YSwst71cz#Le_GPldZSw&mGv_KbFe8Pm z4>7iWyJ#i`T?+DMP9JT|laP!IT-iWjyAXh!7rYArZ$nZ~iXQor5Xil%{+vWAGK(h3 z)b%RO-hL$LIs4(HBonFC>mE43MGJKaK>ko@+YqdrPtBMIM15E!*^Bc<_nLx0uUc`wo6+|5@e&@E2dR5#|q8uTwTv(|%6BYDp-(xGCv|AV*N46ZT?| z+GWyq6&k^3sFbJ}+uIK7$M=9R|6gq{P zL9bukyHQ!D{z(g!e8m`(TJ$Vli1~lVyg2!Z- z4IhBuvTZzn11~EYTNEZbZ}=CyqXHH87)yE4K&Pp+C8G{N8C5Fz?a;hZ+)Re$!vdm2 z%K6=S`7@?I?FPp|K?1B9DzTou-Bq*C(6W(LLtD};xz6v7vqN-FhMrryK`Gw4ZW_$b zCIrE%FsXdw*Qxr7kqDFxXa=A7I7OB>YWcy9)Gn7jyqpK6^Egw}@&G8rPIvP#Z7{@` z*ZeL>=KxvXRs<_E_g5Q;(a4N3Yx!zEw7Xm|p}PY6#^CN}Y5kr~TA^u2SY?DZ>b$$#u&f z5-8ngsz?vx1YRFKyHxss&<6c8Bt2PB$}L1r1`kf(;8+;6=N_;y1>~$1yRlU>viMYy zrt%ZCNw%?8_|3(GrQQvzpX0fLWd=KY z^jv-AZ|f2l2$i`cfE+bGt!W(cQa;IKx%O9OM#hasU+G)f7GyiY8nxGbr;Gc;x8AD) z5eRe*Bjc|03Ri8V=27PgtTmlUYh1Jsh&ow9YN>;iDxE3iN9B_aW zl!{Z)-xYibcWT5l*g4x|R9gypCNppdyc;XlCoyZXtFCHq3)=cBVNsNLGeBYv=xE;f zjJ!4mYTR`b37+?39v1?FCg=gLw5t$^!&o;NEV+`TF};LoPXp2_Rf^G9%hZ^KsvLpO z6t#;xsUk6!d~{h+!fvaHl1TW`vj{z4G}Qh4ex-98ERs%8Uf2rZHM?i7yHD%uE^I}S z=Dh2a%Hn}dRP9u0HA~Yedg1)`@*h&i)Z+Vrejl`77{cIk6)^rO!O8SCI^>OO9Xi;d zi<&l>;8T02Za2)?TmqzgL(PSmE?&!S;iEgThq-Ht9~Ck!iM@{8h_kwvsRxt#vTb4+ z@y3QWna3wo7pFI>Vg$_!mCjaVI+n14*FXH%wZDOk-$)E14NXbrZH~!ozvbR4R5ST% zo3w^XFoE#f1}Iin=_;2heFfw1xCJAMUmD_rZi=UzdgzV$Sj}Hr$bXe8z(K2IS&#v6 zW{th3m2A}yoba%rUs6s5`BG`G>wT}BHW4UXf@!T@8YQ}cJcr$6aM6XHw@~z11ft1} z&`q@t-DAai%JUM?IL?~I&jJX0@CXDD?>aSTUO^FUC$l5LO#_kO0ly7bz>?R-EHul# z&rDeRu(@P*_Wb@<)G?(;iqF9Wycqn@9f6A2+c9!JtZmx%edI}?I_9O5#urV;o3%St z1TeFQhV6D-C+;S)W?7U~ij~T&3vz?Ll4_``Rec% zJ&8B%Q>0K^@N$3%WsY6IY%E)ICMI=%XOQ%n=s~SpV!8H>kFnCuNyk$BdAHlKPEuQf zf25bmFpL2pa0OlY#b{D@#NMIP12z^7^DWzU%dl*UgaD-GH_BiFOh&kYnUfXa#-^~K z$W_zPJ3}c}6if6tofomM!h{!*x$Z1naDh7X6I;Zz}y}kS@Zm)!~G)PF* z_;uO`yC@e-yB5l0rfCl!Ym4KC-uAq5N;n949E-*|Yfc7b4^|A6dM-SQ# zO2v=0|D;FGTPsW?Td4=wx_P;}`moZS0kLxp*QG()oQgK?UEQrB!}nj&bBekt z%#Zdo!X+$GuBQl@zi^R~Rc_zvGfooqh5a*z8qbpVV1Mu%mxBj`nBT8x{dK_?Z|+Hg zQ-4v}j7)#+{D+b`?vNkB`m?@!Mx)^9tJNIY3#LETiC3gSyC@%?Td+|qIM1lJXQ4!K z>aYHO-|=zzhJ_E*BTAp69)9$QCP@QFhE$|?-&rQym~W_^-^;=9Zb1e*QX7t1$m zVvn`n97Oj9a_!pUEWp5_UHzXdcvH4vCvs1c?HvX>YKG?`2%13_FE_6J#4)A>)!kx9 zhBY=C%J6LC+9%wVsdQN;qrtyF#^dXrBtSY1dU-10qxLn%SX@$hQnAH`rbmy0UW{KL zFepHSp!z0YW;MEd>O+M_>k9+!X!6hr04Ljb{rmeWS@&I((5HH07mR$jUutx}OjEj( z5jV(qa^Qq3$BLPu3U}CRHUwd+h`kvCOzlJhcoDvlWE;6z&gR^d3ny;$da zLD=TQ5Kk>W(Gzj{l1f=(4ma;*!>g~cQ&T?UdR5mK96B)b#bd+YSkavFDpPgXTN)iv zI$%IiAO0|GXZkSU3{WmP{g=b}HJi9o<5q%9Uw3Q=C)g3XcNm&tz%!CT?MGuy5j+E{ zWk0G8;bjx;N#Cz;^6SJ05!Bs9u75geL!!YIZgpE?=kyPM?hk)yR{L&M@p6 z0=o_0J?pM1{nfkab}xjwy5~~Kcu<&Tv=+K=u9!ACZ{yThf~i_vO@~~4(<69jiT;3Z ztzqQ_dPxb)9Kp!uDR!#`UlF_rkvm5Lt4}_8VflB%p1wiq-nF z+&-22bN1PM>jOah|I2CF8l5VeZd==>J@+1$n}w%((wrVTsfzIwDSm{(t?RfYof(3c z>6CAR+hor^y%9valwt>}JR3LlyCX&C-&zSHu!g2_3aaOj@r2Ca;7m9HyzwWk9zkJGuqm?*-vq5Xby!4a`M$&hr30YX z?F4bxjOmG7)br;)Ul)WOu0>w%){Em8Kb$J{Ki7mOj@HkB5hlCwgUVStwRB(`$msn3 zW68l6_-QmuY@|h*k!h-dE>&&v=30 zIv3(Tl=pJrKH6z|rv)q59=N?as&_Po3H~a==sNM|4X=W#K*8r$N&#WvHVMQ8zDzLd zV)Dt$dm^J%7u}~piF^kD8Yp_Z&Uk|80}tRszg$ALiocA z&U(s2XW__mKc4sym@3MmQf`RaZ2ZcnKKE3-oF85QR&6*9*Yoc#x~^M{;7jY+&Nx1t z9;OP1mj0CKUwb(Wvpa1A;s-a3=aPnOem&7jJ&5aKY2kjAi{EseM4;=;;4Y}e@sWF= zA0G=hridbHd(+pd7ntI!Pli6S)3UB0XF*&6?nyx9LSypblGr5BFXg^bRHDaZeGF zKYA6I?$BJ$!L3>1>)B@=SqdDI3o3txyAWJ%X`+7$fgnGTVp-1)+LLdd#y_o80#604 zYlXS!e-r&*Hpl$YNw?FUCO!B6n`0ac3lmUA*{JK!y4vN-5Z^ntAy0%#PdCo!;3cP# ze=PC+U8O~-JElo5M!ch(!`Q83c7(#bv0mwAFrrrE5)C~5ch4R(H$BOIVbEpddh3J; zWYV{|9gznU$MoW0C(72_{L`{VHwf0)f?kIvSV!PME*{ zhd_id>2bhvo;mP@Wgu3p2Aky|)HjztWISA0VuGkm!N0#4W6x*^BIJJva$+1S*n4!) zCiO7Sgt7Qu7>7JKB)^RP#3H8x*Ka+C5rq*D8&~zJvVh1l@cY*588DzHswso`$^0{< zaeiKC>U(5clg*a4F7Y$QzIfTj!#wdNZk$~Dm((($rpWbbXsHY>Olrl~je|XOJwK=N zJSBwdWUS7&7){b$u-Of~v(u)OBQK6!AROCBQ@p+q)v&k`$%WuAmy`q^%nA*C8_Lt$ zy`sJB_R8ha=<5bQu#C;Iomk~$cR_2=p{VTaMRN^|+#-uw6KJym1SZ1#h}EA(huyCK EKU&lfD*ylh literal 0 HcmV?d00001 diff --git a/spring-cloud-gcp/1.1.5.RELEASE/images/callouts/1.png b/spring-cloud-gcp/1.1.5.RELEASE/images/callouts/1.png new file mode 100644 index 0000000000000000000000000000000000000000..7d473430b7bec514f7de12f5769fe7c5859e8c5d GIT binary patch literal 329 zcmeAS@N?(olHy`uVBq!ia0vp^JRr;gBp8b2n5}^nQC}X^4DKU-G|w_t}fLBA)Suv#nrW z!^h2QnY_`l!BOq-UXEX{m2up>JTQkX)2m zTvF+fTUlI^nXH#utd~++ke^qgmzgTe~DWM4ffP81J literal 0 HcmV?d00001 diff --git a/spring-cloud-gcp/1.1.5.RELEASE/images/callouts/2.png b/spring-cloud-gcp/1.1.5.RELEASE/images/callouts/2.png new file mode 100644 index 0000000000000000000000000000000000000000..5d09341b2f6d2ea2d1d5dad5d980f14b4b05dfd2 GIT binary patch literal 353 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 literal 0 HcmV?d00001 diff --git a/spring-cloud-gcp/1.1.5.RELEASE/images/logo.png b/spring-cloud-gcp/1.1.5.RELEASE/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ade2ce6ed9d9e9f2f4d9c5729a252ee618a0a5a7 GIT binary patch literal 4387 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 FV1jwin)(0$ literal 0 HcmV?d00001 diff --git a/spring-cloud-gcp/1.1.5.RELEASE/images/warning.png b/spring-cloud-gcp/1.1.5.RELEASE/images/warning.png new file mode 100644 index 0000000000000000000000000000000000000000..0d5b5244605adbb7ab05a1549746a9c35490f95b GIT binary patch literal 2130 zcmbVNYg7|w8V(4q($)50y>JmGlLW#g$xLn}Vd2Si)}#|ptPAQp3Bp-3!-lL0;i^LY?;i#f0m5s49g z3h?3rDQg~EDPlm?FKkgKIcWEK-3X6YU0uzs7H|nq84s39r2!5;pF?SI$QqXy^Ko1x zV~zpENvp@<_Bsd`5MabC#3rvCq&$5dg43yGR zAdy0-rWjC#a1N_+kzUMY#pmogD7!DP(9dEKr3c5ngvUq_6>}Y+w-a81v=eSXnIi_+ zI?U>D1q2C!0zHox#XXKH+@|&rPT*OF5yvY$5J|)WwLqnU)c-5;=UChSlQkaY3@^|g z|J5#YBB}=i+n3Ex9bS$P?xJSKLk)-HHII@;3qG#TG^!+aj>0QkO~7kB0+|yM;YrGB zF>Ge@isQ#73%Tp#k_(tInh3U$uJWY-+DND*o}L-CB5g@#9YWWw~pxZ!Obm>yN#ZHFvL0zA3vNCTJ=t?)~`2O1M{L6un=ml<2x zQEYfifmA?Pz0tqRs^2Utt1pU0BQB1eIY0U_I}c1Y#PP7Ci5s7#IJn}xK{G+{_v^Z+1V#$Erodv>d}ew>RP0wzw+GWkGCBlS1Oj5Z!5NK zUuSR6>pa}RSEZ;qE_w1l#`hQX4E67U6NeP zHZ`T&wj0_H)t|Y1thUqnhub?%e)Y07;a^Tq%FO&iQkR&`qG!dh*2WfW(HuRQj*_DI zeCD1bUA|tMwjOb8E|FRIn$pzEU!2j_N}1Z2ig)vbr5xA0rjC70cmI6*%Uf->hWzaZ z{33HABO5q)vRhzDly2l691@+q3O{}NbSzx+I*k@|L4&3leK#$>u+T#TvTELfKb|IH zc23atf3vmfCa0&TWY@iuoA_Pm<0~ttEC*ut`isb^jXS>X^Sp=l?RXN_OJ*&usGXg$ zTlTv;Ch^vhLDf&+62jl64*&D+=+`sN_dap_1W%p|KQVYIdgPL5uRE9(c4On)DXndL zLNq?%!-u*zmMxyYc4m4Nr>>=A=s}PkJkqr&E^b9>5g+}c1%X}3|WKcg&spcJQ)05zI<<5LTBhNKRg$XRTi2{j)yl_ zj4~kKOvr+m(;vw~9zVh(zC-y9jqQnguz5r7FRq^MyKuXAENp(TAzUVtg&Ts+B_s7) zGn+fN0sG>9GjsPLKTRrvCd`71IZulJ1_1jO9KWbD&_@2UC+LTNpxxrdz#E|j|2nA9 zzB!UTyfAEJ&#%$pQ>QX#8vk@_a5iqukMF;8`wRKe{BI}5$H%OROs4J1#j)|?p|YSh z_SpR^e`VE#F52;WL{!+L(yZLRh40*KS;@box;9(-tE)`mcVp27O*>Z{_Lb*5T3cJA yr~0nPHtg2+UHi&&$8ha;`+hiaUmw&!n@8(?5PqF(KE5>Ym)EG)p&uyBP5%a8^# code { + color: #6D180B; +} + +:not(pre) > code { + background-color: #F2F2F2; + border: 1px solid #CCCCCC; + border-radius: 4px; + padding: 1px 3px 0; + text-shadow: none; + white-space: nowrap; +} + +body > *:first-child { + margin-top: 0 !important; +} + +div { + margin: 0pt; +} + +hr { + border: 1px solid #CCCCCC; + background: #CCCCCC; +} + +h1, h2, h3, h4, h5, h6 { + color: #000000; + cursor: text; + font-weight: bold; + margin: 30px 0 10px; + padding: 0; +} + +h1, h2, h3 { + margin: 40px 0 10px; +} + +h1 { + margin: 70px 0 30px; + padding-top: 20px; +} + +div.part h1 { + border-top: 1px dotted #CCCCCC; +} + +h1, h1 code { + font-size: 32px; +} + +h2, h2 code { + font-size: 24px; +} + +h3, h3 code { + font-size: 20px; +} + +h4, h1 code, h5, h5 code, h6, h6 code { + font-size: 18px; +} + +div.book, div.chapter, div.appendix, div.part, div.preface { + min-width: 300px; + max-width: 1200px; + margin: 0 auto; +} + +p.releaseinfo { + font-weight: bold; + margin-bottom: 40px; + margin-top: 40px; +} + +div.authorgroup { + line-height: 1; +} + +p.copyright { + line-height: 1; + margin-bottom: -5px; +} + +.legalnotice p { + font-style: italic; + font-size: 14px; + line-height: 1; +} + +div.titlepage + p, div.titlepage + p { + margin-top: 0; +} + +pre { + line-height: 1.0; + color: black; +} + +a { + color: #4183C4; + text-decoration: none; +} + +p { + margin: 15px 0; + text-align: left; +} + +ul, ol { + padding-left: 30px; +} + +li p { + margin: 0; +} + +div.table { + margin: 1em; + padding: 0.5em; + text-align: center; +} + +div.table table, div.informaltable table { + display: table; + width: 100%; +} + +div.table td { + padding-left: 7px; + padding-right: 7px; +} + +.sidebar { + line-height: 1.4; + padding: 0 20px; + background-color: #F8F8F8; + border: 1px solid #CCCCCC; + border-radius: 3px 3px 3px 3px; +} + +.sidebar p.title { + color: #6D180B; +} + +pre.programlisting, pre.screen { + font-size: 15px; + padding: 6px 10px; + background-color: #F8F8F8; + border: 1px solid #CCCCCC; + border-radius: 3px 3px 3px 3px; + clear: both; + overflow: auto; + line-height: 1.4; + font-family: Consolas, "Liberation Mono", Courier, monospace; +} + +table { + border-collapse: collapse; + border-spacing: 0; + border: 1px solid #DDDDDD !important; + border-radius: 4px !important; + border-collapse: separate !important; + line-height: 1.6; +} + +table thead { + background: #F5F5F5; +} + +table tr { + border: none; + border-bottom: none; +} + +table th { + font-weight: bold; +} + +table th, table td { + border: none !important; + padding: 6px 13px; +} + +table tr:nth-child(2n) { + background-color: #F8F8F8; +} + +td p { + margin: 0 0 15px 0; +} + +div.table-contents td p { + margin: 0; +} + +div.important *, div.note *, div.tip *, div.warning *, div.navheader *, div.navfooter *, div.calloutlist * { + border: none !important; + background: none !important; + margin: 0; +} + +div.important p, div.note p, div.tip p, div.warning p { + color: #6F6F6F; + line-height: 1.6; +} + +div.important code, div.note code, div.tip code, div.warning code { + background-color: #F2F2F2 !important; + border: 1px solid #CCCCCC !important; + border-radius: 4px !important; + padding: 1px 3px 0 !important; + text-shadow: none !important; + white-space: nowrap !important; +} + +.note th, .tip th, .warning th { + display: none; +} + +.note tr:first-child td, .tip tr:first-child td, .warning tr:first-child td { + border-right: 1px solid #CCCCCC !important; + padding-top: 10px; +} + +div.calloutlist p, div.calloutlist td { + padding: 0; + margin: 0; +} + +div.calloutlist > table > tbody > tr > td:first-child { + padding-left: 10px; + width: 30px !important; +} + +div.important, div.note, div.tip, div.warning { + margin-left: 0px !important; + margin-right: 20px !important; + margin-top: 20px; + margin-bottom: 20px; + padding-top: 10px; + padding-bottom: 10px; +} + +div.toc { + line-height: 1.2; +} + +dl, dt { + margin-top: 1px; + margin-bottom: 0; +} + +div.toc > dl > dt { + font-size: 32px; + font-weight: bold; + margin: 30px 0 10px 0; + display: block; +} + +div.toc > dl > dd > dl > dt { + font-size: 24px; + font-weight: bold; + margin: 20px 0 10px 0; + display: block; +} + +div.toc > dl > dd > dl > dd > dl > dt { + font-weight: bold; + font-size: 20px; + margin: 10px 0 0 0; +} + +tbody.footnotes * { + border: none !important; +} + +div.footnote p { + margin: 0; + line-height: 1; +} + +div.footnote p sup { + margin-right: 6px; + vertical-align: middle; +} + +div.navheader { + border-bottom: 1px solid #CCCCCC; +} + +div.navfooter { + border-top: 1px solid #CCCCCC; +} + +.title { + margin-left: -1em; + padding-left: 1em; +} + +.title > a { + position: absolute; + visibility: hidden; + display: block; + font-size: 0.85em; + margin-top: 0.05em; + margin-left: -1em; + vertical-align: text-top; + color: black; +} + +.title > a:before { + content: "\00A7"; +} + +.title:hover > a, .title > a:hover, .title:hover > a:hover { + visibility: visible; +} + +.title:focus > a, .title > a:focus, .title:focus > a:focus { + outline: 0; +} diff --git a/spring-cloud-gcp/1.1.5.RELEASE/multi/images/background.png b/spring-cloud-gcp/1.1.5.RELEASE/multi/images/background.png new file mode 100644 index 0000000000000000000000000000000000000000..15dca6fbe2669fae3609605e49c69cc414f1b6ed GIT binary patch literal 18255 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 EKU&lfD*ylh literal 0 HcmV?d00001 diff --git a/spring-cloud-gcp/1.1.5.RELEASE/multi/images/callouts/1.png b/spring-cloud-gcp/1.1.5.RELEASE/multi/images/callouts/1.png new file mode 100644 index 0000000000000000000000000000000000000000..7d473430b7bec514f7de12f5769fe7c5859e8c5d GIT binary patch literal 329 zcmeAS@N?(olHy`uVBq!ia0vp^JRr;gBp8b2n5}^nQC}X^4DKU-G|w_t}fLBA)Suv#nrW z!^h2QnY_`l!BOq-UXEX{m2up>JTQkX)2m zTvF+fTUlI^nXH#utd~++ke^qgmzgTe~DWM4ffP81J literal 0 HcmV?d00001 diff --git a/spring-cloud-gcp/1.1.5.RELEASE/multi/images/callouts/2.png b/spring-cloud-gcp/1.1.5.RELEASE/multi/images/callouts/2.png new file mode 100644 index 0000000000000000000000000000000000000000..5d09341b2f6d2ea2d1d5dad5d980f14b4b05dfd2 GIT binary patch literal 353 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 literal 0 HcmV?d00001 diff --git a/spring-cloud-gcp/1.1.5.RELEASE/multi/images/logo.png b/spring-cloud-gcp/1.1.5.RELEASE/multi/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ade2ce6ed9d9e9f2f4d9c5729a252ee618a0a5a7 GIT binary patch literal 4387 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 FV1jwin)(0$ literal 0 HcmV?d00001 diff --git a/spring-cloud-gcp/1.1.5.RELEASE/multi/images/warning.png b/spring-cloud-gcp/1.1.5.RELEASE/multi/images/warning.png new file mode 100644 index 0000000000000000000000000000000000000000..0d5b5244605adbb7ab05a1549746a9c35490f95b GIT binary patch literal 2130 zcmbVNYg7|w8V(4q($)50y>JmGlLW#g$xLn}Vd2Si)}#|ptPAQp3Bp-3!-lL0;i^LY?;i#f0m5s49g z3h?3rDQg~EDPlm?FKkgKIcWEK-3X6YU0uzs7H|nq84s39r2!5;pF?SI$QqXy^Ko1x zV~zpENvp@<_Bsd`5MabC#3rvCq&$5dg43yGR zAdy0-rWjC#a1N_+kzUMY#pmogD7!DP(9dEKr3c5ngvUq_6>}Y+w-a81v=eSXnIi_+ zI?U>D1q2C!0zHox#XXKH+@|&rPT*OF5yvY$5J|)WwLqnU)c-5;=UChSlQkaY3@^|g z|J5#YBB}=i+n3Ex9bS$P?xJSKLk)-HHII@;3qG#TG^!+aj>0QkO~7kB0+|yM;YrGB zF>Ge@isQ#73%Tp#k_(tInh3U$uJWY-+DND*o}L-CB5g@#9YWWw~pxZ!Obm>yN#ZHFvL0zA3vNCTJ=t?)~`2O1M{L6un=ml<2x zQEYfifmA?Pz0tqRs^2Utt1pU0BQB1eIY0U_I}c1Y#PP7Ci5s7#IJn}xK{G+{_v^Z+1V#$Erodv>d}ew>RP0wzw+GWkGCBlS1Oj5Z!5NK zUuSR6>pa}RSEZ;qE_w1l#`hQX4E67U6NeP zHZ`T&wj0_H)t|Y1thUqnhub?%e)Y07;a^Tq%FO&iQkR&`qG!dh*2WfW(HuRQj*_DI zeCD1bUA|tMwjOb8E|FRIn$pzEU!2j_N}1Z2ig)vbr5xA0rjC70cmI6*%Uf->hWzaZ z{33HABO5q)vRhzDly2l691@+q3O{}NbSzx+I*k@|L4&3leK#$>u+T#TvTELfKb|IH zc23atf3vmfCa0&TWY@iuoA_Pm<0~ttEC*ut`isb^jXS>X^Sp=l?RXN_OJ*&usGXg$ zTlTv;Ch^vhLDf&+62jl64*&D+=+`sN_dap_1W%p|KQVYIdgPL5uRE9(c4On)DXndL zLNq?%!-u*zmMxyYc4m4Nr>>=A=s}PkJkqr&E^b9>5g+}c1%X}3|WKcg&spcJQ)05zI<<5LTBhNKRg$XRTi2{j)yl_ zj4~kKOvr+m(;vw~9zVh(zC-y9jqQnguz5r7FRq^MyKuXAENp(TAzUVtg&Ts+B_s7) zGn+fN0sG>9GjsPLKTRrvCd`71IZulJ1_1jO9KWbD&_@2UC+LTNpxxrdz#E|j|2nA9 zzB!UTyfAEJ&#%$pQ>QX#8vk@_a5iqukMF;8`wRKe{BI}5$H%OROs4J1#j)|?p|YSh z_SpR^e`VE#F52;WL{!+L(yZLRh40*KS;@box;9(-tE)`mcVp27O*>Z{_Lb*5T3cJA yr~0nPHtg2+UHi&&$8ha;`+hiaUmw&!n@8(?5PqF(KE5>Ym)EG)p&uyBP5%a8^# + + 18. Cloud Foundry

    \ No newline at end of file diff --git a/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__cloud_identity_aware_proxy_iap_authentication.html b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__cloud_identity_aware_proxy_iap_authentication.html new file mode 100644 index 00000000..88006a2f --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__cloud_identity_aware_proxy_iap_authentication.html @@ -0,0 +1,13 @@ + + + 16. Cloud Identity-Aware Proxy (IAP) Authentication

    16. Cloud Identity-Aware Proxy (IAP) Authentication

    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") validation is automatically configured when the application is running on App Engine Standard or App Engine Flexible. +For other runtime environments, a custom audience must be provided through spring.cloud.gcp.security.iap.audience property. +The custom property, if specified, overrides the automatic App Engine audience detection.

    [Important]Important

    There is no automatic audience string configuration for Compute Engine or Kubernetes Engine. +To use the IAP starter on GCE/GKE, find the Audience string per instructions in the Verify the JWT payload guide, and specify it in the spring.cloud.gcp.security.iap.audience property. +Otherwise, the application will fail to start with No qualifying bean of type 'org.springframework.cloud.gcp.security.iap.AudienceProvider' available message.

    [Note]Note

    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 Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-security-iap</artifactId>
    +</dependency>

    Starter Gradle coordinates:

    dependencies {
    +    compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-security-iap'
    +}

    16.1 Configuration

    The following properties are available.

    [Caution]Caution

    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

     

    16.2 Sample

    A sample application is available.

    \ No newline at end of file diff --git a/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__cloud_memorystore_for_redis.html b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__cloud_memorystore_for_redis.html new file mode 100644 index 00000000..1cdc5daf --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__cloud_memorystore_for_redis.html @@ -0,0 +1,15 @@ + + + 15. Cloud Memorystore for Redis

    15. Cloud Memorystore for Redis

    15.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.

    [Note]Note

    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>

    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/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__dependency_management.html b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__dependency_management.html new file mode 100644 index 00000000..06e44fb9 --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__dependency_management.html @@ -0,0 +1,15 @@ + + + 2. Dependency Management

    2. Dependency Management

    The Spring Cloud GCP 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 to not specify any Spring Cloud GCP dependency versions. +Instead, the version of the BOM you’re using determines the versions of the used dependencies.

    <dependencyManagement>
    +    <dependencies>
    +        <dependency>
    +            <groupId>org.springframework.cloud</groupId>
    +            <artifactId>spring-cloud-gcp-dependencies</artifactId>
    +            <version>{project-version}</version>
    +            <type>pom</type>
    +            <scope>import</scope>
    +        </dependency>
    +    </dependencies>
    +</dependencyManagement>

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

    Gradle users can achieve the same kind of BOM experience using Spring’s dependency-management-plugin Gradle plugin. +For simplicity, the Gradle dependency snippets in the remainder of this document will also omit their versions.

    \ No newline at end of file diff --git a/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__getting_started.html b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__getting_started.html new file mode 100644 index 00000000..d48622e7 --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__getting_started.html @@ -0,0 +1,5 @@ + + + 3. Getting started

    3. Getting started

    There are many available resources to get you up to speed with our libraries as quickly as possible.

    3.1 Spring Initializr

    There are three entries in Spring Initializr for Spring Cloud GCP.

    3.1.1 GCP Support

    The GCP Support entry contains auto-configuration support for every Spring Cloud GCP integration. +Most of the autoconfiguration code is only enabled if other dependencies are added to the classpath.

    Spring Cloud GCP StarterRequired dependencies

    Config

    org.springframework.cloud:spring-cloud-gcp-starter-config

    Cloud Spanner

    org.springframework.cloud:spring-cloud-gcp-starter-data-spanner

    Cloud Datastore

    org.springframework.cloud:spring-cloud-gcp-starter-data-datastore

    Logging

    org.springframework.cloud:spring-cloud-gcp-starter-logging

    SQL - MySql

    org.springframework.cloud:spring-cloud-gcp-starter-sql-mysql

    SQL - PostgreSQL

    org.springframework.cloud:spring-cloud-gcp-starter-sql-postgres

    Trace

    org.springframework.cloud:spring-cloud-gcp-starter-trace

    Vision

    org.springframework.cloud:spring-cloud-gcp-starter-vision

    Security - IAP

    org.springframework.cloud:spring-cloud-gcp-starter-security-iap

    3.1.2 GCP Messaging

    The GCP Messaging entry adds the GCP Support entry and all the required dependencies so that the Google Cloud Pub/Sub integrations work out of the box.

    3.1.3 GCP Storage

    The GCP Storage entry adds the GCP Support entry and all the required dependencies so that the Google Cloud Storage integrations work out of the box.

    3.2 Code Samples

    There are code samples available that demonstrate the usage of all our integrations.

    For example, the Vision API sample shows how to use spring-cloud-gcp-starter-vision to automatically configure Vision API clients.

    3.3 Code Challenges

    In a code challenge, you perform a task step by step, using one integration. +There are a number of challenges available in the Google Developers Codelabs page.

    3.4 Getting Started Guides

    A Spring Getting Started guide on messaging with Spring Integration Channel Adapters for Google Cloud Pub/Sub is available from Spring Guides.

    \ No newline at end of file diff --git a/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__google_cloud_pubsub.html b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__google_cloud_pubsub.html new file mode 100644 index 00000000..904faf21 --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__google_cloud_pubsub.html @@ -0,0 +1,76 @@ + + + 5. Google Cloud Pub/Sub

    5. Google Cloud Pub/Sub

    Spring Cloud GCP 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 Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-pubsub</artifactId>
    +</dependency>

    Gradle coordinates:

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

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

    5.1 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.

    PubSubTemplate depends on a PublisherFactory and a SubscriberFactory. +The PublisherFactory provides a Google Cloud Java Client for Pub/Sub Publisher. +The SubscriberFactory provides the Subscriber for asynchronous message pulling, as well as a SubscriberStub for synchronous pulling. +The Spring Boot starter for GCP Pub/Sub auto-configures a PublisherFactory and SubscriberFactory with default settings and uses the GcpProjectIdProvider and CredentialsProvider auto-configured by the Spring Boot GCP starter.

    The PublisherFactory implementation provided by Spring Cloud GCP Pub/Sub, DefaultPublisherFactory, caches Publisher instances by topic name, in order to optimize resource utilization.

    The PubSubOperations interface is actually a combination of PubSubPublisherOperations and PubSubSubscriberOperations with the corresponding PubSubPublisherTemplate and PubSubSubscriberTemplate implementations, which can be used individually or via the composite PubSubTemplate. +The rest of the documentation refers to PubSubTemplate, but the same applies to PubSubPublisherTemplate and PubSubSubscriberTemplate, depending on whether we’re talking about publishing or subscribing.

    5.1.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.

    Here is an example of how to publish a message to a Google Cloud Pub/Sub topic:

    public void publishMessage() {
    +    this.pubSubTemplate.publish("topic", "your message payload", ImmutableMap.of("key1", "val1"));
    +}

    By default, the SimplePubSubMessageConverter is used to convert payloads of type byte[], ByteString, ByteBuffer, and String to Pub/Sub messages.

    JSON support

    For serialization and deserialization of POJOs using Jackson JSON, configure a JacksonPubSubMessageConverter bean, and the Spring Boot starter for GCP 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 JacksonPubSubMessageConverter jacksonPubSubMessageConverter(ObjectMapper objectMapper) {
    +    return new JacksonPubSubMessageConverter(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.

    Please refer to our Pub/Sub JSON Payload Sample App as a reference for using this functionality.

    5.1.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. +It relies on a SubscriberFactory object, whose only task is to generate Google Cloud Pub/Sub +Subscriber objects. +When listening to a subscription, messages will be pulled from Google Cloud Pub/Sub +asynchronously, at a certain interval.

    The Spring Boot starter for Google Cloud Pub/Sub auto-configures a SubscriberFactory.

    If Pub/Sub message payload conversion is desired, you can use the subscribeAndConvert() method, which will use the converter configured in the template.

    5.1.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 which polls the subscription on a set interval.

    The pullNext() method allows for a single message to be pulled and automatically acknowledged from a subscription. +The pull() method 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. +Instead, since they are of the kind AcknowledgeablePubsubMessage, you can acknowledge them by calling the ack() method, or negatively acknowledge them by calling the nack() method. +The pullAndAck() method does the same as the pull() method and, additionally, acknowledges all received messages.

    The pullAndConvert() method does 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.

    To acknowledge multiple messages received from pull() or pullAndConvert() 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.

    All ack(), nack(), and modifyAckDeadline() methods on messages as well as PubSubSubscriberTemplate are implemented asynchronously, returning a ListenableFuture<Void> to be able to process the asynchronous execution.

    PubSubTemplate uses a special subscriber generated by its SubscriberFactory to synchronously pull messages.

    5.2 Pub/Sub management

    PubSubAdmin is the abstraction provided by Spring Cloud GCP to manage Google Cloud Pub/Sub resources. +It allows for the creation, deletion and listing of topics and subscriptions.

    PubSubAdmin depends on GcpProjectIdProvider and either a CredentialsProvider or a TopicAdminClient and a SubscriptionAdminClient. +If given a CredentialsProvider, it creates a TopicAdminClient and a SubscriptionAdminClient with the Google Cloud Java Library for Pub/Sub default settings. +The Spring Boot starter for GCP Pub/Sub auto-configures a PubSubAdmin object using the GcpProjectIdProvider and the CredentialsProvider auto-configured by the Spring Boot GCP Core starter.

    5.2.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");
    +}

    5.2.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");
    +}

    5.2.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:

    public List<String> listTopics() {
    +    return pubSubAdmin
    +        .listTopics()
    +        .stream()
    +        .map(Topic::getNameAsTopicName)
    +        .map(TopicName::getTopic)
    +        .collect(Collectors.toList());
    +}

    5.2.4 Creating a subscription

    PubSubAdmin implements a method to create subscriptions to existing topics:

    public Subscription createSubscription(String subscriptionName, String topicName, Integer ackDeadline, String pushEndpoint)

    Here is an example of how to create a Google Cloud Pub/Sub subscription:

    public void newSubscription() {
    +    pubSubAdmin.createSubscription("subscriptionName", "topicName", 10, “https://my.endpoint/push”);
    +}

    Alternative methods with default settings are provided for ease of use. +The default value for ackDeadline is 10 seconds. +If pushEndpoint isn’t specified, the subscription uses message pulling, instead.

    public Subscription createSubscription(String subscriptionName, String topicName)
    public Subscription createSubscription(String subscriptionName, String topicName, Integer ackDeadline)
    public Subscription createSubscription(String subscriptionName, String topicName, String pushEndpoint)

    5.2.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");
    +}

    5.2.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:

    public List<String> listSubscriptions() {
    +    return pubSubAdmin
    +        .listSubscriptions()
    +        .stream()
    +        .map(Subscription::getNameAsSubscriptionName)
    +        .map(SubscriptionName::getSubscription)
    +        .collect(Collectors.toList());
    +}

    5.3 Configuration

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

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.pubsub.enabled

    Enables or disables Pub/Sub auto-configuration

    No

    true

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

    Number of threads used by Subscriber instances created by SubscriberFactory

    No

    4

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

    Number of threads used by Publisher instances created by PublisherFactory

    No

    4

    spring.cloud.gcp.pubsub.project-id

    GCP project ID where the Google Cloud Pub/Sub API is hosted, if different from the one in the Spring Cloud GCP 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 Cloud GCP Core Module

    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 Cloud GCP Core Module

    No

     

    spring.cloud.gcp.pubsub.credentials.scopes

    OAuth2 scope for Spring Cloud GCP +Pub/Sub credentials

    No

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

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

    The number of pull workers

    No

    The available number of processors

    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.pull-endpoint

    The endpoint for synchronous pulling messages

    No

    pubsub.googleapis.com:443

    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

    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

    unset (threshold does not apply)

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

    The request byte threshold to use for batching.

    No

    unset (threshold does not apply)

    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

    unset (threshold does not apply)

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

    Enables batching.

    No

    false

    5.4 Sample

    A sample application is available.

    \ No newline at end of file diff --git a/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__google_cloud_vision.html b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__google_cloud_vision.html new file mode 100644 index 00000000..3f9eee24 --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__google_cloud_vision.html @@ -0,0 +1,27 @@ + + + 17. Google Cloud Vision

    17. Google Cloud Vision

    The Google Cloud Vision API allows users to leverage machine learning algorithms for processing images including: image classification, face detection, text extraction, and others.

    Spring Cloud GCP provides:

    • A convenience starter which automatically configures authentication settings and client objects needed to begin using the Google Cloud Vision API.
    • A Cloud Vision Template which simplifies interactions with the Cloud Vision API.

      • Allows you to easily send images to the API as Spring Resources.
      • Offers convenience methods for common operations, such as extracting the text from an image.

    Maven coordinates, using Spring Cloud GCP BOM:

    <dependency>
    +  <groupId>org.springframework.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-starter-vision</artifactId>
    +</dependency>

    Gradle coordinates:

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

    17.1 Cloud Vision Template

    The CloudVisionTemplate offers a simple way to use the Cloud Vision APIs with Spring Resources.

    After you add the spring-cloud-gcp-starter-vision dependency to your project, you may @Autowire an instance of CloudVisionTemplate to use in your code.

    The CloudVisionTemplate offers 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.

    17.2 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());
    +}

    17.3 Sample

    A Sample Spring Boot Application is provided to show how to use the Cloud Vision starter and template.

    \ No newline at end of file diff --git a/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__introduction.html b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__introduction.html new file mode 100644 index 00000000..014e8f52 --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__introduction.html @@ -0,0 +1,3 @@ + + + 1. Introduction

    1. Introduction

    The Spring Cloud GCP project makes the Spring Framework a first-class citizen of Google Cloud Platform (GCP).

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

    1. Analyze your images for text, objects, and other content with Google Cloud Vision
    2. Use Spring Security via Google Cloud IAP
    3. Map objects, relationships, and collections with Spring Data Cloud Spanner and Spring Data Cloud Datastore
    4. Publish and subscribe to Google Cloud Pub/Sub topics
    5. Configure Spring JDBC with a few properties to use Google Cloud SQL
    6. Write and read from Spring Resources backed up by Google Cloud Storage
    7. Exchange messages with Spring Integration using Google Cloud Pub/Sub on the background
    8. Trace the execution of your app with Spring Cloud Sleuth and Google Stackdriver Trace
    9. Configure your app with Spring Cloud Config, backed up by the Google Runtime Configuration API
    10. Consume and produce Google Cloud Storage data via Spring Integration GCS Channel Adapters
    \ No newline at end of file diff --git a/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__kotlin_support.html b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__kotlin_support.html new file mode 100644 index 00000000..6958cce6 --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__kotlin_support.html @@ -0,0 +1,5 @@ + + + 19. Kotlin Support

    19. Kotlin Support

    The latest version of the Spring Framework provides first-class support for Kotlin. +For Kotlin users of Spring, the Spring Cloud GCP 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.

    19.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 Cloud GCP libraries will work within your application without any additional setup.

    \ No newline at end of file diff --git a/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__sample_13.html b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__sample_13.html new file mode 100644 index 00000000..ae39c715 --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__sample_13.html @@ -0,0 +1,3 @@ + + + 20. Sample

    20. Sample

    A Kotlin sample application is provided to demonstrate a working Maven setup and various Spring Cloud GCP integrations from within Kotlin.

    \ No newline at end of file diff --git a/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_cloud_config.html b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_cloud_config.html new file mode 100644 index 00000000..9208d01a --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_cloud_config.html @@ -0,0 +1,35 @@ + + + 12. Spring Cloud Config

    12. Spring Cloud Config

    Spring Cloud GCP 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 Cloud GCP 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.

    [Note]Note

    The Google Cloud Runtime Configuration service is in beta status.

    Maven coordinates, using Spring Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-config</artifactId>
    +</dependency>

    Gradle coordinates:

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

    12.1 Configuration

    The following parameters are configurable in Spring Cloud GCP 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

    GCP 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 Cloud GCP Config credentials

    No

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

    [Note]Note

    These properties should be specified in a bootstrap.yml/bootstrap.properties file, rather than the usual applications.yml/application.properties.

    [Note]Note

    Core properties, as described in Spring Cloud GCP Core Module, do not apply to Spring Cloud GCP Config.

    12.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
    1. Configure your bootstrap.properties file with your application’s configuration data:

      spring.application.name=myapp
      +spring.profiles.active=prod
    2. 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;
      +  }
      +}

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

    12.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 {
    +    compile group: 'org.springframework.boot', name: 'spring-boot-starter-actuator'
    +}
    1. Add @RefreshScope to your Spring configuration class to have parameters be reloadable at runtime.
    2. Add management.endpoints.web.exposure.include=refresh to your application.properties to allow unrestricted access to /actuator/refresh.
    3. Update a property with gcloud:

      $ gcloud beta runtime-config configs variables set \
      +  myapp.queue_size 200 \
      +  --config-name myapp_prod
    4. Send a POST request to the refresh endpoint:

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

    12.4 Sample

    A sample application and a codelab are available.

    \ No newline at end of file diff --git a/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_cloud_sleuth.html b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_cloud_sleuth.html new file mode 100644 index 00000000..354cca88 --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_cloud_sleuth.html @@ -0,0 +1,27 @@ + + + 10. Spring Cloud Sleuth

    10. Spring Cloud Sleuth

    Spring Cloud Sleuth is an instrumentation framework for Spring Boot applications. +It captures trace information and can forward traces to services like Zipkin for storage and analysis.

    Google Cloud Platform provides its own managed distributed tracing service called Stackdriver Trace. +Instead of running and maintaining your own Zipkin instance and storage, you can use Stackdriver Trace to store traces, view trace details, generate latency distributions graphs, and generate performance regression reports.

    This Spring Cloud GCP starter can forward Spring Cloud Sleuth traces to Stackdriver Trace without an intermediary Zipkin server.

    Maven coordinates, using Spring Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-trace</artifactId>
    +</dependency>

    Gradle coordinates:

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

    You must enable Stackdriver Trace API from the Google Cloud Console in order to capture traces. +Navigate to the Stackdriver Trace API for your project and make sure it’s enabled.

    [Note]Note

    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 Stackdriver 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-cloud-trace-context key (e.g., an HTTP request header). +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 Stackdriver 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.

    If a x-cloud-trace-context key isn’t found, StackdriverTracePropagation falls back to tracing with the X-B3 headers.

    10.2 Spring Boot Starter for Stackdriver Trace

    Spring Boot Starter for Stackdriver Trace uses Spring Cloud Sleuth and auto-configures a StackdriverSender that sends the Sleuth’s trace information to Stackdriver Trace.

    All configurations are optional:

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.trace.enabled

    Auto-configure Spring Cloud Sleuth to send traces to Stackdriver Trace.

    No

    true

    spring.cloud.gcp.trace.project-id

    Overrides the project ID from the Spring Cloud GCP Module

    No

     

    spring.cloud.gcp.trace.credentials.location

    Overrides the credentials location from the Spring Cloud GCP Module

    No

     

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

    Overrides the credentials encoded key from the Spring Cloud GCP Module

    No

     

    spring.cloud.gcp.trace.credentials.scopes

    Overrides the credentials scopes from the Spring Cloud GCP 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 GCP Stackdriver Trace. Added for forward compatibility.

    No

    spring.zipkin.messageTimeout

    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 Stackdriver.
    +spring.sleuth.web.skipPattern=(^cleanup.*|.+favicon.*)  # Ignore some URL paths.

    Spring Cloud GCP Trace does override some Sleuth configurations:

    • Always uses 128-bit Trace IDs. +This is required by Stackdriver Trace.
    • Does not use Span joins. +Span joins will share the span ID between the client and server Spans. +Stackdriver requires that every Span ID within a Trace to be unique, so Span joins are not supported.
    • Uses StackdriverHttpClientParser and StackdriverHttpServerParser 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 Integration with Logging

    Integration with Stackdriver Logging is available through the Stackdriver 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.5 Sample

    A sample application and a codelab are available.

    \ No newline at end of file diff --git a/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_cloud_stream.html b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_cloud_stream.html new file mode 100644 index 00000000..b9a2fecb --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_cloud_stream.html @@ -0,0 +1,21 @@ + + + 9. Spring Cloud Stream

    9. Spring Cloud Stream

    Spring Cloud GCP provides a Spring Cloud Stream binder to Google Cloud Pub/Sub.

    The provided binder relies on the Spring Integration Channel Adapters for Google Cloud Pub/Sub.

    Maven coordinates, using Spring Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-pubsub-stream-binder</artifactId>
    +</dependency>

    Gradle coordinates:

    dependencies {
    +    compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-pubsub-stream-binder'
    +}

    9.1 Overview

    This binder binds producers to Google Cloud Pub/Sub topics and consumers to subscriptions.

    [Note]Note

    Partitioning is currently not supported by this binder.

    9.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 Cloud GCP Pub/Sub Starter, you should refer to the configuration section for other Pub/Sub parameters.

    [Note]Note

    To use this binder with a running emulator, configure its host and port via spring.cloud.gcp.pubsub.emulator-host.

    9.2.1 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

    +

    9.2.2 Consumer Destination Configuration

    If automatic resource creation is turned ON and the subscription and/or the topic do not exist for a consumer, a subscription and potentially a topic will be created. +The topic name will be the same as the destination name, and the subscription name will be the destination name followed by the consumer group name.

    Regardless of the auto-create-resources setting, if the consumer group is not specified, an anonymous one will be created with the name anonymous.<destinationName>.<randomUUID>. +Then when the binder shuts down, all Pub/Sub subscriptions created for anonymous consumer groups will be automatically cleaned up.

    For example, for the following configuration, a topic named myEvents and a subscription called myEvents.counsumerGroup1 would be created. +If the consumer group is not specified, a subscription called anonymous.myEvents.a6d83782-c5a3-4861-ac38-e6e2af15a7be would be created and later cleaned up.

    [Important]Important

    If you are manually creating Pub/Sub subscriptions for consumers, make sure that they follow the naming convention of <destinationName>.<consumerGroup>.

    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

    +

    9.3 Sample

    A sample application is available.

    \ No newline at end of file diff --git a/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_data_cloud_datastore.html b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_data_cloud_datastore.html new file mode 100644 index 00000000..589a5b09 --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_data_cloud_datastore.html @@ -0,0 +1,467 @@ + + + 14. Spring Data Cloud Datastore

    14. Spring Data Cloud Datastore

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

    Maven coordinates for this module only, using Spring Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-data-datastore</artifactId>
    +</dependency>

    Gradle coordinates:

    dependencies {
    +    compile group: 'org.springframework.cloud', name: '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>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-data-datastore</artifactId>
    +</dependency>

    Gradle:

    dependencies {
    +    compile group: 'org.springframework.cloud', name: '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.

    14.1 Configuration

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

    • Setup the connection details to Google Cloud Datastore.

    14.1.1 Cloud Datastore settings

    You can the use 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

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

    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 Cloud GCP 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 Cloud GCP Core Module

    No

     

    spring.cloud.gcp.datastore.credentials.scopes

    OAuth2 scope for Spring Cloud GCP +Cloud Datastore 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 GCP project

    14.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.

    14.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

    14.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.

    14.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;
    +	}
    +}

    14.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.

    14.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.

    14.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.

    14.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.

    14.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. The user defined type needs to be mapped to one of the basic types supported by Cloud Datastore.
    3. 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.

    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));
    +	}
    +}

    14.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.

    14.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 ImmutableSet<Album>:

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

    We have to define a read converter only:

    static final Converter<List<?>, ImmutableSet<?>> LIST_IMMUTABLE_SET_CONVERTER =
    +			new Converter<List<?>, ImmutableSet<?>>() {
    +				@Override
    +				public ImmutableSet<?> convert(List<?> source) {
    +					return ImmutableSet.copyOf(source);
    +				}
    +			};

    And add it to the list of custom converters:

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

    14.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

    14.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 org.springframework.cloud.gcp.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.

    [Note]Note

    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 org.springframework.cloud.gcp.data.datastore.core.mapping.Embedded;
    +import org.springframework.cloud.gcp.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 org.springframework.cloud.gcp.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.

    14.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 org.springframework.cloud.gcp.data.datastore.core.mapping.Descendants;
    +import org.springframework.cloud.gcp.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.

    14.3.3 Key Reference Relationships

    General relationships can be stored using the @Reference annotation.

    import org.springframework.cloud.gcp.data.datastore.core.mapping.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.

    14.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

    14.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

    14.4.2 Find by ID(s)

    Datstore reading a single entity or multiple entities in a kind.

    Using DatastoreTemplate you can execute reads, for example:

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

    Cloud Datastore executes 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 executed 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 org.springframework.cloud.gcp.data.datastore.core.mapping.Unindexed;
    +
    +public class ExampleItem {
    +	long indexedField;
    +
    +	@Unindexed
    +	long unindexedField;
    +}

    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.

    14.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.

    Partial Update

    This feature is not supported yet.

    14.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.

    14.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.

    [Note]Note

    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 Section 13.2.10, “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);

    14.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.

    14.5.1 Query methods by convention

    public interface TradeRepository extends DatastoreRepository<Trade, String[]> {
    +  List<Trader> findByAction(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);
    +}

    In the example above the query methods in TradeRepository are generated based on the name of the methods using thehttps://docs.spring.io/spring-data/data-commons/docs/current/reference/html#repositories.query-methods.query-creation[Spring Data Query creation naming convention].

    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 executed 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.

    [Note]Note

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

    14.5.2 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  test_entities_ci WHERE id = @id_val")
    +  TestEntity getOneTestEntity(@Param("id_val") long id);
    +}

    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`s 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();
    +
    +	@Query("SELECT id FROM test_entities_ci WHERE id <= @id_val")
    +	List<String> getIds(@Param("id_val") long id);
    +
    +	@Query("SELECT id FROM test_entities_ci WHERE id <= @id_val limit 1")
    +	String getOneId(@Param("id_val") long id);

    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:

    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);
    +
    +}

    14.5.3 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";
    +    }
    +  );
    +}

    14.5.4 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>.

    14.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 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>

    14.6 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.

    \ No newline at end of file diff --git a/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_data_cloud_spanner.html b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_data_cloud_spanner.html new file mode 100644 index 00000000..4f5f13b7 --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_data_cloud_spanner.html @@ -0,0 +1,465 @@ + + + 13. Spring Data Cloud Spanner

    13. Spring Data Cloud Spanner

    Spring Data is an abstraction for storing and retrieving POJOs in numerous storage technologies. +Spring Cloud GCP adds Spring Data support for Google Cloud Spanner.

    Maven coordinates for this module only, using Spring Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-data-spanner</artifactId>
    +</dependency>

    Gradle coordinates:

    dependencies {
    +    compile group: 'org.springframework.cloud', name: '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>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-data-spanner</artifactId>
    +</dependency>

    Gradle:

    dependencies {
    +    compile group: 'org.springframework.cloud', name: '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 the use 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.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

    GCP project ID where the Google Cloud Spanner API is hosted, if different from the one in the Spring Cloud GCP 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 Cloud GCP 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 Cloud GCP Core Module

    No

     

    spring.cloud.gcp.spanner.credentials.scopes

    OAuth2 scope for Spring Cloud GCP +Cloud Spanner 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

    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 Double 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 FLOAT64,
    +    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 embedded properties' column names must all be unique.

    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. +Cloud Spanner does not support the foreign key constraint, though the parent-child key constraint enforces a similar requirement when used with interleaved tables.

    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 executed 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.

    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.util.Instant
    • java.sql.Date

    13.2.8 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.9 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.10 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. 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
    3. An instance of both Converters needs to be passed to a ConverterAwareMappingSpannerEntityProcessor, which then has to be made available as a @Bean for SpannerEntityProcessor.

    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()));
    +	}
    +}

    13.2.11 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. +Using SpannerTemplate you can execute 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 execute reads, for example:

    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 timestamp and is guaranteed to see all data that has been committed up until the start of this read. +A stale read on the other hand 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(Timestamp.now());
    +List<Trade> trades = this.spannerTemplate.readAll(Trade.class, spannerReadOptions);

    Queries:

    // a query with options:
    +SpannerQueryOptions spannerQueryOptions = new SpannerQueryOptions().setTimestamp(Timestamp.now());
    +List<Trade> trades = this.spannerTemplate.query(Trade.class, Statement.of("SELECT * FROM trades"), spannerQueryOptions);

    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 executing 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 executed using SpannerOperations.executeDmlStatement. +Inserts, updates, and deletions can affect any number of rows and entities.

    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, 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](https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#transaction-declarative) 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](https://cloud.google.com/spanner/docs/dml-tasks) Statements. +DML statements can be executed in transactions via performReadWriteTransaction or using the @Transactional annotation.

    When DML statements are executed outside of transactions, they are executed in [partitioned-mode](https://cloud.google.com/spanner/docs/dml-tasks#partitioned-dml).

    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 = 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-write 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

    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.

    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.

    Custom SQL query methods can accept a single Sort or Pageable parameter that is applied on top of any sorting or paging in the SQL:

    	@Query("SELECT * FROM trades ORDER BY action DESC")
    +	List<Trade> sortedTrades(Pageable pageable);
    +
    +	@Query("SELECT * FROM trades ORDER BY action DESC LIMIT 1")
    + 	Trade sortedTopTrade(Pageable pageable);

    This can be used:

    	List<Trade> customSortedTrades = tradeRepository.sortedTrades(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 executed 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 executed 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);

    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 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, 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>,<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 Sample

    A sample application is available.

    \ No newline at end of file diff --git a/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_integration.html b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_integration.html new file mode 100644 index 00000000..02241aca --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_integration.html @@ -0,0 +1,113 @@ + + + 8. Spring Integration

    8. Spring Integration

    Spring Cloud GCP provides Spring Integration adapters that allow your applications to use Enterprise Integration Patterns backed up by Google Cloud Platform services.

    8.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.

    Maven coordinates, using Spring Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-pubsub</artifactId>
    +</dependency>
    +<dependency>
    +    <groupId>org.springframework.integration</groupId>
    +    <artifactId>spring-integration-core</artifactId>
    +</dependency>

    Gradle coordinates:

    dependencies {
    +    compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-pubsub'
    +    compile group: 'org.springframework.integration', name: 'spring-integration-core'
    +}

    8.1.1 Inbound channel adapter

    PubSubInboundChannelAdapter is the inbound channel adapter for GCP Pub/Sub that listens to a GCP 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.

    @Bean
    +public MessageChannel pubsubInputChannel() {
    +    return new PublishSubscribeChannel();
    +}
    +
    +@Bean
    +public PubSubInboundChannelAdapter messageChannelAdapter(
    +    @Qualifier("pubsubInputChannel") MessageChannel inputChannel,
    +    SubscriberFactory subscriberFactory) {
    +    PubSubInboundChannelAdapter adapter =
    +        new PubSubInboundChannelAdapter(subscriberFactory, "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 GCP Pub/Sub provides a configured SubscriberFactory.

    The PubSubInboundChannelAdapter supports three acknowledgement modes, with AckMode.AUTO being the default value;

    Automatic acking (AckMode.AUTO)

    A message is acked with GCP Pub/Sub if the adapter sent it to the channel and no exceptions were thrown. +If a RuntimeException is thrown while the message is processed, then the message is nacked.

    Automatic acking OK (AckMode.AUTO_ACK)

    A message is acked with GCP Pub/Sub if the adapter sent it to the channel and no exceptions were thrown. +If a RuntimeException is thrown while the message is processed, then the message is neither acked / nor nacked.

    This is useful when using the subscription’s ack deadline timeout as a retry delivery backoff mechanism.

    Manually acking (AckMode.MANUAL)

    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 (n)ack 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();
    +    };
    +}

    8.1.2 Outbound channel adapter

    PubSubMessageHandler is the outbound channel adapter for GCP Pub/Sub that listens for new messages on a Spring MessageChannel. +It uses PubSubTemplate to post them to a GCP 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.

    @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 GCP 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 setPublishFutureCallback() method. +These are useful to process the message ID, in case of success, or the error if any was thrown.

    To override the default destination you can use the GcpPubSubHeaders.DESTINATION 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.

    8.1.3 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, like headers with key "id", "timestamp" and "gcp_pubsub_acknowledgement". +In the process, the outbound mapper also converts the value of the headers into string.

    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);
    [Note]Note

    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.

    8.3 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 Cloud GCP 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 Cloud GCP, you must also provide the spring-integration-file dependency, since it isn’t pulled transitively.

    Maven coordinates, using Spring Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-storage</artifactId>
    +</dependency>
    +<dependency>
    +    <groupId>org.springframework.integration</groupId>
    +    <artifactId>spring-integration-file</artifactId>
    +</dependency>

    Gradle coordinates:

    dependencies {
    +    compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-storage'
    +    compile group: 'org.springframework.integration', name: 'spring-integration-file'
    +}

    8.3.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;
    +}

    8.3.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;
    +}

    8.3.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.4 Sample

    A sample application is available.

    \ No newline at end of file diff --git a/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_jdbc.html b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_jdbc.html new file mode 100644 index 00000000..881ba914 --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_jdbc.html @@ -0,0 +1,42 @@ + + + 7. Spring JDBC

    7. Spring JDBC

    Spring Cloud GCP adds integrations with +Spring JDBC so you can run your MySQL or PostgreSQL databases in Google Cloud SQL using Spring JDBC, or other libraries that depend on it like Spring Data JPA.

    The Cloud SQL support is provided by Spring Cloud GCP 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.

    Maven coordinates, using Spring Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-sql-mysql</artifactId>
    +</dependency>
    +<dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-sql-postgresql</artifactId>
    +</dependency>

    Gradle coordinates:

    dependencies {
    +    compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-sql-mysql'
    +    compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-sql-postgresql'
    +}

    7.1 Prerequisites

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

    To do that, go to the API library page of the Google Cloud Console, search for "Cloud SQL API", click the first result and enable the API.

    [Note]Note

    There are several similar "Cloud SQL" results. +You must access the "Google Cloud SQL API" one and enable the API from there.

    7.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:

    Property name

    Description

    Default value

    spring.cloud.gcp.sql.enabled

    Enables or disables Cloud SQL auto configuration

    true

    spring.cloud.gcp.sql.database-name

    Name of the database to connect to.

     

    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. +For example, my-project-id:my-region:my-instance-name.

     

    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.

    Default credentials provided by the Spring GCP Boot 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.

    Default credentials provided by the Spring GCP Boot starter

    [Note]Note

    If you provide your own spring.datasource.url, it will be ignored, unless you disable Cloud SQL auto configuration with spring.cloud.gcp.sql.enabled=false.

    7.2.1 DataSource creation flow

    Based on the previous properties, the Spring Boot starter for Google Cloud SQL creates a CloudSqlJdbcInfoProvider object which is used to obtain an instance’s JDBC URL and driver class name. +If you provide your own CloudSqlJdbcInfoProvider bean, it is used instead and the properties related to building the JDBC URL or driver class are ignored.

    The DataSourceProperties object provided by Spring Boot Autoconfigure is mutated in order to use the JDBC URL and driver class names provided by CloudSqlJdbcInfoProvider, unless those values were provided in the properties. +It is in the DataSourceProperties mutation step that the credentials factory is registered in a system property to be SqlCredentialFactory.

    DataSource creation is delegated to +Spring Boot. +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.

    7.2.2 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/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_resources.html b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_resources.html new file mode 100644 index 00000000..a7e3f60f --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__spring_resources.html @@ -0,0 +1,18 @@ + + + 6. Spring Resources

    6. 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 Cloud GCP adds a new resource type: a Google Cloud Storage (GCS) object.

    A Spring Boot starter is provided to auto-configure the various Storage components.

    Maven coordinates, using Spring Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-storage</artifactId>
    +</dependency>

    Gradle coordinates:

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

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

    6.1 Google Cloud Storage

    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.

    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 Boot GCP starter.

    6.1.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();

    6.2 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 GCP 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 Cloud GCP 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 Cloud GCP Core Module

    No

     

    spring.cloud.gcp.storage.credentials.scopes

    OAuth2 scope for Spring Cloud GCP Storage credentials

    No

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

    6.3 Sample

    A sample application and a codelab are available.

    \ No newline at end of file diff --git a/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__stackdriver_logging.html b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__stackdriver_logging.html new file mode 100644 index 00000000..a258e249 --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi__stackdriver_logging.html @@ -0,0 +1,60 @@ + + + 11. Stackdriver Logging

    11. Stackdriver Logging

    Maven coordinates, using Spring Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-logging</artifactId>
    +</dependency>

    Gradle coordinates:

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

    Stackdriver Logging is the managed logging service provided by Google Cloud Platform.

    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.

    [Note]Note

    Due to the way logging is set up, the GCP 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.

    [Warning]Warning

    If Spring Cloud GCP 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 Platform 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 Stackdriver via this library with Logback: via direct API calls and through JSON-formatted console logs.

    11.2.1 Log via API

    A Stackdriver appender is available using org/springframework/cloud/gcp/autoconfigure/logging/logback-appender.xml. +This appender builds a Stackdriver Logging log entry from a JUL or Logback log entry, adds a trace ID to it and sends it to Stackdriver 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="org/springframework/cloud/gcp/autoconfigure/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:

    PropertyDefault ValueDescription

    log

    spring.log

    The Stackdriver 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 Stackdriver Logging. +This can also be set via the STACKDRIVER_LOG_FLUSH_LEVEL environmental variable.

    11.2.2 Log via Console

    For Logback, a org/springframework/cloud/gcp/autoconfigure/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="org/springframework/cloud/gcp/autoconfigure/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 Stackdriver Logging. +Therefore, you can just include org/springframework/cloud/gcp/autoconfigure/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. Value of DefaultGcpProjectIdProvider.getProjectId()

    This is used to generate fully qualified Stackdriver Trace ID format: projects/[PROJECT-ID]/traces/[TRACE-ID].

    +

    This format is required to correlate trace between Stackdriver Trace and Stackdriver Logging.

    +

    If projectId is not set and cannot be determined, then it’ll log traceId without the fully qualified format.

    includeTraceId

    true

    Should the traceId be included

    includeSpanId

    true

    Should the spanId 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

    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="org.springframework.cloud.gcp.logging.StackdriverJsonLayout">
    +        <projectId>${projectId}</projectId>
    +
    +        <!--<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>-->
    +      </layout>
    +    </encoder>
    +  </appender>
    +</configuration>

    11.3 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/spring-cloud-gcp/1.1.5.RELEASE/multi/multi_spring-cloud-gcp-core.html b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi_spring-cloud-gcp-core.html new file mode 100644 index 00000000..45aa3f66 --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi_spring-cloud-gcp-core.html @@ -0,0 +1,23 @@ + + + 4. Spring Cloud GCP Core

    4. Spring Cloud GCP Core

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

    Spring Cloud GCP provides a Spring Boot starter to auto-configure the core components.

    Maven coordinates, using Spring Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter</artifactId>
    +</dependency>

    Gradle coordinates:

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

    4.1 Project ID

    GcpProjectIdProvider is a functional interface that returns a GCP project ID string.

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

    The Spring Cloud GCP 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. The Google App Engine project ID
    3. The project ID specified in the JSON credentials file pointed by the GOOGLE_APPLICATION_CREDENTIALS environment variable
    4. The Google Cloud SDK project ID
    5. The Google Compute Engine project ID, from the Google Compute Engine Metadata Server

    4.2 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 Cloud GCP 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. Credentials provided by the Google Cloud SDK gcloud auth application-default login command
    3. Google App Engine built-in credentials
    4. Google Cloud Shell built-in credentials
    5. Google Compute Engine built-in credentials

    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 Cloud GCP 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.

    4.2.1 Scopes

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

    The Spring Cloud GCP 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 Platform 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

    4.3 Environment

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

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

    4.4 Spring Initializr

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

    \ No newline at end of file diff --git a/spring-cloud-gcp/1.1.5.RELEASE/multi/multi_spring-cloud-gcp.html b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi_spring-cloud-gcp.html new file mode 100644 index 00000000..c9718530 --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/multi/multi_spring-cloud-gcp.html @@ -0,0 +1,3 @@ + + + Spring Cloud GCP

    Spring Cloud GCP

    Authors

    João André Martins, Jisha Abubaker, Ray Tsang, Mike Eltsufin, Artem Bilan, Andreas Berger, Balint Pato, Chengyuan Zhao, Dmitry Solomakha, Elena Felder, Daniel Zou

    Table of Contents

    1. Introduction
    2. Dependency Management
    3. Getting started
    3.1. Spring Initializr
    3.1.1. GCP Support
    3.1.2. GCP Messaging
    3.1.3. GCP Storage
    3.2. Code Samples
    3.3. Code Challenges
    3.4. Getting Started Guides
    4. Spring Cloud GCP Core
    4.1. Project ID
    4.2. Credentials
    4.2.1. Scopes
    4.3. Environment
    4.4. Spring Initializr
    5. Google Cloud Pub/Sub
    5.1. Pub/Sub Operations & Template
    5.1.1. Publishing to a topic
    JSON support
    5.1.2. Subscribing to a subscription
    5.1.3. Pulling messages from a subscription
    5.2. Pub/Sub management
    5.2.1. Creating a topic
    5.2.2. Deleting a topic
    5.2.3. Listing topics
    5.2.4. Creating a subscription
    5.2.5. Deleting a subscription
    5.2.6. Listing subscriptions
    5.3. Configuration
    5.4. Sample
    6. Spring Resources
    6.1. Google Cloud Storage
    6.1.1. Setting the Content Type
    6.2. Configuration
    6.3. Sample
    7. Spring JDBC
    7.1. Prerequisites
    7.2. Spring Boot Starter for Google Cloud SQL
    7.2.1. DataSource creation flow
    7.2.2. Troubleshooting tips
    Connection issues
    Errors like c.g.cloud.sql.core.SslSocketFactory : Re-throwing cached exception due to attempt to refresh instance information too soon after error
    PostgreSQL: java.net.SocketException: already connected issue
    7.3. Samples
    8. Spring Integration
    8.1. Channel Adapters for Cloud Pub/Sub
    8.1.1. Inbound channel adapter
    8.1.2. Outbound channel adapter
    8.1.3. Header mapping
    8.2. Sample
    8.3. Channel Adapters for Google Cloud Storage
    8.3.1. Inbound channel adapter
    8.3.2. Inbound streaming channel adapter
    8.3.3. Outbound channel adapter
    8.4. Sample
    9. Spring Cloud Stream
    9.1. Overview
    9.2. Configuration
    9.2.1. Producer Destination Configuration
    9.2.2. Consumer Destination Configuration
    9.3. Sample
    10. Spring Cloud Sleuth
    10.1. Tracing
    10.2. Spring Boot Starter for Stackdriver Trace
    10.3. Overriding the auto-configuration
    10.4. Integration with Logging
    10.5. Sample
    11. Stackdriver Logging
    11.1. Web MVC Interceptor
    11.2. Logback Support
    11.2.1. Log via API
    11.2.2. Log via Console
    11.3. Sample
    12. Spring Cloud Config
    12.1. Configuration
    12.2. Quick start
    12.3. Refreshing the configuration at runtime
    12.4. Sample
    13. Spring Data Cloud Spanner
    13.1. Configuration
    13.1.1. Cloud Spanner settings
    13.1.2. Repository settings
    13.1.3. Autoconfiguration
    13.2. Object Mapping
    13.2.1. Constructors
    13.2.2. Table
    SpEL expressions for table names
    13.2.3. Primary Keys
    13.2.4. Columns
    13.2.5. Embedded Objects
    13.2.6. Relationships
    13.2.7. Supported Types
    13.2.8. Lists
    13.2.9. Lists of Structs
    13.2.10. Custom types
    13.2.11. Custom Converter for Struct Array Columns
    13.3. Spanner Operations & Template
    13.3.1. SQL Query
    13.3.2. Read
    13.3.3. Advanced reads
    Stale read
    Read from a secondary index
    Read with offsets and limits
    Sorting
    Partial read
    Summary of options for Query vs Read
    13.3.4. Write / Update
    Insert
    Update
    Upsert
    Partial Update
    13.3.5. DML
    13.3.6. Transactions
    Read/Write Transaction
    Read-only Transaction
    Declarative Transactions with @Transactional Annotation
    13.3.7. DML Statements
    13.4. Repositories
    13.4.1. CRUD Repository
    13.4.2. Paging and Sorting Repository
    13.4.3. Spanner Repository
    13.5. Query Methods
    13.5.1. Query methods by convention
    13.5.2. Custom SQL/DML query methods
    Query methods with named queries properties
    Query methods with annotation
    13.5.3. Projections
    13.5.4. REST Repositories
    13.6. Database and Schema Admin
    13.7. Sample
    14. Spring Data Cloud Datastore
    14.1. Configuration
    14.1.1. Cloud Datastore settings
    14.1.2. Repository settings
    14.1.3. Autoconfiguration
    14.2. Object Mapping
    14.2.1. Constructors
    14.2.2. Kind
    14.2.3. Keys
    14.2.4. Fields
    14.2.5. Supported Types
    14.2.6. Custom types
    14.2.7. Collections and arrays
    14.2.8. Custom Converter for collections
    14.3. Relationships
    14.3.1. Embedded Entities
    Maps
    14.3.2. Ancestor-Descendant Relationships
    14.3.3. Key Reference Relationships
    14.4. Datastore Operations & Template
    14.4.1. GQL Query
    14.4.2. Find by ID(s)
    Indexes
    Read with offsets, limits, and sorting
    Partial read
    14.4.3. Write / Update
    Partial Update
    14.4.4. Transactions
    Declarative Transactions with @Transactional Annotation
    14.4.5. Read-Write Support for Maps
    14.5. Repositories
    14.5.1. Query methods by convention
    14.5.2. Custom GQL query methods
    Query methods with annotation
    Query methods with named queries properties
    14.5.3. Transactions
    14.5.4. Projections
    14.5.5. REST Repositories
    14.6. Sample
    15. Cloud Memorystore for Redis
    15.1. Spring Caching
    16. Cloud Identity-Aware Proxy (IAP) Authentication
    16.1. Configuration
    16.2. Sample
    17. Google Cloud Vision
    17.1. Cloud Vision Template
    17.2. Detect Image Labels Example
    17.3. Sample
    18. Cloud Foundry
    19. Kotlin Support
    19.1. Prerequisites
    20. Sample
    \ No newline at end of file diff --git a/spring-cloud-gcp/1.1.5.RELEASE/single/css/highlight.css b/spring-cloud-gcp/1.1.5.RELEASE/single/css/highlight.css new file mode 100644 index 00000000..3850f8b9 --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/single/css/highlight.css @@ -0,0 +1,35 @@ +/* + code highlight CSS resemblign the Eclipse IDE default color schema + @author Costin Leau +*/ + +.hl-keyword { + color: #7F0055; + font-weight: bold; +} + +.hl-comment { + color: #3F5F5F; + font-style: italic; +} + +.hl-multiline-comment { + color: #3F5FBF; + font-style: italic; +} + +.hl-tag { + color: #3F7F7F; +} + +.hl-attribute { + color: #7F007F; +} + +.hl-value { + color: #2A00FF; +} + +.hl-string { + color: #2A00FF; +} \ No newline at end of file diff --git a/spring-cloud-gcp/1.1.5.RELEASE/single/css/manual-multipage.css b/spring-cloud-gcp/1.1.5.RELEASE/single/css/manual-multipage.css new file mode 100644 index 00000000..b790654b --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/single/css/manual-multipage.css @@ -0,0 +1,9 @@ +@IMPORT url("manual.css"); + +body.firstpage { + background: url("../images/background.png") no-repeat center top; +} + +div.part h1 { + border-top: none; +} diff --git a/spring-cloud-gcp/1.1.5.RELEASE/single/css/manual-singlepage.css b/spring-cloud-gcp/1.1.5.RELEASE/single/css/manual-singlepage.css new file mode 100644 index 00000000..303192a8 --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/single/css/manual-singlepage.css @@ -0,0 +1,6 @@ +@IMPORT url("manual.css"); + +body { + background: url("../images/background.png") no-repeat center top; +} + diff --git a/spring-cloud-gcp/1.1.5.RELEASE/single/css/manual.css b/spring-cloud-gcp/1.1.5.RELEASE/single/css/manual.css new file mode 100644 index 00000000..20cf07da --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/single/css/manual.css @@ -0,0 +1,342 @@ +@IMPORT url("highlight.css"); + +html { + padding: 0pt; + margin: 0pt; +} + +body { + color: #333333; + margin: 15px 30px; + font-family: Helvetica, Arial, Freesans, Clean, Sans-serif; + line-height: 1.6; + -webkit-font-smoothing: antialiased; +} + +code { + font-size: 16px; + font-family: Consolas, "Liberation Mono", Courier, monospace; +} + +:not(a) > code { + color: #6D180B; +} + +:not(pre) > code { + background-color: #F2F2F2; + border: 1px solid #CCCCCC; + border-radius: 4px; + padding: 1px 3px 0; + text-shadow: none; + white-space: nowrap; +} + +body > *:first-child { + margin-top: 0 !important; +} + +div { + margin: 0pt; +} + +hr { + border: 1px solid #CCCCCC; + background: #CCCCCC; +} + +h1, h2, h3, h4, h5, h6 { + color: #000000; + cursor: text; + font-weight: bold; + margin: 30px 0 10px; + padding: 0; +} + +h1, h2, h3 { + margin: 40px 0 10px; +} + +h1 { + margin: 70px 0 30px; + padding-top: 20px; +} + +div.part h1 { + border-top: 1px dotted #CCCCCC; +} + +h1, h1 code { + font-size: 32px; +} + +h2, h2 code { + font-size: 24px; +} + +h3, h3 code { + font-size: 20px; +} + +h4, h1 code, h5, h5 code, h6, h6 code { + font-size: 18px; +} + +div.book, div.chapter, div.appendix, div.part, div.preface { + min-width: 300px; + max-width: 1200px; + margin: 0 auto; +} + +p.releaseinfo { + font-weight: bold; + margin-bottom: 40px; + margin-top: 40px; +} + +div.authorgroup { + line-height: 1; +} + +p.copyright { + line-height: 1; + margin-bottom: -5px; +} + +.legalnotice p { + font-style: italic; + font-size: 14px; + line-height: 1; +} + +div.titlepage + p, div.titlepage + p { + margin-top: 0; +} + +pre { + line-height: 1.0; + color: black; +} + +a { + color: #4183C4; + text-decoration: none; +} + +p { + margin: 15px 0; + text-align: left; +} + +ul, ol { + padding-left: 30px; +} + +li p { + margin: 0; +} + +div.table { + margin: 1em; + padding: 0.5em; + text-align: center; +} + +div.table table, div.informaltable table { + display: table; + width: 100%; +} + +div.table td { + padding-left: 7px; + padding-right: 7px; +} + +.sidebar { + line-height: 1.4; + padding: 0 20px; + background-color: #F8F8F8; + border: 1px solid #CCCCCC; + border-radius: 3px 3px 3px 3px; +} + +.sidebar p.title { + color: #6D180B; +} + +pre.programlisting, pre.screen { + font-size: 15px; + padding: 6px 10px; + background-color: #F8F8F8; + border: 1px solid #CCCCCC; + border-radius: 3px 3px 3px 3px; + clear: both; + overflow: auto; + line-height: 1.4; + font-family: Consolas, "Liberation Mono", Courier, monospace; +} + +table { + border-collapse: collapse; + border-spacing: 0; + border: 1px solid #DDDDDD !important; + border-radius: 4px !important; + border-collapse: separate !important; + line-height: 1.6; +} + +table thead { + background: #F5F5F5; +} + +table tr { + border: none; + border-bottom: none; +} + +table th { + font-weight: bold; +} + +table th, table td { + border: none !important; + padding: 6px 13px; +} + +table tr:nth-child(2n) { + background-color: #F8F8F8; +} + +td p { + margin: 0 0 15px 0; +} + +div.table-contents td p { + margin: 0; +} + +div.important *, div.note *, div.tip *, div.warning *, div.navheader *, div.navfooter *, div.calloutlist * { + border: none !important; + background: none !important; + margin: 0; +} + +div.important p, div.note p, div.tip p, div.warning p { + color: #6F6F6F; + line-height: 1.6; +} + +div.important code, div.note code, div.tip code, div.warning code { + background-color: #F2F2F2 !important; + border: 1px solid #CCCCCC !important; + border-radius: 4px !important; + padding: 1px 3px 0 !important; + text-shadow: none !important; + white-space: nowrap !important; +} + +.note th, .tip th, .warning th { + display: none; +} + +.note tr:first-child td, .tip tr:first-child td, .warning tr:first-child td { + border-right: 1px solid #CCCCCC !important; + padding-top: 10px; +} + +div.calloutlist p, div.calloutlist td { + padding: 0; + margin: 0; +} + +div.calloutlist > table > tbody > tr > td:first-child { + padding-left: 10px; + width: 30px !important; +} + +div.important, div.note, div.tip, div.warning { + margin-left: 0px !important; + margin-right: 20px !important; + margin-top: 20px; + margin-bottom: 20px; + padding-top: 10px; + padding-bottom: 10px; +} + +div.toc { + line-height: 1.2; +} + +dl, dt { + margin-top: 1px; + margin-bottom: 0; +} + +div.toc > dl > dt { + font-size: 32px; + font-weight: bold; + margin: 30px 0 10px 0; + display: block; +} + +div.toc > dl > dd > dl > dt { + font-size: 24px; + font-weight: bold; + margin: 20px 0 10px 0; + display: block; +} + +div.toc > dl > dd > dl > dd > dl > dt { + font-weight: bold; + font-size: 20px; + margin: 10px 0 0 0; +} + +tbody.footnotes * { + border: none !important; +} + +div.footnote p { + margin: 0; + line-height: 1; +} + +div.footnote p sup { + margin-right: 6px; + vertical-align: middle; +} + +div.navheader { + border-bottom: 1px solid #CCCCCC; +} + +div.navfooter { + border-top: 1px solid #CCCCCC; +} + +.title { + margin-left: -1em; + padding-left: 1em; +} + +.title > a { + position: absolute; + visibility: hidden; + display: block; + font-size: 0.85em; + margin-top: 0.05em; + margin-left: -1em; + vertical-align: text-top; + color: black; +} + +.title > a:before { + content: "\00A7"; +} + +.title:hover > a, .title > a:hover, .title:hover > a:hover { + visibility: visible; +} + +.title:focus > a, .title > a:focus, .title:focus > a:focus { + outline: 0; +} diff --git a/spring-cloud-gcp/1.1.5.RELEASE/single/images/background.png b/spring-cloud-gcp/1.1.5.RELEASE/single/images/background.png new file mode 100644 index 0000000000000000000000000000000000000000..15dca6fbe2669fae3609605e49c69cc414f1b6ed GIT binary patch literal 18255 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 EKU&lfD*ylh literal 0 HcmV?d00001 diff --git a/spring-cloud-gcp/1.1.5.RELEASE/single/images/callouts/1.png b/spring-cloud-gcp/1.1.5.RELEASE/single/images/callouts/1.png new file mode 100644 index 0000000000000000000000000000000000000000..7d473430b7bec514f7de12f5769fe7c5859e8c5d GIT binary patch literal 329 zcmeAS@N?(olHy`uVBq!ia0vp^JRr;gBp8b2n5}^nQC}X^4DKU-G|w_t}fLBA)Suv#nrW z!^h2QnY_`l!BOq-UXEX{m2up>JTQkX)2m zTvF+fTUlI^nXH#utd~++ke^qgmzgTe~DWM4ffP81J literal 0 HcmV?d00001 diff --git a/spring-cloud-gcp/1.1.5.RELEASE/single/images/callouts/2.png b/spring-cloud-gcp/1.1.5.RELEASE/single/images/callouts/2.png new file mode 100644 index 0000000000000000000000000000000000000000..5d09341b2f6d2ea2d1d5dad5d980f14b4b05dfd2 GIT binary patch literal 353 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 literal 0 HcmV?d00001 diff --git a/spring-cloud-gcp/1.1.5.RELEASE/single/images/logo.png b/spring-cloud-gcp/1.1.5.RELEASE/single/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ade2ce6ed9d9e9f2f4d9c5729a252ee618a0a5a7 GIT binary patch literal 4387 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 FV1jwin)(0$ literal 0 HcmV?d00001 diff --git a/spring-cloud-gcp/1.1.5.RELEASE/single/images/warning.png b/spring-cloud-gcp/1.1.5.RELEASE/single/images/warning.png new file mode 100644 index 0000000000000000000000000000000000000000..0d5b5244605adbb7ab05a1549746a9c35490f95b GIT binary patch literal 2130 zcmbVNYg7|w8V(4q($)50y>JmGlLW#g$xLn}Vd2Si)}#|ptPAQp3Bp-3!-lL0;i^LY?;i#f0m5s49g z3h?3rDQg~EDPlm?FKkgKIcWEK-3X6YU0uzs7H|nq84s39r2!5;pF?SI$QqXy^Ko1x zV~zpENvp@<_Bsd`5MabC#3rvCq&$5dg43yGR zAdy0-rWjC#a1N_+kzUMY#pmogD7!DP(9dEKr3c5ngvUq_6>}Y+w-a81v=eSXnIi_+ zI?U>D1q2C!0zHox#XXKH+@|&rPT*OF5yvY$5J|)WwLqnU)c-5;=UChSlQkaY3@^|g z|J5#YBB}=i+n3Ex9bS$P?xJSKLk)-HHII@;3qG#TG^!+aj>0QkO~7kB0+|yM;YrGB zF>Ge@isQ#73%Tp#k_(tInh3U$uJWY-+DND*o}L-CB5g@#9YWWw~pxZ!Obm>yN#ZHFvL0zA3vNCTJ=t?)~`2O1M{L6un=ml<2x zQEYfifmA?Pz0tqRs^2Utt1pU0BQB1eIY0U_I}c1Y#PP7Ci5s7#IJn}xK{G+{_v^Z+1V#$Erodv>d}ew>RP0wzw+GWkGCBlS1Oj5Z!5NK zUuSR6>pa}RSEZ;qE_w1l#`hQX4E67U6NeP zHZ`T&wj0_H)t|Y1thUqnhub?%e)Y07;a^Tq%FO&iQkR&`qG!dh*2WfW(HuRQj*_DI zeCD1bUA|tMwjOb8E|FRIn$pzEU!2j_N}1Z2ig)vbr5xA0rjC70cmI6*%Uf->hWzaZ z{33HABO5q)vRhzDly2l691@+q3O{}NbSzx+I*k@|L4&3leK#$>u+T#TvTELfKb|IH zc23atf3vmfCa0&TWY@iuoA_Pm<0~ttEC*ut`isb^jXS>X^Sp=l?RXN_OJ*&usGXg$ zTlTv;Ch^vhLDf&+62jl64*&D+=+`sN_dap_1W%p|KQVYIdgPL5uRE9(c4On)DXndL zLNq?%!-u*zmMxyYc4m4Nr>>=A=s}PkJkqr&E^b9>5g+}c1%X}3|WKcg&spcJQ)05zI<<5LTBhNKRg$XRTi2{j)yl_ zj4~kKOvr+m(;vw~9zVh(zC-y9jqQnguz5r7FRq^MyKuXAENp(TAzUVtg&Ts+B_s7) zGn+fN0sG>9GjsPLKTRrvCd`71IZulJ1_1jO9KWbD&_@2UC+LTNpxxrdz#E|j|2nA9 zzB!UTyfAEJ&#%$pQ>QX#8vk@_a5iqukMF;8`wRKe{BI}5$H%OROs4J1#j)|?p|YSh z_SpR^e`VE#F52;WL{!+L(yZLRh40*KS;@box;9(-tE)`mcVp27O*>Z{_Lb*5T3cJA yr~0nPHtg2+UHi&&$8ha;`+hiaUmw&!n@8(?5PqF(KE5>Ym)EG)p&uyBP5%a8^# + + Spring Cloud GCP

    Spring Cloud GCP

    Authors

    João André Martins, Jisha Abubaker, Ray Tsang, Mike Eltsufin, Artem Bilan, Andreas Berger, Balint Pato, Chengyuan Zhao, Dmitry Solomakha, Elena Felder, Daniel Zou

    Table of Contents

    1. Introduction
    2. Dependency Management
    3. Getting started
    3.1. Spring Initializr
    3.1.1. GCP Support
    3.1.2. GCP Messaging
    3.1.3. GCP Storage
    3.2. Code Samples
    3.3. Code Challenges
    3.4. Getting Started Guides
    4. Spring Cloud GCP Core
    4.1. Project ID
    4.2. Credentials
    4.2.1. Scopes
    4.3. Environment
    4.4. Spring Initializr
    5. Google Cloud Pub/Sub
    5.1. Pub/Sub Operations & Template
    5.1.1. Publishing to a topic
    JSON support
    5.1.2. Subscribing to a subscription
    5.1.3. Pulling messages from a subscription
    5.2. Pub/Sub management
    5.2.1. Creating a topic
    5.2.2. Deleting a topic
    5.2.3. Listing topics
    5.2.4. Creating a subscription
    5.2.5. Deleting a subscription
    5.2.6. Listing subscriptions
    5.3. Configuration
    5.4. Sample
    6. Spring Resources
    6.1. Google Cloud Storage
    6.1.1. Setting the Content Type
    6.2. Configuration
    6.3. Sample
    7. Spring JDBC
    7.1. Prerequisites
    7.2. Spring Boot Starter for Google Cloud SQL
    7.2.1. DataSource creation flow
    7.2.2. Troubleshooting tips
    Connection issues
    Errors like c.g.cloud.sql.core.SslSocketFactory : Re-throwing cached exception due to attempt to refresh instance information too soon after error
    PostgreSQL: java.net.SocketException: already connected issue
    7.3. Samples
    8. Spring Integration
    8.1. Channel Adapters for Cloud Pub/Sub
    8.1.1. Inbound channel adapter
    8.1.2. Outbound channel adapter
    8.1.3. Header mapping
    8.2. Sample
    8.3. Channel Adapters for Google Cloud Storage
    8.3.1. Inbound channel adapter
    8.3.2. Inbound streaming channel adapter
    8.3.3. Outbound channel adapter
    8.4. Sample
    9. Spring Cloud Stream
    9.1. Overview
    9.2. Configuration
    9.2.1. Producer Destination Configuration
    9.2.2. Consumer Destination Configuration
    9.3. Sample
    10. Spring Cloud Sleuth
    10.1. Tracing
    10.2. Spring Boot Starter for Stackdriver Trace
    10.3. Overriding the auto-configuration
    10.4. Integration with Logging
    10.5. Sample
    11. Stackdriver Logging
    11.1. Web MVC Interceptor
    11.2. Logback Support
    11.2.1. Log via API
    11.2.2. Log via Console
    11.3. Sample
    12. Spring Cloud Config
    12.1. Configuration
    12.2. Quick start
    12.3. Refreshing the configuration at runtime
    12.4. Sample
    13. Spring Data Cloud Spanner
    13.1. Configuration
    13.1.1. Cloud Spanner settings
    13.1.2. Repository settings
    13.1.3. Autoconfiguration
    13.2. Object Mapping
    13.2.1. Constructors
    13.2.2. Table
    SpEL expressions for table names
    13.2.3. Primary Keys
    13.2.4. Columns
    13.2.5. Embedded Objects
    13.2.6. Relationships
    13.2.7. Supported Types
    13.2.8. Lists
    13.2.9. Lists of Structs
    13.2.10. Custom types
    13.2.11. Custom Converter for Struct Array Columns
    13.3. Spanner Operations & Template
    13.3.1. SQL Query
    13.3.2. Read
    13.3.3. Advanced reads
    Stale read
    Read from a secondary index
    Read with offsets and limits
    Sorting
    Partial read
    Summary of options for Query vs Read
    13.3.4. Write / Update
    Insert
    Update
    Upsert
    Partial Update
    13.3.5. DML
    13.3.6. Transactions
    Read/Write Transaction
    Read-only Transaction
    Declarative Transactions with @Transactional Annotation
    13.3.7. DML Statements
    13.4. Repositories
    13.4.1. CRUD Repository
    13.4.2. Paging and Sorting Repository
    13.4.3. Spanner Repository
    13.5. Query Methods
    13.5.1. Query methods by convention
    13.5.2. Custom SQL/DML query methods
    Query methods with named queries properties
    Query methods with annotation
    13.5.3. Projections
    13.5.4. REST Repositories
    13.6. Database and Schema Admin
    13.7. Sample
    14. Spring Data Cloud Datastore
    14.1. Configuration
    14.1.1. Cloud Datastore settings
    14.1.2. Repository settings
    14.1.3. Autoconfiguration
    14.2. Object Mapping
    14.2.1. Constructors
    14.2.2. Kind
    14.2.3. Keys
    14.2.4. Fields
    14.2.5. Supported Types
    14.2.6. Custom types
    14.2.7. Collections and arrays
    14.2.8. Custom Converter for collections
    14.3. Relationships
    14.3.1. Embedded Entities
    Maps
    14.3.2. Ancestor-Descendant Relationships
    14.3.3. Key Reference Relationships
    14.4. Datastore Operations & Template
    14.4.1. GQL Query
    14.4.2. Find by ID(s)
    Indexes
    Read with offsets, limits, and sorting
    Partial read
    14.4.3. Write / Update
    Partial Update
    14.4.4. Transactions
    Declarative Transactions with @Transactional Annotation
    14.4.5. Read-Write Support for Maps
    14.5. Repositories
    14.5.1. Query methods by convention
    14.5.2. Custom GQL query methods
    Query methods with annotation
    Query methods with named queries properties
    14.5.3. Transactions
    14.5.4. Projections
    14.5.5. REST Repositories
    14.6. Sample
    15. Cloud Memorystore for Redis
    15.1. Spring Caching
    16. Cloud Identity-Aware Proxy (IAP) Authentication
    16.1. Configuration
    16.2. Sample
    17. Google Cloud Vision
    17.1. Cloud Vision Template
    17.2. Detect Image Labels Example
    17.3. Sample
    18. Cloud Foundry
    19. Kotlin Support
    19.1. Prerequisites
    20. Sample

    1. Introduction

    The Spring Cloud GCP project makes the Spring Framework a first-class citizen of Google Cloud Platform (GCP).

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

    1. Analyze your images for text, objects, and other content with Google Cloud Vision
    2. Use Spring Security via Google Cloud IAP
    3. Map objects, relationships, and collections with Spring Data Cloud Spanner and Spring Data Cloud Datastore
    4. Publish and subscribe to Google Cloud Pub/Sub topics
    5. Configure Spring JDBC with a few properties to use Google Cloud SQL
    6. Write and read from Spring Resources backed up by Google Cloud Storage
    7. Exchange messages with Spring Integration using Google Cloud Pub/Sub on the background
    8. Trace the execution of your app with Spring Cloud Sleuth and Google Stackdriver Trace
    9. Configure your app with Spring Cloud Config, backed up by the Google Runtime Configuration API
    10. Consume and produce Google Cloud Storage data via Spring Integration GCS Channel Adapters

    2. Dependency Management

    The Spring Cloud GCP 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 to not specify any Spring Cloud GCP dependency versions. +Instead, the version of the BOM you’re using determines the versions of the used dependencies.

    <dependencyManagement>
    +    <dependencies>
    +        <dependency>
    +            <groupId>org.springframework.cloud</groupId>
    +            <artifactId>spring-cloud-gcp-dependencies</artifactId>
    +            <version>{project-version}</version>
    +            <type>pom</type>
    +            <scope>import</scope>
    +        </dependency>
    +    </dependencies>
    +</dependencyManagement>

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

    Gradle users can achieve the same kind of BOM experience using Spring’s dependency-management-plugin Gradle plugin. +For simplicity, the Gradle dependency snippets in the remainder of this document will also omit their versions.

    3. Getting started

    There are many available resources to get you up to speed with our libraries as quickly as possible.

    3.1 Spring Initializr

    There are three entries in Spring Initializr for Spring Cloud GCP.

    3.1.1 GCP Support

    The GCP Support entry contains auto-configuration support for every Spring Cloud GCP integration. +Most of the autoconfiguration code is only enabled if other dependencies are added to the classpath.

    Spring Cloud GCP StarterRequired dependencies

    Config

    org.springframework.cloud:spring-cloud-gcp-starter-config

    Cloud Spanner

    org.springframework.cloud:spring-cloud-gcp-starter-data-spanner

    Cloud Datastore

    org.springframework.cloud:spring-cloud-gcp-starter-data-datastore

    Logging

    org.springframework.cloud:spring-cloud-gcp-starter-logging

    SQL - MySql

    org.springframework.cloud:spring-cloud-gcp-starter-sql-mysql

    SQL - PostgreSQL

    org.springframework.cloud:spring-cloud-gcp-starter-sql-postgres

    Trace

    org.springframework.cloud:spring-cloud-gcp-starter-trace

    Vision

    org.springframework.cloud:spring-cloud-gcp-starter-vision

    Security - IAP

    org.springframework.cloud:spring-cloud-gcp-starter-security-iap

    3.1.2 GCP Messaging

    The GCP Messaging entry adds the GCP Support entry and all the required dependencies so that the Google Cloud Pub/Sub integrations work out of the box.

    3.1.3 GCP Storage

    The GCP Storage entry adds the GCP Support entry and all the required dependencies so that the Google Cloud Storage integrations work out of the box.

    3.2 Code Samples

    There are code samples available that demonstrate the usage of all our integrations.

    For example, the Vision API sample shows how to use spring-cloud-gcp-starter-vision to automatically configure Vision API clients.

    3.3 Code Challenges

    In a code challenge, you perform a task step by step, using one integration. +There are a number of challenges available in the Google Developers Codelabs page.

    3.4 Getting Started Guides

    A Spring Getting Started guide on messaging with Spring Integration Channel Adapters for Google Cloud Pub/Sub is available from Spring Guides.

    4. Spring Cloud GCP Core

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

    Spring Cloud GCP provides a Spring Boot starter to auto-configure the core components.

    Maven coordinates, using Spring Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter</artifactId>
    +</dependency>

    Gradle coordinates:

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

    4.1 Project ID

    GcpProjectIdProvider is a functional interface that returns a GCP project ID string.

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

    The Spring Cloud GCP 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. The Google App Engine project ID
    3. The project ID specified in the JSON credentials file pointed by the GOOGLE_APPLICATION_CREDENTIALS environment variable
    4. The Google Cloud SDK project ID
    5. The Google Compute Engine project ID, from the Google Compute Engine Metadata Server

    4.2 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 Cloud GCP 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. Credentials provided by the Google Cloud SDK gcloud auth application-default login command
    3. Google App Engine built-in credentials
    4. Google Cloud Shell built-in credentials
    5. Google Compute Engine built-in credentials

    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 Cloud GCP 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.

    4.2.1 Scopes

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

    The Spring Cloud GCP 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 Platform 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

    4.3 Environment

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

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

    4.4 Spring Initializr

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

    5. Google Cloud Pub/Sub

    Spring Cloud GCP 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 Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-pubsub</artifactId>
    +</dependency>

    Gradle coordinates:

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

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

    5.1 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.

    PubSubTemplate depends on a PublisherFactory and a SubscriberFactory. +The PublisherFactory provides a Google Cloud Java Client for Pub/Sub Publisher. +The SubscriberFactory provides the Subscriber for asynchronous message pulling, as well as a SubscriberStub for synchronous pulling. +The Spring Boot starter for GCP Pub/Sub auto-configures a PublisherFactory and SubscriberFactory with default settings and uses the GcpProjectIdProvider and CredentialsProvider auto-configured by the Spring Boot GCP starter.

    The PublisherFactory implementation provided by Spring Cloud GCP Pub/Sub, DefaultPublisherFactory, caches Publisher instances by topic name, in order to optimize resource utilization.

    The PubSubOperations interface is actually a combination of PubSubPublisherOperations and PubSubSubscriberOperations with the corresponding PubSubPublisherTemplate and PubSubSubscriberTemplate implementations, which can be used individually or via the composite PubSubTemplate. +The rest of the documentation refers to PubSubTemplate, but the same applies to PubSubPublisherTemplate and PubSubSubscriberTemplate, depending on whether we’re talking about publishing or subscribing.

    5.1.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.

    Here is an example of how to publish a message to a Google Cloud Pub/Sub topic:

    public void publishMessage() {
    +    this.pubSubTemplate.publish("topic", "your message payload", ImmutableMap.of("key1", "val1"));
    +}

    By default, the SimplePubSubMessageConverter is used to convert payloads of type byte[], ByteString, ByteBuffer, and String to Pub/Sub messages.

    JSON support

    For serialization and deserialization of POJOs using Jackson JSON, configure a JacksonPubSubMessageConverter bean, and the Spring Boot starter for GCP 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 JacksonPubSubMessageConverter jacksonPubSubMessageConverter(ObjectMapper objectMapper) {
    +    return new JacksonPubSubMessageConverter(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.

    Please refer to our Pub/Sub JSON Payload Sample App as a reference for using this functionality.

    5.1.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. +It relies on a SubscriberFactory object, whose only task is to generate Google Cloud Pub/Sub +Subscriber objects. +When listening to a subscription, messages will be pulled from Google Cloud Pub/Sub +asynchronously, at a certain interval.

    The Spring Boot starter for Google Cloud Pub/Sub auto-configures a SubscriberFactory.

    If Pub/Sub message payload conversion is desired, you can use the subscribeAndConvert() method, which will use the converter configured in the template.

    5.1.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 which polls the subscription on a set interval.

    The pullNext() method allows for a single message to be pulled and automatically acknowledged from a subscription. +The pull() method 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. +Instead, since they are of the kind AcknowledgeablePubsubMessage, you can acknowledge them by calling the ack() method, or negatively acknowledge them by calling the nack() method. +The pullAndAck() method does the same as the pull() method and, additionally, acknowledges all received messages.

    The pullAndConvert() method does 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.

    To acknowledge multiple messages received from pull() or pullAndConvert() 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.

    All ack(), nack(), and modifyAckDeadline() methods on messages as well as PubSubSubscriberTemplate are implemented asynchronously, returning a ListenableFuture<Void> to be able to process the asynchronous execution.

    PubSubTemplate uses a special subscriber generated by its SubscriberFactory to synchronously pull messages.

    5.2 Pub/Sub management

    PubSubAdmin is the abstraction provided by Spring Cloud GCP to manage Google Cloud Pub/Sub resources. +It allows for the creation, deletion and listing of topics and subscriptions.

    PubSubAdmin depends on GcpProjectIdProvider and either a CredentialsProvider or a TopicAdminClient and a SubscriptionAdminClient. +If given a CredentialsProvider, it creates a TopicAdminClient and a SubscriptionAdminClient with the Google Cloud Java Library for Pub/Sub default settings. +The Spring Boot starter for GCP Pub/Sub auto-configures a PubSubAdmin object using the GcpProjectIdProvider and the CredentialsProvider auto-configured by the Spring Boot GCP Core starter.

    5.2.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");
    +}

    5.2.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");
    +}

    5.2.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:

    public List<String> listTopics() {
    +    return pubSubAdmin
    +        .listTopics()
    +        .stream()
    +        .map(Topic::getNameAsTopicName)
    +        .map(TopicName::getTopic)
    +        .collect(Collectors.toList());
    +}

    5.2.4 Creating a subscription

    PubSubAdmin implements a method to create subscriptions to existing topics:

    public Subscription createSubscription(String subscriptionName, String topicName, Integer ackDeadline, String pushEndpoint)

    Here is an example of how to create a Google Cloud Pub/Sub subscription:

    public void newSubscription() {
    +    pubSubAdmin.createSubscription("subscriptionName", "topicName", 10, “https://my.endpoint/push”);
    +}

    Alternative methods with default settings are provided for ease of use. +The default value for ackDeadline is 10 seconds. +If pushEndpoint isn’t specified, the subscription uses message pulling, instead.

    public Subscription createSubscription(String subscriptionName, String topicName)
    public Subscription createSubscription(String subscriptionName, String topicName, Integer ackDeadline)
    public Subscription createSubscription(String subscriptionName, String topicName, String pushEndpoint)

    5.2.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");
    +}

    5.2.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:

    public List<String> listSubscriptions() {
    +    return pubSubAdmin
    +        .listSubscriptions()
    +        .stream()
    +        .map(Subscription::getNameAsSubscriptionName)
    +        .map(SubscriptionName::getSubscription)
    +        .collect(Collectors.toList());
    +}

    5.3 Configuration

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

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.pubsub.enabled

    Enables or disables Pub/Sub auto-configuration

    No

    true

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

    Number of threads used by Subscriber instances created by SubscriberFactory

    No

    4

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

    Number of threads used by Publisher instances created by PublisherFactory

    No

    4

    spring.cloud.gcp.pubsub.project-id

    GCP project ID where the Google Cloud Pub/Sub API is hosted, if different from the one in the Spring Cloud GCP 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 Cloud GCP Core Module

    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 Cloud GCP Core Module

    No

     

    spring.cloud.gcp.pubsub.credentials.scopes

    OAuth2 scope for Spring Cloud GCP +Pub/Sub credentials

    No

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

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

    The number of pull workers

    No

    The available number of processors

    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.pull-endpoint

    The endpoint for synchronous pulling messages

    No

    pubsub.googleapis.com:443

    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

    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

    unset (threshold does not apply)

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

    The request byte threshold to use for batching.

    No

    unset (threshold does not apply)

    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

    unset (threshold does not apply)

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

    Enables batching.

    No

    false

    5.4 Sample

    A sample application is available.

    6. 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 Cloud GCP adds a new resource type: a Google Cloud Storage (GCS) object.

    A Spring Boot starter is provided to auto-configure the various Storage components.

    Maven coordinates, using Spring Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-storage</artifactId>
    +</dependency>

    Gradle coordinates:

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

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

    6.1 Google Cloud Storage

    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.

    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 Boot GCP starter.

    6.1.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();

    6.2 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 GCP 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 Cloud GCP 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 Cloud GCP Core Module

    No

     

    spring.cloud.gcp.storage.credentials.scopes

    OAuth2 scope for Spring Cloud GCP Storage credentials

    No

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

    6.3 Sample

    A sample application and a codelab are available.

    7. Spring JDBC

    Spring Cloud GCP adds integrations with +Spring JDBC so you can run your MySQL or PostgreSQL databases in Google Cloud SQL using Spring JDBC, or other libraries that depend on it like Spring Data JPA.

    The Cloud SQL support is provided by Spring Cloud GCP 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.

    Maven coordinates, using Spring Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-sql-mysql</artifactId>
    +</dependency>
    +<dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-sql-postgresql</artifactId>
    +</dependency>

    Gradle coordinates:

    dependencies {
    +    compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-sql-mysql'
    +    compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-sql-postgresql'
    +}

    7.1 Prerequisites

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

    To do that, go to the API library page of the Google Cloud Console, search for "Cloud SQL API", click the first result and enable the API.

    [Note]Note

    There are several similar "Cloud SQL" results. +You must access the "Google Cloud SQL API" one and enable the API from there.

    7.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:

    Property name

    Description

    Default value

    spring.cloud.gcp.sql.enabled

    Enables or disables Cloud SQL auto configuration

    true

    spring.cloud.gcp.sql.database-name

    Name of the database to connect to.

     

    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. +For example, my-project-id:my-region:my-instance-name.

     

    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.

    Default credentials provided by the Spring GCP Boot 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.

    Default credentials provided by the Spring GCP Boot starter

    [Note]Note

    If you provide your own spring.datasource.url, it will be ignored, unless you disable Cloud SQL auto configuration with spring.cloud.gcp.sql.enabled=false.

    7.2.1 DataSource creation flow

    Based on the previous properties, the Spring Boot starter for Google Cloud SQL creates a CloudSqlJdbcInfoProvider object which is used to obtain an instance’s JDBC URL and driver class name. +If you provide your own CloudSqlJdbcInfoProvider bean, it is used instead and the properties related to building the JDBC URL or driver class are ignored.

    The DataSourceProperties object provided by Spring Boot Autoconfigure is mutated in order to use the JDBC URL and driver class names provided by CloudSqlJdbcInfoProvider, unless those values were provided in the properties. +It is in the DataSourceProperties mutation step that the credentials factory is registered in a system property to be SqlCredentialFactory.

    DataSource creation is delegated to +Spring Boot. +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.

    7.2.2 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>

    8. Spring Integration

    Spring Cloud GCP provides Spring Integration adapters that allow your applications to use Enterprise Integration Patterns backed up by Google Cloud Platform services.

    8.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.

    Maven coordinates, using Spring Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-pubsub</artifactId>
    +</dependency>
    +<dependency>
    +    <groupId>org.springframework.integration</groupId>
    +    <artifactId>spring-integration-core</artifactId>
    +</dependency>

    Gradle coordinates:

    dependencies {
    +    compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-pubsub'
    +    compile group: 'org.springframework.integration', name: 'spring-integration-core'
    +}

    8.1.1 Inbound channel adapter

    PubSubInboundChannelAdapter is the inbound channel adapter for GCP Pub/Sub that listens to a GCP 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.

    @Bean
    +public MessageChannel pubsubInputChannel() {
    +    return new PublishSubscribeChannel();
    +}
    +
    +@Bean
    +public PubSubInboundChannelAdapter messageChannelAdapter(
    +    @Qualifier("pubsubInputChannel") MessageChannel inputChannel,
    +    SubscriberFactory subscriberFactory) {
    +    PubSubInboundChannelAdapter adapter =
    +        new PubSubInboundChannelAdapter(subscriberFactory, "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 GCP Pub/Sub provides a configured SubscriberFactory.

    The PubSubInboundChannelAdapter supports three acknowledgement modes, with AckMode.AUTO being the default value;

    Automatic acking (AckMode.AUTO)

    A message is acked with GCP Pub/Sub if the adapter sent it to the channel and no exceptions were thrown. +If a RuntimeException is thrown while the message is processed, then the message is nacked.

    Automatic acking OK (AckMode.AUTO_ACK)

    A message is acked with GCP Pub/Sub if the adapter sent it to the channel and no exceptions were thrown. +If a RuntimeException is thrown while the message is processed, then the message is neither acked / nor nacked.

    This is useful when using the subscription’s ack deadline timeout as a retry delivery backoff mechanism.

    Manually acking (AckMode.MANUAL)

    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 (n)ack 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();
    +    };
    +}

    8.1.2 Outbound channel adapter

    PubSubMessageHandler is the outbound channel adapter for GCP Pub/Sub that listens for new messages on a Spring MessageChannel. +It uses PubSubTemplate to post them to a GCP 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.

    @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 GCP 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 setPublishFutureCallback() method. +These are useful to process the message ID, in case of success, or the error if any was thrown.

    To override the default destination you can use the GcpPubSubHeaders.DESTINATION 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.

    8.1.3 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, like headers with key "id", "timestamp" and "gcp_pubsub_acknowledgement". +In the process, the outbound mapper also converts the value of the headers into string.

    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);
    [Note]Note

    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.

    8.3 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 Cloud GCP 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 Cloud GCP, you must also provide the spring-integration-file dependency, since it isn’t pulled transitively.

    Maven coordinates, using Spring Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-storage</artifactId>
    +</dependency>
    +<dependency>
    +    <groupId>org.springframework.integration</groupId>
    +    <artifactId>spring-integration-file</artifactId>
    +</dependency>

    Gradle coordinates:

    dependencies {
    +    compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-storage'
    +    compile group: 'org.springframework.integration', name: 'spring-integration-file'
    +}

    8.3.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;
    +}

    8.3.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;
    +}

    8.3.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.4 Sample

    A sample application is available.

    9. Spring Cloud Stream

    Spring Cloud GCP provides a Spring Cloud Stream binder to Google Cloud Pub/Sub.

    The provided binder relies on the Spring Integration Channel Adapters for Google Cloud Pub/Sub.

    Maven coordinates, using Spring Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-pubsub-stream-binder</artifactId>
    +</dependency>

    Gradle coordinates:

    dependencies {
    +    compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-pubsub-stream-binder'
    +}

    9.1 Overview

    This binder binds producers to Google Cloud Pub/Sub topics and consumers to subscriptions.

    [Note]Note

    Partitioning is currently not supported by this binder.

    9.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 Cloud GCP Pub/Sub Starter, you should refer to the configuration section for other Pub/Sub parameters.

    [Note]Note

    To use this binder with a running emulator, configure its host and port via spring.cloud.gcp.pubsub.emulator-host.

    9.2.1 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

    +

    9.2.2 Consumer Destination Configuration

    If automatic resource creation is turned ON and the subscription and/or the topic do not exist for a consumer, a subscription and potentially a topic will be created. +The topic name will be the same as the destination name, and the subscription name will be the destination name followed by the consumer group name.

    Regardless of the auto-create-resources setting, if the consumer group is not specified, an anonymous one will be created with the name anonymous.<destinationName>.<randomUUID>. +Then when the binder shuts down, all Pub/Sub subscriptions created for anonymous consumer groups will be automatically cleaned up.

    For example, for the following configuration, a topic named myEvents and a subscription called myEvents.counsumerGroup1 would be created. +If the consumer group is not specified, a subscription called anonymous.myEvents.a6d83782-c5a3-4861-ac38-e6e2af15a7be would be created and later cleaned up.

    [Important]Important

    If you are manually creating Pub/Sub subscriptions for consumers, make sure that they follow the naming convention of <destinationName>.<consumerGroup>.

    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

    +

    9.3 Sample

    A sample application is available.

    10. Spring Cloud Sleuth

    Spring Cloud Sleuth is an instrumentation framework for Spring Boot applications. +It captures trace information and can forward traces to services like Zipkin for storage and analysis.

    Google Cloud Platform provides its own managed distributed tracing service called Stackdriver Trace. +Instead of running and maintaining your own Zipkin instance and storage, you can use Stackdriver Trace to store traces, view trace details, generate latency distributions graphs, and generate performance regression reports.

    This Spring Cloud GCP starter can forward Spring Cloud Sleuth traces to Stackdriver Trace without an intermediary Zipkin server.

    Maven coordinates, using Spring Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-trace</artifactId>
    +</dependency>

    Gradle coordinates:

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

    You must enable Stackdriver Trace API from the Google Cloud Console in order to capture traces. +Navigate to the Stackdriver Trace API for your project and make sure it’s enabled.

    [Note]Note

    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 Stackdriver 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-cloud-trace-context key (e.g., an HTTP request header). +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 Stackdriver 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.

    If a x-cloud-trace-context key isn’t found, StackdriverTracePropagation falls back to tracing with the X-B3 headers.

    10.2 Spring Boot Starter for Stackdriver Trace

    Spring Boot Starter for Stackdriver Trace uses Spring Cloud Sleuth and auto-configures a StackdriverSender that sends the Sleuth’s trace information to Stackdriver Trace.

    All configurations are optional:

    Name

    Description

    Required

    Default value

    spring.cloud.gcp.trace.enabled

    Auto-configure Spring Cloud Sleuth to send traces to Stackdriver Trace.

    No

    true

    spring.cloud.gcp.trace.project-id

    Overrides the project ID from the Spring Cloud GCP Module

    No

     

    spring.cloud.gcp.trace.credentials.location

    Overrides the credentials location from the Spring Cloud GCP Module

    No

     

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

    Overrides the credentials encoded key from the Spring Cloud GCP Module

    No

     

    spring.cloud.gcp.trace.credentials.scopes

    Overrides the credentials scopes from the Spring Cloud GCP 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 GCP Stackdriver Trace. Added for forward compatibility.

    No

    spring.zipkin.messageTimeout

    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 Stackdriver.
    +spring.sleuth.web.skipPattern=(^cleanup.*|.+favicon.*)  # Ignore some URL paths.

    Spring Cloud GCP Trace does override some Sleuth configurations:

    • Always uses 128-bit Trace IDs. +This is required by Stackdriver Trace.
    • Does not use Span joins. +Span joins will share the span ID between the client and server Spans. +Stackdriver requires that every Span ID within a Trace to be unique, so Span joins are not supported.
    • Uses StackdriverHttpClientParser and StackdriverHttpServerParser 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 Integration with Logging

    Integration with Stackdriver Logging is available through the Stackdriver 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.5 Sample

    A sample application and a codelab are available.

    11. Stackdriver Logging

    Maven coordinates, using Spring Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-logging</artifactId>
    +</dependency>

    Gradle coordinates:

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

    Stackdriver Logging is the managed logging service provided by Google Cloud Platform.

    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.

    [Note]Note

    Due to the way logging is set up, the GCP 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.

    [Warning]Warning

    If Spring Cloud GCP 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 Platform 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 Stackdriver via this library with Logback: via direct API calls and through JSON-formatted console logs.

    11.2.1 Log via API

    A Stackdriver appender is available using org/springframework/cloud/gcp/autoconfigure/logging/logback-appender.xml. +This appender builds a Stackdriver Logging log entry from a JUL or Logback log entry, adds a trace ID to it and sends it to Stackdriver 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="org/springframework/cloud/gcp/autoconfigure/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:

    PropertyDefault ValueDescription

    log

    spring.log

    The Stackdriver 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 Stackdriver Logging. +This can also be set via the STACKDRIVER_LOG_FLUSH_LEVEL environmental variable.

    11.2.2 Log via Console

    For Logback, a org/springframework/cloud/gcp/autoconfigure/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="org/springframework/cloud/gcp/autoconfigure/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 Stackdriver Logging. +Therefore, you can just include org/springframework/cloud/gcp/autoconfigure/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. Value of DefaultGcpProjectIdProvider.getProjectId()

    This is used to generate fully qualified Stackdriver Trace ID format: projects/[PROJECT-ID]/traces/[TRACE-ID].

    +

    This format is required to correlate trace between Stackdriver Trace and Stackdriver Logging.

    +

    If projectId is not set and cannot be determined, then it’ll log traceId without the fully qualified format.

    includeTraceId

    true

    Should the traceId be included

    includeSpanId

    true

    Should the spanId 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

    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="org.springframework.cloud.gcp.logging.StackdriverJsonLayout">
    +        <projectId>${projectId}</projectId>
    +
    +        <!--<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>-->
    +      </layout>
    +    </encoder>
    +  </appender>
    +</configuration>

    11.3 Sample

    A Sample Spring Boot Application is provided to show how to use the Cloud logging starter.

    12. Spring Cloud Config

    Spring Cloud GCP 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 Cloud GCP 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.

    [Note]Note

    The Google Cloud Runtime Configuration service is in beta status.

    Maven coordinates, using Spring Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-config</artifactId>
    +</dependency>

    Gradle coordinates:

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

    12.1 Configuration

    The following parameters are configurable in Spring Cloud GCP 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

    GCP 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 Cloud GCP Config credentials

    No

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

    [Note]Note

    These properties should be specified in a bootstrap.yml/bootstrap.properties file, rather than the usual applications.yml/application.properties.

    [Note]Note

    Core properties, as described in Spring Cloud GCP Core Module, do not apply to Spring Cloud GCP Config.

    12.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
    1. Configure your bootstrap.properties file with your application’s configuration data:

      spring.application.name=myapp
      +spring.profiles.active=prod
    2. 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;
      +  }
      +}

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

    12.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 {
    +    compile group: 'org.springframework.boot', name: 'spring-boot-starter-actuator'
    +}
    1. Add @RefreshScope to your Spring configuration class to have parameters be reloadable at runtime.
    2. Add management.endpoints.web.exposure.include=refresh to your application.properties to allow unrestricted access to /actuator/refresh.
    3. Update a property with gcloud:

      $ gcloud beta runtime-config configs variables set \
      +  myapp.queue_size 200 \
      +  --config-name myapp_prod
    4. Send a POST request to the refresh endpoint:

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

    12.4 Sample

    A sample application and a codelab are available.

    13. Spring Data Cloud Spanner

    Spring Data is an abstraction for storing and retrieving POJOs in numerous storage technologies. +Spring Cloud GCP adds Spring Data support for Google Cloud Spanner.

    Maven coordinates for this module only, using Spring Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-data-spanner</artifactId>
    +</dependency>

    Gradle coordinates:

    dependencies {
    +    compile group: 'org.springframework.cloud', name: '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>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-data-spanner</artifactId>
    +</dependency>

    Gradle:

    dependencies {
    +    compile group: 'org.springframework.cloud', name: '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 the use 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.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

    GCP project ID where the Google Cloud Spanner API is hosted, if different from the one in the Spring Cloud GCP 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 Cloud GCP 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 Cloud GCP Core Module

    No

     

    spring.cloud.gcp.spanner.credentials.scopes

    OAuth2 scope for Spring Cloud GCP +Cloud Spanner 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

    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 Double 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 FLOAT64,
    +    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 embedded properties' column names must all be unique.

    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. +Cloud Spanner does not support the foreign key constraint, though the parent-child key constraint enforces a similar requirement when used with interleaved tables.

    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 executed 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.

    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.util.Instant
    • java.sql.Date

    13.2.8 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.9 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.10 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. 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
    3. An instance of both Converters needs to be passed to a ConverterAwareMappingSpannerEntityProcessor, which then has to be made available as a @Bean for SpannerEntityProcessor.

    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()));
    +	}
    +}

    13.2.11 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. +Using SpannerTemplate you can execute 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 execute reads, for example:

    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 timestamp and is guaranteed to see all data that has been committed up until the start of this read. +A stale read on the other hand 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(Timestamp.now());
    +List<Trade> trades = this.spannerTemplate.readAll(Trade.class, spannerReadOptions);

    Queries:

    // a query with options:
    +SpannerQueryOptions spannerQueryOptions = new SpannerQueryOptions().setTimestamp(Timestamp.now());
    +List<Trade> trades = this.spannerTemplate.query(Trade.class, Statement.of("SELECT * FROM trades"), spannerQueryOptions);

    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 executing 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 executed using SpannerOperations.executeDmlStatement. +Inserts, updates, and deletions can affect any number of rows and entities.

    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, 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](https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#transaction-declarative) 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](https://cloud.google.com/spanner/docs/dml-tasks) Statements. +DML statements can be executed in transactions via performReadWriteTransaction or using the @Transactional annotation.

    When DML statements are executed outside of transactions, they are executed in [partitioned-mode](https://cloud.google.com/spanner/docs/dml-tasks#partitioned-dml).

    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 = 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-write 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

    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.

    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.

    Custom SQL query methods can accept a single Sort or Pageable parameter that is applied on top of any sorting or paging in the SQL:

    	@Query("SELECT * FROM trades ORDER BY action DESC")
    +	List<Trade> sortedTrades(Pageable pageable);
    +
    +	@Query("SELECT * FROM trades ORDER BY action DESC LIMIT 1")
    + 	Trade sortedTopTrade(Pageable pageable);

    This can be used:

    	List<Trade> customSortedTrades = tradeRepository.sortedTrades(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 executed 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 executed 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);

    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 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, 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>,<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 Sample

    A sample application is available.

    14. Spring Data Cloud Datastore

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

    Maven coordinates for this module only, using Spring Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-data-datastore</artifactId>
    +</dependency>

    Gradle coordinates:

    dependencies {
    +    compile group: 'org.springframework.cloud', name: '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>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-data-datastore</artifactId>
    +</dependency>

    Gradle:

    dependencies {
    +    compile group: 'org.springframework.cloud', name: '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.

    14.1 Configuration

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

    • Setup the connection details to Google Cloud Datastore.

    14.1.1 Cloud Datastore settings

    You can the use 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

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

    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 Cloud GCP 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 Cloud GCP Core Module

    No

     

    spring.cloud.gcp.datastore.credentials.scopes

    OAuth2 scope for Spring Cloud GCP +Cloud Datastore 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 GCP project

    14.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.

    14.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

    14.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.

    14.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;
    +	}
    +}

    14.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.

    14.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.

    14.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.

    14.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.

    14.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. The user defined type needs to be mapped to one of the basic types supported by Cloud Datastore.
    3. 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.

    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));
    +	}
    +}

    14.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.

    14.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 ImmutableSet<Album>:

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

    We have to define a read converter only:

    static final Converter<List<?>, ImmutableSet<?>> LIST_IMMUTABLE_SET_CONVERTER =
    +			new Converter<List<?>, ImmutableSet<?>>() {
    +				@Override
    +				public ImmutableSet<?> convert(List<?> source) {
    +					return ImmutableSet.copyOf(source);
    +				}
    +			};

    And add it to the list of custom converters:

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

    14.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

    14.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 org.springframework.cloud.gcp.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.

    [Note]Note

    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 org.springframework.cloud.gcp.data.datastore.core.mapping.Embedded;
    +import org.springframework.cloud.gcp.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 org.springframework.cloud.gcp.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.

    14.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 org.springframework.cloud.gcp.data.datastore.core.mapping.Descendants;
    +import org.springframework.cloud.gcp.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.

    14.3.3 Key Reference Relationships

    General relationships can be stored using the @Reference annotation.

    import org.springframework.cloud.gcp.data.datastore.core.mapping.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.

    14.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

    14.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

    14.4.2 Find by ID(s)

    Datstore reading a single entity or multiple entities in a kind.

    Using DatastoreTemplate you can execute reads, for example:

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

    Cloud Datastore executes 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 executed 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 org.springframework.cloud.gcp.data.datastore.core.mapping.Unindexed;
    +
    +public class ExampleItem {
    +	long indexedField;
    +
    +	@Unindexed
    +	long unindexedField;
    +}

    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.

    14.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.

    Partial Update

    This feature is not supported yet.

    14.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.

    14.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.

    [Note]Note

    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 Section 13.2.10, “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);

    14.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.

    14.5.1 Query methods by convention

    public interface TradeRepository extends DatastoreRepository<Trade, String[]> {
    +  List<Trader> findByAction(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);
    +}

    In the example above the query methods in TradeRepository are generated based on the name of the methods using thehttps://docs.spring.io/spring-data/data-commons/docs/current/reference/html#repositories.query-methods.query-creation[Spring Data Query creation naming convention].

    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 executed 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.

    [Note]Note

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

    14.5.2 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  test_entities_ci WHERE id = @id_val")
    +  TestEntity getOneTestEntity(@Param("id_val") long id);
    +}

    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`s 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();
    +
    +	@Query("SELECT id FROM test_entities_ci WHERE id <= @id_val")
    +	List<String> getIds(@Param("id_val") long id);
    +
    +	@Query("SELECT id FROM test_entities_ci WHERE id <= @id_val limit 1")
    +	String getOneId(@Param("id_val") long id);

    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:

    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);
    +
    +}

    14.5.3 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";
    +    }
    +  );
    +}

    14.5.4 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>.

    14.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 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>

    14.6 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. Cloud Memorystore for Redis

    15.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.

    [Note]Note

    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>

    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.

    16. Cloud Identity-Aware Proxy (IAP) Authentication

    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") validation is automatically configured when the application is running on App Engine Standard or App Engine Flexible. +For other runtime environments, a custom audience must be provided through spring.cloud.gcp.security.iap.audience property. +The custom property, if specified, overrides the automatic App Engine audience detection.

    [Important]Important

    There is no automatic audience string configuration for Compute Engine or Kubernetes Engine. +To use the IAP starter on GCE/GKE, find the Audience string per instructions in the Verify the JWT payload guide, and specify it in the spring.cloud.gcp.security.iap.audience property. +Otherwise, the application will fail to start with No qualifying bean of type 'org.springframework.cloud.gcp.security.iap.AudienceProvider' available message.

    [Note]Note

    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 Cloud GCP BOM:

    <dependency>
    +    <groupId>org.springframework.cloud</groupId>
    +    <artifactId>spring-cloud-gcp-starter-security-iap</artifactId>
    +</dependency>

    Starter Gradle coordinates:

    dependencies {
    +    compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-security-iap'
    +}

    16.1 Configuration

    The following properties are available.

    [Caution]Caution

    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

     

    16.2 Sample

    A sample application is available.

    17. Google Cloud Vision

    The Google Cloud Vision API allows users to leverage machine learning algorithms for processing images including: image classification, face detection, text extraction, and others.

    Spring Cloud GCP provides:

    • A convenience starter which automatically configures authentication settings and client objects needed to begin using the Google Cloud Vision API.
    • A Cloud Vision Template which simplifies interactions with the Cloud Vision API.

      • Allows you to easily send images to the API as Spring Resources.
      • Offers convenience methods for common operations, such as extracting the text from an image.

    Maven coordinates, using Spring Cloud GCP BOM:

    <dependency>
    +  <groupId>org.springframework.cloud</groupId>
    +  <artifactId>spring-cloud-gcp-starter-vision</artifactId>
    +</dependency>

    Gradle coordinates:

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

    17.1 Cloud Vision Template

    The CloudVisionTemplate offers a simple way to use the Cloud Vision APIs with Spring Resources.

    After you add the spring-cloud-gcp-starter-vision dependency to your project, you may @Autowire an instance of CloudVisionTemplate to use in your code.

    The CloudVisionTemplate offers 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.

    17.2 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());
    +}

    17.3 Sample

    A Sample Spring Boot Application is provided to show how to use the Cloud Vision starter and template.

    18. Cloud Foundry

    Spring Cloud GCP provides support for Cloud Foundry’s GCP Service Broker. +Our Pub/Sub, Cloud Spanner, Storage, Stackdriver 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 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.

    [Note]Note

    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.

    [Warning]Warning

    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).

    19. Kotlin Support

    The latest version of the Spring Framework provides first-class support for Kotlin. +For Kotlin users of Spring, the Spring Cloud GCP 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.

    19.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 Cloud GCP libraries will work within your application without any additional setup.

    20. Sample

    A Kotlin sample application is provided to demonstrate a working Maven setup and various Spring Cloud GCP integrations from within Kotlin.

    \ No newline at end of file diff --git a/spring-cloud-gcp/1.1.5.RELEASE/spring-cloud-gcp.html b/spring-cloud-gcp/1.1.5.RELEASE/spring-cloud-gcp.html new file mode 100644 index 00000000..7d07dd8e --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/spring-cloud-gcp.html @@ -0,0 +1,117 @@ + + + + + + + +spring-cloud-gcp + + + + + + + + +
    +
    +
    +
    +

    1.1.5.RELEASE

    +
    +
    +
    +
    +

    Pick The Documentation Option

    +
    +
    + +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/spring-cloud-gcp/1.1.5.RELEASE/spring-cloud-gcp.xml b/spring-cloud-gcp/1.1.5.RELEASE/spring-cloud-gcp.xml new file mode 100644 index 00000000..77fa2f6d --- /dev/null +++ b/spring-cloud-gcp/1.1.5.RELEASE/spring-cloud-gcp.xml @@ -0,0 +1,4290 @@ + + + + + +Spring Cloud GCP +2020-01-31 + + + +João +André +Martins + + + + +Jisha +Abubaker + + + + +Ray +Tsang + + + + +Mike +Eltsufin + + + + +Artem +Bilan + + + + +Andreas +Berger + + + + +Balint +Pato + + + + +Chengyuan +Zhao + + + + +Dmitry +Solomakha + + + + +Elena +Felder + + + + +Daniel +Zou + + + + + +Introduction +The Spring Cloud GCP project makes the Spring Framework a first-class citizen of Google Cloud Platform (GCP). +Spring Cloud GCP lets you leverage the power and simplicity of the Spring Framework to: + + +Analyze your images for text, objects, and other content with Google Cloud Vision + + +Use Spring Security via Google Cloud IAP + + +Map objects, relationships, and collections with Spring Data Cloud Spanner and Spring Data Cloud Datastore + + +Publish and subscribe to Google Cloud Pub/Sub topics + + +Configure Spring JDBC with a few properties to use Google Cloud SQL + + +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 Stackdriver 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 + + + + +Dependency Management +The Spring Cloud GCP 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 to not specify any Spring Cloud GCP dependency versions. +Instead, the version of the BOM you’re using determines the versions of the used dependencies. +<dependencyManagement> + <dependencies> + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-gcp-dependencies</artifactId> + <version>{project-version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> +</dependencyManagement> +In the following sections, it will be assumed you are using the Spring Cloud GCP BOM and the dependency snippets will not contain versions. +Gradle users can achieve the same kind of BOM experience using Spring’s dependency-management-plugin Gradle plugin. +For simplicity, the Gradle dependency snippets in the remainder of this document will also omit their versions. + + +Getting started +There are many available resources to get you up to speed with our libraries as quickly as possible. +
    +Spring Initializr +There are three entries in Spring Initializr for Spring Cloud GCP. +
    +GCP Support +The GCP Support entry contains auto-configuration support for every Spring Cloud GCP integration. +Most of the autoconfiguration code is only enabled if other dependencies are added to the classpath. + + + + + + +Spring Cloud GCP Starter +Required dependencies + + + + +Config +org.springframework.cloud:spring-cloud-gcp-starter-config + + +Cloud Spanner +org.springframework.cloud:spring-cloud-gcp-starter-data-spanner + + +Cloud Datastore +org.springframework.cloud:spring-cloud-gcp-starter-data-datastore + + +Logging +org.springframework.cloud:spring-cloud-gcp-starter-logging + + +SQL - MySql +org.springframework.cloud:spring-cloud-gcp-starter-sql-mysql + + +SQL - PostgreSQL +org.springframework.cloud:spring-cloud-gcp-starter-sql-postgres + + +Trace +org.springframework.cloud:spring-cloud-gcp-starter-trace + + +Vision +org.springframework.cloud:spring-cloud-gcp-starter-vision + + +Security - IAP +org.springframework.cloud:spring-cloud-gcp-starter-security-iap + + + + +
    +
    +GCP Messaging +The GCP Messaging entry adds the GCP Support entry and all the required dependencies so that the Google Cloud Pub/Sub integrations work out of the box. +
    +
    +GCP Storage +The GCP Storage entry adds the GCP Support entry and all the required dependencies so that the Google Cloud Storage integrations work out of the box. +
    +
    +
    +Code Samples +There are code samples available that demonstrate the usage of all our integrations. +For example, the Vision API sample shows how to use spring-cloud-gcp-starter-vision to automatically configure Vision API clients. +
    +
    +Code Challenges +In a code challenge, you perform a task step by step, using one integration. +There are a number of challenges available in the Google Developers Codelabs page. +
    +
    +Getting Started Guides +A Spring Getting Started guide on messaging with Spring Integration Channel Adapters for Google Cloud Pub/Sub is available from Spring Guides. +
    +
    + +Spring Cloud GCP Core +Each Spring Cloud GCP module uses GcpProjectIdProvider and CredentialsProvider to get the GCP project ID and access credentials. +Spring Cloud GCP provides a Spring Boot starter to auto-configure the core components. +Maven coordinates, using Spring Cloud GCP BOM: +<dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-gcp-starter</artifactId> +</dependency> +Gradle coordinates: +dependencies { + compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter' +} +
    +Project ID +GcpProjectIdProvider is a functional interface that returns a GCP project ID string. +public interface GcpProjectIdProvider { + String getProjectId(); +} +The Spring Cloud GCP 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: + + +The project ID specified by the GOOGLE_CLOUD_PROJECT environment variable + + +The Google App Engine project ID + + +The project ID specified in the JSON credentials file pointed by the GOOGLE_APPLICATION_CREDENTIALS environment variable + + +The Google Cloud SDK project ID + + +The Google Compute Engine project ID, from the Google Compute Engine Metadata Server + + +
    +
    +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 Cloud GCP 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: + + +Credentials file pointed to by the GOOGLE_APPLICATION_CREDENTIALS environment variable + + +Credentials provided by the Google Cloud SDK gcloud auth application-default login command + + +Google App Engine built-in credentials + + +Google Cloud Shell built-in credentials + + +Google Compute Engine built-in credentials + + +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 Cloud GCP 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 Cloud GCP Starter contain scopes for every service supported by Spring Cloud GCP. + + + + + + +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 (Write/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 Cloud GCP 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 Platform 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 Cloud GCP starter, that returns a GcpEnvironment enum. +The provider can help determine programmatically in which GCP environment (App Engine Flexible, App Engine Standard, Kubernetes Engine or Compute Engine) the application is deployed. +public interface GcpEnvironmentProvider { + GcpEnvironment getCurrentEnvironment(); +} +
    +
    +Spring Initializr +This starter is available from Spring Initializr through the GCP Support entry. +
    +
    + +Google Cloud Pub/Sub +Spring Cloud GCP 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 Cloud GCP BOM: +<dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-gcp-starter-pubsub</artifactId> +</dependency> +Gradle coordinates: +dependencies { + compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-pubsub' +} +This starter is also available from Spring Initializr through the GCP Messaging entry. +
    +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. +PubSubTemplate depends on a PublisherFactory and a SubscriberFactory. +The PublisherFactory provides a Google Cloud Java Client for Pub/Sub Publisher. +The SubscriberFactory provides the Subscriber for asynchronous message pulling, as well as a SubscriberStub for synchronous pulling. +The Spring Boot starter for GCP Pub/Sub auto-configures a PublisherFactory and SubscriberFactory with default settings and uses the GcpProjectIdProvider and CredentialsProvider auto-configured by the Spring Boot GCP starter. +The PublisherFactory implementation provided by Spring Cloud GCP Pub/Sub, DefaultPublisherFactory, caches Publisher instances by topic name, in order to optimize resource utilization. +The PubSubOperations interface is actually a combination of PubSubPublisherOperations and PubSubSubscriberOperations with the corresponding PubSubPublisherTemplate and PubSubSubscriberTemplate implementations, which can be used individually or via the composite PubSubTemplate. +The rest of the documentation refers to PubSubTemplate, but the same applies to PubSubPublisherTemplate and PubSubSubscriberTemplate, depending on whether we’re talking about publishing or subscribing. +
    +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. +Here is an example of how to publish a message to a Google Cloud Pub/Sub topic: +public void publishMessage() { + this.pubSubTemplate.publish("topic", "your message payload", ImmutableMap.of("key1", "val1")); +} +By default, the SimplePubSubMessageConverter is used to convert payloads of type byte[], ByteString, ByteBuffer, and String to Pub/Sub messages. +
    +JSON support +For serialization and deserialization of POJOs using Jackson JSON, configure a JacksonPubSubMessageConverter bean, and the Spring Boot starter for GCP 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 JacksonPubSubMessageConverter jacksonPubSubMessageConverter(ObjectMapper objectMapper) { + return new JacksonPubSubMessageConverter(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. +Please refer to our Pub/Sub JSON Payload Sample App as a reference for using this functionality. +
    +
    +
    +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. +It relies on a SubscriberFactory object, whose only task is to generate Google Cloud Pub/Sub +Subscriber objects. +When listening to a subscription, messages will be pulled from Google Cloud Pub/Sub +asynchronously, at a certain interval. +The Spring Boot starter for Google Cloud Pub/Sub auto-configures a SubscriberFactory. +If Pub/Sub message payload conversion is desired, you can use the subscribeAndConvert() method, which will use the converter configured in the template. +
    +
    +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 which polls the subscription on a set interval. +The pullNext() method allows for a single message to be pulled and automatically acknowledged from a subscription. +The pull() method 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. +Instead, since they are of the kind AcknowledgeablePubsubMessage, you can acknowledge them by calling the ack() method, or negatively acknowledge them by calling the nack() method. +The pullAndAck() method does the same as the pull() method and, additionally, acknowledges all received messages. +The pullAndConvert() method does 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. +To acknowledge multiple messages received from pull() or pullAndConvert() 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. +All ack(), nack(), and modifyAckDeadline() methods on messages as well as PubSubSubscriberTemplate are implemented asynchronously, returning a ListenableFuture<Void> to be able to process the asynchronous execution. +PubSubTemplate uses a special subscriber generated by its SubscriberFactory to synchronously pull messages. +
    +
    +
    +Pub/Sub management +PubSubAdmin is the abstraction provided by Spring Cloud GCP to manage Google Cloud Pub/Sub resources. +It allows for the creation, deletion and listing of topics and subscriptions. +PubSubAdmin depends on GcpProjectIdProvider and either a CredentialsProvider or a TopicAdminClient and a SubscriptionAdminClient. +If given a CredentialsProvider, it creates a TopicAdminClient and a SubscriptionAdminClient with the Google Cloud Java Library for Pub/Sub default settings. +The Spring Boot starter for GCP Pub/Sub auto-configures a PubSubAdmin object using the GcpProjectIdProvider and the CredentialsProvider auto-configured by the Spring Boot GCP 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: +public List<String> listTopics() { + return pubSubAdmin + .listTopics() + .stream() + .map(Topic::getNameAsTopicName) + .map(TopicName::getTopic) + .collect(Collectors.toList()); +} +
    +
    +Creating a subscription +PubSubAdmin implements a method to create subscriptions to existing topics: +public Subscription createSubscription(String subscriptionName, String topicName, Integer ackDeadline, String pushEndpoint) +Here is an example of how to create a Google Cloud Pub/Sub subscription: +public void newSubscription() { + pubSubAdmin.createSubscription("subscriptionName", "topicName", 10, “https://my.endpoint/push”); +} +Alternative methods with default settings are provided for ease of use. +The default value for ackDeadline is 10 seconds. +If pushEndpoint isn’t specified, the subscription uses message pulling, instead. +public Subscription createSubscription(String subscriptionName, String topicName) +public Subscription createSubscription(String subscriptionName, String topicName, Integer ackDeadline) +public Subscription createSubscription(String subscriptionName, String topicName, String pushEndpoint) +
    +
    +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: +public List<String> listSubscriptions() { + return pubSubAdmin + .listSubscriptions() + .stream() + .map(Subscription::getNameAsSubscriptionName) + .map(SubscriptionName::getSubscription) + .collect(Collectors.toList()); +} +
    +
    +
    +Configuration +The Spring Boot starter for Google Cloud Pub/Sub provides the following configuration options: + + + + + + + + +Name +Description +Required +Default value + + +spring.cloud.gcp.pubsub.enabled +Enables or disables Pub/Sub auto-configuration +No +true + + +spring.cloud.gcp.pubsub.subscriber.executor-threads +Number of threads used by Subscriber instances created by SubscriberFactory +No +4 + + +spring.cloud.gcp.pubsub.publisher.executor-threads +Number of threads used by Publisher instances created by PublisherFactory +No +4 + + +spring.cloud.gcp.pubsub.project-id +GCP project ID where the Google Cloud Pub/Sub API is hosted, if different from the one in the Spring Cloud GCP 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 Cloud GCP Core Module +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 Cloud GCP Core Module +No + + + +spring.cloud.gcp.pubsub.credentials.scopes +OAuth2 scope for Spring Cloud GCP +Pub/Sub credentials +No +https://www.googleapis.com/auth/pubsub + + +spring.cloud.gcp.pubsub.subscriber.parallel-pull-count +The number of pull workers +No +The available number of processors + + +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.pull-endpoint +The endpoint for synchronous pulling messages +No +pubsub.googleapis.com:443 + + +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 + + +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 +unset (threshold does not apply) + + +spring.cloud.gcp.pubsub.publisher.batching.request-byte-threshold +The request byte threshold to use for batching. +No +unset (threshold does not apply) + + +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 +unset (threshold does not apply) + + +spring.cloud.gcp.pubsub.publisher.batching.enabled +Enables batching. +No +false + + + + +
    +
    +Sample +A sample application is available. +
    +
    + +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 Cloud GCP adds a new resource type: a Google Cloud Storage (GCS) object. +A Spring Boot starter is provided to auto-configure the various Storage components. +Maven coordinates, using Spring Cloud GCP BOM: +<dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-gcp-starter-storage</artifactId> +</dependency> +Gradle coordinates: +dependencies { + compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-storage' +} +This starter is also available from Spring Initializr through the GCP Storage entry. +
    +Google Cloud Storage +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. +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 Boot GCP 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 GCP 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 Cloud GCP 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 Cloud GCP Core Module +No + + + +spring.cloud.gcp.storage.credentials.scopes +OAuth2 scope for Spring Cloud GCP Storage credentials +No +https://www.googleapis.com/auth/devstorage.read_write + + + + +
    +
    +Sample +A sample application and a codelab are available. +
    +
    + +Spring JDBC +Spring Cloud GCP adds integrations with +Spring JDBC so you can run your MySQL or PostgreSQL databases in Google Cloud SQL using Spring JDBC, or other libraries that depend on it like Spring Data JPA. +The Cloud SQL support is provided by Spring Cloud GCP 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. +Maven coordinates, using Spring Cloud GCP BOM: +<dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-gcp-starter-sql-mysql</artifactId> +</dependency> +<dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-gcp-starter-sql-postgresql</artifactId> +</dependency> +Gradle coordinates: +dependencies { + compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-sql-mysql' + compile group: 'org.springframework.cloud', name: '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 GCP project. +To do that, go to the API library page of the Google Cloud Console, search for "Cloud SQL API", click the first result and enable the API. + +There are several similar "Cloud SQL" results. +You must access the "Google Cloud SQL API" one and enable the API from there. + +
    +
    +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: + + + + + + + +Property name +Description +Default value + + +spring.cloud.gcp.sql.enabled +Enables or disables Cloud SQL auto configuration +true + + +spring.cloud.gcp.sql.database-name +Name of the database to connect to. + + + +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. +For example, my-project-id:my-region:my-instance-name. + + + +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. +Default credentials provided by the Spring GCP Boot 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. +Default credentials provided by the Spring GCP Boot starter + + + + + +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. + +
    +<literal>DataSource</literal> creation flow +Based on the previous properties, the Spring Boot starter for Google Cloud SQL creates a CloudSqlJdbcInfoProvider object which is used to obtain an instance’s JDBC URL and driver class name. +If you provide your own CloudSqlJdbcInfoProvider bean, it is used instead and the properties related to building the JDBC URL or driver class are ignored. +The DataSourceProperties object provided by Spring Boot Autoconfigure is mutated in order to use the JDBC URL and driver class names provided by CloudSqlJdbcInfoProvider, unless those values were provided in the properties. +It is in the DataSourceProperties mutation step that the credentials factory is registered in a system property to be SqlCredentialFactory. +DataSource creation is delegated to +Spring Boot. +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. +
    +
    +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 <literal>c.g.cloud.sql.core.SslSocketFactory : Re-throwing cached exception due to attempt to refresh instance information too soon after error</literal> +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: <literal>java.net.SocketException: already connected</literal> 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> +
    +
    +
    +
    +Samples +Available sample applications and codelabs: + + +Spring Cloud GCP MySQL + + +Spring Cloud GCP PostgreSQL + + +Spring Data JPA with Spring Cloud GCP SQL + + +Codelab: Spring Pet Clinic using Cloud SQL + + +
    +
    + +Spring Integration +Spring Cloud GCP provides Spring Integration adapters that allow your applications to use Enterprise Integration Patterns backed up by Google Cloud Platform 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. +Maven coordinates, using Spring Cloud GCP BOM: +<dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-gcp-pubsub</artifactId> +</dependency> +<dependency> + <groupId>org.springframework.integration</groupId> + <artifactId>spring-integration-core</artifactId> +</dependency> +Gradle coordinates: +dependencies { + compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-pubsub' + compile group: 'org.springframework.integration', name: 'spring-integration-core' +} +
    +Inbound channel adapter +PubSubInboundChannelAdapter is the inbound channel adapter for GCP Pub/Sub that listens to a GCP 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. +@Bean +public MessageChannel pubsubInputChannel() { + return new PublishSubscribeChannel(); +} + +@Bean +public PubSubInboundChannelAdapter messageChannelAdapter( + @Qualifier("pubsubInputChannel") MessageChannel inputChannel, + SubscriberFactory subscriberFactory) { + PubSubInboundChannelAdapter adapter = + new PubSubInboundChannelAdapter(subscriberFactory, "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 GCP Pub/Sub provides a configured SubscriberFactory. +The PubSubInboundChannelAdapter supports three acknowledgement modes, with AckMode.AUTO being the default value; +Automatic acking (AckMode.AUTO) +A message is acked with GCP Pub/Sub if the adapter sent it to the channel and no exceptions were thrown. +If a RuntimeException is thrown while the message is processed, then the message is nacked. +Automatic acking OK (AckMode.AUTO_ACK) +A message is acked with GCP Pub/Sub if the adapter sent it to the channel and no exceptions were thrown. +If a RuntimeException is thrown while the message is processed, then the message is neither acked / nor nacked. +This is useful when using the subscription’s ack deadline timeout as a retry delivery backoff mechanism. +Manually acking (AckMode.MANUAL) +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 (n)ack 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(); + }; +} +
    +
    +Outbound channel adapter +PubSubMessageHandler is the outbound channel adapter for GCP Pub/Sub that listens for new messages on a Spring MessageChannel. +It uses PubSubTemplate to post them to a GCP 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. +@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 GCP 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 setPublishFutureCallback() method. +These are useful to process the message ID, in case of success, or the error if any was thrown. +To override the default destination you can use the GcpPubSubHeaders.DESTINATION 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. +
    +
    +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, like headers with key "id", "timestamp" and "gcp_pubsub_acknowledgement". +In the process, the outbound mapper also converts the value of the headers into string. +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. +
    +
    +
    +Sample +Available examples: + + +sender and receiver sample application + + +JSON payloads sample application + + +codelab + + +
    +
    +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 Cloud GCP 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 Cloud GCP, you must also provide the spring-integration-file dependency, since it isn’t pulled transitively. +Maven coordinates, using Spring Cloud GCP BOM: +<dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-gcp-storage</artifactId> +</dependency> +<dependency> + <groupId>org.springframework.integration</groupId> + <artifactId>spring-integration-file</artifactId> +</dependency> +Gradle coordinates: +dependencies { + compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-storage' + compile group: 'org.springframework.integration', name: '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; +} +
    +
    +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; +} +
    +
    +
    +Sample +A sample application is available. +
    +
    + +Spring Cloud Stream +Spring Cloud GCP provides a Spring Cloud Stream binder to Google Cloud Pub/Sub. +The provided binder relies on the Spring Integration Channel Adapters for Google Cloud Pub/Sub. +Maven coordinates, using Spring Cloud GCP BOM: +<dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-gcp-pubsub-stream-binder</artifactId> +</dependency> +Gradle coordinates: +dependencies { + compile group: 'org.springframework.cloud', name: '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 Cloud GCP 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 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 +If automatic resource creation is turned ON and the subscription and/or the topic do not exist for a consumer, a subscription and potentially a topic will be created. +The topic name will be the same as the destination name, and the subscription name will be the destination name followed by the consumer group name. +Regardless of the auto-create-resources setting, if the consumer group is not specified, an anonymous one will be created with the name anonymous.<destinationName>.<randomUUID>. +Then when the binder shuts down, all Pub/Sub subscriptions created for anonymous consumer groups will be automatically cleaned up. +For example, for the following configuration, a topic named myEvents and a subscription called myEvents.counsumerGroup1 would be created. +If the consumer group is not specified, a subscription called anonymous.myEvents.a6d83782-c5a3-4861-ac38-e6e2af15a7be would be created and later cleaned up. + +If you are manually creating Pub/Sub subscriptions for consumers, make sure that they follow the naming convention of <destinationName>.<consumerGroup>. + + +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 + + +
    +
    +
    +Sample +A sample application is available. +
    +
    + +Spring Cloud Sleuth +Spring Cloud Sleuth is an instrumentation framework for Spring Boot applications. +It captures trace information and can forward traces to services like Zipkin for storage and analysis. +Google Cloud Platform provides its own managed distributed tracing service called Stackdriver Trace. +Instead of running and maintaining your own Zipkin instance and storage, you can use Stackdriver Trace to store traces, view trace details, generate latency distributions graphs, and generate performance regression reports. +This Spring Cloud GCP starter can forward Spring Cloud Sleuth traces to Stackdriver Trace without an intermediary Zipkin server. +Maven coordinates, using Spring Cloud GCP BOM: +<dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-gcp-starter-trace</artifactId> +</dependency> +Gradle coordinates: +dependencies { + compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-trace' +} +You must enable Stackdriver Trace API from the Google Cloud Console in order to capture traces. +Navigate to the Stackdriver 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 Stackdriver 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-cloud-trace-context key (e.g., an HTTP request header). +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 Stackdriver 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. +If a x-cloud-trace-context key isn’t found, StackdriverTracePropagation falls back to tracing with the X-B3 headers. +
    +
    +Spring Boot Starter for Stackdriver Trace +Spring Boot Starter for Stackdriver Trace uses Spring Cloud Sleuth and auto-configures a StackdriverSender that sends the Sleuth’s trace information to Stackdriver Trace. +All configurations are optional: + + + + + + + + +Name +Description +Required +Default value + + +spring.cloud.gcp.trace.enabled +Auto-configure Spring Cloud Sleuth to send traces to Stackdriver Trace. +No +true + + +spring.cloud.gcp.trace.project-id +Overrides the project ID from the Spring Cloud GCP Module +No + + + +spring.cloud.gcp.trace.credentials.location +Overrides the credentials location from the Spring Cloud GCP Module +No + + + +spring.cloud.gcp.trace.credentials.encoded-key +Overrides the credentials encoded key from the Spring Cloud GCP Module +No + + + +spring.cloud.gcp.trace.credentials.scopes +Overrides the credentials scopes from the Spring Cloud GCP 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 GCP Stackdriver Trace. Added for forward compatibility. +No +spring.zipkin.messageTimeout + + + + +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 Stackdriver. +spring.sleuth.web.skipPattern=(^cleanup.*|.+favicon.*) # Ignore some URL paths. +Spring Cloud GCP Trace does override some Sleuth configurations: + + +Always uses 128-bit Trace IDs. +This is required by Stackdriver Trace. + + +Does not use Span joins. +Span joins will share the span ID between the client and server Spans. +Stackdriver requires that every Span ID within a Trace to be unique, so Span joins are not supported. + + +Uses StackdriverHttpClientParser and StackdriverHttpServerParser 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. +
    +
    +Integration with Logging +Integration with Stackdriver Logging is available through the Stackdriver 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. +
    +
    +Sample +A sample application and a codelab are available. +
    +
    + +Stackdriver Logging +Maven coordinates, using Spring Cloud GCP BOM: +<dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-gcp-starter-logging</artifactId> +</dependency> +Gradle coordinates: +dependencies { + compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-logging' +} +Stackdriver Logging is the managed logging service provided by Google Cloud Platform. +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 GCP 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 Cloud GCP 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 Platform 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 Stackdriver via this library with Logback: via direct API calls and through JSON-formatted console logs. +
    +Log via API +A Stackdriver appender is available using org/springframework/cloud/gcp/autoconfigure/logging/logback-appender.xml. +This appender builds a Stackdriver Logging log entry from a JUL or Logback log entry, adds a trace ID to it and sends it to Stackdriver 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="org/springframework/cloud/gcp/autoconfigure/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: + + + + + + + +Property +Default Value +Description + + + + +log +spring.log +The Stackdriver 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 Stackdriver Logging. +This can also be set via the STACKDRIVER_LOG_FLUSH_LEVEL environmental variable. + + + + +
    +
    +Log via Console +For Logback, a org/springframework/cloud/gcp/autoconfigure/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="org/springframework/cloud/gcp/autoconfigure/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 Stackdriver Logging. +Therefore, you can just include org/springframework/cloud/gcp/autoconfigure/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: + + + + + + + +Property +Default Value +Description + + + + +projectId +If not set, default value is determined in the following order: + + +SPRING_CLOUD_GCP_LOGGING_PROJECT_ID Environmental Variable. + + +Value of DefaultGcpProjectIdProvider.getProjectId() + + +This is used to generate fully qualified Stackdriver Trace ID format: projects/[PROJECT-ID]/traces/[TRACE-ID]. +This format is required to correlate trace between Stackdriver Trace and Stackdriver Logging. +If projectId is not set and cannot be determined, then it’ll log traceId without the fully qualified format. + + +includeTraceId +true +Should the traceId be included + + +includeSpanId +true +Should the spanId 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 + + + + +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="org.springframework.cloud.gcp.logging.StackdriverJsonLayout"> + <projectId>${projectId}</projectId> + + <!--<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>--> + </layout> + </encoder> + </appender> +</configuration> +
    +
    +
    +Sample +A Sample Spring Boot Application is provided to show how to use the Cloud logging starter. +
    +
    + +Spring Cloud Config +Spring Cloud GCP 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 Cloud GCP 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. + +The Google Cloud Runtime Configuration service is in beta status. + +Maven coordinates, using Spring Cloud GCP BOM: +<dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-gcp-starter-config</artifactId> +</dependency> +Gradle coordinates: +dependencies { + compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-config' +} +
    +Configuration +The following parameters are configurable in Spring Cloud GCP 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 +GCP 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 Cloud GCP 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 Cloud GCP Core Module, do not apply to Spring Cloud GCP Config. + +
    +
    +Quick start + + +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 + + +Configure your bootstrap.properties file with your application’s configuration data: +spring.application.name=myapp +spring.profiles.active=prod + + +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; + } +} + + +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. + + +Add the Spring Boot Actuator dependency: + + +Maven coordinates: +<dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-actuator</artifactId> +</dependency> +Gradle coordinates: +dependencies { + compile group: 'org.springframework.boot', name: 'spring-boot-starter-actuator' +} + + +Add @RefreshScope to your Spring configuration class to have parameters be reloadable at runtime. + + +Add management.endpoints.web.exposure.include=refresh to your application.properties to allow unrestricted access to /actuator/refresh. + + +Update a property with gcloud: +$ gcloud beta runtime-config configs variables set \ + myapp.queue_size 200 \ + --config-name myapp_prod + + +Send a POST request to the refresh endpoint: +$ curl -XPOST https://myapp.host.com/actuator/refresh + + +
    +
    +Sample +A sample application and a codelab are available. +
    +
    + +Spring Data Cloud Spanner +Spring Data is an abstraction for storing and retrieving POJOs in numerous storage technologies. +Spring Cloud GCP adds Spring Data support for Google Cloud Spanner. +Maven coordinates for this module only, using Spring Cloud GCP BOM: +<dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-gcp-data-spanner</artifactId> +</dependency> +Gradle coordinates: +dependencies { + compile group: 'org.springframework.cloud', name: '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>org.springframework.cloud</groupId> + <artifactId>spring-cloud-gcp-starter-data-spanner</artifactId> +</dependency> +Gradle: +dependencies { + compile group: 'org.springframework.cloud', name: '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 the use 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.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 +GCP project ID where the Google Cloud Spanner API is hosted, if different from the one in the Spring Cloud GCP 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 Cloud GCP 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 Cloud GCP Core Module +No + + + +spring.cloud.gcp.spanner.credentials.scopes +OAuth2 scope for Spring Cloud GCP +Cloud Spanner 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 + + + + +
    +
    +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 Double 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 FLOAT64, + 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 embedded properties' column names must all be unique. +
    +
    +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. +Cloud Spanner does not support the foreign key constraint, though the parent-child key constraint enforces a similar requirement when used with interleaved tables. +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 executed 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. +
    +
    +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.util.Instant + + +java.sql.Date + + +
    +
    +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. + + +Converters need to implement the org.springframework.core.convert.converter.Converter interface in both directions. + + +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 + + + + +An instance of both Converters needs to be passed to a ConverterAwareMappingSpannerEntityProcessor, which then has to be made available as a @Bean for SpannerEntityProcessor. + + +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())); + } +} +
    +
    +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. +Using SpannerTemplate you can execute 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 execute reads, for example: +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 timestamp and is guaranteed to see all data that has been committed up until the start of this read. +A stale read on the other hand 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(Timestamp.now()); +List<Trade> trades = this.spannerTemplate.readAll(Trade.class, spannerReadOptions); +Queries: +// a query with options: +SpannerQueryOptions spannerQueryOptions = new SpannerQueryOptions().setTimestamp(Timestamp.now()); +List<Trade> trades = this.spannerTemplate.query(Trade.class, Statement.of("SELECT * FROM trades"), spannerQueryOptions); +
    +
    +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 executing 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 executed using SpannerOperations.executeDmlStatement. +Inserts, updates, and deletions can affect any number of rows and entities. +
    +
    +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, 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](https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#transaction-declarative) 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](https://cloud.google.com/spanner/docs/dml-tasks) Statements. +DML statements can be executed in transactions via performReadWriteTransaction or using the @Transactional annotation. +When DML statements are executed outside of transactions, they are executed in [partitioned-mode](https://cloud.google.com/spanner/docs/dml-tasks#partitioned-dml). +
    +
    +
    +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 = 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-write 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 + + +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. +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. +Custom SQL query methods can accept a single Sort or Pageable parameter that is applied on top of any sorting or paging in the SQL: + @Query("SELECT * FROM trades ORDER BY action DESC") + List<Trade> sortedTrades(Pageable pageable); + + @Query("SELECT * FROM trades ORDER BY action DESC LIMIT 1") + Trade sortedTopTrade(Pageable pageable); +This can be used: + List<Trade> customSortedTrades = tradeRepository.sortedTrades(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 executed 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 executed 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); +
    +
    +
    +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>. +
    +
    +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, 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>,<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. +
    +
    +Sample +A sample application is available. +
    +
    + +Spring Data Cloud Datastore +Spring Data is an abstraction for storing and retrieving POJOs in numerous storage technologies. +Spring Cloud GCP adds Spring Data support for Google Cloud Datastore. +Maven coordinates for this module only, using Spring Cloud GCP BOM: +<dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-gcp-data-datastore</artifactId> +</dependency> +Gradle coordinates: +dependencies { + compile group: 'org.springframework.cloud', name: '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>org.springframework.cloud</groupId> + <artifactId>spring-cloud-gcp-starter-data-datastore</artifactId> +</dependency> +Gradle: +dependencies { + compile group: 'org.springframework.cloud', name: '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 the use 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 +GCP project ID where the Google Cloud Datastore API is hosted, if different from the one in the Spring Cloud GCP Core Module +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 Cloud GCP 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 Cloud GCP Core Module +No + + + +spring.cloud.gcp.datastore.credentials.scopes +OAuth2 scope for Spring Cloud GCP +Cloud Datastore 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 GCP project + + + + +
    +
    +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 + + +
    +
    +
    +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: + + + + + + +Type +Stored 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. + + +Converters need to implement the org.springframework.core.convert.converter.Converter interface in both directions. + + +The user defined type needs to be mapped to one of the basic types supported by Cloud Datastore. + + +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. + + +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 ImmutableSet<Album>: +@Entity +public class Singer { + + @Id + String singerId; + + String name; + + ImmutableSet<Album> albums; +} +We have to define a read converter only: +static final Converter<List<?>, ImmutableSet<?>> LIST_IMMUTABLE_SET_CONVERTER = + new Converter<List<?>, ImmutableSet<?>>() { + @Override + public ImmutableSet<?> convert(List<?> source) { + return ImmutableSet.copyOf(source); + } + }; +And add it to the list of custom converters: +@Configuration +public class ConverterConfiguration { + @Bean + public DatastoreCustomConversions datastoreCustomConversions() { + return new DatastoreCustomConversions( + Arrays.asList( + LIST_IMMUTABLE_SET_CONVERTER, + + ALBUM_STRING_CONVERTER, + STRING_ALBUM_CONVERTER)); + } +} +
    +
    +
    +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 + + +
    +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 org.springframework.cloud.gcp.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 org.springframework.cloud.gcp.data.datastore.core.mapping.Embedded; +import org.springframework.cloud.gcp.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 org.springframework.cloud.gcp.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 org.springframework.cloud.gcp.data.datastore.core.mapping.Descendants; +import org.springframework.cloud.gcp.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.cloud.gcp.data.datastore.core.mapping.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) +Datstore reading a single entity or multiple entities in a kind. +Using DatastoreTemplate you can execute reads, for example: +Trader trader = this.datastoreTemplate.findById("trader1", Trader.class); + +List<Trader> traders = this.datastoreTemplate.findAllById(ImmutableList.of("trader1", "trader2"), Trader.class); + +List<Trader> allTraders = this.datastoreTemplate.findAll(Trader.class); +Cloud Datastore executes 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 executed 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 org.springframework.cloud.gcp.data.datastore.core.mapping.Unindexed; + +public class ExampleItem { + long indexedField; + + @Unindexed + long unindexedField; +} +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. +
    +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. +
    +
    +
    +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 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); + + 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); +} +In the example above the query methods in TradeRepository are generated based on the name of the methods using thehttps://docs.spring.io/spring-data/data-commons/docs/current/reference/html#repositories.query-methods.query-creation[Spring Data Query creation naming convention]. +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 executed 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 execute an additional query to compute total number of pages. +Methods that return Slice, on the other hand, don’t execute any additional queries and therefore are much more efficient. + +
    +
    +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 test_entities_ci WHERE id = @id_val") + TestEntity getOneTestEntity(@Param("id_val") long id); +} +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`s 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(); + + @Query("SELECT id FROM test_entities_ci WHERE id <= @id_val") + List<String> getIds(@Param("id_val") long id); + + @Query("SELECT id FROM test_entities_ci WHERE id <= @id_val limit 1") + String getOneId(@Param("id_val") long id); +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: +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> +
    +
    +
    +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. +
    +
    + +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> +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. +
    +
    + +Cloud Identity-Aware Proxy (IAP) Authentication +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") validation is automatically configured when the application is running on App Engine Standard or App Engine Flexible. +For other runtime environments, a custom audience must be provided through spring.cloud.gcp.security.iap.audience property. +The custom property, if specified, overrides the automatic App Engine audience detection. + +There is no automatic audience string configuration for Compute Engine or Kubernetes Engine. +To use the IAP starter on GCE/GKE, find the Audience string per instructions in the Verify the JWT payload guide, and specify it in the spring.cloud.gcp.security.iap.audience property. +Otherwise, the application will fail to start with No qualifying bean of type 'org.springframework.cloud.gcp.security.iap.AudienceProvider' available message. + + +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 Cloud GCP BOM: +<dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-gcp-starter-security-iap</artifactId> +</dependency> +Starter Gradle coordinates: +dependencies { + compile group: 'org.springframework.cloud', name: '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. + + + + + + + + + +Name +Description +Required +Default + + + + +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. +
    +
    + +Google Cloud Vision +The Google Cloud Vision API allows users to leverage machine learning algorithms for processing images including: image classification, face detection, text extraction, and others. +Spring Cloud GCP provides: + + +A convenience starter which automatically configures authentication settings and client objects needed to begin using the Google Cloud Vision API. + + +A Cloud Vision Template which simplifies interactions with the Cloud Vision API. + + +Allows you to easily send images to the API as Spring Resources. + + +Offers convenience methods for common operations, such as extracting the text from an image. + + + + +Maven coordinates, using Spring Cloud GCP BOM: +<dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-gcp-starter-vision</artifactId> +</dependency> +Gradle coordinates: +dependencies { + compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-vision' +} +
    +Cloud Vision Template +The CloudVisionTemplate offers a simple way to use the Cloud Vision APIs with Spring Resources. +After you add the spring-cloud-gcp-starter-vision dependency to your project, you may @Autowire an instance of CloudVisionTemplate to use in your code. +The CloudVisionTemplate offers 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()); +} +
    +
    +Sample +A Sample Spring Boot Application is provided to show how to use the Cloud Vision starter and template. +
    +
    + +Cloud Foundry +Spring Cloud GCP provides support for Cloud Foundry’s GCP Service Broker. +Our Pub/Sub, Cloud Spanner, Storage, Stackdriver 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 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). + + + +Kotlin Support +The latest version of the Spring Framework provides first-class support for Kotlin. +For Kotlin users of Spring, the Spring Cloud GCP 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: + + +Kotlin Maven Plugin + + +Kotlin Gradle Plugin + + +Depending on your application’s needs, you may need to augment your build configuration with compiler plugins: + + +Kotlin Spring Plugin: Makes your Spring configuration classes/members non-final for convenience. + + +Kotlin JPA Plugin: Enables using JPA in Kotlin applications. + + +Once your Kotlin project is properly configured, the Spring Cloud GCP 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 Cloud GCP integrations from within Kotlin. + +
    \ No newline at end of file

    18. Cloud Foundry

    Spring Cloud GCP provides support for Cloud Foundry’s GCP Service Broker. +Our Pub/Sub, Cloud Spanner, Storage, Stackdriver 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 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.

    [Note]Note

    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.

    [Warning]Warning

    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).