[
  {
    "path": ".github/CODEOWNERS",
    "content": "# Code owners file.\n# This file controls who is tagged for review for any given pull request.\n\n# The java-samples-reviewers team is the default owner for anything not\n# explicitly taken by someone else.\n*                         @GoogleCloudPlatform/java-samples-reviewers\n"
  },
  {
    "path": ".github/blunderbuss.yml",
    "content": "assign_issues:\n  - GoogleCloudPlatform/java-samples-reviewers\nassign_prs:\n  - GoogleCloudPlatform/java-samples-reviewers\n"
  },
  {
    "path": ".github/sync-repo-settings.yaml",
    "content": "rebaseMergeAllowed: true\nsquashMergeAllowed: true\nmergeCommitAllowed: false\nbranchProtectionRules:\n- pattern: main\n  isAdminEnforced: false\n  requiredStatusCheckContexts:\n    - 'Kokoro CI - Java 8'\n    - 'Kokoro CI - Java 11'\n    - 'Kokoro CI - Lint'\n    - 'cla/google'\n  requiredApprovingReviewCount: 1\n  requiresCodeOwnerReviews: true\n  requiresStrictStatusChecks: true\npermissionRules:\n  - team: java-samples-reviewers\n    permission: push\n  - team: yoshi-java\n    permission: push\n  - team: devrel-java-admin\n    permission: admin\n  - team: yoshi-admins\n    permission: admin\n"
  },
  {
    "path": ".gitignore",
    "content": "# Eclipse files\n.project\n.classpath\n.settings\n\n# IntelliJ IDEA\n.Idea\n*.iml\n.idea/\n\n# Target folders\ntarget/\nbuild/\nout/\n.gradle/\nbin/\n\n## Vim ##\n# swap\n[._]*.s[a-w][a-z]\n[._]s[a-w][a-z]\n# session\nSession.vim\n# temporary\n.netrwhist\n*~\n# auto-generated tag files\ntags\n\n## Secrets ##\nclient-secret.json\n\n**/pom.xml.versionsBackup"
  },
  {
    "path": ".gitmodules",
    "content": ""
  },
  {
    "path": ".kokoro/java11/common.cfg",
    "content": "# Copyright 2019 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Format: //devtools/kokoro/config/proto/build.proto\n\n# Build timeout of 5 hours\ntimeout_mins: 300\n\n# Download trampoline resources.\ngfile_resources: \"/bigstore/cloud-devrel-kokoro-resources/trampoline\"\n\n# Use the trampoline script to run in docker.\nbuild_file: \"getting-started-java/.kokoro/trampoline.sh\"\n\naction {\n  define_artifacts {\n    regex: \"**/*sponge_log.xml\"\n  }\n}\n\n# Set the JAVA VERSION env var.\nenv_vars: {\n    key: \"JAVA_VERSION\"\n    value: \"1.8,11\"\n}\n\n# Configure the docker image for kokoro-trampoline.\nenv_vars: {\n    key: \"TRAMPOLINE_IMAGE\"\n    value: \"gcr.io/cloud-devrel-kokoro-resources/java11\"\n}\n"
  },
  {
    "path": ".kokoro/java11/continuous.cfg",
    "content": "# Copyright 2019 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Format: //devtools/kokoro/config/proto/build.proto\n\n# Tell the trampoline which tests to run.\nenv_vars: {\n    key: \"TRAMPOLINE_BUILD_FILE\"\n    value: \"github/getting-started-java/.kokoro/tests/run_tests.sh\"\n}\n"
  },
  {
    "path": ".kokoro/java11/periodic.cfg",
    "content": "# Copyright 2019 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Format: //devtools/kokoro/config/proto/build.proto\n\n# Tell the trampoline which build file to use.\nenv_vars: {\n    key: \"TRAMPOLINE_BUILD_FILE\"\n    value: \"github/getting-started-java/.kokoro/tests/run_tests.sh\"\n}\n"
  },
  {
    "path": ".kokoro/java11/presubmit.cfg",
    "content": "# Copyright 2019 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Format: //devtools/kokoro/config/proto/build.proto\n\n# Tell the trampoline which build file to use.\nenv_vars: {\n    key: \"TRAMPOLINE_BUILD_FILE\"\n    value: \"github/getting-started-java/.kokoro/tests/run_diff_only.sh\"\n}\n"
  },
  {
    "path": ".kokoro/java8/common.cfg",
    "content": "# Copyright 2019 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Format: //devtools/kokoro/config/proto/build.proto\n\n# Build timeout of 5 hours\ntimeout_mins: 300\n\n# Download trampoline resources.\ngfile_resources: \"/bigstore/cloud-devrel-kokoro-resources/trampoline\"\n\n# Use the trampoline script to run in docker.\nbuild_file: \"getting-started-java/.kokoro/trampoline.sh\"\n\naction {\n  define_artifacts {\n    regex: \"**/*sponge_log.xml\"\n  }\n}\n\n# Set the JAVA VERSION env var.\nenv_vars: {\n    key: \"JAVA_VERSION\"\n    value: \"1.8\"\n}\n\n# Configure the docker image for kokoro-trampoline.\nenv_vars: {\n    key: \"TRAMPOLINE_IMAGE\"\n    value: \"gcr.io/cloud-devrel-kokoro-resources/java8\"\n}\n"
  },
  {
    "path": ".kokoro/java8/continuous.cfg",
    "content": "# Copyright 2019 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Format: //devtools/kokoro/config/proto/build.proto\n\n# Tell trampoline which tests to run.\nenv_vars: {\n    key: \"TRAMPOLINE_BUILD_FILE\"\n    value: \"github/getting-started-java/.kokoro/tests/run_tests.sh\"\n}\n\n"
  },
  {
    "path": ".kokoro/java8/periodic.cfg",
    "content": "# Copyright 2019 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Format: //devtools/kokoro/config/proto/build.proto\n\n# Tell the trampoline which build file to use.\nenv_vars: {\n    key: \"TRAMPOLINE_BUILD_FILE\"\n    value: \"github/getting-started-java/.kokoro/tests/run_tests.sh\"\n}\n\n"
  },
  {
    "path": ".kokoro/java8/presubmit.cfg",
    "content": "# Copyright 2019 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Format: //devtools/kokoro/config/proto/build.proto\n\n# Tell the trampoline which build file to use.\nenv_vars: {\n    key: \"TRAMPOLINE_BUILD_FILE\"\n    value: \"github/getting-started-java/.kokoro/tests/run_diff_only.sh\"\n}\n"
  },
  {
    "path": ".kokoro/lint/common.cfg",
    "content": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Format: //devtools/kokoro/config/proto/build.proto\n\n# Use the trampoline to bounce the script into docker.\ngfile_resources: \"/bigstore/cloud-devrel-kokoro-resources/trampoline\"\nbuild_file: \"getting-started-java/.kokoro/trampoline.sh\"\nenv_vars: {\n    key: \"TRAMPOLINE_IMAGE\"\n    value: \"gcr.io/cloud-devrel-kokoro-resources/java8\"\n}\nenv_vars: {\n    key: \"TRAMPOLINE_BUILD_FILE\"\n    value: \"github/getting-started-java/.kokoro/tests/run_lint.sh\"\n}\n\n# Access btlr binaries used in the tests\ngfile_resources: \"/bigstore/cloud-devrel-kokoro-resources/btlr\"\n\n\n# Upload logs to result-store\naction {\n  define_artifacts {\n    regex: \"**/*sponge_log.xml\"\n  }\n}\n"
  },
  {
    "path": ".kokoro/lint/presubmit.cfg",
    "content": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Format: //devtools/kokoro/config/proto/build.proto\n\n# Tell the trampoline which build file to use.\nenv_vars: {\n    key: \"GIT_DIFF\"\n    value: \"origin/main... .\"\n}\n"
  },
  {
    "path": ".kokoro/tests/run_diff_only.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2017 Google Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nmydir=\"${0%/*}\"\n\"$mydir\"/run_tests.sh --only-diff"
  },
  {
    "path": ".kokoro/tests/run_lint.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# `-e` enables the script to automatically fail when a command fails\n# `-o pipefail` sets the exit code to the rightmost comment to exit with a non-zero\nset -eo pipefail\n\n# If on kokoro, add btlr to the path and cd into repo root\nif [ -n \"$KOKORO_GFILE_DIR\" ]; then\n  bltr_dir=\"$KOKORO_GFILE_DIR/v0.0.1/\"\n  chmod +x \"${bltr_dir}\"btlr\n  export PATH=\"$PATH:$bltr_dir\"\n  cd github/getting-started-java || exit\nfi\n\nopts=()\nif [ -n \"$GIT_DIFF\" ]; then\n  opts+=(\n    \"--git-diff\"\n    \"$GIT_DIFF\"\n  )\nfi\n\nbtlr \"${opts[@]}\" run \"**/pom.xml\" -- mvn -P lint --quiet --batch-mode checkstyle:check\n"
  },
  {
    "path": ".kokoro/tests/run_tests.sh",
    "content": "#!/bin/bash\n# Copyright 2017 Google Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# `-e` enables the script to automatically fail when a command fails\n# `-o pipefail` sets the exit code to the rightmost comment to exit with a non-zero\nset -eo pipefail\n# Enables `**` to include files nested inside sub-folders\nshopt -s globstar\n\nSCRIPT_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null 2>&1 && pwd )\"\n\n# `--script-debug` can be added make local testing of this script easier\nif [[ $* == *--script-debug* ]]; then\n    SCRIPT_DEBUG=\"true\"\n    JAVA_VERSION=\"1.8\"\nelse\n    SCRIPT_DEBUG=\"false\"\nfi\n\n# `--only-diff` will only run tests on projects container changes from the main branch.\nif [[ $* == *--only-diff* ]]; then\n    ONLY_DIFF=\"true\"\nelse\n    ONLY_DIFF=\"false\"\nfi\n\n# Verify Java versions have been specified\nif [[ -z ${JAVA_VERSION+x} ]]; then\n    echo -e \"'JAVA_VERSION' env var should be a comma delimited list of valid java versions.\"\n    exit 1\nfi\n\nif [[ \"$SCRIPT_DEBUG\" != \"true\" ]]; then\n    # Update `gcloud` and log versioning for debugging\n    apt update && apt -y upgrade google-cloud-sdk\n\n    echo \"********** GCLOUD INFO ***********\"\n    gcloud -v\n    echo \"********** MAVEN INFO  ***********\"\n    mvn -v\n    echo \"********** GRADLE INFO ***********\"\n    gradle -v\n\n    # Setup required env variables\n    export GOOGLE_CLOUD_PROJECT=java-docs-samples-testing\n    export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/secrets/java-docs-samples-service-account.json\n\n    # Grab latest version of secrets\n    mkdir -p \"${KOKORO_GFILE_DIR}/secrets\"\n    gcloud secrets versions access latest --secret=\"java-docs-samples-service-account\" > \"$GOOGLE_APPLICATION_CREDENTIALS\"\n    gcloud secrets versions access latest --secret=\"java-firestore-samples-secrets\" > \"${KOKORO_GFILE_DIR}/secrets/java-firestore-samples-secrets.txt\"\n    \n    # Execute secret file contents\n    source \"${KOKORO_GFILE_DIR}/secrets/java-firestore-samples-secrets.txt\"\n    # Activate service account\n    gcloud auth activate-service-account \\\n        --key-file=\"$GOOGLE_APPLICATION_CREDENTIALS\" \\\n        --project=\"$GOOGLE_CLOUD_PROJECT\"\n\n    cd github/getting-started-java\nfi\n\necho -e \"\\n******************** TESTING PROJECTS ********************\"\n# Switch to 'fail at end' to allow all tests to complete before exiting.\nset +e\n# Use RTN to return a non-zero value if the test fails.\nRTN=0\nROOT=$(pwd)\n\ngit config --global --add safe.directory $PWD\n\n# Find all POMs in the repository (may break on whitespace).\nfor file in **/pom.xml; do\n    cd \"$ROOT\"\n    # Navigate to the project folder.\n    file=$(dirname \"$file\")\n    cd \"$file\"\n\n    # If $DIFF_ONLY is true, skip projects without changes.\n    if [[ \"$ONLY_DIFF\" = \"true\" ]]; then\n        git diff --quiet origin/main.. .\n        CHANGED=$?\n        if [[ \"$CHANGED\" -eq 0 ]]; then\n          # echo -e \"\\n Skipping $file: no changes in folder.\\n\"\n          continue\n        fi\n    fi\n\n    echo \"------------------------------------------------------------\"\n    echo \"- testing $file\"\n    echo \"------------------------------------------------------------\"\n\n    # Fail the tests if no Java version was found.\n    POM_JAVA=$(grep -oP '(?<=<maven.compiler.target>).*?(?=</maven.compiler.target>)' pom.xml)\n    ALLOWED_VERSIONS=(\"1.8\" \"11\")\n    # shellcheck disable=SC2199\n    # shellcheck disable=SC2076\n    if [[ \"$POM_JAVA\" = \"\" ]] || [[ !  \"${ALLOWED_VERSIONS[@]}\" =~ \"${POM_JAVA}\" ]]; then\n        RTN=1\n        echo -e \"\\n Testing failed: Unable to determine Java version. Please set in pom:\"\n        echo -e \"\\n<properties>\"\n        echo -e \"  <maven.compiler.target>1.8</maven.compiler.target>\"\n        echo -e \"  <maven.compiler.source>1.8</maven.compiler.source>\"\n        echo -e \"</properties>\\n\"\n        continue\n    fi\n\n    # Skip tests that don't have the correct Java version.\n    # shellcheck disable=SC2076\n    if ! [[ \",$JAVA_VERSION,\" =~ \",$POM_JAVA,\" ]]; then\n        echo -e \"\\n Skipping tests: Java version ($POM_JAVA) not required ($JAVA_VERSION)\\n\"\n        continue\n    fi\n\n    # Use maven to execute the tests for the project.\n    mvn --quiet --batch-mode --fail-at-end clean verify \\\n       -Dfile.encoding=\"UTF-8\" \\\n       -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn \\\n       -Dmaven.test.redirectTestOutputToFile=true \\\n       -Dbigtable.projectID=\"${GOOGLE_CLOUD_PROJECT}\" \\\n       -Dbigtable.instanceID=instance\n    EXIT=$?\n\n    if [[ $EXIT -ne 0 ]]; then\n      RTN=1\n      echo -e \"\\n Testing failed: Maven returned a non-zero exit code. \\n\"\n    else\n      echo -e \"\\n Testing completed.\\n\"\n    fi\n\ndone\n\nexit \"$RTN\"\n"
  },
  {
    "path": ".kokoro/trampoline.sh",
    "content": "#!/bin/bash\n# Copyright 2017 Google Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\npython3 \"${KOKORO_GFILE_DIR}/trampoline_v1.py\"\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Code of Conduct\n\nAs contributors and maintainers of this project,\nand in the interest of fostering an open and welcoming community,\nwe pledge to respect all people who contribute through reporting issues,\nposting feature requests, updating documentation,\nsubmitting pull requests or patches, and other activities.\n\nWe are committed to making participation in this project\na harassment-free experience for everyone,\nregardless of level of experience, gender, gender identity and expression,\nsexual orientation, disability, personal appearance,\nbody size, race, ethnicity, age, religion, or nationality.\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery\n* Personal attacks\n* Trolling or insulting/derogatory comments\n* Public or private harassment\n* Publishing other's private information,\nsuch as physical or electronic\naddresses, without explicit permission\n* Other unethical or unprofessional conduct.\n\nProject maintainers have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct.\nBy adopting this Code of Conduct,\nproject maintainers commit themselves to fairly and consistently\napplying these principles to every aspect of managing this project.\nProject maintainers who do not follow or enforce the Code of Conduct\nmay be permanently removed from the project team.\n\nThis code of conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community.\n\nInstances of abusive, harassing, or otherwise unacceptable behavior\nmay be reported by opening an issue\nor contacting one or more of the project maintainers.\n\nThis Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,\navailable at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# How to become a contributor and submit your own code\n\n## Contributor License Agreements\n\nWe'd love to accept your sample apps and patches! Before we can take them, we\nhave to jump a couple of legal hurdles.\n\nPlease fill out either the individual or corporate Contributor License Agreement\n(CLA):\n\n  * If you are an individual writing original source code and you're sure you\n    own the intellectual property, then you'll need to sign an [individual CLA](https://developers.google.com/open-source/cla/individual).\n  * If you work for a company that wants to allow you to contribute your work,\n    then you'll need to sign a [corporate CLA](https://developers.google.com/open-source/cla/corporate).\n\nFollow either of the two links above to access the appropriate CLA and\ninstructions for how to sign and return it. Once we receive it, we'll be able to\naccept your pull requests.\n\n## Contributing A Patch\n\n1. Submit an issue describing your proposed change to the repository in question.\n2. The repository owner will respond to your issue promptly.\n3. If your proposed change is accepted, and you haven't already done so, sign a\n   CLA (see details above).\n4. Fork the desired repo, then develop and test your code changes.\n5. Ensure that your code adheres to the existing style in the sample to which\n   you are contributing. Refer to the [Google Java Style Guide](http://google.github.io/styleguide/javaguide.html) and the\n   [Google Cloud Platform Community Style Guide](https://cloud.google.com/community/tutorials/styleguide) for the\n   recommended coding standards for this organization.\n6. Ensure that your code has an appropriate set of unit tests which all pass.\n7. Submit a pull request.\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# Getting started on Google Cloud Platform for Java®\n\n[![CircleCI Build Status](https://circleci.com/gh/GoogleCloudPlatform/getting-started-java.svg?style=shield&circle-token=51b789e102291cbeae6817678d02da0f4cf25f1f)](https://circleci.com/gh/GoogleCloudPlatform/getting-started-java)\n[![Coverage Status](https://codecov.io/gh/GoogleCloudPlatform/getting-started-java/branch/main/graph/badge.svg)](https://codecov.io/gh/GoogleCloudPlatform/getting-started-java)\n\nThe code for the samples is contained in individual folders on this repository.\nFollow the instructions at [Getting Started on Google Cloud Platform for Java](https://cloud.google.com/java/) or the README files in each folder for instructions on how to run locally and deploy.\n\nManaged VMs on Google Cloud Platform use the [Java Servlets](http://www.oracle.com/technetwork/java/overview-137084.html) & [Java Server Pages](http://www.oracle.com/technetwork/java/index-jsp-138231.html) on [Jetty](http://www.eclipse.org/jetty/).\n\n1. [Helloworld-servlet](helloworld-servlet) Servlet based Hello World app\n1. [HelloWorld-jsp](helloworld-jsp) Java Server Pages based Hello World app\n1. [Bookshelf](bookshelf) A full featured app that demonstrates Authentication and CRUD operations for [Cloud Datastore](https://cloud.google.com/datastore/docs/concepts/overview?hl=en) and [Cloud SQL](https://cloud.google.com/sql/docs/introduction).\n\n## Google Cloud Samples\n\nTo browse ready to use code samples check [Google Cloud samples](https://cloud.google.com/docs/samples).\n\n## Contributing changes\n\n* See [CONTRIBUTING.md](CONTRIBUTING.md)\n\n\n## Licensing\n\n* See [LICENSE](LICENSE)\n\nJava is a registered trademark of Oracle Corporation and/or its affiliates.\n"
  },
  {
    "path": "appengine-standard-java8/deployAll.sh",
    "content": "#!/bin/bash\n# Copyright 2017 Google Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# set -x\n# set -v\n\n# gcloud config configurations activate qa\n\nfor app in \"helloworld\" \"kotlin-appengine-standard\" \\\n      \"kotlin-sb-appengine-standard\" \\\n      \"springboot-appengine-standard\" \"kotlin-spark-appengine-standard\" \\\n      \"sparkjava-appengine-standard\"\ndo\n  (cd \"${app}\"\n      sed --in-place='.xx' \"s/<\\/runtime>/<\\/runtime><service>${app}<\\/service>/\" \\\n          src/main/webapp/WEB-INF/appengine-web.xml\n      mvn -B --fail-at-end -q package appengine:deploy -Dapp.deploy.version=\"1\" \\\n          -Dapp.stage.quickstart=true -Dapp.deploy.force=true -Dapp.deploy.promote=true \\\n          -Dapp.deploy.projectId=\"${GOOGLE_CLOUD_PROJECT}\" -DskipTests=true\n      mv src/main/webapp/WEB-INF/appengine-web.xml.xx src/main/webapp/WEB-INF/appengine-web.xml)\ndone\n\necho \"STATUS: ${?}\"\n"
  },
  {
    "path": "appengine-standard-java8/helloworld-gae-javasdk-tools/README.md",
    "content": "HelloWorld for App Engine Standard (Java 8) using the App Engine Java SDK tooling\n============================\n\nThis sample demonstrates how to deploy an application on Google App Engine.\n\nSee the [Google App Engine standard environment documentation][ae-docs] for more\ndetailed instructions.\n\n[ae-docs]: https://cloud.google.com/appengine/docs/java/\n\n\n* [Java 8](http://www.oracle.com/technetwork/java/javase/downloads/index.html)\n* [Maven](https://maven.apache.org/download.cgi) (at least 3.5)\n* [Gradle](https://gradle.org/gradle-download/) (optional)\n* [Google Cloud SDK](https://cloud.google.com/sdk/) (aka gcloud)\n\n## Setup\n\n• Download and initialize the [Cloud SDK](https://cloud.google.com/sdk/)\n```\ngcloud init\n```\n* Create an App Engine app within the current Google Cloud Project\n\n```\ngcloud app create\n```\n\n* In the `pom.xml`, update the [App Engine Maven Plugin](https://cloud.google.com/appengine/docs/standard/java/tools/maven-reference)\nwith your Google Cloud Project Id:\n\n```\n<plugin>\n  <groupId>com.google.cloud.tools</groupId>\n  <artifactId>appengine-maven-plugin</artifactId>\n  <version>2.3.0</version>\n  <configuration>\n    <projectId>GCLOUD_CONFIG</projectId>\n    <version>GCLOUD_CONFIG</version>\n  </configuration>\n</plugin>\n```\n**Note:** `GCLOUD_CONFIG` is a special version for autogenerating an App Engine \nversion. Change this field to specify a specific version name.\n\n## Maven\n### Running locally\n\n    mvn clean package appengine:run\n\nTo use visit: http://localhost:8080/\n\n### Deploying\n\nUpdate `src/main/webapp/WEB-INF/appengine-web.xml` `<application>` tag with the Project ID.\n\n    mvn clean package appengine:deploy\n\nTo use visit:  https://YOUR-PROJECT-ID.appspot.com\n\n## Gradle\n\nFor more information see the [plugin project](https://github.com/GoogleCloudPlatform/gradle-appengine-plugin#gradle-app-engine-plugin-) on github.\n\n### Running locally\n\n    gradle appengineRun\n\nIf you do not have gradle installed, you can run using `./gradlew appengineRun`.\n\nTo use visit: http://localhost:8080/\n\n### Deploying\n\nUpdate `src/main/webapp/WEB-INF/appengine-web.xml` `<application>` tag with the Project ID.\n\n    gradle appengineUpdate\n\nIf you do not have gradle installed, you can deploy using `./gradlew appengineDeploy`.\n\nTo use visit:  https://1-dot-YOUR-PROJECT-ID.appspot.com\n\nThis is using version-dot-project naming.\n\n## Testing\n\n    mvn verify\n\n or\n\n    gradle test\n\nAs you add / modify the source code (`src/main/java/...`) it's very useful to add [unit testing](https://cloud.google.com/appengine/docs/java/tools/localunittesting)\nto (`src/main/test/...`).  The following resources are quite useful:\n\n* [Junit4](http://junit.org/junit4/)\n* [Mockito](http://mockito.org/)\n* [Truth](http://google.github.io/truth/)\n"
  },
  {
    "path": "appengine-standard-java8/helloworld-gae-javasdk-tools/build.gradle",
    "content": "// Copyright 2017 Google Inc.\n//\n//  Licensed under the Apache License, Version 2.0 (the \"License\");\n//  you may not use this file except in compliance with the License.\n//  You may obtain a copy of the License at\n//\n//        http://www.apache.org/licenses/LICENSE-2.0\n//\n//  Unless required by applicable law or agreed to in writing, software\n//  distributed under the License is distributed on an \"AS IS\" BASIS,\n//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//  See the License for the specific language governing permissions and\n//  limitations under the License.\n// [START gradle]\nbuildscript {    // Configuration for building\n  repositories {\n    jcenter()    // Bintray's repository - a fast Maven Central mirror & more\n    mavenCentral()\n  }\n  dependencies {\n    classpath 'com.google.appengine:gradle-appengine-plugin:+'    // latest App Engine Gradle tasks\n  }\n}\n\nrepositories {   // repositories for Jar's you access in your code\n  mavenCentral()\n  jcenter()\n}\n\napply plugin: 'java'                              // standard Java tasks\napply plugin: 'war'                               // standard Web Archive plugin\napply plugin: 'appengine'  // App Engine tasks\n\ndependencies {\n  appengineSdk 'com.google.appengine:appengine-java-sdk:1.9.93'\n\n  compile 'com.google.appengine:appengine-api-1.0-sdk:+'  // Latest App Engine Api's\n  providedCompile 'javax.servlet:javax.servlet-api:4.0.1'\n\n  compile 'jstl:jstl:1.2'\n\n// Add your dependencies here.\n//  compile 'com.google.cloud:google-cloud:+'   // Latest Cloud API's http://googlecloudplatform.github.io/google-cloud-java\n\n  testCompile 'junit:junit:4.13.2'\n  testCompile 'com.google.truth:truth:1.2.0'\n  testCompile 'org.mockito:mockito-all:1.10.19'\n\n  testCompile 'com.google.appengine:appengine-testing:+'\n  testCompile 'com.google.appengine:appengine-api-stubs:+'\n  testCompile 'com.google.appengine:appengine-tools-sdk:+'\n}\n\n// Always run unit tests\nappengineUpdate.dependsOn test\nappengineStage.dependsOn test\n\n// [START model]\nappengine {  // App Engine tasks configuration\n  downloadSdk = true\n}\n\ntest {\n  useJUnit()\n  testLogging.showStandardStreams = true\n  beforeTest { descriptor ->\n     logger.lifecycle(\"test: \" + descriptor + \"  Running\")\n  }\n\n  onOutput { descriptor, event ->\n     logger.lifecycle(\"test: \" + descriptor + \": \" + event.message )\n  }\n  afterTest { descriptor, result ->\n    logger.lifecycle(\"test: \" + descriptor + \": \" + result )\n  }\n}\n// [END model]\n\ngroup   = \"com.example.appengine_standard_java8\"        // Generated output GroupId\nversion = \"1.0-SNAPSHOT\"        // Version in generated output\n\nsourceCompatibility = 1.8     // App Engine Flexible uses Java 8\ntargetCompatibility = 1.8     // App Engine Flexible uses Java 8\n// [END gradle]\n"
  },
  {
    "path": "appengine-standard-java8/helloworld-gae-javasdk-tools/gradle/wrapper/gradle-wrapper.properties",
    "content": "#Tue Jun 13 16:53:48 PDT 2017\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.0-bin.zip\n"
  },
  {
    "path": "appengine-standard-java8/helloworld-gae-javasdk-tools/gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave ( ) {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "appengine-standard-java8/helloworld-gae-javasdk-tools/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windows variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "appengine-standard-java8/helloworld-gae-javasdk-tools/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\nCopyright 2017 Google Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n-->\n<!-- [START pom] -->\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\n  <modelVersion>4.0.0</modelVersion>\n  <packaging>war</packaging>\n  <version>1.0-SNAPSHOT</version>\n\n  <groupId>com.example.appengine_standard_java8</groupId>\n  <artifactId>helloworld_gae_tooling</artifactId>\n\n  <!-- [START_EXCLUDE silent] -->\n  <!-- Parent POM defines common plugins and properties. -->\n  <parent>\n    <groupId>com.google.cloud.samples</groupId>\n    <artifactId>shared-configuration</artifactId>\n    <version>1.2.0</version>\n  </parent>\n  <!-- [END_EXCLUDE] -->\n\n  <!-- [START properties] -->\n  <properties>\n    <maven.compiler.source>1.8</maven.compiler.source>\n    <maven.compiler.target>1.8</maven.compiler.target>\n    <!-- [START_EXCLUDE silent] -->\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>\n    <archiveClasses>true</archiveClasses>\n    <failOnMissingWebXml>false</failOnMissingWebXml>\n    <!-- [END_EXCLUDE] -->\n  </properties>\n  <!-- [END properties] -->\n\n  <dependencies>\n    <!-- Compile/runtime dependencies -->\n    <!-- [START api-dependencies] -->\n    <dependency>\n      <groupId>com.google.appengine</groupId>\n      <artifactId>appengine-api-1.0-sdk</artifactId>\n      <version>2.0.15</version>\n    </dependency>\n    <!-- [END api-dependencies] -->\n\n    <!-- [START servlet] -->\n    <dependency>\n      <groupId>javax.servlet</groupId>\n      <artifactId>javax.servlet-api</artifactId>\n      <version>4.0.1</version>\n      <type>jar</type>\n      <scope>provided</scope>\n    </dependency>\n    <!-- [END servlet] -->\n\n    <dependency>\n      <groupId>jstl</groupId>\n      <artifactId>jstl</artifactId>\n      <version>1.2</version>\n    </dependency>\n    <!-- [START_EXCLUDE silent] -->\n    <!-- Test Dependencies -->\n    <dependency>\n      <groupId>com.google.appengine</groupId>\n      <artifactId>appengine-testing</artifactId>\n      <version>2.0.15</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>com.google.appengine</groupId>\n      <artifactId>appengine-api-stubs</artifactId>\n      <version>2.0.15</version>\n      <scope>test</scope>\n    </dependency>\n\n    <dependency>\n      <groupId>com.google.appengine</groupId>\n      <artifactId>appengine-tools-sdk</artifactId>\n      <version>2.0.15</version>\n      <scope>test</scope>\n    </dependency>\n\n    <dependency>\n      <groupId>com.google.truth</groupId>\n      <artifactId>truth</artifactId>\n      <version>1.2.0</version>\n      <scope>test</scope>\n    </dependency>\n\n    <dependency>\n      <groupId>junit</groupId>\n      <artifactId>junit</artifactId>\n      <version>4.13.2</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-core</artifactId>\n      <version>4.5.0</version>\n      <scope>test</scope>\n    </dependency>\n    <!-- [END_EXCLUDE] -->\n  </dependencies>\n\n  <build>\n    <!-- for hot reload of the web application-->\n    <outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes</outputDirectory>\n    <plugins>\n      <!-- [START gaejavaplugin] -->\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>appengine-maven-plugin</artifactId>\n        <version>2.4.4</version>\n        <configuration>\n          <!-- can be set w/ -DprojectId=myProjectId on command line -->\n          <projectId>GCLOUD_CONFIG</projectId>\n          <!-- set the GAE version or use \"GCLOUD_CONFIG\" for an autogenerated GAE version -->\n          <version>GCLOUD_CONFIG</version>\n          <fullScanSeconds>1</fullScanSeconds>\n        </configuration>\n      </plugin>\n      <plugin>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <version>3.11.0</version>\n        <configuration>\n          <source>1.8</source>\n          <target>1.8</target>\n        </configuration>\n      </plugin>\n      <!-- [END gaejavaplugin] -->\n\n      <!-- [START_EXCLUDE silent] -->\n      <plugin>\n        <groupId>org.codehaus.mojo</groupId>\n        <artifactId>versions-maven-plugin</artifactId>\n        <version>2.11.0</version>\n        <executions>\n          <execution>\n            <phase>compile</phase>\n            <goals>\n              <goal>display-dependency-updates</goal>\n              <goal>display-plugin-updates</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n\n      <plugin>\n        <artifactId>maven-war-plugin</artifactId>\n        <version>3.4.0</version>\n      </plugin>\n\n      <plugin>\n        <artifactId>maven-clean-plugin</artifactId>\n        <version>3.3.2</version>\n      </plugin>\n\n      <plugin>\n        <artifactId>maven-install-plugin</artifactId>\n        <version>3.1.1</version>\n      </plugin>\n\n      <plugin>\n        <artifactId>maven-surefire-plugin</artifactId>\n        <version>3.1.2</version>\n      </plugin>\n\n      <plugin>\n        <artifactId>maven-site-plugin</artifactId>\n        <version>3.12.1</version>\n      </plugin>\n\n      <plugin>\n        <artifactId>maven-resources-plugin</artifactId>\n        <version>3.3.0</version>\n      </plugin>\n\n      <plugin>\n        <artifactId>maven-deploy-plugin</artifactId>\n        <version>3.0.0</version>\n      </plugin>\n\n      <plugin>\n        <artifactId>maven-enforcer-plugin</artifactId>\n        <version>3.1.0</version>\n        <executions>\n          <execution>\n            <id>enforce-maven</id>\n            <goals>\n              <goal>enforce</goal>\n            </goals>\n            <configuration>\n              <rules>\n                <requireMavenVersion>\n                  <version>3.5</version>\n                </requireMavenVersion>\n                <requirePluginVersions>\n                   <message>Best Practice is to always define plugin versions!</message>\n                   <banLatest>true</banLatest>\n                   <banRelease>true</banRelease>\n                   <phases>clean,deploy,verify,appengine:run,appengine:deploy,appengine:update,appengine:devappaserver,site</phases>\n                </requirePluginVersions>\n              </rules>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n      <!-- [END_EXCLUDE] -->\n    </plugins>\n  </build>\n</project>\n<!-- [END pom] -->\n"
  },
  {
    "path": "appengine-standard-java8/helloworld-gae-javasdk-tools/settings.gradle",
    "content": "/*\n * This settings file was generated by the Gradle 'init' task.\n *\n * The settings file is used to specify which projects to include in your build.\n * In a single project build this file can be empty or even removed.\n *\n * Detailed information about configuring a multi-project build in Gradle can be found\n * in the user guide at https://docs.gradle.org/3.5/userguide/multi_project_builds.html\n */\n\n/*\n// To declare projects as part of a multi-project build use the 'include' method\ninclude 'shared'\ninclude 'api'\ninclude 'services:webservice'\n*/\n\nrootProject.name = 'helloworld_gae_tooling'\n"
  },
  {
    "path": "appengine-standard-java8/helloworld-gae-javasdk-tools/src/main/java/com/example/appengine/java8/HelloAppEngine.java",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.appengine.java8;\n\n// [START example]\nimport com.google.appengine.api.utils.SystemProperty;\nimport java.io.IOException;\nimport java.util.Properties;\nimport javax.servlet.annotation.WebServlet;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required.\n@WebServlet(name = \"HelloAppEngine\", value = \"/hello\")\npublic class HelloAppEngine extends HttpServlet {\n\n  @Override\n  public void doGet(HttpServletRequest request, HttpServletResponse response)\n      throws IOException {\n\n    Properties properties = System.getProperties();\n\n    response.setContentType(\"text/plain\");\n    response.getWriter().println(\"Hello App Engine - Standard using \"\n        + SystemProperty.version.get() + \" Java \" + properties.get(\"java.specification.version\"));\n\n  }\n\n  public static String getInfo() {\n    return \"Version: \" + System.getProperty(\"java.version\")\n          + \" OS: \" + System.getProperty(\"os.name\")\n          + \" User: \" + System.getProperty(\"user.name\");\n  }\n\n}\n// [END example]\n"
  },
  {
    "path": "appengine-standard-java8/helloworld-gae-javasdk-tools/src/main/webapp/WEB-INF/appengine-web.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Google Inc.\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n\n        http://www.apache.org/licenses/LICENSE-2.0\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n-->\n<!-- [START config] -->\n<appengine-web-app xmlns=\"http://appengine.google.com/ns/1.0\">\n  <application>helloworld-gae</application>\n  <runtime>java8</runtime>\n  <threadsafe>true</threadsafe>\n</appengine-web-app>\n  <!-- [END config] -->\n"
  },
  {
    "path": "appengine-standard-java8/helloworld-gae-javasdk-tools/src/main/webapp/index.jsp",
    "content": "<!DOCTYPE html>\n<!-- [START_EXCLUDE] -->\n<%--\n  ~ Copyright 2017 Google Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\"); you\n  ~ may not use this file except in compliance with the License. You may\n  ~ obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n  ~ implied. See the License for the specific language governing\n  ~ permissions and limitations under the License.\n  --%>\n<!-- [END_EXCLUDE] -->\n<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %>\n<%@ page import=\"com.example.appengine.java8.HelloAppEngine\" %>\n<html>\n<head>\n  <link href='//fonts.googleapis.com/css?family=Marmelad' rel='stylesheet' type='text/css'>\n  <title>Hello App Engine Standard Java 8</title>\n</head>\n<body>\n    <h1>Hello App Engine -- Java 8!</h1>\n\n  <p>This is <%= HelloAppEngine.getInfo() %>.</p>\n  <table>\n    <tr>\n      <td colspan=\"2\" style=\"font-weight:bold;\">Available Servlets:</td>\n    </tr>\n    <tr>\n      <td><a href='/hello'>Hello App Engine</a></td>\n    </tr>\n  </table>\n\n</body>\n</html>\n"
  },
  {
    "path": "appengine-standard-java8/helloworld-gae-javasdk-tools/src/test/java/com/example/appengine/java8/HelloAppEngineTest.java",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.appengine.java8;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.Mockito.when;\n\nimport com.google.appengine.tools.development.testing.LocalServiceTestHelper;\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\n\n/**\n * Unit tests for {@link HelloAppEngine}.\n */\n// [START example]\n@RunWith(JUnit4.class)\npublic class HelloAppEngineTest {\n  private static final String FAKE_URL = \"fake.fk/hello\";\n  // Set up a helper so that the ApiProxy returns a valid environment for local testing.\n  private final LocalServiceTestHelper helper = new LocalServiceTestHelper();\n\n  @Mock private HttpServletRequest mockRequest;\n  @Mock private HttpServletResponse mockResponse;\n  private StringWriter responseWriter;\n  private HelloAppEngine servletUnderTest;\n\n  @Before\n  public void setUp() throws Exception {\n    MockitoAnnotations.initMocks(this);\n    helper.setUp();\n\n    //  Set up some fake HTTP requests\n    when(mockRequest.getRequestURI()).thenReturn(FAKE_URL);\n\n    // Set up a fake HTTP response.\n    responseWriter = new StringWriter();\n    when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter));\n\n    servletUnderTest = new HelloAppEngine();\n  }\n\n  @After public void tearDown() {\n    helper.tearDown();\n  }\n\n  @Test\n  public void doGetWritesResponse() throws Exception {\n    servletUnderTest.doGet(mockRequest, mockResponse);\n\n    // We expect our hello world response.\n    assertThat(responseWriter.toString())\n        .contains(\"Hello App Engine - Standard \");\n  }\n\n  @Test\n  public void helloInfoTest() {\n    String result = HelloAppEngine.getInfo();\n    assertThat(result)\n        .containsMatch(\"^Version:\\\\s+.+OS:\\\\s+.+User:\\\\s\");\n  }\n}\n// [END example]\n"
  },
  {
    "path": "appengine-standard-java8/kotlin-appengine-standard/.gitignore",
    "content": "hotspot.log\n*.iml\n*.ipr\n*.iws\n.gradle/\nbuild/\ntarget/\nclasses/\n/var\npom.xml.versionsBackup\ntest-output/\n/atlassian-ide-plugin.xml\n.idea\n.DS_Store\n.classpath\n.settings\n.project\ntemp-testng-customsuite.xml\ntest-output\n.externalToolBuilders\n*~\n"
  },
  {
    "path": "appengine-standard-java8/kotlin-appengine-standard/README.md",
    "content": "App Engine Java Kotlin Servlet 3.1 with Java8\n===\n\n## Sample Servlet 3.1 written in Kotlin for use with App Engine Java8 Standard.\n\nSee the [Google App Engine standard environment documentation][ae-docs] for more\ndetailed instructions.\n\n[ae-docs]: https://cloud.google.com/appengine/docs/java/\n\n* [Java 8](http://www.oracle.com/technetwork/java/javase/downloads/index.html)\n* [Maven](https://maven.apache.org/download.cgi) (at least 3.5)\n* [Google Cloud SDK](https://cloud.google.com/sdk/) (aka gcloud command line tool)\n\n## Setup\n\n* Download and initialize the [Cloud SDK](https://cloud.google.com/sdk/)\n\n```\ngcloud init\n```\n\n* Create an App Engine app within the current Google Cloud Project\n\n```\ngcloud app create\n```\n\n* In the `pom.xml`, update the [App Engine Maven Plugin](https://cloud.google.com/appengine/docs/standard/java/tools/maven-reference)\nwith your Google Cloud Project Id:\n\n```\n<plugin>\n  <groupId>com.google.cloud.tools</groupId>\n  <artifactId>appengine-maven-plugin</artifactId>\n  <version>2.3.0</version>\n  <configuration>\n    <projectId>GCLOUD_CONFIG</projectId>\n    <version>GCLOUD_CONFIG</version>\n  </configuration>\n</plugin>\n```\n**Note:** `GCLOUD_CONFIG` is a special version for autogenerating an App Engine\nversion. Change this field to specify a specific version name. \n\n## Maven\n### Running locally\n\n`mvn package appengine:run`\n\nTo use visit: http://localhost:8080/\n\n### Deploying\n\n`mvn package appengine:deploy`\n\nTo use visit:  https://YOUR-PROJECT-ID.appspot.com\n\n## Testing\n\n`mvn verify`\n\nAs you add / modify the source code (`src/main/java/...`) it's very useful to add [unit testing](https://cloud.google.com/appengine/docs/java/tools/localunittesting)\nto (`src/main/test/...`).  The following resources are quite useful:\n\n* [Junit4](http://junit.org/junit4/)\n* [Mockito](http://mockito.org/)\n* [Truth](http://google.github.io/truth/)\n\n\nFor further information, consult the\n[Java App Engine](https://developers.google.com/appengine/docs/java/overview) documentation.\n"
  },
  {
    "path": "appengine-standard-java8/kotlin-appengine-standard/nbactions.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\nCopyright 2017 Google Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n-->\n<actions>\n    <action>\n        <actionName>CUSTOM-appengine:devserver</actionName>\n        <displayName>appengine:devserver</displayName>\n        <goals>\n            <goal>appengine:devserver</goal>\n        </goals>\n    </action>\n    <action>\n        <actionName>CUSTOM-appengine:update</actionName>\n        <displayName>appengine:update</displayName>\n        <goals>\n            <goal>appengine:update</goal>\n        </goals>\n    </action>\n    <action>\n        <actionName>CUSTOM-appengine:rollback</actionName>\n        <displayName>appengine:rollback</displayName>\n        <goals>\n            <goal>appengine:rollback</goal>\n        </goals>\n    </action>\n    <action>\n        <actionName>CUSTOM-appengine:update_cron</actionName>\n        <displayName>appengine:update_cron</displayName>\n        <goals>\n            <goal>appengine:update_cron</goal>\n        </goals>\n    </action>\n    <action>\n        <actionName>CUSTOM-appengine:update_dos</actionName>\n        <displayName>appengine:update_dos</displayName>\n        <goals>\n            <goal>appengine:update_dos</goal>\n        </goals>\n    </action>\n    <action>\n        <actionName>CUSTOM-appengine:update_indexes</actionName>\n        <displayName>appengine:update_indexes</displayName>\n        <goals>\n            <goal>appengine:update_indexes</goal>\n        </goals>\n    </action>\n    <action>\n        <actionName>CUSTOM-appengine:update_queues</actionName>\n        <displayName>appengine:update_queues</displayName>\n        <goals>\n            <goal>appengine:update_queues</goal>\n        </goals>\n    </action>\n    <action>\n            <actionName>run</actionName>\n            <packagings>\n                <packaging>war</packaging>\n                <packaging>ear</packaging>\n                <packaging>ejb</packaging>\n            </packagings>\n            <goals>\n                <goal>appengine:devserver</goal>\n            </goals>\n            <properties>\n                <netbeans.deploy>true</netbeans.deploy>\n            </properties>\n        </action>\n</actions>\n"
  },
  {
    "path": "appengine-standard-java8/kotlin-appengine-standard/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\nCopyright 2017 Google Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n-->\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\n  <modelVersion>4.0.0</modelVersion>\n  <packaging>war</packaging>\n  <version>1.0-SNAPSHOT</version>\n\n  <groupId>com.google.appengine.demos</groupId>\n  <artifactId>kotlin-appengine-standard</artifactId>\n\n  <!-- Parent POM defines common plugins and properties. -->\n  <parent>\n    <groupId>com.google.cloud.samples</groupId>\n    <artifactId>shared-configuration</artifactId>\n    <version>1.2.0</version>\n  </parent>\n\n  <properties>\n    <java.version>1.8</java.version>\n    <kotlin.version>1.3.72</kotlin.version>\n    <maven.compiler.target>1.8</maven.compiler.target>\n    <maven.compiler.source>1.8</maven.compiler.source>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.google.appengine</groupId>\n      <artifactId>appengine-api-1.0-sdk</artifactId>\n      <version>2.0.15</version>\n    </dependency>\n    <dependency>\n      <groupId>jstl</groupId>\n      <artifactId>jstl</artifactId>\n      <version>1.2</version>\n    </dependency>\n    <dependency>\n      <groupId>javax.servlet</groupId>\n      <artifactId>javax.servlet-api</artifactId>\n      <version>4.0.1</version>\n      <type>jar</type>\n    </dependency>\n    <dependency>\n      <groupId>org.jetbrains.kotlin</groupId>\n      <artifactId>kotlin-stdlib</artifactId>\n      <version>1.6.0</version>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <outputDirectory>target/${project.artifactId}-${project.version}/WEB-INF/classes</outputDirectory>\n    <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>\n    <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>\n    <plugins>\n      <plugin>\n        <artifactId>kotlin-maven-plugin</artifactId>\n        <groupId>org.jetbrains.kotlin</groupId>\n        <version>1.5.31</version>\n        <configuration/>\n        <executions>\n          <execution>\n            <id>compile</id>\n            <phase>compile</phase>\n            <goals>\n              <goal>compile</goal>\n            </goals>\n          </execution>\n          <execution>\n            <id>test-compile</id>\n            <phase>test-compile</phase>\n            <goals>\n              <goal>test-compile</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-war-plugin</artifactId>\n        <version>3.3.2</version>\n        <configuration>\n          <archiveClasses>true</archiveClasses>\n          <failOnMissingWebXml>false</failOnMissingWebXml>\n        </configuration>\n      </plugin>\n      <!-- [START cloudplugin] -->\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>appengine-maven-plugin</artifactId>\n        <version>2.4.4</version>\n        <configuration>\n          <!-- can be set w/ -DprojectId=myProjectId on command line -->\n          <projectId>GCLOUD_CONFIG</projectId>\n          <!-- set the GAE version or use \"GCLOUD_CONFIG\" for an autogenerated GAE version -->\n          <version>GCLOUD_CONFIG</version>\n        </configuration>\n      </plugin>\n      <!-- [END cloudplugin] -->\n      <plugin>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <version>3.11.0</version>\n        <configuration>\n          <source>1.8</source>\n          <target>1.8</target>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n\n</project>\n"
  },
  {
    "path": "appengine-standard-java8/kotlin-appengine-standard/src/main/kotlin/HomeController.kt",
    "content": "// See https://github.com/JetBrains/kotlin-examples/blob/master/LICENSE\npackage org.jetbrains.kotlin.demo\n\nimport javax.servlet.annotation.WebServlet\nimport javax.servlet.http.HttpServlet\nimport javax.servlet.http.HttpServletRequest\nimport javax.servlet.http.HttpServletResponse\n\n@WebServlet(name = \"Hello\", value = [\"/\"])\nclass HomeController : HttpServlet() {\n    override fun doGet(req: HttpServletRequest, res: HttpServletResponse) {\n        res.writer.write(\"Hello, World! I am a Servlet 3.1 running on Java8 App Engine Standard, and written in Kotlin...\")\n \n    }\n}\n"
  },
  {
    "path": "appengine-standard-java8/kotlin-appengine-standard/src/main/webapp/WEB-INF/appengine-web.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\nCopyright 2017 Google Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n-->\n<appengine-web-app xmlns=\"http://appengine.google.com/ns/1.0\">\n    <threadsafe>true</threadsafe>\n    <runtime>java8</runtime>\n    <system-properties>\n        <property name=\"java.util.logging.config.file\" value=\"WEB-INF/logging.properties\"/>\n    </system-properties>\n</appengine-web-app>\n"
  },
  {
    "path": "appengine-standard-java8/kotlin-appengine-standard/src/main/webapp/WEB-INF/logging.properties",
    "content": "# Copyright 2017 Google Inc.\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#     http://www.apache.org/licenses/LICENSE-2.0\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# A default java.util.logging configuration.\n# (All App Engine logging is through java.util.logging by default).\n#\n# To use this configuration, copy it into your application's WEB-INF\n# folder and add the following to your appengine-web.xml:\n# \n# <system-properties>\n#   <property name=\"java.util.logging.config.file\" value=\"WEB-INF/logging.properties\"/>\n# </system-properties>\n#\n\n# Set the default logging level for all loggers to WARNING\n.level = WARNING\n"
  },
  {
    "path": "appengine-standard-java8/kotlin-sb-appengine-standard/.gitignore",
    "content": "hotspot.log\n*.iml\n*.ipr\n*.iws\n.gradle/\nbuild/\ntarget/\nclasses/\n/var\npom.xml.versionsBackup\ntest-output/\n/atlassian-ide-plugin.xml\n.idea\n.DS_Store\n.classpath\n.settings\n.project\ntemp-testng-customsuite.xml\ntest-output\n.externalToolBuilders\n*~\n"
  },
  {
    "path": "appengine-standard-java8/kotlin-sb-appengine-standard/README.md",
    "content": "App Engine Java SpringBoot Kotlin application\n===\n\n## Sample SpringBoot application written in Kotlin for use with App Engine Java8 Standard.\n\nSee the [Google App Engine standard environment documentation][ae-docs] for more\ndetailed instructions.\n\n[ae-docs]: https://cloud.google.com/appengine/docs/java/\n\n* [Java 8](http://www.oracle.com/technetwork/java/javase/downloads/index.html)\n* [Maven](https://maven.apache.org/download.cgi) (at least 3.5)\n* [Google Cloud SDK](https://cloud.google.com/sdk/) (aka gcloud command line tool)\n\n## Setup\n\n* Download and initialize the [Cloud SDK](https://cloud.google.com/sdk/)\n\n```\ngcloud init\n```\n\n* Create an App Engine app within the current Google Cloud Project\n\n```\ngcloud app create\n```\n\n* In the `pom.xml`, update the [App Engine Maven Plugin](https://cloud.google.com/appengine/docs/standard/java/tools/maven-reference)\nwith your Google Cloud Project Id:\n\n```\n<plugin>\n  <groupId>com.google.cloud.tools</groupId>\n  <artifactId>appengine-maven-plugin</artifactId>\n  <version>2.3.0</version>\n  <configuration>\n    <projectId>GCLOUD_CONFIG</projectId>\n    <version>GCLOUD_CONFIG</version>\n  </configuration>\n</plugin>\n```\n**Note:** `GCLOUD_CONFIG` is a special version for autogenerating an App Engine\nversion. Change this field to specify a specific version name.\n\n## Maven\n### Running locally\n\n`mvn package appengine:run`\n\nTo use visit: http://localhost:8080/\n\n### Deploying\n\n`mvn package appengine:deploy`\n\nTo use visit:  https://YOUR-PROJECT-ID.appspot.com\n\n## Testing\n\n`mvn verify`\n\nAs you add / modify the source code (`src/main/java/...`) it's very useful to add [unit testing](https://cloud.google.com/appengine/docs/java/tools/localunittesting)\nto (`src/main/test/...`).  The following resources are quite useful:\n\n* [Junit4](http://junit.org/junit4/)\n* [Mockito](http://mockito.org/)\n* [Truth](http://google.github.io/truth/)\n\n\nFor further information, consult the\n[Java App Engine](https://developers.google.com/appengine/docs/java/overview) documentation.\n"
  },
  {
    "path": "appengine-standard-java8/kotlin-sb-appengine-standard/nbactions.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\nCopyright 2017 Google Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n-->\n<actions>\n    <action>\n        <actionName>CUSTOM-appengine:devserver</actionName>\n        <displayName>appengine:devserver</displayName>\n        <goals>\n            <goal>appengine:devserver</goal>\n        </goals>\n    </action>\n    <action>\n        <actionName>CUSTOM-appengine:update</actionName>\n        <displayName>appengine:update</displayName>\n        <goals>\n            <goal>appengine:update</goal>\n        </goals>\n    </action>\n    <action>\n        <actionName>CUSTOM-appengine:rollback</actionName>\n        <displayName>appengine:rollback</displayName>\n        <goals>\n            <goal>appengine:rollback</goal>\n        </goals>\n    </action>\n    <action>\n        <actionName>CUSTOM-appengine:update_cron</actionName>\n        <displayName>appengine:update_cron</displayName>\n        <goals>\n            <goal>appengine:update_cron</goal>\n        </goals>\n    </action>\n    <action>\n        <actionName>CUSTOM-appengine:update_dos</actionName>\n        <displayName>appengine:update_dos</displayName>\n        <goals>\n            <goal>appengine:update_dos</goal>\n        </goals>\n    </action>\n    <action>\n        <actionName>CUSTOM-appengine:update_indexes</actionName>\n        <displayName>appengine:update_indexes</displayName>\n        <goals>\n            <goal>appengine:update_indexes</goal>\n        </goals>\n    </action>\n    <action>\n        <actionName>CUSTOM-appengine:update_queues</actionName>\n        <displayName>appengine:update_queues</displayName>\n        <goals>\n            <goal>appengine:update_queues</goal>\n        </goals>\n    </action>\n    <action>\n            <actionName>run</actionName>\n            <packagings>\n                <packaging>war</packaging>\n                <packaging>ear</packaging>\n                <packaging>ejb</packaging>\n            </packagings>\n            <goals>\n                <goal>appengine:devserver</goal>\n            </goals>\n            <properties>\n                <netbeans.deploy>true</netbeans.deploy>\n            </properties>\n        </action>\n</actions>\n"
  },
  {
    "path": "appengine-standard-java8/kotlin-sb-appengine-standard/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\nCopyright 2017 Google Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n-->\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\n  <modelVersion>4.0.0</modelVersion>\n  <packaging>war</packaging>\n  <version>1.0-SNAPSHOT</version>\n\n  <groupId>com.google.appengine.demos</groupId>\n  <artifactId>kotlin-sb-appengine-standard</artifactId>\n\n  <parent>\n    <groupId>com.google.cloud.samples</groupId>\n    <artifactId>shared-configuration</artifactId>\n    <version>1.2.0</version>\n  </parent>\n\n  <properties>\n    <java.version>1.8</java.version>\n    <kotlin.version>1.6.0</kotlin.version>\n    <maven.compiler.target>1.8</maven.compiler.target>\n    <maven.compiler.source>1.8</maven.compiler.source>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.google.appengine</groupId>\n      <artifactId>appengine-api-1.0-sdk</artifactId>\n      <version>2.0.15</version>\n    </dependency>\n    <dependency>\n      <groupId>jstl</groupId>\n      <artifactId>jstl</artifactId>\n      <version>1.2</version>\n    </dependency>\n    <dependency>\n      <groupId>org.springframework.boot</groupId>\n      <artifactId>spring-boot-starter-web</artifactId>\n      <version>3.1.1</version>\n      <exclusions>\n        <exclusion>\n          <groupId>org.slf4j</groupId>\n          <artifactId>jul-to-slf4j</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.springframework.boot</groupId>\n          <artifactId>spring-boot-starter-tomcat</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>javax.servlet</groupId>\n      <artifactId>javax.servlet-api</artifactId>\n      <version>4.0.1</version>\n      <type>jar</type>\n    </dependency>\n    <dependency>\n      <groupId>org.jetbrains.kotlin</groupId>\n      <artifactId>kotlin-stdlib-jdk8</artifactId>\n      <version>${kotlin.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.jetbrains.kotlin</groupId>\n      <artifactId>kotlin-reflect</artifactId>\n      <version>${kotlin.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.jetbrains.kotlin</groupId>\n      <artifactId>kotlin-maven-allopen</artifactId>\n      <version>${kotlin.version}</version>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <outputDirectory>target/${project.artifactId}-${project.version}/WEB-INF/classes</outputDirectory>\n    <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>\n    <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>\n    <plugins>\n      <plugin>\n        <artifactId>kotlin-maven-plugin</artifactId>\n        <groupId>org.jetbrains.kotlin</groupId>\n        <version>${kotlin.version}</version>\n        <configuration>\n          <compilerPlugins>\n            <plugin>spring</plugin>\n          </compilerPlugins>\n          <jvmTarget>${java.version}</jvmTarget>\n        </configuration>\n        <executions>\n          <execution>\n            <id>compile</id>\n            <phase>compile</phase>\n            <goals>\n              <goal>compile</goal>\n            </goals>\n          </execution>\n          <execution>\n            <id>test-compile</id>\n            <phase>test-compile</phase>\n            <goals>\n              <goal>test-compile</goal>\n            </goals>\n          </execution>\n        </executions>\n          <dependencies>\n            <dependency>\n              <groupId>org.jetbrains.kotlin</groupId>\n              <artifactId>kotlin-maven-allopen</artifactId>\n              <version>${kotlin.version}</version>\n            </dependency>\n          </dependencies>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-war-plugin</artifactId>\n        <version>3.3.2</version>\n        <configuration>\n          <archiveClasses>true</archiveClasses>\n          <failOnMissingWebXml>false</failOnMissingWebXml>\n        </configuration>\n      </plugin>\n\n      <!-- [START cloudplugin] -->\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>appengine-maven-plugin</artifactId>\n        <version>2.4.4</version>\n        <configuration>\n          <!-- can be set w/ -DprojectId=myProjectId on command line -->\n          <projectId>GCLOUD_CONFIG</projectId>\n          <!-- set the GAE version or use \"GCLOUD_CONFIG\" for an autogenerated GAE version -->\n          <version>GCLOUD_CONFIG</version>\n        </configuration>\n      </plugin>\n      <!-- [END cloudplugin] -->\n      <plugin>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <version>3.11.0</version>\n        <configuration>\n          <source>${java.version}</source>\n          <target>${java.version}</target>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n\n</project>\n"
  },
  {
    "path": "appengine-standard-java8/kotlin-sb-appengine-standard/src/main/kotlin/org/jetbrains/kotlin/demo/Application.kt",
    "content": "// See https://github.com/JetBrains/kotlin-examples/blob/master/LICENSE\npackage org.jetbrains.kotlin.demo\n\nimport org.springframework.boot.SpringApplication\nimport org.springframework.boot.autoconfigure.SpringBootApplication\nimport org.springframework.boot.web.servlet.support.SpringBootServletInitializer\n\n@SpringBootApplication\nclass Application : SpringBootServletInitializer() {\n\n}\n\nfun main(args: Array<String>) {\n\tSpringApplication.run(Application::class.java, *args)\n}\n"
  },
  {
    "path": "appengine-standard-java8/kotlin-sb-appengine-standard/src/main/kotlin/org/jetbrains/kotlin/demo/Greeting.kt",
    "content": "// See https://github.com/JetBrains/kotlin-examples/blob/master/LICENSE\npackage org.jetbrains.kotlin.demo\n\ndata class Greeting(val id: Long, val content: String)\n"
  },
  {
    "path": "appengine-standard-java8/kotlin-sb-appengine-standard/src/main/kotlin/org/jetbrains/kotlin/demo/GreetingController.kt",
    "content": "// See https://github.com/JetBrains/kotlin-examples/blob/master/LICENSE\npackage org.jetbrains.kotlin.demo\n\nimport org.springframework.web.bind.annotation.GetMapping\nimport org.springframework.web.bind.annotation.RequestParam\nimport org.springframework.web.bind.annotation.RestController\nimport java.util.concurrent.atomic.AtomicLong\n\n@RestController\nclass GreetingController {\n\n    val counter = AtomicLong()\n\n    @GetMapping(\"/greeting\")\n    fun greeting(@RequestParam(value = \"name\", defaultValue = \"World\") name: String) =\n                Greeting(counter.incrementAndGet(), \"Hello, $name, from a SpringBoot Application written in Kotlin, running on Google App Engine Java8 Standard...\")\n}\n"
  },
  {
    "path": "appengine-standard-java8/kotlin-sb-appengine-standard/src/main/webapp/WEB-INF/appengine-web.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\nCopyright 2017 Google Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n-->\n<appengine-web-app xmlns=\"http://appengine.google.com/ns/1.0\">\n    <threadsafe>true</threadsafe>\n    <runtime>java8</runtime>\n    <system-properties>\n        <property name=\"java.util.logging.config.file\" value=\"WEB-INF/logging.properties\"/>\n    </system-properties>\n</appengine-web-app>\n"
  },
  {
    "path": "appengine-standard-java8/kotlin-sb-appengine-standard/src/main/webapp/WEB-INF/logging.properties",
    "content": "# Copyright 2017 Google Inc.\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#     http://www.apache.org/licenses/LICENSE-2.0\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# A default java.util.logging configuration.\n# (All App Engine logging is through java.util.logging by default).\n#\n# To use this configuration, copy it into your application's WEB-INF\n# folder and add the following to your appengine-web.xml:\n# \n# <system-properties>\n#   <property name=\"java.util.logging.config.file\" value=\"WEB-INF/logging.properties\"/>\n# </system-properties>\n#\n\n# Set the default logging level for all loggers to WARNING\n.level = WARNING\n"
  },
  {
    "path": "appengine-standard-java8/kotlin-spark-appengine-standard/.gitignore",
    "content": "hotspot.log\n*.iml\n*.ipr\n*.iws\n.gradle/\nbuild/\ntarget/\nclasses/\n/var\npom.xml.versionsBackup\ntest-output/\n/atlassian-ide-plugin.xml\n.idea\n.DS_Store\n.classpath\n.settings\n.project\ntemp-testng-customsuite.xml\ntest-output\n.externalToolBuilders\n*~\n"
  },
  {
    "path": "appengine-standard-java8/kotlin-spark-appengine-standard/README.md",
    "content": "App Engine SparkJava Kotlin with Java8\n===\n\n## Sample SparkJava application written in Kotlin for use with App Engine Java8 Standard.\n\nFor Spark Kotlin documentation, see [Spark Kotlin](https://github.com/perwendel/spark-kotlin/).\n\nSee the [Google App Engine standard environment documentation][ae-docs] for more\ndetailed instructions.\n\n[ae-docs]: https://cloud.google.com/appengine/docs/java/\n\n* [Java 8](http://www.oracle.com/technetwork/java/javase/downloads/index.html)\n* [Maven](https://maven.apache.org/download.cgi) (at least 3.5)\n* [Google Cloud SDK](https://cloud.google.com/sdk/) (aka gcloud command line tool)\n\n## Setup\n\n* Download and initialize the [Cloud SDK](https://cloud.google.com/sdk/)\n\n```\ngcloud init\n```\n\n* Create an App Engine app within the current Google Cloud Project\n\n```\ngcloud app create\n```\n\n* In the `pom.xml`, update the [App Engine Maven Plugin](https://cloud.google.com/appengine/docs/standard/java/tools/maven-reference)\nwith your Google Cloud Project Id:\n\n```\n<plugin>\n  <groupId>com.google.cloud.tools</groupId>\n  <artifactId>appengine-maven-plugin</artifactId>\n  <version>2.3.0</version>\n  <configuration>\n    <projectId>GCLOUD_CONFIG</projectId>\n    <version>GCLOUD_CONFIG</version>\n  </configuration>\n</plugin>\n```\n**Note:** `GCLOUD_CONFIG` is a special version for autogenerating an App Engine\nversion. Change this field to specify a specific version name.\n\n## Maven\n### Running locally\n\n`mvn package appengine:run`\n\nTo use visit: http://localhost:8080/\n\n### Deploying\n\n`mvn package appengine:deploy`\n\nTo use visit:  https://YOUR-PROJECT-ID.appspot.com\n\n## Testing\n\n`mvn verify`\n\nAs you add / modify the source code (`src/main/java/...`) it's very useful to add [unit testing](https://cloud.google.com/appengine/docs/java/tools/localunittesting)\nto (`src/main/test/...`).  The following resources are quite useful:\n\n* [Junit4](http://junit.org/junit4/)\n* [Mockito](http://mockito.org/)\n* [Truth](http://google.github.io/truth/)\n\n\nFor further information, consult the\n[Java App Engine](https://developers.google.com/appengine/docs/java/overview) documentation.\n"
  },
  {
    "path": "appengine-standard-java8/kotlin-spark-appengine-standard/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\nCopyright 2017 Google Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n-->\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\n  <modelVersion>4.0.0</modelVersion>\n  <packaging>war</packaging>\n  <version>1.0-SNAPSHOT</version>\n\n  <groupId>com.google.appengine.demos</groupId>\n  <artifactId>kotlin-spark-appengine-standard</artifactId>\n\n  <!-- Parent POM defines common plugins and properties. -->\n  <parent>\n    <groupId>com.google.cloud.samples</groupId>\n    <artifactId>shared-configuration</artifactId>\n    <version>1.2.0</version>\n  </parent>\n\n  <properties>\n    <maven.compiler.target>1.8</maven.compiler.target>\n    <maven.compiler.source>1.8</maven.compiler.source>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.google.appengine</groupId>\n      <artifactId>appengine-api-1.0-sdk</artifactId>\n      <version>2.0.15</version>\n    </dependency>\n    <dependency>\n      <groupId>jstl</groupId>\n      <artifactId>jstl</artifactId>\n      <version>1.2</version>\n    </dependency>\n    <dependency>\n      <groupId>javax.servlet</groupId>\n      <artifactId>javax.servlet-api</artifactId>\n      <version>4.0.1</version>\n      <type>jar</type>\n    </dependency>\n    <dependency>\n      <groupId>org.jetbrains.kotlin</groupId>\n      <artifactId>kotlin-stdlib-jre8</artifactId>\n      <version>1.2.71</version>\n    </dependency>\n    <dependency>\n      <groupId>com.sparkjava</groupId>\n      <artifactId>spark-kotlin</artifactId>\n      <version>1.0.0-alpha</version>\n      <exclusions>\n        <exclusion>\n          <groupId>org.eclipse.jetty</groupId>\n          <artifactId>jetty-server</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.eclipse.jetty</groupId>\n          <artifactId>jetty-io</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.eclipse.jetty</groupId>\n          <artifactId>jetty-security</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.eclipse.jetty</groupId>\n          <artifactId>jetty-webapp</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.eclipse.jetty</groupId>\n          <artifactId>jetty-util</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.eclipse.jetty</groupId>\n          <artifactId>jetty-client</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.eclipse.jetty.websocket</groupId>\n          <artifactId>websocket-api</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.eclipse.jetty.websocket</groupId>\n          <artifactId>websocket-client</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.eclipse.jetty.websocket</groupId>\n          <artifactId>websocket-common</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.eclipse.jetty.websocket</groupId>\n          <artifactId>websocket-server</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.slf4j</groupId>\n          <artifactId>slf4j-api</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>org.eclipse.jetty.websocket</groupId>\n          <artifactId>websocket-servlet</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>org.slf4j</groupId>\n      <artifactId>slf4j-api</artifactId>\n      <version>2.0.11</version> <!-- TEST if not 1.7.25 -->\n    </dependency>\n  </dependencies>\n\n  <build>\n    <outputDirectory>target/${project.artifactId}-${project.version}/WEB-INF/classes</outputDirectory>\n    <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>\n    <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>\n    <plugins>\n      <plugin>\n        <artifactId>kotlin-maven-plugin</artifactId>\n        <groupId>org.jetbrains.kotlin</groupId>\n        <version>1.5.31</version>\n        <configuration/>\n        <executions>\n          <execution>\n            <id>compile</id>\n            <phase>compile</phase>\n            <goals>\n              <goal>compile</goal>\n            </goals>\n          </execution>\n          <execution>\n            <id>test-compile</id>\n            <phase>test-compile</phase>\n            <goals>\n              <goal>test-compile</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-war-plugin</artifactId>\n        <version>3.3.2</version>\n        <configuration>\n          <archiveClasses>true</archiveClasses>\n        </configuration>\n      </plugin>\n      <!-- [START cloudplugin] -->\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>appengine-maven-plugin</artifactId>\n        <version>2.4.4</version>\n        <configuration>\n          <!-- can be set w/ -DprojectId=myProjectId on command line -->\n          <projectId>GCLOUD_CONFIG</projectId>\n          <!-- set the GAE version or use \"GCLOUD_CONFIG\" for an autogenerated GAE version -->\n          <version>GCLOUD_CONFIG</version>\n        </configuration>\n      </plugin>\n      <!-- [END cloudplugin] -->\n      <plugin>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <version>3.11.0</version>\n        <configuration>\n          <source>1.8</source>\n          <target>1.8</target>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n\n</project>\n"
  },
  {
    "path": "appengine-standard-java8/kotlin-spark-appengine-standard/src/main/kotlin/MainApp.kt",
    "content": "/**\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport spark.kotlin.Http\nimport spark.kotlin.ignite\nimport spark.servlet.SparkApplication\n\n/**\n * Example usage of spark-kotlin.\n * See https://github.com/perwendel/spark-kotlin\n */\nclass MainApp : SparkApplication {\n    override fun init() {\n        \n        val http: Http = ignite()\n\n        http.get(\"/\") {\n            \"\"\"Hello Spark Kotlin running on Java8 App Engine Standard.\n            <p>You can try /hello<p> or /saymy/:name<p> or redirect\n            <p>or /nothing\"\"\"\n        }\n        http.get(\"/hello\") {\n            \"Hello Spark Kotlin running on Java8 App Engine Standard.\"\n        }\n\n        http.get(\"/nothing\") {\n            status(404)\n            \"Oops, we couldn't find what you're looking for.\"\n        }\n\n        http.get(\"/saymy/:name\") {\n            params(\":name\")\n        }\n\n        http.get(\"/redirect\") {\n            redirect(\"/hello\");\n        }\n    }\n}"
  },
  {
    "path": "appengine-standard-java8/kotlin-spark-appengine-standard/src/main/kotlin/SparkInitFilter.kt",
    "content": "/**\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport javax.servlet.annotation.WebFilter\nimport javax.servlet.annotation.WebInitParam\n\nimport spark.servlet.SparkFilter\n\n\n// Use Servlet annotation to define the Spark filter without web.xml:\n@WebFilter(\n        filterName = \"SparkInitFilter\",\n        urlPatterns = arrayOf(\"/*\"),\n        initParams = arrayOf(\n                WebInitParam(\n                        name = \"applicationClass\",\n                        value = \"MainApp\")\n\n        ))\nclass SparkInitFilter : SparkFilter() {\n}\n"
  },
  {
    "path": "appengine-standard-java8/kotlin-spark-appengine-standard/src/main/webapp/WEB-INF/appengine-web.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\nCopyright 2017 Google Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n-->\n<appengine-web-app xmlns=\"http://appengine.google.com/ns/1.0\">\n    <threadsafe>true</threadsafe>\n    <runtime>java8</runtime>\n    <system-properties>\n        <property name=\"java.util.logging.config.file\" value=\"WEB-INF/logging.properties\"/>\n    </system-properties>\n</appengine-web-app>\n"
  },
  {
    "path": "appengine-standard-java8/kotlin-spark-appengine-standard/src/main/webapp/WEB-INF/logging.properties",
    "content": "# Copyright 2017 Google Inc.\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#     http://www.apache.org/licenses/LICENSE-2.0\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# A default java.util.logging configuration.\n# (All App Engine logging is through java.util.logging by default).\n#\n# To use this configuration, copy it into your application's WEB-INF\n# folder and add the following to your appengine-web.xml:\n# \n# <system-properties>\n#   <property name=\"java.util.logging.config.file\" value=\"WEB-INF/logging.properties\"/>\n# </system-properties>\n#\n\n# Set the default logging level for all loggers to WARNING\n.level = WARNING\n"
  },
  {
    "path": "background/README.md",
    "content": "# Background Processing App on Cloud Run Tutorial\n\nContains the code for using Cloud Firestore, Cloud Translate, and Cloud Pub/Sub.\n\nThis is part of the [getting started experience](https://cloud.google.com/java/getting-started).\n\n### Running Locally\n\nTo run your project locally:\n\n* Choose a Pub/Sub Topic Name and generate a Pub/Sub Verification Token using `uuidgen` or an\n  online UUID generator such as [uuidgenerator.net](https://www.uuidgenerator.net/).\n\n      export PUBSUB_TOPIC=<your-topic-name>\n      export PUBSUB_VERIFICATION_TOKEN=<your-verification-token>\n      export FIRESTORE_CLOUD_PROJECT=<your-project-id>\n* Create a Pub/Sub topic:\n\n      gcloud pubsub topics create $PUBSUB_TOPIC\n\n* Run with the Jetty Maven plugin:\n\n      mvn jetty:run-war\n\n**Note**: If you run into an error about `Invalid Credentials`, you may have to run:\n\n    gcloud auth application-default login\n    \n* Navigate to http://localhost:8080/\n* Click `+ Request Translation`, and fill out the form using a phrase, a source language code (\"en\"\n  for English) and a target language code (e.g. \"es\" for Spanish).\n* Click `Submit`. This will submit the request to your Pub/Sub topic and redirect you back to the\n  list page.\n\nYou will see that nothing has changed. This because there is no subscription on that Pub/Sub topic\nyet. Since you can't set up a Pub/Sub push subscription to post requests to `localhost`, you can\ninstead send a manual request with `curl` (from a second terminal, in the \n`getting-started-java/background` directory):\n\n    curl -H \"Content-Type: application/json\" -i --data @sample_message.json \\\n       \"localhost:8080/pubsub/push?token=$PUBSUB_VERIFICATION_TOKEN\"\n       \nRefresh `http://localhost:8080` now and you will see a translated entry in the list.\n\n### Deploying to Cloud Run\n\nTo build your image:\n\n* Update the parameters in `pom.xml`:\n  * Replace `MY_PROJECT` with your project ID.\n* Build and deploy to your GCR with the [Jib][jib] Maven plugin.\n\n      mvn clean package jib:build\n* Deploy the app to Cloud Run:\n\n      gcloud beta run deploy background --image gcr.io/<MY_PROJECT>/background \\\n            --platform managed --region us-central1 --memory 512M \\\n            --update-env-vars PUBSUB_TOPIC=$PUBSUB_TOPIC,PUBSUB_VERIFICATION_TOKEN=$PUBSUB_VERIFICATION_TOKEN\n\n  Where <MY_PROJECT> is the name of the project you created.\n* Create a Pub/Sub Subscription that will send requests to the Cloud Run endpoint created\n  with the previous command:\n      \n      gcloud pubsub subscriptions create <your-subscription-name> \\\n            --topic $PUBSUB_TOPIC --push-endpoint \\\n            <CLOUD_RUN_ENDPOINT>/translate?token=$PUBSUB_VERIFICATION_TOKEN \\\n            --ack-deadline 30\n            \n  This command will output a link to visit the page, hereafter called <CLOUD_RUN_ENDPOINT>.\n* Now fill out the `+ Request Translation` form again, this time at <CLOUD_RUN_ENDPOINT>. When you\n  click `Submit` it will redirect you back to /translate.\n  * The new request will take a moment to show, so refresh after a minute or two.\n  \n[jib]: https://github.com/GoogleContainerTools/jib\n  \n### Architecture\n\nThe flow of translation requests fits together as such:\n\n* When the `+ Request Translation` form is submitted, it posts a message to the Pub/Sub topic you\n  created with the Text as (encoded) data, and the source/target language codes as attributes.\n* The Subscription you created receives this data and pushes it to the Cloud Run endpoint (with a\n  POST request to /translate).\n* The /translate endpoint processes POST requests (that include the correct\n  PUBSUB_VERIFICATION_TOKEN) by performing the Translate request and saving the result in Firestore.\n* When you visit the Cloud Run endpoint, it reads the past 10 requests from Firestore and shows them\n  in a table at the `/` or `/translate` endpoints.\n"
  },
  {
    "path": "background/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\nCopyright 2019 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<project>\n  <modelVersion>4.0.0</modelVersion>\n  <packaging>war</packaging>\n\n  <groupId>com.example.getstarted</groupId>\n  <artifactId>background-processing</artifactId>\n  <version>1.0-SNAPSHOT</version>\n\n  <parent>\n    <groupId>com.google.cloud.samples</groupId>\n    <artifactId>shared-configuration</artifactId>\n    <version>1.2.0</version>\n  </parent>\n\n  <properties>\n    <gcloud.appId>MY_PROJECT</gcloud.appId>\n\n    <failOnMissingWebXml>false</failOnMissingWebXml>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <maven.compiler.source>1.8</maven.compiler.source>\n    <maven.compiler.target>1.8</maven.compiler.target>\n    <maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>\n    <maven.compiler.showWarnings>true</maven.compiler.showWarnings>\n    <maven.compiler.failOnWarning>false</maven.compiler.failOnWarning>\n    <maven.war.filteringDeploymentDescriptors>false</maven.war.filteringDeploymentDescriptors>\n    <jetty.version>9.4.51.v20230217</jetty.version>\n  </properties>\n\n  <dependencies>\n    <!-- [START getting_started_background_processing_dependencies] -->\n    <dependency>\n      <groupId>com.google.cloud</groupId>\n      <artifactId>google-cloud-firestore</artifactId>\n      <version>3.13.2</version>\n    </dependency>\n    <dependency>\n      <groupId>com.google.cloud</groupId>\n      <artifactId>google-cloud-translate</artifactId>\n      <version>2.20.0</version>\n    </dependency>\n\n    <dependency>\n      <groupId>com.google.cloud</groupId>\n      <artifactId>google-cloud-pubsub</artifactId>\n      <version>1.123.17</version>\n    </dependency>\n    <!-- [END getting_started_background_processing_dependencies] -->\n\n    <dependency>\n      <groupId>javax.servlet</groupId>\n      <artifactId>javax.servlet-api</artifactId>\n      <version>4.0.1</version>\n    </dependency>\n\n    <dependency>\n      <groupId>com.google.guava</groupId>\n      <artifactId>guava</artifactId>\n      <version>33.1.0-jre</version>\n      <scope>compile</scope>\n    </dependency>\n\n    <dependency>\n      <groupId>io.opencensus</groupId>\n      <artifactId>opencensus-contrib-http-util</artifactId>\n      <version>0.31.1</version>\n    </dependency>\n\n    <dependency>\n      <groupId>jstl</groupId>\n      <artifactId>jstl</artifactId>\n      <version>1.2</version>\n    </dependency>\n\n    <!-- Test dependencies -->\n    <dependency>\n      <groupId>junit</groupId>\n      <artifactId>junit</artifactId>\n      <version>4.13.2</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.seleniumhq.selenium</groupId>\n      <artifactId>selenium-server</artifactId>\n      <version>4.0.0-alpha-2</version>\n    </dependency>\n    <dependency>\n      <groupId>org.seleniumhq.selenium</groupId>\n      <artifactId>selenium-chrome-driver</artifactId>\n      <version>4.10.0</version>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <finalName>background-processing</finalName>\n    <!-- Optional - for hot reload of the web application when using an IDE Eclipse / IDEA -->\n    <outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes\n    </outputDirectory>\n    <plugins>\n      <plugin>\n        <groupId>org.eclipse.jetty</groupId>\n        <artifactId>jetty-maven-plugin</artifactId>\n        <version>${jetty.version}</version>\n      </plugin>\n\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>3.4.0</version>\n        <configuration>\n          <to>\n            <image>gcr.io/${gcloud.appId}/background</image>\n          </to>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "background/sample_message.json",
    "content": "{\n  \"message\":{\n    \"data\":\"Hello world!\",\n    \"attributes\":{\n      \"sourceLang\":\"en\",\n      \"targetLang\":\"es\"\n    },\n    \"messageId\":\"181789827785065\",\n    \"publishTime\":\"2018-01-06T00:41:01.839Z\"\n  }\n}\n"
  },
  {
    "path": "background/src/main/java/com/getstarted/background/functions/CreateServlet.java",
    "content": "/* Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.getstarted.background.functions;\n\nimport com.google.cloud.pubsub.v1.Publisher;\nimport com.google.protobuf.ByteString;\nimport com.google.pubsub.v1.PubsubMessage;\nimport java.io.IOException;\nimport java.util.Enumeration;\nimport java.util.concurrent.ExecutionException;\nimport java.util.logging.Logger;\nimport javax.servlet.ServletException;\nimport javax.servlet.annotation.WebServlet;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n/** Servlet for the Translation Request form. */\n@WebServlet(\n    name = \"create\",\n    urlPatterns = {\"/create\"})\npublic class CreateServlet extends HttpServlet {\n  private static Logger logger = Logger.getLogger(CreateServlet.class.getName());\n\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp)\n      throws ServletException, IOException {\n    req.setAttribute(\"action\", \"Add\");\n    req.setAttribute(\"destination\", \"create\");\n    req.setAttribute(\"page\", \"form\");\n    req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n  }\n\n  // [START getting_started_background_app_request]\n  @Override\n  public void doPost(HttpServletRequest req, HttpServletResponse resp)\n      throws ServletException, IOException {\n    String text = req.getParameter(\"data\");\n    String sourceLang = req.getParameter(\"sourceLang\");\n    String targetLang = req.getParameter(\"targetLang\");\n\n    Enumeration<String> paramNames = req.getParameterNames();\n    while (paramNames.hasMoreElements()) {\n      String paramName = paramNames.nextElement();\n      logger.warning(\"Param name: \" + paramName + \" = \" + req.getParameter(paramName));\n    }\n\n    Publisher publisher = (Publisher) getServletContext().getAttribute(\"publisher\");\n\n    PubsubMessage pubsubMessage =\n        PubsubMessage.newBuilder()\n            .setData(ByteString.copyFromUtf8(text))\n            .putAttributes(\"sourceLang\", sourceLang)\n            .putAttributes(\"targetLang\", targetLang)\n            .build();\n\n    try {\n      publisher.publish(pubsubMessage).get();\n    } catch (InterruptedException | ExecutionException e) {\n      throw new ServletException(\"Exception publishing message to topic.\", e);\n    }\n\n    resp.sendRedirect(\"/\");\n  }\n  // [END getting_started_background_app_request]\n}\n"
  },
  {
    "path": "background/src/main/java/com/getstarted/background/functions/TranslateServlet.java",
    "content": "/* Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.getstarted.background.functions;\n\nimport com.getstarted.background.objects.PubSubMessage;\nimport com.getstarted.background.objects.TranslateMessage;\nimport com.google.api.core.ApiFuture;\nimport com.google.cloud.firestore.CollectionReference;\nimport com.google.cloud.firestore.DocumentSnapshot;\nimport com.google.cloud.firestore.Firestore;\nimport com.google.cloud.firestore.QueryDocumentSnapshot;\nimport com.google.cloud.firestore.QuerySnapshot;\nimport com.google.cloud.firestore.SetOptions;\nimport com.google.cloud.firestore.WriteResult;\nimport com.google.cloud.translate.Translate;\nimport com.google.cloud.translate.Translation;\nimport com.google.common.collect.Lists;\nimport com.google.gson.Gson;\nimport java.io.IOException;\nimport java.io.UnsupportedEncodingException;\nimport java.util.Base64;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.concurrent.ExecutionException;\nimport java.util.stream.Collectors;\nimport javax.servlet.ServletException;\nimport javax.servlet.annotation.WebServlet;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n@WebServlet(\n    name = \"translate\",\n    urlPatterns = {\"/\", \"/translate\"})\npublic class TranslateServlet extends HttpServlet {\n  private static final Gson gson = new Gson();\n  private static final String PUBSUB_VERIFICATION_TOKEN =\n      System.getenv(\"PUBSUB_VERIFICATION_TOKEN\");\n\n  // [START getting_started_background_app_list]\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp)\n      throws ServletException, IOException {\n    Firestore firestore = (Firestore) this.getServletContext().getAttribute(\"firestore\");\n    CollectionReference translations = firestore.collection(\"translations\");\n    QuerySnapshot snapshot;\n    try {\n      snapshot = translations.limit(10).get().get();\n    } catch (InterruptedException | ExecutionException e) {\n      throw new ServletException(\"Exception retrieving documents from Firestore.\", e);\n    }\n    List<TranslateMessage> translateMessages = Lists.newArrayList();\n    List<QueryDocumentSnapshot> documents = Lists.newArrayList(snapshot.getDocuments());\n    documents.sort(Comparator.comparing(DocumentSnapshot::getCreateTime));\n\n    for (DocumentSnapshot document : Lists.reverse(documents)) {\n      String encoded = gson.toJson(document.getData());\n      TranslateMessage message = gson.fromJson(encoded, TranslateMessage.class);\n      message.setData(decode(message.getData()));\n      translateMessages.add(message);\n    }\n    req.setAttribute(\"messages\", translateMessages);\n    req.setAttribute(\"page\", \"list\");\n    req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n  }\n  // [END getting_started_background_app_list]\n\n  /**\n   * Handle a posted message from Pubsub.\n   *\n   * @param req The message Pubsub posts to this process.\n   * @param resp Not used.\n   */\n  @Override\n  public void doPost(HttpServletRequest req, HttpServletResponse resp)\n      throws IOException, ServletException {\n\n    // Block requests that don't contain the proper verification token.\n    String pubsubVerificationToken = PUBSUB_VERIFICATION_TOKEN;\n    if (req.getParameter(\"token\").compareTo(pubsubVerificationToken) != 0) {\n      resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);\n      return;\n    }\n\n    // [START getting_started_background_translate_string]\n    String body = req.getReader().lines().collect(Collectors.joining(System.lineSeparator()));\n\n    PubSubMessage pubsubMessage = gson.fromJson(body, PubSubMessage.class);\n    TranslateMessage message = pubsubMessage.getMessage();\n\n    // Use Translate service client to translate the message.\n    Translate translate = (Translate) this.getServletContext().getAttribute(\"translate\");\n    message.setData(decode(message.getData()));\n    Translation translation =\n        translate.translate(\n            message.getData(),\n            Translate.TranslateOption.sourceLanguage(message.getAttributes().getSourceLang()),\n            Translate.TranslateOption.targetLanguage(message.getAttributes().getTargetLang()));\n    // [END getting_started_background_translate_string]\n\n    message.setTranslatedText(translation.getTranslatedText());\n\n    try {\n      // [START getting_started_background_translate]\n      // Use Firestore service client to store the translation in Firestore.\n      Firestore firestore = (Firestore) this.getServletContext().getAttribute(\"firestore\");\n\n      CollectionReference translations = firestore.collection(\"translations\");\n\n      ApiFuture<WriteResult> setFuture = translations.document().set(message, SetOptions.merge());\n\n      setFuture.get();\n      resp.getWriter().write(translation.getTranslatedText());\n      // [END getting_started_background_translate]\n    } catch (InterruptedException | ExecutionException e) {\n      throw new ServletException(\"Exception storing data in Firestore.\", e);\n    }\n  }\n\n  private String decode(String data) throws UnsupportedEncodingException {\n    return new String(Base64.getDecoder().decode(data), \"UTF-8\");\n  }\n}\n"
  },
  {
    "path": "background/src/main/java/com/getstarted/background/objects/PubSubMessage.java",
    "content": "/* Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.getstarted.background.objects;\n\n// [START getting_started_background_pub_sub_message]\npublic class PubSubMessage {\n  private TranslateMessage message;\n  private String subscription;\n\n  public TranslateMessage getMessage() {\n    return message;\n  }\n\n  public void setMessage(TranslateMessage message) {\n    this.message = message;\n  }\n\n  public String getSubscription() {\n    return subscription;\n  }\n\n  public void setSubscription(String subscription) {\n    this.subscription = subscription;\n  }\n}\n// [END getting_started_background_pub_sub_message]\n"
  },
  {
    "path": "background/src/main/java/com/getstarted/background/objects/TranslateAttributes.java",
    "content": "/* Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.getstarted.background.objects;\n\n// [START getting_started_background_translate_attributes]\npublic class TranslateAttributes {\n  private String sourceLang;\n  private String targetLang;\n\n  public String getSourceLang() {\n    return sourceLang;\n  }\n\n  public void setSourceLang(String sourceLang) {\n    this.sourceLang = sourceLang;\n  }\n\n  public String getTargetLang() {\n    return targetLang;\n  }\n\n  public void setTargetLang(String targetLang) {\n    this.targetLang = targetLang;\n  }\n}\n// [END getting_started_background_translate_attributes]\n"
  },
  {
    "path": "background/src/main/java/com/getstarted/background/objects/TranslateMessage.java",
    "content": "/* Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.getstarted.background.objects;\n\n// [START getting_started_background_translate_message]\npublic class TranslateMessage {\n  public String data;\n  public TranslateAttributes attributes;\n  public String messageId;\n  public String publishTime;\n  public String translatedText;\n  public String sourceLang = \"en\";\n  public String targetLang = \"en\";\n\n  public String getData() {\n    return data;\n  }\n\n  public void setData(String data) {\n    this.data = data;\n  }\n\n  public TranslateAttributes getAttributes() {\n    return attributes;\n  }\n\n  public void setAttributes(TranslateAttributes attributes) {\n    this.attributes = attributes;\n  }\n\n  public String getMessageId() {\n    return messageId;\n  }\n\n  public void setMessageId(String messageId) {\n    this.messageId = messageId;\n  }\n\n  public String getPublishTime() {\n    return publishTime;\n  }\n\n  public void setPublishTime(String publishTime) {\n    this.publishTime = publishTime;\n  }\n\n  public String getTranslatedText() {\n    return translatedText;\n  }\n\n  public void setTranslatedText(String translatedText) {\n    this.translatedText = translatedText;\n  }\n\n  public String getSourceLang() {\n    return sourceLang;\n  }\n\n  public void setSourceLang(String sourceLang) {\n    this.sourceLang = sourceLang;\n  }\n\n  public String getTargetLang() {\n    return targetLang;\n  }\n\n  public void setTargetLang(String targetLang) {\n    this.targetLang = targetLang;\n  }\n}\n// [END getting_started_background_translate_message]\n"
  },
  {
    "path": "background/src/main/java/com/getstarted/background/util/BackgroundContextListener.java",
    "content": "/* Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.getstarted.background.util;\n\nimport com.google.cloud.ServiceOptions;\nimport com.google.cloud.firestore.Firestore;\nimport com.google.cloud.firestore.FirestoreOptions;\nimport com.google.cloud.pubsub.v1.Publisher;\nimport com.google.cloud.translate.Translate;\nimport com.google.cloud.translate.TranslateOptions;\nimport com.google.pubsub.v1.TopicName;\nimport java.io.IOException;\nimport javax.servlet.ServletContextEvent;\nimport javax.servlet.ServletContextListener;\nimport javax.servlet.annotation.WebListener;\n\n// [START background_context_listener]\n@WebListener(\"Creates Firestore and TranslateServlet service clients for reuse between requests.\")\npublic class BackgroundContextListener implements ServletContextListener {\n  @Override\n  public void contextDestroyed(javax.servlet.ServletContextEvent event) {}\n\n  @Override\n  public void contextInitialized(ServletContextEvent event) {\n    String firestoreProjectId = System.getenv(\"FIRESTORE_CLOUD_PROJECT\");\n    Firestore firestore = (Firestore) event.getServletContext().getAttribute(\"firestore\");\n    if (firestore == null) {\n      firestore =\n          FirestoreOptions.getDefaultInstance().toBuilder()\n              .setProjectId(firestoreProjectId)\n              .build()\n              .getService();\n      event.getServletContext().setAttribute(\"firestore\", firestore);\n    }\n\n    Translate translate = (Translate) event.getServletContext().getAttribute(\"translate\");\n    if (translate == null) {\n      translate = TranslateOptions.getDefaultInstance().getService();\n      event.getServletContext().setAttribute(\"translate\", translate);\n    }\n\n    String topicId = System.getenv(\"PUBSUB_TOPIC\");\n    TopicName topicName = TopicName.of(firestoreProjectId, topicId);\n    Publisher publisher = (Publisher) event.getServletContext().getAttribute(\"publisher\");\n    if (publisher == null) {\n      try {\n        publisher = Publisher.newBuilder(topicName).build();\n        event.getServletContext().setAttribute(\"publisher\", publisher);\n      } catch (IOException e) {\n        e.printStackTrace();\n      }\n    }\n  }\n}\n// [END background_context_listener]\n"
  },
  {
    "path": "background/src/main/webapp/base.jsp",
    "content": "<!--\nCopyright 2019 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START getting_started_background_jsp_base] -->\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\" prefix=\"fn\" %>\n<html lang=\"en\">\n  <head>\n    <title>Background Processing - Java on Google Cloud Platform</title>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link rel=\"stylesheet\" href=\"//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css\">\n  </head>\n  <body>\n    <div class=\"navbar navbar-default\">\n      <div class=\"container\">\n        <div class=\"navbar-header\">\n          <div class=\"navbar-brand\">Background Processing</div>\n        </div>\n      </div>\n    </div>\n    <c:import url=\"/${page}.jsp\" />\n  </body>\n</html>\n<!-- [END getting_started_background_jsp_base]-->\n"
  },
  {
    "path": "background/src/main/webapp/form.jsp",
    "content": "<!--\nCopyright 2019 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START getting_started_background_jsp_form] -->\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\"%>\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\" prefix=\"fn\"%>\n<div class=\"container\">\n  <h3>\n    <c:out value=\"${action}\" /> book\n  </h3>\n\n  <form method=\"POST\" action=\"${destination}\">\n\n    <div class=\"form-group\">\n      <label for=\"data\">Text</label>\n      <input type=\"text\" name=\"data\" id=\"data\" class=\"form-control\" />\n    </div>\n\n    <div class=\"form-group\">\n      <h4><a href=\"https://cloud.google.com/translate/docs/languages\">See language codes</a></h4>\n      <label for=\"sourceLang\">Source Language Code</label>\n      <input type=\"text\" name=\"sourceLang\" id=\"sourceLang\" class=\"form-control\" />\n    </div>\n    <div class=\"form-group\">\n      <label for=\"targetLang\">Target Language Code</label>\n      <input type=\"text\" name=\"targetLang\" id=\"targetLang\" class=\"form-control\" />\n    </div>\n\n    <button type=\"submit\" class=\"btn btn-success\">Submit</button>\n  </form>\n</div>\n<!-- [END getting_started_background_jsp_form] -->\n"
  },
  {
    "path": "background/src/main/webapp/list.jsp",
    "content": "<!--\nCopyright 2019 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START getting_started_background_jsp_list] -->\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\" prefix=\"fn\" %>\n<style>\ntable, th, td {\n  border: 1px solid black;\n  padding: 5px;\n}\n</style>\n<div class=\"container\">\n  <h3>Translations</h3>\n  <a href=\"/create\" class=\"btn btn-success btn-sm\">\n    <i class=\"glyphicon glyphicon-plus\"></i>\n    Request translation\n  </a>\n  <c:choose>\n  <c:when test=\"${empty messages}\">\n  <p>No translations found.</p>\n  </c:when>\n  <c:otherwise>\n  <table>\n    <tr>\n      <th>Timestamp</th>\n      <th>Message</th>\n      <th>Source Language</th>\n      <th>Target Language</th>\n      <th>Translation</th>\n    </tr>\n    <c:forEach items=\"${messages}\" var=\"message\">\n    <tr>\n      <td>${message.publishTime}</td>\n      <td>${fn:escapeXml(message.data)}</td>\n      <td>${message.attributes.sourceLang}</td>\n      <td>${message.attributes.targetLang}</td>\n      <td>${fn:escapeXml(message.translatedText)}</td>\n    </tr>\n    </c:forEach>\n  </table>\n  <c:if test=\"${not empty cursor}\">\n  <nav>\n    <ul class=\"pager\">\n      <li><a href=\"?cursor=${fn:escapeXml(cursor)}\">More</a></li>\n    </ul>\n  </nav>\n  </c:if>\n  </c:otherwise>\n  </c:choose>\n</div>\n<!-- [END getting_started_background_jsp_list] -->\n"
  },
  {
    "path": "background/src/test/java/com/getstarted/background/UserJourneyTestIT.java",
    "content": "/* Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.getstarted.background;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\nimport com.google.cloud.firestore.Firestore;\nimport com.google.cloud.firestore.FirestoreOptions;\nimport com.google.cloud.firestore.QueryDocumentSnapshot;\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.concurrent.ExecutionException;\nimport org.junit.After;\nimport org.junit.AfterClass;\nimport org.junit.Before;\nimport org.junit.BeforeClass;\nimport org.junit.Ignore;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\nimport org.openqa.selenium.By;\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.WebElement;\nimport org.openqa.selenium.chrome.ChromeDriver;\nimport org.openqa.selenium.chrome.ChromeDriverService;\nimport org.openqa.selenium.remote.service.DriverService;\nimport org.openqa.selenium.support.ui.ExpectedConditions;\nimport org.openqa.selenium.support.ui.WebDriverWait;\n\n/*\n * I can't figure out how to test server-side logging in a selenium test, so just confidence-check\n * that it hasn't broken anything.\n */\n@RunWith(JUnit4.class)\n@SuppressWarnings(\"checkstyle:AbbreviationAsWordInName\")\n@Ignore(\"Issue #498\")\npublic class UserJourneyTestIT {\n\n  private static final String TEXT = \"Hello World!\";\n  private static final String SOURCE_LANG_CODE = \"en\";\n  private static final String TARGET_LANG_CODE = \"es\";\n\n  private static DriverService service;\n  private WebDriver driver;\n\n  @BeforeClass\n  public static void setupClass() throws Exception {\n    service = ChromeDriverService.createDefaultService();\n    service.start();\n  }\n\n  @AfterClass\n  public static void tearDownClass() throws ExecutionException, InterruptedException {\n    // Clear the firestore list if we're not using the local emulator\n    Firestore firestore = FirestoreOptions.getDefaultInstance().getService();\n    for (QueryDocumentSnapshot docSnapshot :\n        firestore.collection(\"translations\").get().get().getDocuments()) {\n      try {\n        docSnapshot.getReference().delete().get();\n      } catch (InterruptedException | ExecutionException e) {\n        e.printStackTrace();\n      }\n    }\n\n    service.stop();\n  }\n\n  @Before\n  public void setup() {\n    driver = new ChromeDriver();\n  }\n\n  @After\n  public void tearDown() {\n    driver.quit();\n  }\n\n  private WebElement checkLandingPage() {\n    WebElement button = driver.findElement(By.cssSelector(\"a.btn\"));\n    assertEquals(\"Request Translation\", button.getText());\n\n    WebElement heading = driver.findElement(By.cssSelector(\"body>.container h3\"));\n    assertEquals(\"Translations\", heading.getText());\n\n    List<WebElement> list = driver.findElements(By.cssSelector(\"body>.container tr\"));\n    assertEquals(\"Should be no entries in translation list.\", 1, list.size());\n\n    return button;\n  }\n\n  private void checkRequestTranslationPage() {\n    List<WebElement> inputContainers = driver.findElements(By.cssSelector(\"form .form-group\"));\n    assertTrue(\"Should have more than 2 inputs\", inputContainers.size() > 2);\n    assertEquals(\n        \"First input should be Text\",\n        \"Text\",\n        inputContainers.get(0).findElement(By.tagName(\"label\")).getText());\n    assertEquals(\n        \"Second input should be Source Language Code\",\n        \"Source Language Code\",\n        inputContainers.get(1).findElement(By.tagName(\"label\")).getText());\n    assertEquals(\n        \"Third input should be Target Language Code\",\n        \"Target Language Code\",\n        inputContainers.get(2).findElement(By.tagName(\"label\")).getText());\n  }\n\n  private void submitForm() {\n    driver.findElement(By.cssSelector(\"[name=data]\")).sendKeys(UserJourneyTestIT.TEXT);\n    driver\n        .findElement(By.cssSelector(\"[name=sourceLang]\"))\n        .sendKeys(UserJourneyTestIT.SOURCE_LANG_CODE);\n    driver\n        .findElement(By.cssSelector(\"[name=targetLang]\"))\n        .sendKeys(UserJourneyTestIT.TARGET_LANG_CODE);\n\n    driver.findElement(By.cssSelector(\"button[type=submit]\")).submit();\n  }\n\n  @Test\n  public void userJourney() {\n    // Do selenium tests on the deployed version, if applicable\n    String endpoint = \"http://localhost:8080\";\n    System.out.println(\"Testing endpoint: \" + endpoint);\n    driver.get(endpoint);\n\n    try {\n      WebElement button = checkLandingPage();\n\n      button.click();\n      new WebDriverWait(driver, 10L) // 10 seconds\n          .until(ExpectedConditions.urlMatches(\".*/create$\")::apply);\n\n      checkRequestTranslationPage();\n\n      submitForm();\n      new WebDriverWait(driver, 10L)  // 10 seconds\n          .until(ExpectedConditions.urlMatches(\".*/\")::apply);\n    } catch (Exception e) {\n      System.err.println(driver.getPageSource());\n      throw e;\n    }\n  }\n}\n"
  },
  {
    "path": "bookshelf/1-cloud-run/README.md",
    "content": "# Bookshelf App for Java on Cloud Run Tutorial\n\nContains the code for using Cloud Firestore.\n\nThis is part of a [Bookshelf tutorial](https://cloud.google.com/java/getting-started).\n\nMost users can get this running by updating the parameters in `pom.xml`. You'll\nalso need to [create a bucket][create-bucket] in Google Cloud Storage, referred\nto below as `MY_BUCKET`.\n\n[create-bucket]: https://cloud.google.com/storage/docs/creating-buckets\n\n### Running Locally\n\nTo run your project locally:\n\n* Set the `BOOKSHELF_BUCKET` environment variable:\n\n      export BOOKSHELF_BUCKET=<YOUR_BUCKET_NAME>\n\n  Where <YOUR_BUCKET_NAME> is the bucket you created above.\n* Run with the Jetty Maven plugin:\n\n      mvn jetty:run\n\n**Note**: If you run into an error about `Invalid Credentials`, you may have to run:\n\n    gcloud auth application-default login\n\n### Deploying to Cloud Run\n\nTo build your image:\n\n* Update the parameters in `pom.xml`:\n  * Replace `MY_PROJECT` with your project ID.\n* Build and deploy to your GCR with [Jib][jib] Maven plugin.\n\n      mvn clean package jib:build\n      \n* Deploy the app to Cloud Run:\n\n      gcloud run deploy bookshelf --image gcr.io/<MY_PROJECT>/bookshelf \\\n            --region us-central1 --memory 512M \\\n            --update-env-vars BOOKSHELF_BUCKET=\"<YOUR_BUCKET_NAME>\"\n\nWhere <MY_PROJECT> is the name of the project you created.\n\nThis command will output a link to visit the page.\n\n[jib]: https://github.com/GoogleContainerTools/jib\n[configure-memory]: https://cloud.google.com/run/docs/configuring/memory-limits\n"
  },
  {
    "path": "bookshelf/1-cloud-run/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\nCopyright 2019 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<project>\n  <modelVersion>4.0.0</modelVersion>\n  <packaging>war</packaging>\n\n  <groupId>com.example.getstarted</groupId>\n  <artifactId>bookshelf-cloud-run</artifactId>\n  <version>1.0-SNAPSHOT</version>\n\n  <!-- Parent POM defines common plugins and properties. -->\n  <parent>\n    <groupId>com.google.cloud.samples</groupId>\n    <artifactId>shared-configuration</artifactId>\n    <version>1.2.0</version>\n  </parent>\n\n  <properties>\n    <!-- [START bookshelf_config] -->\n    <gcloud.appId>MY_PROJECT</gcloud.appId>\n    <!-- [END bookshelf_config] -->\n\n    <failOnMissingWebXml>false</failOnMissingWebXml>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <maven.compiler.source>1.8</maven.compiler.source>\n    <maven.compiler.target>1.8</maven.compiler.target>\n    <maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>\n    <maven.compiler.showWarnings>true</maven.compiler.showWarnings>\n    <maven.compiler.failOnWarning>false</maven.compiler.failOnWarning>\n    <jetty.version>9.4.51.v20230217</jetty.version>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>javax.servlet</groupId>\n      <artifactId>javax.servlet-api</artifactId>\n      <version>4.0.1</version>\n    </dependency>\n\n    <!-- https://mvnrepository.com/artifact/com.google.cloud/google-cloud-firestore -->\n    <dependency>\n      <groupId>com.google.cloud</groupId>\n      <artifactId>google-cloud-firestore</artifactId>\n      <version>3.13.2</version>\n    </dependency>\n\n    <dependency>\n      <groupId>joda-time</groupId>\n      <artifactId>joda-time</artifactId>\n      <version>2.12.6</version>\n    </dependency>\n\n    <dependency>\n      <groupId>com.google.guava</groupId>\n      <artifactId>guava</artifactId>\n      <version>33.1.0-jre</version>\n      <scope>compile</scope>\n    </dependency>\n\n    <dependency>\n      <groupId>io.opencensus</groupId>\n      <artifactId>opencensus-contrib-http-util</artifactId>\n      <version>0.31.1</version>\n    </dependency>\n\n    <dependency>\n      <groupId>jstl</groupId>\n      <artifactId>jstl</artifactId>\n      <version>1.2</version>\n    </dependency>\n\n    <dependency>\n      <groupId>com.google.cloud</groupId>\n      <artifactId>google-cloud-storage</artifactId>\n      <version>2.23.0</version>\n    </dependency>\n\n    <dependency>\n      <groupId>commons-fileupload</groupId>\n      <artifactId>commons-fileupload</artifactId>\n      <version>1.5</version>\n    </dependency>\n\n    <dependency>\n      <groupId>commons-io</groupId>\n      <artifactId>commons-io</artifactId>\n      <version>2.13.0</version>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <finalName>bookshelf-cloud-run</finalName>\n    <!-- Optional - for hot reload of the web application when using an IDE Eclipse / IDEA -->\n    <outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes\n    </outputDirectory>\n    <plugins>\n      <plugin>\n        <groupId>org.eclipse.jetty</groupId>\n        <artifactId>jetty-maven-plugin</artifactId>\n        <version>${jetty.version}</version>\n      </plugin>\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>3.4.0</version>\n        <configuration>\n          <from>\n            <image>jetty:10.0.9-jre11</image>\n          </from>\n          <container>\n            <entrypoint>java,-jar,/usr/local/jetty/start.jar</entrypoint>\n          </container>\n          <to>\n            <image>gcr.io/${gcloud.appId}/bookshelf</image>\n          </to>\n        </configuration>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-war-plugin</artifactId>\n        <version>3.3.2</version>\n        <configuration>\n          <failOnMissingWebXml>false</failOnMissingWebXml>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>"
  },
  {
    "path": "bookshelf/1-cloud-run/src/main/java/com/example/getstarted/basicactions/CreateBookServlet.java",
    "content": "/* Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\n// [START bookshelf_create_servlet]\n\nimport com.example.getstarted.daos.BookDao;\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.util.CloudStorageHelper;\nimport com.google.common.base.Strings;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport javax.servlet.ServletException;\nimport javax.servlet.annotation.WebServlet;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport javax.servlet.http.HttpSession;\nimport org.apache.commons.fileupload.FileItemIterator;\nimport org.apache.commons.fileupload.FileItemStream;\nimport org.apache.commons.fileupload.FileUploadException;\nimport org.apache.commons.fileupload.servlet.ServletFileUpload;\nimport org.apache.commons.fileupload.util.Streams;\n\n@SuppressWarnings(\"serial\")\n@WebServlet(\n    name = \"create\",\n    urlPatterns = {\"/create\"})\npublic class CreateBookServlet extends HttpServlet {\n\n  private static final Logger logger = Logger.getLogger(CreateBookServlet.class.getName());\n\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp)\n      throws ServletException, IOException {\n    req.setAttribute(\"action\", \"Add\");\n    req.setAttribute(\"destination\", \"create\");\n    req.setAttribute(\"page\", \"form\");\n    req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n  }\n\n  @Override\n  public void doPost(HttpServletRequest req, HttpServletResponse resp)\n      throws ServletException, IOException {\n    assert ServletFileUpload.isMultipartContent(req);\n    CloudStorageHelper storageHelper =\n        (CloudStorageHelper) getServletContext().getAttribute(\"storageHelper\");\n\n    String newImageUrl = null;\n    Map<String, String> params = new HashMap<String, String>();\n    try {\n      FileItemIterator iter = new ServletFileUpload().getItemIterator(req);\n      while (iter.hasNext()) {\n        FileItemStream item = iter.next();\n        if (item.isFormField()) {\n          params.put(item.getFieldName(), Streams.asString(item.openStream()));\n        } else if (!Strings.isNullOrEmpty(item.getName())) {\n          newImageUrl =\n              storageHelper.uploadFile(\n                  item, System.getenv(\"BOOKSHELF_BUCKET\"));\n        }\n      }\n    } catch (FileUploadException e) {\n      throw new IOException(e);\n    }\n\n    String createdByString = \"\";\n    String createdByIdString = \"\";\n    HttpSession session = req.getSession();\n    if (session.getAttribute(\"userEmail\") != null) { // Does the user have a logged in session?\n      createdByString = (String) session.getAttribute(\"userEmail\");\n      createdByIdString = (String) session.getAttribute(\"userId\");\n    }\n\n    Book book =\n        new Book.Builder()\n            .author(params.get(\"author\"))\n            .description(params.get(\"description\"))\n            .publishedDate(params.get(\"publishedDate\"))\n            .title(params.get(\"title\"))\n            .imageUrl(null == newImageUrl ? params.get(\"imageUrl\") : newImageUrl)\n            .createdBy(createdByString)\n            .createdById(createdByIdString)\n            .build();\n\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    String id = dao.createBook(book);\n    logger.log(Level.INFO, \"Created book {0}\", book);\n    resp.sendRedirect(\"/read?id=\" + id);\n  }\n}\n// [END bookshelf_create_servlet]\n"
  },
  {
    "path": "bookshelf/1-cloud-run/src/main/java/com/example/getstarted/basicactions/DeleteBookServlet.java",
    "content": "/* Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\n// [START bookshelf_delete_servlet]\n\nimport com.example.getstarted.daos.BookDao;\nimport java.io.IOException;\nimport javax.servlet.annotation.WebServlet;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n@SuppressWarnings(\"serial\")\n@WebServlet(\n    name = \"delete\",\n    urlPatterns = {\"/delete\"})\npublic class DeleteBookServlet extends HttpServlet {\n\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {\n    String id = req.getParameter(\"id\");\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    dao.deleteBook(id);\n    resp.sendRedirect(\"/books\");\n  }\n}\n// [END bookshelf_delete_servlet]\n"
  },
  {
    "path": "bookshelf/1-cloud-run/src/main/java/com/example/getstarted/basicactions/ErrorsBookServlet.java",
    "content": "/* Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\n// [START bookshelf_errors_servlet]\n\nimport javax.servlet.ServletException;\nimport javax.servlet.annotation.WebServlet;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n@SuppressWarnings(\"serial\")\n@WebServlet(\n    name = \"errors\",\n    urlPatterns = {\"/errors\"})\npublic class ErrorsBookServlet extends HttpServlet {\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException {\n    throw new ServletException(\"Expected exception.\");\n  }\n}\n// [END bookshelf_errors_servlet]\n"
  },
  {
    "path": "bookshelf/1-cloud-run/src/main/java/com/example/getstarted/basicactions/ListBookServlet.java",
    "content": "/* Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\n// [START bookshelf_list_books_servlet]\nimport com.example.getstarted.daos.BookDao;\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.objects.Result;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport javax.servlet.ServletException;\nimport javax.servlet.annotation.WebServlet;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n// a url pattern of \"\" makes this servlet the root servlet\n@SuppressWarnings(\"serial\")\n@WebServlet(\n    name = \"list\",\n    urlPatterns = {\"\", \"/books\"},\n    loadOnStartup = 1)\npublic class ListBookServlet extends HttpServlet {\n\n  private static final Logger logger = Logger.getLogger(ListBookServlet.class.getName());\n\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp)\n      throws IOException, ServletException {\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    String startCursor = req.getParameter(\"cursor\");\n    List<Book> books = null;\n    String endCursor = null;\n    try {\n      Result<Book> result = dao.listBooks(startCursor);\n      logger.log(Level.INFO, \"Retrieved list of all books\");\n      books = result.getResult();\n      endCursor = result.getCursor();\n    } catch (Exception e) {\n      throw new ServletException(\"Error listing books\", e);\n    }\n    req.getSession().getServletContext().setAttribute(\"books\", books);\n    StringBuilder bookNames = new StringBuilder();\n    for (Book book : books) {\n      bookNames.append(book.getTitle()).append(\" \");\n    }\n    logger.log(Level.INFO, \"Loaded books: \" + bookNames.toString());\n    req.setAttribute(\"cursor\", endCursor);\n    req.setAttribute(\"page\", \"list\");\n    req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n  }\n}\n// [END bookshelf_list_books_servlet]\n"
  },
  {
    "path": "bookshelf/1-cloud-run/src/main/java/com/example/getstarted/basicactions/ReadBookServlet.java",
    "content": "/* Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\n// [START bookshelf_read_servlet]\n\nimport com.example.getstarted.daos.BookDao;\nimport com.example.getstarted.objects.Book;\nimport java.io.IOException;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport javax.servlet.ServletException;\nimport javax.servlet.annotation.WebServlet;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n@SuppressWarnings(\"serial\")\n@WebServlet(\n    name = \"read\",\n    urlPatterns = {\"/read\"})\npublic class ReadBookServlet extends HttpServlet {\n\n  private final Logger logger = Logger.getLogger(ReadBookServlet.class.getName());\n\n  @Override\n  public void doGet(HttpServletRequest req,\n                    HttpServletResponse resp) throws ServletException, IOException {\n    String id = req.getParameter(\"id\");\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    Book book = dao.readBook(id);\n    logger.log(Level.INFO, \"Read book with id {0}\", id);\n    req.setAttribute(\"book\", book);\n    req.setAttribute(\"page\", \"view\");\n    req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n  }\n}\n// [END bookshelf_read_servlet]\n"
  },
  {
    "path": "bookshelf/1-cloud-run/src/main/java/com/example/getstarted/basicactions/UpdateBookServlet.java",
    "content": "/* Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\n// [START bookshelf_update_servlet]\n\nimport com.example.getstarted.daos.BookDao;\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.util.CloudStorageHelper;\nimport com.google.common.base.Strings;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport javax.servlet.ServletException;\nimport javax.servlet.annotation.WebServlet;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport org.apache.commons.fileupload.FileItemIterator;\nimport org.apache.commons.fileupload.FileItemStream;\nimport org.apache.commons.fileupload.FileUploadException;\nimport org.apache.commons.fileupload.servlet.ServletFileUpload;\nimport org.apache.commons.fileupload.util.Streams;\n\n@SuppressWarnings(\"serial\")\n@WebServlet(\n    name = \"update\",\n    urlPatterns = {\"/update\"})\npublic class UpdateBookServlet extends HttpServlet {\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException {\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    try {\n      Book book = dao.readBook(req.getParameter(\"id\"));\n      req.setAttribute(\"book\", book);\n      req.setAttribute(\"action\", \"Edit\");\n      req.setAttribute(\"destination\", \"update\");\n      req.setAttribute(\"page\", \"form\");\n      req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n    } catch (Exception e) {\n      throw new ServletException(\"Error loading book for editing\", e);\n    }\n  }\n\n  @Override\n  public void doPost(HttpServletRequest req, HttpServletResponse resp)\n      throws ServletException, IOException {\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n\n    assert ServletFileUpload.isMultipartContent(req);\n    CloudStorageHelper storageHelper =\n        (CloudStorageHelper) getServletContext().getAttribute(\"storageHelper\");\n\n    String newImageUrl = null;\n    Map<String, String> params = new HashMap<String, String>();\n    try {\n      FileItemIterator iter = new ServletFileUpload().getItemIterator(req);\n      while (iter.hasNext()) {\n        FileItemStream item = iter.next();\n        if (item.isFormField()) {\n          params.put(item.getFieldName(), Streams.asString(item.openStream()));\n        } else if (!Strings.isNullOrEmpty(item.getName())) {\n          newImageUrl =\n              storageHelper.uploadFile(\n                  item, System.getenv(\"BOOKSHELF_BUCKET\"));\n        }\n      }\n    } catch (FileUploadException e) {\n      throw new IOException(e);\n    }\n\n    Book oldBook = dao.readBook(params.get(\"id\"));\n\n    Book book =\n        new Book.Builder()\n            .author(params.get(\"author\"))\n            .description(params.get(\"description\"))\n            .publishedDate(params.get(\"publishedDate\"))\n            .title(params.get(\"title\"))\n            .imageUrl(null == newImageUrl ? params.get(\"imageUrl\") : newImageUrl)\n            .id(params.get(\"id\"))\n            .createdBy(oldBook.getCreatedBy())\n            .createdById(oldBook.getCreatedById())\n            .build();\n\n    dao.updateBook(book);\n    resp.sendRedirect(\"/read?id=\" + params.get(\"id\"));\n  }\n}\n// [END bookshelf_update_servlet]\n"
  },
  {
    "path": "bookshelf/1-cloud-run/src/main/java/com/example/getstarted/daos/BookDao.java",
    "content": "/* Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.daos;\n\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.objects.Result;\n\n// [START bookshelf_base_dao]\npublic interface BookDao {\n  String createBook(Book book);\n\n  Book readBook(String bookId);\n\n  void updateBook(Book book);\n\n  void deleteBook(String bookId);\n\n  Result<Book> listBooks(String startCursor);\n\n  Result<Book> listBooksByUser(String userId, String startCursor);\n}\n// [END bookshelf_base_dao]\n"
  },
  {
    "path": "bookshelf/1-cloud-run/src/main/java/com/example/getstarted/daos/FirestoreDao.java",
    "content": "/* Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.daos;\n\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.objects.Result;\nimport com.google.cloud.firestore.CollectionReference;\nimport com.google.cloud.firestore.DocumentReference;\nimport com.google.cloud.firestore.DocumentSnapshot;\nimport com.google.cloud.firestore.Firestore;\nimport com.google.cloud.firestore.FirestoreOptions;\nimport com.google.cloud.firestore.Query;\nimport com.google.cloud.firestore.QueryDocumentSnapshot;\nimport com.google.cloud.firestore.QuerySnapshot;\nimport com.google.common.collect.Lists;\nimport com.google.common.collect.Maps;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.ExecutionException;\n\n// [START bookshelf_firestore_client]\npublic class FirestoreDao implements BookDao {\n  private CollectionReference booksCollection;\n\n  public FirestoreDao() {\n    Firestore firestore = FirestoreOptions.getDefaultInstance().getService();\n    booksCollection = firestore.collection(\"books\");\n  }\n\n  // [START bookshelf_firestore_document_to_book]\n  private Book documentToBook(DocumentSnapshot document) {\n    Map<String, Object> data = document.getData();\n    if (data == null) {\n      System.out.println(\"No data in document \" + document.getId());\n      return null;\n    }\n\n    return new Book.Builder()\n        .author((String) data.get(Book.AUTHOR))\n        .description((String) data.get(Book.DESCRIPTION))\n        .publishedDate((String) data.get(Book.PUBLISHED_DATE))\n        .imageUrl((String) data.get(Book.IMAGE_URL))\n        .createdBy((String) data.get(Book.CREATED_BY))\n        .createdById((String) data.get(Book.CREATED_BY_ID))\n        .title((String) data.get(Book.TITLE))\n        .id(document.getId())\n        .build();\n  }\n  // [END bookshelf_firestore_document_to_book]\n\n  // [START bookshelf_firestore_create_book]\n  @Override\n  public String createBook(Book book) {\n    String id = UUID.randomUUID().toString();\n    DocumentReference document = booksCollection.document(id);\n    Map<String, Object> data = Maps.newHashMap();\n\n    data.put(Book.AUTHOR, book.getAuthor());\n    data.put(Book.DESCRIPTION, book.getDescription());\n    data.put(Book.PUBLISHED_DATE, book.getPublishedDate());\n    data.put(Book.TITLE, book.getTitle());\n    data.put(Book.IMAGE_URL, book.getImageUrl());\n    data.put(Book.CREATED_BY, book.getCreatedBy());\n    data.put(Book.CREATED_BY_ID, book.getCreatedById());\n    try {\n      document.set(data).get();\n    } catch (InterruptedException | ExecutionException e) {\n      e.printStackTrace();\n    }\n\n    return id;\n  }\n  // [END bookshelf_firestore_create_book]\n\n  // [START bookshelf_firestore_read]\n  @Override\n  public Book readBook(String bookId) {\n    try {\n      DocumentSnapshot document = booksCollection.document(bookId).get().get();\n\n      return documentToBook(document);\n    } catch (InterruptedException | ExecutionException e) {\n      e.printStackTrace();\n    }\n    return null;\n  }\n  // [END bookshelf_firestore_read]\n\n  // [START bookshelf_firestore_update]\n  @Override\n  public void updateBook(Book book) {\n    DocumentReference document = booksCollection.document(book.getId());\n    Map<String, Object> data = Maps.newHashMap();\n\n    data.put(Book.AUTHOR, book.getAuthor());\n    data.put(Book.DESCRIPTION, book.getDescription());\n    data.put(Book.PUBLISHED_DATE, book.getPublishedDate());\n    data.put(Book.TITLE, book.getTitle());\n    data.put(Book.IMAGE_URL, book.getImageUrl());\n    data.put(Book.CREATED_BY, book.getCreatedBy());\n    data.put(Book.CREATED_BY_ID, book.getCreatedById());\n    try {\n      document.set(data).get();\n    } catch (InterruptedException | ExecutionException e) {\n      e.printStackTrace();\n    }\n  }\n  // [END bookshelf_firestore_update]\n\n  // [START bookshelf_firestore_delete]\n  @Override\n  public void deleteBook(String bookId) {\n    try {\n      booksCollection.document(bookId).delete().get();\n    } catch (InterruptedException | ExecutionException e) {\n      e.printStackTrace();\n    }\n  }\n  // [END bookshelf_firestore_delete]\n\n  // [START bookshelf_firestore_documents_to_books]\n  private List<Book> documentsToBooks(List<QueryDocumentSnapshot> documents) {\n    List<Book> resultBooks = new ArrayList<>();\n    for (QueryDocumentSnapshot snapshot : documents) {\n      resultBooks.add(documentToBook(snapshot));\n    }\n    return resultBooks;\n  }\n  // [END bookshelf_firestore_documents_to_books]\n\n  // [START bookshelf_firestore_list_books]\n  @Override\n  public Result<Book> listBooks(String startTitle) {\n    Query booksQuery = booksCollection.orderBy(\"title\").limit(10);\n    if (startTitle != null) {\n      booksQuery = booksQuery.startAfter(startTitle);\n    }\n    try {\n      QuerySnapshot snapshot = booksQuery.get().get();\n      List<Book> results = documentsToBooks(snapshot.getDocuments());\n      String newCursor = null;\n      if (results.size() > 0) {\n        newCursor = results.get(results.size() - 1).getTitle();\n      }\n      return new Result<>(results, newCursor);\n    } catch (InterruptedException | ExecutionException e) {\n      e.printStackTrace();\n    }\n    return new Result<>(Lists.newArrayList(), null);\n  }\n  // [END bookshelf_firestore_list_books]\n\n  // [START bookshelf_firestore_list_by_user]\n  @Override\n  public Result<Book> listBooksByUser(String userId, String startTitle) {\n    Query booksQuery =\n        booksCollection.orderBy(\"title\").whereEqualTo(Book.CREATED_BY_ID, userId).limit(10);\n    if (startTitle != null) {\n      booksQuery = booksQuery.startAfter(startTitle);\n    }\n    try {\n      QuerySnapshot snapshot = booksQuery.get().get();\n      List<Book> results = documentsToBooks(snapshot.getDocuments());\n      String newCursor = null;\n      if (results.size() > 0) {\n        newCursor = results.get(results.size() - 1).getTitle();\n      }\n      return new Result<>(results, newCursor);\n    } catch (InterruptedException | ExecutionException e) {\n      e.printStackTrace();\n    }\n    return new Result<>(Lists.newArrayList(), null);\n  }\n  // [END bookshelf_firestore_list_by_user]\n}\n// [END bookshelf_firestore_client]\n"
  },
  {
    "path": "bookshelf/1-cloud-run/src/main/java/com/example/getstarted/objects/Book.java",
    "content": "/* Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.objects;\n\n// [START bookshelf_book]\npublic class Book {\n  private String title;\n  private String author;\n  private String createdBy;\n  private String createdById;\n  private String publishedDate;\n  private String description;\n  private String id;\n  private String imageUrl;\n  public static final String AUTHOR = \"author\";\n  public static final String CREATED_BY = \"createdBy\";\n  public static final String CREATED_BY_ID = \"createdById\";\n  public static final String DESCRIPTION = \"description\";\n  public static final String ID = \"id\";\n  public static final String PUBLISHED_DATE = \"publishedDate\";\n  public static final String TITLE = \"title\";\n  public static final String IMAGE_URL = \"imageUrl\";\n\n  // We use a Builder pattern here to simplify and standardize construction of\n  // Book objects.\n  private Book(Builder builder) {\n    this.title = builder.title;\n    this.author = builder.author;\n    this.createdBy = builder.createdBy;\n    this.createdById = builder.createdById;\n    this.publishedDate = builder.publishedDate;\n    this.description = builder.description;\n    this.id = builder.id;\n    this.imageUrl = builder.imageUrl;\n  }\n\n  public static class Builder {\n    private String title;\n    private String author;\n    private String createdBy;\n    private String createdById;\n    private String publishedDate;\n    private String description;\n    private String id;\n    private String imageUrl;\n\n    public Builder title(String title) {\n      this.title = title;\n      return this;\n    }\n\n    public Builder author(String author) {\n      this.author = author;\n      return this;\n    }\n\n    public Builder createdBy(String createdBy) {\n      this.createdBy = createdBy;\n      return this;\n    }\n\n    public Builder createdById(String createdById) {\n      this.createdById = createdById;\n      return this;\n    }\n\n    public Builder publishedDate(String publishedDate) {\n      this.publishedDate = publishedDate;\n      return this;\n    }\n\n    public Builder description(String description) {\n      this.description = description;\n      return this;\n    }\n\n    public Builder id(String id) {\n      this.id = id;\n      return this;\n    }\n\n    public Builder imageUrl(String imageUrl) {\n      this.imageUrl = imageUrl;\n      return this;\n    }\n\n    public Book build() {\n      return new Book(this);\n    }\n  }\n\n  public String getTitle() {\n    return title;\n  }\n\n  public void setTitle(String title) {\n    this.title = title;\n  }\n\n  public String getAuthor() {\n    return author;\n  }\n\n  public void setAuthor(String author) {\n    this.author = author;\n  }\n\n  public String getCreatedBy() {\n    return createdBy;\n  }\n\n  public void setCreatedBy(String createdBy) {\n    this.createdBy = createdBy;\n  }\n\n  public String getCreatedById() {\n    return createdById;\n  }\n\n  public void setCreatedById(String createdById) {\n    this.createdById = createdById;\n  }\n\n  public String getPublishedDate() {\n    return publishedDate;\n  }\n\n  public void setPublishedDate(String publishedDate) {\n    this.publishedDate = publishedDate;\n  }\n\n  public String getDescription() {\n    return description;\n  }\n\n  public void setDescription(String description) {\n    this.description = description;\n  }\n\n  public String getId() {\n    return id;\n  }\n\n  public void setId(String id) {\n    this.id = id;\n  }\n\n  public String getImageUrl() {\n    return imageUrl;\n  }\n\n  public void setImageUrl(String imageUrl) {\n    this.imageUrl = imageUrl;\n  }\n\n  @Override\n  public String toString() {\n    return \"Title: \"\n        + title\n        + \", Author: \"\n        + author\n        + \", Published date: \"\n        + publishedDate\n        + \", Added by: \"\n        + createdBy;\n  }\n}\n// [END bookshelf_book]\n"
  },
  {
    "path": "bookshelf/1-cloud-run/src/main/java/com/example/getstarted/objects/Result.java",
    "content": "/* Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.objects;\n\nimport java.util.List;\n\n// [START bookshelf_result]\npublic class Result<K> {\n  private String cursor;\n  private List<K> result;\n\n  public String getCursor() {\n    return cursor;\n  }\n\n  public void setCursor(String cursor) {\n    this.cursor = cursor;\n  }\n\n  public List<K> getResult() {\n    return result;\n  }\n\n  public void setResult(List<K> result) {\n    this.result = result;\n  }\n\n  public Result(List<K> result, String cursor) {\n    this.result = result;\n    this.cursor = cursor;\n  }\n\n  public Result(List<K> result) {\n    this.result = result;\n    this.cursor = null;\n  }\n}\n// [END bookshelf_result]\n"
  },
  {
    "path": "bookshelf/1-cloud-run/src/main/java/com/example/getstarted/util/BookshelfContextListener.java",
    "content": "/* Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.util;\n\nimport com.example.getstarted.daos.BookDao;\nimport com.example.getstarted.daos.FirestoreDao;\nimport com.google.common.base.Strings;\nimport javax.servlet.ServletContextEvent;\nimport javax.servlet.ServletContextListener;\nimport javax.servlet.annotation.WebListener;\n\n@WebListener(\"Creates BookDao and other servlet context objects for reuse.\")\npublic class BookshelfContextListener implements ServletContextListener {\n  @Override\n  public void contextDestroyed(javax.servlet.ServletContextEvent event) {\n  }\n\n  @Override\n  public void contextInitialized(ServletContextEvent event) {\n    // This function is called when the application starts and will safely set a few required\n    // context attributes such as the BookDao.\n\n    BookDao dao = (BookDao) event.getServletContext().getAttribute(\"dao\");\n    if (dao == null) {\n      dao = new FirestoreDao();\n      event.getServletContext().setAttribute(\"dao\", dao);\n    }\n\n    Boolean isCloudStorageConfigured = (Boolean) event.getServletContext()\n        .getAttribute(\"isCloudStorageConfigured\");\n    if (isCloudStorageConfigured == null) {\n      event.getServletContext()\n          .setAttribute(\n              \"isCloudStorageConfigured\",\n              !Strings.isNullOrEmpty(System.getenv(\"BOOKSHELF_BUCKET\")));\n    }\n\n    CloudStorageHelper storageHelper = (CloudStorageHelper) event.getServletContext().getAttribute(\n        \"storageHelper\");\n    if (storageHelper == null) {\n      storageHelper = new CloudStorageHelper();\n      event.getServletContext().setAttribute(\"storageHelper\", storageHelper);\n    }\n  }\n}\n"
  },
  {
    "path": "bookshelf/1-cloud-run/src/main/java/com/example/getstarted/util/CloudStorageHelper.java",
    "content": "/*\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.util;\n\nimport com.google.cloud.storage.Acl;\nimport com.google.cloud.storage.Acl.Role;\nimport com.google.cloud.storage.Acl.User;\nimport com.google.cloud.storage.BlobInfo;\nimport com.google.cloud.storage.Storage;\nimport com.google.cloud.storage.StorageOptions;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport javax.servlet.ServletException;\nimport org.apache.commons.fileupload.FileItemStream;\nimport org.joda.time.DateTime;\nimport org.joda.time.DateTimeZone;\nimport org.joda.time.format.DateTimeFormat;\nimport org.joda.time.format.DateTimeFormatter;\n\n// [START bookshelf_cloud_storage_client]\npublic class CloudStorageHelper {\n\n  private final Logger logger = Logger.getLogger(CloudStorageHelper.class.getName());\n  private static Storage storage = null;\n\n  // [START bookshelf_cloud_storage_client_init]\n  static {\n    storage = StorageOptions.getDefaultInstance().getService();\n  }\n  // [END bookshelf_cloud_storage_client_init]\n\n  // [START bookshelf_cloud_storage_client_upload_file]\n\n  /**\n   * Uploads a file to Google Cloud Storage to the bucket specified in the BUCKET_NAME environment\n   * variable, appending a timestamp to end of the uploaded filename.\n   */\n  public String uploadFile(FileItemStream fileStream, final String bucketName)\n      throws IOException, ServletException {\n    checkFileExtension(fileStream.getName());\n\n    System.out.println(\"FileStream name: \" + fileStream.getName() + \"\\nBucket name: \" + bucketName);\n\n    DateTimeFormatter dtf = DateTimeFormat.forPattern(\"-YYYY-MM-dd-HHmmssSSS\");\n    DateTime dt = DateTime.now(DateTimeZone.UTC);\n    String dtString = dt.toString(dtf);\n    final String fileName = fileStream.getName() + dtString;\n\n    // the inputstream is closed by default, so we don't need to close it here\n    @SuppressWarnings(\"deprecation\")\n    BlobInfo blobInfo =\n        storage.create(\n            BlobInfo.newBuilder(bucketName, fileName)\n                // Modify access list to allow all users with link to read file\n                .setAcl(new ArrayList<>(Arrays.asList(Acl.of(User.ofAllUsers(), Role.READER))))\n                .build(),\n            fileStream.openStream());\n    logger.log(\n        Level.INFO, \"Uploaded file {0} as {1}\", new Object[] {fileStream.getName(), fileName});\n    // return the public download link\n    return blobInfo.getMediaLink();\n  }\n  // [END bookshelf_cloud_storage_client_upload_file]\n\n  // [START bookshelf_cloud_storage_client_check_file_extension]\n\n  /** Checks that the file extension is supported. */\n  private void checkFileExtension(String fileName) throws ServletException {\n    if (fileName != null && !fileName.isEmpty() && fileName.contains(\".\")) {\n      String[] allowedExt = {\".jpg\", \".jpeg\", \".png\", \".gif\"};\n      for (String ext : allowedExt) {\n        if (fileName.endsWith(ext)) {\n          return;\n        }\n      }\n      throw new ServletException(\"file must be an image\");\n    }\n  }\n  // [END bookshelf_cloud_storage_client_check_file_extension]\n}\n// [END bookshelf_cloud_storage_client]\n"
  },
  {
    "path": "bookshelf/1-cloud-run/src/main/webapp/WEB-INF/web.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n Copyright 2023 Google LLC\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n       http://www.apache.org/licenses/LICENSE-2.0\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<web-app xmlns=\"http://java.sun.com/xml/ns/javaee\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xmlns:web=\"http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd\"\n    xsi:schemaLocation=\"http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd\"\n    version=\"2.5\">\n\n    <servlet>\n        <servlet-name>list</servlet-name>\n        <servlet-class>com.example.getstarted.basicactions.ListBookServlet</servlet-class>\n        <load-on-startup>1</load-on-startup>\n    </servlet>\n    <servlet-mapping>\n        <servlet-name>list</servlet-name>\n        <url-pattern>/</url-pattern>\n        <url-pattern>/books</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n        <servlet-name>create</servlet-name>\n        <servlet-class>com.example.getstarted.basicactions.CreateBookServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n        <servlet-name>create</servlet-name>\n        <url-pattern>/create</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n        <servlet-name>update</servlet-name>\n        <servlet-class>com.example.getstarted.basicactions.UpdateBookServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n        <servlet-name>update</servlet-name>\n        <url-pattern>/update</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n        <servlet-name>read</servlet-name>\n        <servlet-class>com.example.getstarted.basicactions.ReadBookServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n        <servlet-name>read</servlet-name>\n        <url-pattern>/read</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n        <servlet-name>delete</servlet-name>\n        <servlet-class>com.example.getstarted.basicactions.DeleteBookServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n        <servlet-name>delete</servlet-name>\n        <url-pattern>/delete</url-pattern>\n    </servlet-mapping>\n\n</web-app>"
  },
  {
    "path": "bookshelf/1-cloud-run/src/main/webapp/base.jsp",
    "content": "<!--\nCopyright 2019 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START bookshelf_jsp_base] -->\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\" prefix=\"fn\" %>\n<html lang=\"en\">\n  <head>\n    <title>Bookshelf - Java on Google Cloud Platform</title>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link rel=\"stylesheet\" href=\"//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css\">\n  </head>\n  <body>\n    <div class=\"navbar navbar-default\">\n      <div class=\"container\">\n        <div class=\"navbar-header\">\n          <div class=\"navbar-brand\">Bookshelf</div>\n        </div>\n      </div>\n    </div>\n    <c:import url=\"/${page}.jsp\" />\n  </body>\n</html>\n<!-- [END bookshelf_jsp_base]-->\n"
  },
  {
    "path": "bookshelf/1-cloud-run/src/main/webapp/form.jsp",
    "content": "<!--\nCopyright 2019 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START bookshelf_jsp_form] -->\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\"%>\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\" prefix=\"fn\"%>\n<div class=\"container\">\n  <h3>\n    <c:out value=\"${action}\" /> book\n  </h3>\n\n  <form method=\"POST\" action=\"${destination}\" enctype=\"multipart/form-data\">\n\n    <div class=\"form-group\">\n      <label for=\"title\">Title</label>\n      <input type=\"text\" name=\"title\" id=\"title\" value=\"${fn:escapeXml(book.title)}\" class=\"form-control\" />\n    </div>\n\n    <div class=\"form-group\">\n      <label for=\"author\">Author</label>\n      <input type=\"text\" name=\"author\" id=\"author\" value=\"${fn:escapeXml(book.author)}\" class=\"form-control\" />\n    </div>\n\n    <div class=\"form-group\">\n      <label for=\"publishedDate\">Date Published</label>\n      <input type=\"text\" name=\"publishedDate\" id=\"publishedDate\" value=\"${fn:escapeXml(book.publishedDate)}\" class=\"form-control\" />\n    </div>\n\n    <div class=\"form-group\">\n      <label for=\"description\">Description</label>\n      <textarea name=\"description\" id=\"description\" class=\"form-control\">${fn:escapeXml(book.description)}</textarea>\n    </div>\n\n    <div class=\"form-group ${isCloudStorageConfigured ? '' : 'hidden'}\">\n      <label for=\"image\">Cover Image</label>\n      <input type=\"file\" name=\"file\" id=\"file\" class=\"form-control\" />\n    </div>\n\n    <div class=\"form-group hidden\">\n      <label for=\"imageUrl\">Cover Image URL</label>\n      <input type=\"hidden\" name=\"id\" value=\"${book.id}\" />\n      <input type=\"text\" name=\"imageUrl\" id=\"imageUrl\" value=\"${fn:escapeXml(book.imageUrl)}\" class=\"form-control\" />\n    </div>\n\n    <button type=\"submit\" class=\"btn btn-success\">Save</button>\n  </form>\n</div>\n<!-- [END bookshelf_jsp_form] -->\n"
  },
  {
    "path": "bookshelf/1-cloud-run/src/main/webapp/list.jsp",
    "content": "<!--\nCopyright 2019 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START bookshelf_jsp_list] -->\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\" prefix=\"fn\" %>\n<div class=\"container\">\n  <h3>Books</h3>\n  <a href=\"/create\" class=\"btn btn-success btn-sm\">\n    <i class=\"glyphicon glyphicon-plus\"></i>\n    Add book\n  </a>\n  <c:choose>\n  <c:when test=\"${empty books}\">\n  <p>No books found</p>\n  </c:when>\n  <c:otherwise>\n  <c:forEach items=\"${books}\" var=\"book\">\n  <div class=\"media\">\n    <a href=\"/read?id=${book.id}\">\n      <div class=\"media-left\">\n        <img alt=\"ahhh\" src=\"${fn:escapeXml(not empty book.imageUrl?book.imageUrl:'http://placekitten.com/g/128/192')}\">\n      </div>\n      <div class=\"media-body\">\n        <h4>${fn:escapeXml(book.title)}</h4>\n        <p>${fn:escapeXml(book.author)}</p>\n      </div>\n    </a>\n  </div>\n  </c:forEach>\n  <c:if test=\"${not empty cursor}\">\n  <nav>\n    <ul class=\"pager\">\n      <li><a href=\"?cursor=${fn:escapeXml(cursor)}\">More</a></li>\n    </ul>\n  </nav>\n  </c:if>\n  </c:otherwise>\n  </c:choose>\n</div>\n<!-- [END bookshelf_jsp_list] -->\n"
  },
  {
    "path": "bookshelf/1-cloud-run/src/main/webapp/view.jsp",
    "content": "<!--\nCopyright 2019 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START bookshelf_jsp_view] -->\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\" prefix=\"fn\" %>\n<div class=\"container\">\n  <h3>Book</h3>\n  <div class=\"btn-group\">\n    <a href=\"/update?id=${book.id}\" class=\"btn btn-primary btn-sm\">\n      <i class=\"glyphicon glyphicon-edit\"></i>\n      Edit book\n    </a>\n    <a href=\"/delete?id=${book.id}\" class=\"btn btn-danger btn-sm\">\n      <i class=\"glyphicon glyphicon-trash\"></i>\n      Delete book\n    </a>\n  </div>\n\n  <div class=\"media\">\n    <div class=\"media-left\">\n      <img class=\"book-image\" src=\"${fn:escapeXml(not empty book.imageUrl?book.imageUrl:'http://placekitten.com/g/128/192')}\">\n    </div>\n    <div class=\"media-body\">\n      <h4 class=\"book-title\">\n        ${fn:escapeXml(book.title)}\n        <small>${fn:escapeXml(book.publishedDate)}</small>\n      </h4>\n      <h5 class=\"book-author\">By ${fn:escapeXml(not empty book.author?book.author:'Unknown')}</h5>\n      <p class=\"book-description\">${fn:escapeXml(book.description)}</p>\n      <small class=\"book-added-by\">Added by\n        ${fn:escapeXml(not empty book.createdBy?book.createdBy:'Anonymous')}</small>\n    </div>\n  </div>\n</div>\n<!-- [END bookshelf_jsp_view] -->\n"
  },
  {
    "path": "bookshelf-standard/2-structured-data/README.md",
    "content": "mvn package appengine:deploymvn package appengine:deploymvn package appengine:deploy# Bookshelf App for Java on App Engine Standard Tutorial\n## Structured Data\n\nContains the code for using Cloud Datastore and Cloud SQL v2.\n\nThis is part of a [Bookshelf tutorial](https://cloud.google.com/java/getting-started/tutorial-app).\n\nMost users can get this running by updating the parameters in `pom.xml`.\n\n### Running Locally\n\n    mvn -Plocal clean appengine:devserver\n\n### Deploying to App Engine Standard\n\n* In the `pom.xml`, update the [App Engine Maven Plugin](https://cloud.google.com/appengine/docs/standard/java/tools/maven-reference)\nwith your Google Cloud Project Id:\n\n  ```\n  <plugin>\n    <groupId>com.google.cloud.tools</groupId>\n    <artifactId>appengine-maven-plugin</artifactId>\n    <version>2.3.0</version>\n    <configuration>\n      <projectId>GCLOUD_CONFIG</projectId>\n      <version>bookshelf</version>\n    </configuration>\n  </plugin>\n  ```\n\n* Deploy your App\n\n    mvn package appengine:deploy\n\nVisit it at http://bookshelf.<your-project-id>.appspot.com\n"
  },
  {
    "path": "bookshelf-standard/2-structured-data/jenkins.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2017 Google Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Fail on non-zero return and print command to stdout\nset -xe\n\n# Jenkins provides values for GOOGLE_CLOUD_PROJECT and GOOGLE_VERSION_ID\n\n# Make sure it works on GAE7\n\n# Deploy and run selenium tests\nmvn clean appengine:update verify \\\n  -Pselenium \\\n  -Dappengine.appId=\"${GOOGLE_CLOUD_PROJECT}\" \\\n  -Dappengine.version=\"${GOOGLE_VERSION_ID}\" \\\n  -Dappengine.additionalParams=\"--service_account_json_key_file=${GOOGLE_APPLICATION_CREDENTIALS}\"\n\n\n# Make sure it deploys on GAE8\n\nFILE_PATH=src/main/webapp/WEB-INF/appengine-web.xml\nsed -i'.bak' '/<appengine-web-app/ a <runtime>java8</runtime>' \"${FILE_PATH}\"\n# Restore the backup after we're done\ntrap 'mv \"${FILE_PATH}\"{.bak,}' EXIT\n# Deploy and run selenium tests\nmvn clean appengine:update verify \\\n  -Pselenium \\\n  -Dappengine.appId=\"${GOOGLE_CLOUD_PROJECT}\" \\\n  -Dappengine.version=\"${GOOGLE_VERSION_ID}\" \\\n  -Dappengine.additionalParams=\"--service_account_json_key_file=${GOOGLE_APPLICATION_CREDENTIALS}\"\n\n"
  },
  {
    "path": "bookshelf-standard/2-structured-data/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\nCopyright 2016 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<project>                               <!-- REQUIRED -->\n\n  <modelVersion>4.0.0</modelVersion>    <!-- REQUIRED -->\n  <packaging>war</packaging>            <!-- REQUIRED -->\n\n  <groupId>com.example.standard.gettingstarted</groupId>\n  <artifactId>bookshelf-standard-2</artifactId>    <!-- Name of your project -->\n  <version>1.0-SNAPSHOT</version>       <!-- xx.xx.xx -SNAPSHOT means development -->\n\n  <parent> <!-- Only used for testing - NOT REQUIRED -->\n    <groupId>com.google.cloud.samples</groupId>\n    <artifactId>shared-configuration</artifactId>\n    <version>1.2.0</version>\n  </parent>\n\n  <properties>\n    <!-- [START config] -->\n    <bookshelf.storageType>datastore</bookshelf.storageType>   <!-- datastore or cloudsql -->\n\n    <sql.dbName>bookshelf</sql.dbName>                        <!-- A reasonable default -->\n    <!-- Instance Connection Name - project:region:dbName -->\n    <!-- -Dsql.instanceName=localhost to use a local MySQL server -->\n    <sql.instanceName>DATABASE-connectionName-HERE\n    </sql.instanceName> <!-- See `gcloud sql instances describe [instanceName]` -->\n    <sql.userName>root</sql.userName>                         <!-- A reasonable default -->\n    <sql.password>MYSQL-ROOT-PASSWORD-HERE</sql.password> <!-- -Dsql.password=myRootPassword1234 -->\n    <!-- [END config] -->\n\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <maven.compiler.source>1.8</maven.compiler.source> <!-- REQUIRED -->\n    <maven.compiler.target>1.8</maven.compiler.target> <!-- REQUIRED -->\n    <maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>\n    <maven.compiler.showWarnings>true</maven.compiler.showWarnings>\n    <maven.war.filteringDeploymentDescriptors>true</maven.war.filteringDeploymentDescriptors>\n  </properties>\n\n  <!-- THINGS ONLY USED WHEN RUN LOCALLY -->\n  <profiles>\n    <profile>\n      <id>local</id>\n      <dependencies>\n        <dependency>\n          <groupId>com.google.cloud.sql</groupId>\n          <artifactId>mysql-socket-factory</artifactId>\n          <version>1.6.1</version>\n        </dependency>\n        <dependency>                        <!-- http://dev.mysql.com/doc/connector-j/en/ -->\n          <groupId>mysql</groupId>\n          <artifactId>mysql-connector-java</artifactId>\n          <version>8.0.30</version>  <!-- v5.x.x is Java 7, v6.x.x is Java 8 -->\n        </dependency>\n        <dependency>\n          <groupId>com.google.api-client</groupId>\n          <artifactId>google-api-client-appengine</artifactId>\n          <version>2.0.0</version>\n        </dependency>\n      </dependencies>\n    </profile>\n  </profiles>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.google.appengine</groupId>\n      <artifactId>appengine-api-1.0-sdk</artifactId>\n      <version>2.0.15</version>\n    </dependency>\n\n    <dependency>                        <!-- REQUIRED -->\n      <groupId>javax.servlet</groupId>  <!-- Java Servlet API -->\n      <artifactId>javax.servlet-api</artifactId>\n      <version>4.0.1</version>\n      <scope>provided</scope>           <!-- Provided by the Jetty Servlet engine -->\n    </dependency>\n\n    <dependency>                        <!-- Java Server Pages -->\n      <groupId>javax.servlet</groupId>\n      <artifactId>jsp-api</artifactId>\n      <version>2.0</version>\n    </dependency>\n\n    <dependency>                        <!-- JSP standard tag library -->\n      <groupId>jstl</groupId>\n      <artifactId>jstl</artifactId>\n      <version>1.2</version>\n    </dependency>\n\n    <dependency>                        <!-- Apache Taglibs -->\n      <groupId>taglibs</groupId>\n      <artifactId>standard</artifactId>\n      <version>1.1.2</version>\n    </dependency>\n\n    <dependency>                        <!-- Google Core Libraries for Java -->\n      <groupId>com.google.guava</groupId>\n      <artifactId>guava</artifactId>  <!-- https://github.com/google/guava/wiki -->\n      <version>33.1.0-jre</version>\n      <scope>compile</scope>\n    </dependency>\n\n    <dependency>                        <!-- http://www.joda.org/joda-time/ -->\n      <groupId>joda-time</groupId>\n      <artifactId>joda-time</artifactId>\n      <version>2.12.6</version>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <!-- Optional - for hot reload of the web application when using an IDE Eclipse / IDEA -->\n    <outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes\n    </outputDirectory>\n    <plugins>\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>appengine-maven-plugin</artifactId>\n        <version>2.4.4</version>\n        <configuration>\n          <!-- can be set w/ -DprojectId=myProjectId on command line -->\n          <projectId>GCLOUD_CONFIG</projectId>\n          <version>bookshelf</version>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <version>3.11.0</version>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "bookshelf-standard/2-structured-data/src/main/java/com/example/getstarted/basicactions/CreateBookServlet.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport com.example.getstarted.daos.BookDao;\nimport com.example.getstarted.objects.Book;\nimport java.io.IOException;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class CreateBookServlet extends HttpServlet {\n\n  // [START setup]\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,\n      IOException {\n    req.setAttribute(\"action\", \"Add\");          // Part of the Header in form.jsp\n    req.setAttribute(\"destination\", \"create\");  // The urlPattern to invoke (this Servlet)\n    req.setAttribute(\"page\", \"form\");           // Tells base.jsp to include form.jsp\n    req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n  }\n  // [END setup]\n\n  // [START formpost]\n  @Override\n  public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,\n      IOException {\n    // [START bookBuilder]\n    Book book = new Book.Builder()\n        .author(req.getParameter(\"author\"))   // form parameter\n        .description(req.getParameter(\"description\"))\n        .publishedDate(req.getParameter(\"publishedDate\"))\n        .title(req.getParameter(\"title\"))\n        .imageUrl(null)\n        .build();\n    // [END bookBuilder]\n\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    try {\n      Long id = dao.createBook(book);\n      resp.sendRedirect(\"/read?id=\" + id.toString());   // read what we just wrote\n    } catch (Exception e) {\n      throw new ServletException(\"Error creating book\", e);\n    }\n  }\n  // [END formpost]\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/2-structured-data/src/main/java/com/example/getstarted/basicactions/DeleteBookServlet.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport com.example.getstarted.daos.BookDao;\nimport java.io.IOException;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class DeleteBookServlet extends HttpServlet {\n\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,\n      IOException {\n    Long id = Long.decode(req.getParameter(\"id\"));\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    try {\n      dao.deleteBook(id);\n      resp.sendRedirect(\"/books\");\n    } catch (Exception e) {\n      throw new ServletException(\"Error deleting book\", e);\n    }\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/2-structured-data/src/main/java/com/example/getstarted/basicactions/ListBookServlet.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport com.example.getstarted.daos.BookDao;\nimport com.example.getstarted.daos.CloudSqlDao;\nimport com.example.getstarted.daos.DatastoreDao;\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.objects.Result;\nimport java.io.IOException;\nimport java.sql.SQLException;\nimport java.util.List;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class ListBookServlet extends HttpServlet {\n\n  @Override\n  public void init() throws ServletException {\n    BookDao dao = null;\n\n    // Creates the DAO based on the Context Parameters\n    String storageType = this.getServletContext().getInitParameter(\"bookshelf.storageType\");\n    switch (storageType) {\n      case \"datastore\":\n        dao = new DatastoreDao();\n        break;\n      case \"cloudsql\":\n        try {\n          // Use this url when using dev appserver, but connecting to Cloud SQL\n          String connect = this.getServletContext().getInitParameter(\"sql.urlRemote\");\n          if (connect.contains(\"localhost\")) {\n            // Use this url when using a local mysql server\n            connect = this.getServletContext().getInitParameter(\"sql.urlLocal\");\n          } else if (System.getProperty(\"com.google.appengine.runtime.version\")\n              .startsWith(\"Google App Engine/\")) {\n            // Use this url when on App Engine, connecting to Cloud SQL.\n            // Uses a special adapter because of the App Engine sandbox.\n            connect = this.getServletContext().getInitParameter(\"sql.urlRemoteGAE\");\n          }\n          dao = new CloudSqlDao(connect);\n        } catch (SQLException e) {\n          throw new ServletException(\"SQL error\", e);\n        }\n        break;\n      default:\n        throw new IllegalStateException(\n            \"Invalid storage type. Check if bookshelf.storageType property is set.\");\n    }\n    this.getServletContext().setAttribute(\"dao\", dao);\n  }\n\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException,\n      ServletException {\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    String startCursor = req.getParameter(\"cursor\");\n    List<Book> books = null;\n    String endCursor = null;\n    try {\n      Result<Book> result = dao.listBooks(startCursor);\n      books = result.result;\n      endCursor = result.cursor;\n    } catch (Exception e) {\n      throw new ServletException(\"Error listing books\", e);\n    }\n    req.getSession().getServletContext().setAttribute(\"books\", books);\n    StringBuilder bookNames = new StringBuilder();\n    for (Book book : books) {\n      bookNames.append(book.getTitle() + \" \");\n    }\n    req.setAttribute(\"cursor\", endCursor);\n    req.setAttribute(\"page\", \"list\");\n    req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/2-structured-data/src/main/java/com/example/getstarted/basicactions/ReadBookServlet.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport com.example.getstarted.daos.BookDao;\nimport com.example.getstarted.objects.Book;\nimport java.io.IOException;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class ReadBookServlet extends HttpServlet {\n\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException,\n      ServletException {\n    Long id = Long.decode(req.getParameter(\"id\"));\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    try {\n      Book book = dao.readBook(id);\n      req.setAttribute(\"book\", book);\n      req.setAttribute(\"page\", \"view\");\n      req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n    } catch (Exception e) {\n      throw new ServletException(\"Error reading book\", e);\n    }\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/2-structured-data/src/main/java/com/example/getstarted/basicactions/UpdateBookServlet.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport com.example.getstarted.daos.BookDao;\nimport com.example.getstarted.objects.Book;\nimport java.io.IOException;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class UpdateBookServlet extends HttpServlet {\n\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,\n      IOException {\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    try {\n      Book book = dao.readBook(Long.decode(req.getParameter(\"id\")));\n      req.setAttribute(\"book\", book);\n      req.setAttribute(\"action\", \"Edit\");\n      req.setAttribute(\"destination\", \"update\");\n      req.setAttribute(\"page\", \"form\");\n      req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n    } catch (Exception e) {\n      throw new ServletException(\"Error loading book for editing\", e);\n    }\n  }\n\n  @Override\n  public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,\n      IOException {\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    try {\n      // [START bookBuilder]\n      Book book = new Book.Builder()\n          .author(req.getParameter(\"author\"))\n          .description(req.getParameter(\"description\"))\n          .id(Long.decode(req.getParameter(\"id\")))\n          .publishedDate(req.getParameter(\"publishedDate\"))\n          .title(req.getParameter(\"title\"))\n          .build();\n      // [END bookBuilder]\n      dao.updateBook(book);\n      resp.sendRedirect(\"/read?id=\" + req.getParameter(\"id\"));\n    } catch (Exception e) {\n      throw new ServletException(\"Error updating book\", e);\n    }\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/2-structured-data/src/main/java/com/example/getstarted/daos/BookDao.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.daos;\n\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.objects.Result;\nimport java.sql.SQLException;\n\n// [START example]\npublic interface BookDao {\n  Long createBook(Book book) throws SQLException;\n\n  Book readBook(Long bookId) throws SQLException;\n\n  void updateBook(Book book) throws SQLException;\n\n  void deleteBook(Long bookId) throws SQLException;\n\n  Result<Book> listBooks(String startCursor) throws SQLException;\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/2-structured-data/src/main/java/com/example/getstarted/daos/CloudSqlDao.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.daos;\n\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.objects.Result;\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.ArrayList;\nimport java.util.List;\n\n// [START example]\npublic class CloudSqlDao implements BookDao {\n  // [START constructor]\n  private String sqlUrl;\n\n  /**\n   * A data access object for Bookshelf using a Google Cloud SQL server for storage.\n   */\n  public CloudSqlDao(final String url) throws SQLException {\n\n    sqlUrl = url;\n    final String createTableSql = \"CREATE TABLE IF NOT EXISTS books2 ( id INT NOT NULL \"\n        + \"AUTO_INCREMENT, author VARCHAR(255), createdBy VARCHAR(255), createdById VARCHAR(255), \"\n        + \"description VARCHAR(255), publishedDate VARCHAR(255), title VARCHAR(255), imageUrl \"\n        + \"VARCHAR(255), PRIMARY KEY (id))\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl)) {\n      conn.createStatement().executeUpdate(createTableSql);\n    }\n  }\n\n  // [END constructor]\n  // [START create]\n  @Override\n  public Long createBook(Book book) throws SQLException {\n    final String createBookString = \"INSERT INTO books2 \"\n        + \"(author, createdBy, createdById, description, publishedDate, title, imageUrl) \"\n        + \"VALUES (?, ?, ?, ?, ?, ?, ?)\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl);\n         final PreparedStatement createBookStmt = conn.prepareStatement(createBookString,\n             Statement.RETURN_GENERATED_KEYS)) {\n      createBookStmt.setString(1, book.getAuthor());\n      createBookStmt.setString(2, book.getCreatedBy());\n      createBookStmt.setString(3, book.getCreatedById());\n      createBookStmt.setString(4, book.getDescription());\n      createBookStmt.setString(5, book.getPublishedDate());\n      createBookStmt.setString(6, book.getTitle());\n      createBookStmt.setString(7, book.getImageUrl());\n      createBookStmt.executeUpdate();\n      try (ResultSet keys = createBookStmt.getGeneratedKeys()) {\n        keys.next();\n        return keys.getLong(1);\n      }\n    }\n  }\n\n  // [END create]\n  // [START read]\n  @Override\n  public Book readBook(Long bookId) throws SQLException {\n    final String readBookString = \"SELECT * FROM books2 WHERE id = ?\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl);\n         PreparedStatement readBookStmt = conn.prepareStatement(readBookString)) {\n      readBookStmt.setLong(1, bookId);\n      try (ResultSet keys = readBookStmt.executeQuery()) {\n        keys.next();\n        return new Book.Builder()\n            .author(keys.getString(Book.AUTHOR))\n            .createdBy(keys.getString(Book.CREATED_BY))\n            .createdById(keys.getString(Book.CREATED_BY_ID))\n            .description(keys.getString(Book.DESCRIPTION))\n            .id(keys.getLong(Book.ID))\n            .publishedDate(keys.getString(Book.PUBLISHED_DATE))\n            .title(keys.getString(Book.TITLE))\n            .imageUrl(keys.getString(Book.IMAGE_URL))\n            .build();\n      }\n    }\n  }\n\n  // [END read]\n  // [START update]\n  @Override\n  public void updateBook(Book book) throws SQLException {\n    final String updateBookString = \"UPDATE books2 SET author = ?, createdBy = ?, createdById = ?, \"\n        + \"description = ?, publishedDate = ?, title = ?, imageUrl = ? WHERE id = ?\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl);\n         PreparedStatement updateBookStmt = conn.prepareStatement(updateBookString)) {\n      updateBookStmt.setString(1, book.getAuthor());\n      updateBookStmt.setString(2, book.getCreatedBy());\n      updateBookStmt.setString(3, book.getCreatedById());\n      updateBookStmt.setString(4, book.getDescription());\n      updateBookStmt.setString(5, book.getPublishedDate());\n      updateBookStmt.setString(6, book.getTitle());\n      updateBookStmt.setString(7, book.getImageUrl());\n      updateBookStmt.setLong(8, book.getId());\n      updateBookStmt.executeUpdate();\n    }\n  }\n\n  // [END update]\n  // [START delete]\n  @Override\n  public void deleteBook(Long bookId) throws SQLException {\n    final String deleteBookString = \"DELETE FROM books2 WHERE id = ?\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl);\n         PreparedStatement deleteBookStmt = conn.prepareStatement(deleteBookString)) {\n      deleteBookStmt.setLong(1, bookId);\n      deleteBookStmt.executeUpdate();\n    }\n  }\n\n  // [END delete]\n  // [START listbooks]\n  @Override\n  public Result<Book> listBooks(String cursor) throws SQLException {\n    int offset = 0;\n    if (cursor != null && !cursor.equals(\"\")) {\n      offset = Integer.parseInt(cursor);\n    }\n    final String listBooksString = \"SELECT SQL_CALC_FOUND_ROWS author, createdBy, createdById, \"\n        + \"description, id, publishedDate, title, imageUrl FROM books2 ORDER BY title ASC \"\n        + \"LIMIT 10 OFFSET ?\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl);\n         PreparedStatement listBooksStmt = conn.prepareStatement(listBooksString)) {\n      listBooksStmt.setInt(1, offset);\n      List<Book> resultBooks = new ArrayList<>();\n      try (ResultSet rs = listBooksStmt.executeQuery()) {\n        while (rs.next()) {\n          Book book = new Book.Builder()\n              .author(rs.getString(Book.AUTHOR))\n              .createdBy(rs.getString(Book.CREATED_BY))\n              .createdById(rs.getString(Book.CREATED_BY_ID))\n              .description(rs.getString(Book.DESCRIPTION))\n              .id(rs.getLong(Book.ID))\n              .publishedDate(rs.getString(Book.PUBLISHED_DATE))\n              .title(rs.getString(Book.TITLE))\n              .imageUrl(rs.getString(Book.IMAGE_URL))\n              .build();\n          resultBooks.add(book);\n        }\n      }\n      try (ResultSet rs = conn.createStatement().executeQuery(\"SELECT FOUND_ROWS()\")) {\n        int totalNumRows = 0;\n        if (rs.next()) {\n          totalNumRows = rs.getInt(1);\n        }\n        if (totalNumRows > offset + 10) {\n          return new Result<>(resultBooks, Integer.toString(offset + 10));\n        } else {\n          return new Result<>(resultBooks);\n        }\n      }\n    }\n  }\n  // [END listbooks]\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/2-structured-data/src/main/java/com/example/getstarted/daos/DatastoreDao.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.daos;\n\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.objects.Result;\nimport com.google.appengine.api.datastore.Cursor;\nimport com.google.appengine.api.datastore.DatastoreService;\nimport com.google.appengine.api.datastore.DatastoreServiceFactory;\nimport com.google.appengine.api.datastore.Entity;\nimport com.google.appengine.api.datastore.EntityNotFoundException;\nimport com.google.appengine.api.datastore.FetchOptions;\nimport com.google.appengine.api.datastore.Key;\nimport com.google.appengine.api.datastore.KeyFactory;\nimport com.google.appengine.api.datastore.PreparedQuery;\nimport com.google.appengine.api.datastore.Query;\nimport com.google.appengine.api.datastore.Query.SortDirection;\nimport com.google.appengine.api.datastore.QueryResultIterator;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\n\n// [START example]\npublic class DatastoreDao implements BookDao {\n\n  // [START constructor]\n  private DatastoreService datastore;\n  private static final String BOOK_KIND = \"Book2\";\n\n  public DatastoreDao() {\n    datastore = DatastoreServiceFactory.getDatastoreService(); // Authorized Datastore service\n  }\n  // [END constructor]\n\n  // [START entityToBook]\n  public Book entityToBook(Entity entity) {\n    return new Book.Builder()                                     // Convert to Book form\n        .author((String) entity.getProperty(Book.AUTHOR))\n        .description((String) entity.getProperty(Book.DESCRIPTION))\n        .id(entity.getKey().getId())\n        .publishedDate((String) entity.getProperty(Book.PUBLISHED_DATE))\n        .title((String) entity.getProperty(Book.TITLE))\n        .build();\n  }\n  // [END entityToBook]\n\n  // [START create]\n  @Override\n  public Long createBook(Book book) {\n    Entity incBookEntity = new Entity(BOOK_KIND);  // Key will be assigned once written\n    incBookEntity.setProperty(Book.AUTHOR, book.getAuthor());\n    incBookEntity.setProperty(Book.DESCRIPTION, book.getDescription());\n    incBookEntity.setProperty(Book.PUBLISHED_DATE, book.getPublishedDate());\n    incBookEntity.setProperty(Book.TITLE, book.getTitle());\n\n    Key bookKey = datastore.put(incBookEntity); // Save the Entity\n    return bookKey.getId();                     // The ID of the Key\n  }\n  // [END create]\n\n  // [START read]\n  @Override\n  public Book readBook(Long bookId) {\n    try {\n      Entity bookEntity = datastore.get(KeyFactory.createKey(BOOK_KIND, bookId));\n      return entityToBook(bookEntity);\n    } catch (EntityNotFoundException e) {\n      return null;\n    }\n  }\n  // [END read]\n\n  // [START update]\n  @Override\n  public void updateBook(Book book) {\n    Key key = KeyFactory.createKey(BOOK_KIND, book.getId());  // From a book, create a Key\n    Entity entity = new Entity(key);         // Convert Book to an Entity\n    entity.setProperty(Book.AUTHOR, book.getAuthor());\n    entity.setProperty(Book.DESCRIPTION, book.getDescription());\n    entity.setProperty(Book.PUBLISHED_DATE, book.getPublishedDate());\n    entity.setProperty(Book.TITLE, book.getTitle());\n\n    datastore.put(entity);                   // Update the Entity\n  }\n  // [END update]\n\n  // [START delete]\n  @Override\n  public void deleteBook(Long bookId) {\n    Key key = KeyFactory.createKey(BOOK_KIND, bookId);        // Create the Key\n    datastore.delete(key);                      // Delete the Entity\n  }\n  // [END delete]\n\n  // [START entitiesToBooks]\n  public List<Book> entitiesToBooks(Iterator<Entity> results) {\n    List<Book> resultBooks = new ArrayList<>();\n    while (results.hasNext()) {  // We still have data\n      resultBooks.add(entityToBook(results.next()));      // Add the Book to the List\n    }\n    return resultBooks;\n  }\n  // [END entitiesToBooks]\n\n  // [START listbooks]\n  @Override\n  public Result<Book> listBooks(String startCursorString) {\n    FetchOptions fetchOptions = FetchOptions.Builder.withLimit(10); // Only show 10 at a time\n    if (startCursorString != null && !startCursorString.equals(\"\")) {\n      fetchOptions.startCursor(Cursor.fromWebSafeString(startCursorString)); // Where we left off\n    }\n    Query query = new Query(BOOK_KIND) // We only care about Books\n        .addSort(Book.TITLE, SortDirection.ASCENDING); // Use default Index \"title\"\n    PreparedQuery preparedQuery = datastore.prepare(query);\n    QueryResultIterator<Entity> results = preparedQuery.asQueryResultIterator(fetchOptions);\n\n    List<Book> resultBooks = entitiesToBooks(results);     // Retrieve and convert Entities\n    Cursor cursor = results.getCursor();              // Where to start next time\n    if (cursor != null && resultBooks.size() == 10) {         // Are we paging? Save Cursor\n      String cursorString = cursor.toWebSafeString();               // Cursors are WebSafe\n      return new Result<>(resultBooks, cursorString);\n    } else {\n      return new Result<>(resultBooks);\n    }\n  }\n  // [END listbooks]\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/2-structured-data/src/main/java/com/example/getstarted/objects/Book.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.objects;\n\n// [START example]\npublic class Book {\n  // [START book]\n  private String title;\n  private String author;\n  private String createdBy;\n  private String createdById;\n  private String publishedDate;\n  private String description;\n  private Long id;\n  private String imageUrl;\n  // [END book]\n  // [START keys]\n  public static final String AUTHOR = \"author\";\n  public static final String CREATED_BY = \"createdBy\";\n  public static final String CREATED_BY_ID = \"createdById\";\n  public static final String DESCRIPTION = \"description\";\n  public static final String ID = \"id\";\n  public static final String PUBLISHED_DATE = \"publishedDate\";\n  public static final String TITLE = \"title\";\n  public static final String IMAGE_URL = \"imageUrl\";\n  // [END keys]\n\n  // [START constructor]\n  // We use a Builder pattern here to simplify and standardize construction of Book objects.\n  private Book(Builder builder) {\n    this.title = builder.title;\n    this.author = builder.author;\n    this.createdBy = builder.createdBy;\n    this.createdById = builder.createdById;\n    this.publishedDate = builder.publishedDate;\n    this.description = builder.description;\n    this.id = builder.id;\n    this.imageUrl = builder.imageUrl;\n  }\n  // [END constructor]\n\n  // [START builder]\n  public static class Builder {\n    private String title;\n    private String author;\n    private String createdBy;\n    private String createdById;\n    private String publishedDate;\n    private String description;\n    private Long id;\n    private String imageUrl;\n\n    public Builder title(String title) {\n      this.title = title;\n      return this;\n    }\n\n    public Builder author(String author) {\n      this.author = author;\n      return this;\n    }\n\n    public Builder createdBy(String createdBy) {\n      this.createdBy = createdBy;\n      return this;\n    }\n\n    public Builder createdById(String createdById) {\n      this.createdById = createdById;\n      return this;\n    }\n\n    public Builder publishedDate(String publishedDate) {\n      this.publishedDate = publishedDate;\n      return this;\n    }\n\n    public Builder description(String description) {\n      this.description = description;\n      return this;\n    }\n\n    public Builder id(Long id) {\n      this.id = id;\n      return this;\n    }\n\n    public Builder imageUrl(String imageUrl) {\n      this.imageUrl = imageUrl;\n      return this;\n    }\n\n    public Book build() {\n      return new Book(this);\n    }\n  }\n\n  public String getTitle() {\n    return title;\n  }\n\n  public void setTitle(String title) {\n    this.title = title;\n  }\n\n  public String getAuthor() {\n    return author;\n  }\n\n  public void setAuthor(String author) {\n    this.author = author;\n  }\n\n  public String getCreatedBy() {\n    return createdBy;\n  }\n\n  public void setCreatedBy(String createdBy) {\n    this.createdBy = createdBy;\n  }\n\n  public String getCreatedById() {\n    return createdById;\n  }\n\n  public void setCreatedById(String createdById) {\n    this.createdById = createdById;\n  }\n\n  public String getPublishedDate() {\n    return publishedDate;\n  }\n\n  public void setPublishedDate(String publishedDate) {\n    this.publishedDate = publishedDate;\n  }\n\n  public String getDescription() {\n    return description;\n  }\n\n  public void setDescription(String description) {\n    this.description = description;\n  }\n\n  public Long getId() {\n    return id;\n  }\n\n  public void setId(Long id) {\n    this.id = id;\n  }\n\n  public String getImageUrl() {\n    return imageUrl;\n  }\n\n  public void setImageUrl(String imageUrl) {\n    this.imageUrl = imageUrl;\n  }\n\n  // [END builder]\n  @Override\n  public String toString() {\n    return\n        \"Title: \" + title + \", Author: \" + author + \", Published date: \" + publishedDate\n            + \", Added by: \" + createdBy;\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/2-structured-data/src/main/java/com/example/getstarted/objects/Result.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.objects;\n\nimport java.util.List;\n\n// [START example]\npublic class Result<K> {\n\n  public String cursor;\n  public List<K> result;\n\n  public Result(List<K> result, String cursor) {\n    this.result = result;\n    this.cursor = cursor;\n  }\n\n  public Result(List<K> result) {\n    this.result = result;\n    this.cursor = null;\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/2-structured-data/src/main/java/com/example/getstarted/util/DatastoreSessionFilter.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.util;\n\nimport com.google.appengine.api.datastore.DatastoreService;\nimport com.google.appengine.api.datastore.DatastoreServiceFactory;\nimport com.google.appengine.api.datastore.Entity;\nimport com.google.appengine.api.datastore.EntityNotFoundException;\nimport com.google.appengine.api.datastore.Key;\nimport com.google.appengine.api.datastore.KeyFactory;\nimport com.google.appengine.api.datastore.Query;\nimport com.google.appengine.api.datastore.Query.FilterOperator;\nimport com.google.appengine.api.datastore.Query.FilterPredicate;\nimport com.google.appengine.api.datastore.Transaction;\nimport com.google.common.collect.FluentIterable;\nimport com.google.common.collect.MapDifference;\nimport com.google.common.collect.Maps;\nimport java.io.IOException;\nimport java.math.BigInteger;\nimport java.security.SecureRandom;\nimport java.util.Enumeration;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.Map;\nimport javax.servlet.Filter;\nimport javax.servlet.FilterChain;\nimport javax.servlet.FilterConfig;\nimport javax.servlet.ServletException;\nimport javax.servlet.ServletRequest;\nimport javax.servlet.ServletResponse;\nimport javax.servlet.http.Cookie;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport javax.servlet.http.HttpSession;\nimport org.joda.time.DateTime;\nimport org.joda.time.DateTimeZone;\nimport org.joda.time.format.DateTimeFormat;\nimport org.joda.time.format.DateTimeFormatter;\n\n// [START init]\npublic class DatastoreSessionFilter implements Filter {\n\n  private static DatastoreService datastore;\n  private static final DateTimeFormatter DTF = DateTimeFormat.forPattern(\"yyyyMMddHHmmssSSS\");\n  private static final String SESSION_KIND = \"SessionVariable\";\n\n  @Override\n  public void init(FilterConfig config) throws ServletException {\n    // initialize local copy of datastore session variables\n\n    datastore = DatastoreServiceFactory.getDatastoreService();\n    // Delete all sessions unmodified for over two days\n    DateTime dt = DateTime.now(DateTimeZone.UTC);\n    Query query = new Query(SESSION_KIND).setFilter(new FilterPredicate(\n            \"lastModified\", FilterOperator.LESS_THAN_OR_EQUAL, dt.minusDays(2).toString(DTF)));\n    Iterator<Entity> results = datastore.prepare(query).asIterator();\n    while (results.hasNext()) {\n      Entity stateEntity = results.next();\n      datastore.delete(stateEntity.getKey());\n    }\n  }\n  // [END init]\n\n  @Override\n  public void doFilter(ServletRequest servletReq, ServletResponse servletResp, FilterChain chain)\n      throws IOException, ServletException {\n    HttpServletRequest req = (HttpServletRequest) servletReq;\n    HttpServletResponse resp = (HttpServletResponse) servletResp;\n\n    // Check if the session cookie is there, if not there, make a session cookie using a unique\n    // identifier.\n    String sessionId = getCookieValue(req, \"bookshelfSessionId\");\n    if (sessionId.equals(\"\")) {\n      String sessionNum = new BigInteger(130, new SecureRandom()).toString(32);\n      Cookie session = new Cookie(\"bookshelfSessionId\", sessionNum);\n      session.setPath(\"/\");\n      resp.addCookie(session);\n    }\n\n    Map<String, String> datastoreMap = loadSessionVariables(req);  // session variables for request\n\n    chain.doFilter(servletReq, servletResp);  // Allow the servlet to process request and response\n\n    HttpSession session = req.getSession();   // Create session map\n    Map<String, String> sessionMap = new HashMap<>();\n    Enumeration<String> attrNames = session.getAttributeNames();\n    while (attrNames.hasMoreElements()) {\n      String attrName = attrNames.nextElement();\n      sessionMap.put(attrName, (String) session.getAttribute(attrName));\n    }\n\n    // Create a diff between the new session variables and the existing session variables\n    // to minimize datastore access\n    MapDifference<String, String> diff = Maps.difference(sessionMap, datastoreMap);\n    Map<String, String> setMap = diff.entriesOnlyOnLeft();\n    Map<String, String> deleteMap = diff.entriesOnlyOnRight();\n\n    // Apply the diff\n    setSessionVariables(sessionId, setMap);\n    deleteSessionVariables(\n        sessionId,\n        FluentIterable.from(deleteMap.keySet()).toArray(String.class));\n  }\n\n  @SuppressWarnings({\"unused\", \"JdkObsolete\"})\n  private String mapToString(Map<String, String> map) {\n    StringBuffer names = new StringBuffer();\n    for (String name : map.keySet()) {\n      names.append(name + \" \");\n    }\n    return names.toString();\n  }\n\n  @Override\n  public void destroy() {\n  }\n\n  protected String getCookieValue(HttpServletRequest req, String cookieName) {\n    Cookie[] cookies = req.getCookies();\n    if (cookies != null) {\n      for (Cookie cookie : cookies) {\n        if (cookie.getName().equals(cookieName)) {\n          return cookie.getValue();\n        }\n      }\n    }\n    return \"\";\n  }\n\n  // [START deleteSessionVariables]\n  /**\n   * Delete a value stored in the project's datastore.\n   *\n   * @param sessionId Request from which the session is extracted.\n   */\n  protected void deleteSessionVariables(String sessionId, String... varNames) {\n    if (sessionId.equals(\"\")) {\n      return;\n    }\n    Key key = KeyFactory.createKey(SESSION_KIND, sessionId);\n    Transaction transaction = datastore.beginTransaction();\n    try {\n      Entity stateEntity = datastore.get(transaction, key);\n      for (String varName : varNames) {\n        stateEntity.removeProperty(varName);\n      }\n      datastore.put(transaction, stateEntity);\n      transaction.commit();\n    } catch (EntityNotFoundException e) {\n      // Ignore - if there's no session, there's nothing to delete.\n    } finally {\n      if (transaction.isActive()) {\n        transaction.rollback();\n      }\n    }\n  }\n  // [END deleteSessionVariables]\n\n  protected void deleteSessionWithValue(String varName, String varValue) {\n    Transaction transaction = datastore.beginTransaction();\n    try {\n      Query query = new Query(SESSION_KIND)\n          .setFilter(new FilterPredicate(varName, FilterOperator.EQUAL, varValue));\n      Iterator<Entity> results = datastore.prepare(transaction, query).asIterator();\n      while (results.hasNext()) {\n        Entity stateEntity = results.next();\n        datastore.delete(transaction, stateEntity.getKey());\n      }\n      transaction.commit();\n    } finally {\n      if (transaction.isActive()) {\n        transaction.rollback();\n      }\n    }\n  }\n\n  // [START setSessionVariables]\n  /**\n   * Stores the state value in each key-value pair in the project's datastore.\n   *\n   * @param sessionId Request from which to extract session.\n   * @param varName the name of the desired session variable\n   * @param varValue the value of the desired session variable\n   */\n  protected void setSessionVariables(String sessionId, Map<String, String> setMap) {\n    if (sessionId.equals(\"\")) {\n      return;\n    }\n    Key key = KeyFactory.createKey(SESSION_KIND, sessionId);\n    Transaction transaction = datastore.beginTransaction();\n    DateTime dt = DateTime.now(DateTimeZone.UTC);\n    dt.toString(DTF);\n    try {\n      Entity stateEntity;\n      try {\n        stateEntity = datastore.get(transaction, key);\n      } catch (EntityNotFoundException e) {\n        stateEntity = new Entity(key);\n      }\n      for (String varName : setMap.keySet()) {\n        stateEntity.setProperty(varName, setMap.get(varName));\n      }\n      stateEntity.setProperty(\"lastModified\", dt.toString(DTF));\n      datastore.put(transaction, stateEntity);\n      transaction.commit();\n    } finally {\n      if (transaction.isActive()) {\n        transaction.rollback();\n      }\n    }\n  }\n  // [END setSessionVariables]\n\n  // [START loadSessionVariables]\n  /**\n   * Take an HttpServletRequest, and copy all of the current session variables over to it\n   *\n   * @param req Request from which to extract session.\n   * @return a map of strings containing all the session variables loaded or an empty map.\n   */\n  protected Map<String, String> loadSessionVariables(HttpServletRequest req)\n      throws ServletException {\n    Map<String, String> datastoreMap = new HashMap<>();\n    String sessionId = getCookieValue(req, \"bookshelfSessionId\");\n    if (sessionId.equals(\"\")) {\n      return datastoreMap;\n    }\n    Key key = KeyFactory.createKey(SESSION_KIND, sessionId);\n    Transaction transaction = datastore.beginTransaction();\n    try {\n      Entity stateEntity = datastore.get(transaction, key);\n      Map<String, Object> properties = stateEntity.getProperties();\n      for (Map.Entry<String, Object> property : properties.entrySet()) {\n        req.getSession().setAttribute(property.getKey(), property.getValue());\n        datastoreMap.put(property.getKey(), (String) property.getValue());\n      }\n      transaction.commit();\n    } catch (EntityNotFoundException e) {\n      // Ignore - if there's no session, there's nothing to delete.\n    } finally {\n      if (transaction.isActive()) {\n        transaction.rollback();\n      }\n    }\n    return datastoreMap;\n  }\n  // [END loadSessionVariables]\n}\n"
  },
  {
    "path": "bookshelf-standard/2-structured-data/src/main/webapp/WEB-INF/appengine-web.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2015 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<appengine-web-app xmlns=\"http://appengine.google.com/ns/1.0\">\n  <threadsafe>true</threadsafe>\n  <use-google-connector-j>true</use-google-connector-j>\n\n  <!-- Configure java.util.logging -->\n  <system-properties>\n    <property name=\"java.util.logging.config.file\" value=\"WEB-INF/logging.properties\"/>\n  </system-properties>\n\n  <static-files>\n    <include path=\"/static/**\" />\n  </static-files>\n\n  <resource-files>\n    <include path=\"/resources/**\" />\n  </resource-files>\n\n  <sessions-enabled>true</sessions-enabled>\n  <async-session-persistence enabled=\"true\" />\n</appengine-web-app>\n"
  },
  {
    "path": "bookshelf-standard/2-structured-data/src/main/webapp/WEB-INF/logging.properties",
    "content": ""
  },
  {
    "path": "bookshelf-standard/2-structured-data/src/main/webapp/WEB-INF/web.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n Copyright 2016 Google Inc.\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n       http://www.apache.org/licenses/LICENSE-2.0\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START webxml] -->\n<web-app xmlns=\"http://java.sun.com/xml/ns/javaee\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xmlns:web=\"http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd\"\n         xsi:schemaLocation=\"http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd\" \n         version=\"2.5\">\n    <!--\n      A web.xml is needed to explicitly set the order in which filters process requests. Any filters\n      not included in web.xml will be loaded after filters listed below.\n    -->\n    <filter>\n      <filter-name>DatastoreSessionFilter</filter-name>\n      <filter-class>com.example.getstarted.util.DatastoreSessionFilter</filter-class>\n    </filter>\n    <filter-mapping>\n      <filter-name>DatastoreSessionFilter</filter-name>\n      <url-pattern>/</url-pattern>\n      <url-pattern>/books</url-pattern>\n      <url-pattern>/books/mine</url-pattern>\n      <url-pattern>/create</url-pattern>\n      <url-pattern>/delete</url-pattern>\n      <url-pattern>/login</url-pattern>\n      <url-pattern>/logout</url-pattern>\n      <url-pattern>/oauth2callback</url-pattern>\n      <url-pattern>/read</url-pattern>\n      <url-pattern>/update</url-pattern>\n    </filter-mapping>\n\n    <servlet>\n      <servlet-name>list</servlet-name>\n      <servlet-class>com.example.getstarted.basicactions.ListBookServlet</servlet-class>\n      <load-on-startup>1</load-on-startup>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>list</servlet-name>\n      <url-pattern>/</url-pattern>\n      <url-pattern>/books</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n      <servlet-name>create</servlet-name>\n      <servlet-class>com.example.getstarted.basicactions.CreateBookServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>create</servlet-name>\n      <url-pattern>/create</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n      <servlet-name>update</servlet-name>\n      <servlet-class>com.example.getstarted.basicactions.UpdateBookServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>update</servlet-name>\n      <url-pattern>/update</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n      <servlet-name>read</servlet-name>\n      <servlet-class>com.example.getstarted.basicactions.ReadBookServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>read</servlet-name>\n      <url-pattern>/read</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n      <servlet-name>delete</servlet-name>\n      <servlet-class>com.example.getstarted.basicactions.DeleteBookServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>delete</servlet-name>\n      <url-pattern>/delete</url-pattern>\n    </servlet-mapping>\n\n    <!-- [START config] -->\n    <context-param>\n        <param-name>bookshelf.storageType</param-name>\n        <param-value>${bookshelf.storageType}</param-value>\n    </context-param>\n\n    <context-param>\n        <param-name>sql.urlRemoteGAE</param-name>\n        <param-value>jdbc:google:mysql://${sql.instanceName}/${sql.dbName}?user=${sql.userName}&amp;password=${sql.password}</param-value>\n    </context-param>\n\n    <context-param>\n        <param-name>sql.urlRemote</param-name>\n        <param-value>jdbc:mysql://google/${sql.dbName}?cloudSqlInstance=${sql.instanceName}&amp;socketFactory=com.google.cloud.sql.mysql.SocketFactory&amp;user=${sql.userName}&amp;password=${sql.password}</param-value>\n    </context-param>\n\n    <context-param>\n        <param-name>sql.urlLocal</param-name>\n        <param-value>jdbc:mysql://localhost/${sql.dbName}?user=${sql.userName}&amp;password=${sql.password}&amp;useSSL=false</param-value>\n    </context-param>\n    <!-- [END config] -->\n</web-app>\n<!-- [END webxml] -->\n"
  },
  {
    "path": "bookshelf-standard/2-structured-data/src/main/webapp/base.jsp",
    "content": "<!--\nCopyright 2016 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START base] -->\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\" prefix=\"fn\" %>\n<html lang=\"en\">\n  <head>\n    <title>Bookshelf - Java on Google Cloud Platform</title>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link rel=\"stylesheet\" href=\"//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css\">\n  </head>\n  <body>\n    <div class=\"navbar navbar-default\">\n      <div class=\"container\">\n        <div class=\"navbar-header\">\n          <div class=\"navbar-brand\">Bookshelf</div>\n        </div>\n        <ul class=\"nav navbar-nav\">\n          <li><a href=\"/\">Books</a></li>\n          <c:if test=\"${isAuthConfigured}\"><li><a href=\"/books/mine\">My Books</a></li></c:if>\n        </ul>\n        <p class=\"navbar-text navbar-right\">\n          <c:choose>\n          <c:when test=\"${not empty token}\">\n          <!-- using pageContext requires jsp-api artifact in pom.xml -->\n          <a href=\"/logout\">\n            <c:if test=\"${not empty userImageUrl}\">\n              <img class=\"img-circle\" src=\"${fn:escapeXml(userImageUrl)}\" width=\"24\">\n            </c:if>\n            ${fn:escapeXml(userEmail)}\n          </a>\n          </c:when>\n          <c:when test=\"${isAuthConfigured}\">\n          <a href=\"/login\">Login</a>\n          </c:when>\n          </c:choose>\n        </p>\n      </div>\n    </div>\n    <c:import url=\"/${page}.jsp\" />\n  </body>\n</html>\n<!-- [END base]-->\n"
  },
  {
    "path": "bookshelf-standard/2-structured-data/src/main/webapp/form.jsp",
    "content": "<!--\nCopyright 2016 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START form] -->\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\"%>\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\" prefix=\"fn\"%>\n<div class=\"container\">\n  <h3>\n    <c:out value=\"${action}\" /> book\n  </h3>\n\n  <form method=\"POST\" action=\"${destination}\">\n\n    <div class=\"form-group\">\n      <label for=\"title\">Title</label>\n      <input type=\"text\" name=\"title\" id=\"title\" value=\"${fn:escapeXml(book.title)}\" class=\"form-control\" />\n    </div>\n\n    <div class=\"form-group\">\n      <label for=\"author\">Author</label>\n      <input type=\"text\" name=\"author\" id=\"author\" value=\"${fn:escapeXml(book.author)}\" class=\"form-control\" />\n    </div>\n\n    <div class=\"form-group\">\n      <label for=\"publishedDate\">Date Published</label>\n      <input type=\"text\" name=\"publishedDate\" id=\"publishedDate\" value=\"${fn:escapeXml(book.publishedDate)}\" class=\"form-control\" />\n    </div>\n\n    <div class=\"form-group\">\n      <label for=\"description\">Description</label>\n      <textarea name=\"description\" id=\"description\" class=\"form-control\">${fn:escapeXml(book.description)}</textarea>\n    </div>\n\n    <div class=\"form-group ${isCloudStorageConfigured ? '' : 'hidden'}\">\n      <label for=\"image\">Cover Image</label>\n      <input type=\"file\" name=\"file\" id=\"file\" class=\"form-control\" />\n    </div>\n\n    <div class=\"form-group hidden\">\n      <label for=\"imageUrl\">Cover Image URL</label>\n      <input type=\"hidden\" name=\"id\" value=\"${book.id}\" />\n      <input type=\"text\" name=\"imageUrl\" id=\"imageUrl\" value=\"${fn:escapeXml(book.imageUrl)}\" class=\"form-control\" />\n    </div>\n\n    <button type=\"submit\" class=\"btn btn-success\">Save</button>\n  </form>\n</div>\n<!-- [END form] -->\n"
  },
  {
    "path": "bookshelf-standard/2-structured-data/src/main/webapp/list.jsp",
    "content": "<!--\nCopyright 2016 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START list] -->\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\" prefix=\"fn\" %>\n<div class=\"container\">\n  <h3>Books</h3>\n  <a href=\"/create\" class=\"btn btn-success btn-sm\">\n    <i class=\"glyphicon glyphicon-plus\"></i>\n    Add book\n  </a>\n  <c:choose>\n  <c:when test=\"${empty books}\">\n  <p>No books found</p>\n  </c:when>\n  <c:otherwise>\n  <c:forEach items=\"${books}\" var=\"book\">\n  <div class=\"media\">\n    <a href=\"/read?id=${book.id}\">\n      <div class=\"media-left\">\n        <img alt=\"ahhh\" src=\"${fn:escapeXml(not empty book.imageUrl?book.imageUrl:'http://placekitten.com/g/128/192')}\">\n      </div>\n      <div class=\"media-body\">\n        <h4>${fn:escapeXml(book.title)}</h4>\n        <p>${fn:escapeXml(book.author)}</p>\n      </div>\n    </a>\n  </div>\n  </c:forEach>\n  <c:if test=\"${not empty cursor}\">\n  <nav>\n    <ul class=\"pager\">\n      <li><a href=\"?cursor=${fn:escapeXml(cursor)}\">More</a></li>\n    </ul>\n  </nav>\n  </c:if>\n  </c:otherwise>\n  </c:choose>\n</div>\n<!-- [END list] -->\n"
  },
  {
    "path": "bookshelf-standard/2-structured-data/src/main/webapp/view.jsp",
    "content": "<!--\nCopyright 2016 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START view] -->\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\" prefix=\"fn\" %>\n<div class=\"container\">\n  <h3>Book</h3>\n  <div class=\"btn-group\">\n    <a href=\"/update?id=${book.id}\" class=\"btn btn-primary btn-sm\">\n      <i class=\"glyphicon glyphicon-edit\"></i>\n      Edit book\n    </a>\n    <a href=\"/delete?id=${book.id}\" class=\"btn btn-danger btn-sm\">\n      <i class=\"glyphicon glyphicon-trash\"></i>\n      Delete book\n    </a>\n  </div>\n\n  <div class=\"media\">\n    <div class=\"media-left\">\n      <img class=\"book-image\" src=\"${fn:escapeXml(not empty book.imageUrl?book.imageUrl:'http://placekitten.com/g/128/192')}\">\n    </div>\n    <div class=\"media-body\">\n      <h4 class=\"book-title\">\n        ${fn:escapeXml(book.title)}\n        <small>${fn:escapeXml(book.publishedDate)}</small>\n      </h4>\n      <h5 class=\"book-author\">By ${fn:escapeXml(not empty book.author?book.author:'Unknown')}</h5>\n      <p class=\"book-description\">${fn:escapeXml(book.description)}</p>\n      <small class=\"book-added-by\">Added by\n        ${fn:escapeXml(not empty book.createdBy?book.createdBy:'Anonymous')}</small>\n    </div>\n  </div>\n</div>\n<!-- [END view] -->\n"
  },
  {
    "path": "bookshelf-standard/3-binary-data/README.md",
    "content": "# Bookshelf App for Java on App Engine Standard Tutorial\n## Binary Data\n\nContains the code for using Cloud Datastore and Cloud SQL v2.\n\nThis is part of a [Bookshelf tutorial](https://cloud.google.com/java/getting-started/tutorial-app).\n\nMost users can get this running by updating the parameters in `pom.xml`. You'll\nalso need to [create a bucket][create-bucket] in Google Cloud Storage, referred\nto below as `MY-BUCKET`.\n\n[create-bucket]: https://cloud.google.com/storage/docs/creating-buckets\n\n### Running Locally\n\n    mvn -Plocal clean appengine:devserver -Dbookshelf.bucket=MY-BUCKET\n\n**Note**: If you run into an error about `Invalid Credentials`, you may have to\nrun:\n\n    gcloud auth application-default login\n\n### Deploying to App Engine Standard\n\n* In the `pom.xml`, update the [App Engine Maven Plugin](https://cloud.google.com/appengine/docs/standard/java/tools/maven-reference)\nwith your Google Cloud Project Id:\n\n  ```\n  <plugin>\n    <groupId>com.google.cloud.tools</groupId>\n    <artifactId>appengine-maven-plugin</artifactId>\n    <version>2.3.0</version>\n    <configuration>\n      <projectId>GCLOUD_CONFIG</projectId>\n      <version>bookshelf</version>\n    </configuration>\n  </plugin>\n  ```\n\n* Deploy your App\n\n    mvn package appengine:deploy \\\n        -Dbookshelf.bucket=MY-BUCKET\n\nVisit it at http://bookshelf.<your-project-id>.appspot.com\n"
  },
  {
    "path": "bookshelf-standard/3-binary-data/jenkins.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2017 Google Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Fail on non-zero return and print command to stdout\nset -xe\n\n# Jenkins provides values for GOOGLE_PROJECT_ID and GOOGLE_VERSION_ID\n\n# Make sure it works on GAE7\n\n# Deploy and run selenium tests\nmvn clean appengine:update verify \\\n  -Pselenium \\\n  -Dappengine.appId=\"${GOOGLE_PROJECT_ID}\" \\\n  -Dappengine.version=\"${GOOGLE_VERSION_ID}\" \\\n  -Dbookshelf.bucket=\"${GCS_BUCKET}\" \\\n  -Dappengine.additionalParams=\"--service_account_json_key_file=${GOOGLE_APPLICATION_CREDENTIALS}\"\n\n\n# Make sure it deploys on GAE8\n\nFILE_PATH=src/main/webapp/WEB-INF/appengine-web.xml\nsed -i'.bak' '/<appengine-web-app/ a <runtime>java8</runtime>' \"${FILE_PATH}\"\n# Restore the backup after we're done\ntrap 'mv \"${FILE_PATH}\"{.bak,}' EXIT\n# Deploy and run selenium tests\nmvn clean appengine:update verify \\\n  -Pselenium \\\n  -Dappengine.appId=\"${GOOGLE_PROJECT_ID}\" \\\n  -Dappengine.version=\"${GOOGLE_VERSION_ID}\" \\\n  -Dbookshelf.bucket=\"${GCS_BUCKET}\" \\\n  -Dappengine.additionalParams=\"--service_account_json_key_file=${GOOGLE_APPLICATION_CREDENTIALS}\"\n\n"
  },
  {
    "path": "bookshelf-standard/3-binary-data/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\nCopyright 2016 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<project>                               <!-- REQUIRED -->\n\n  <modelVersion>4.0.0</modelVersion>    <!-- REQUIRED -->\n  <packaging>war</packaging>            <!-- REQUIRED -->\n\n  <groupId>com.example.standard.gettingstarted</groupId>\n  <artifactId>bookshelf-standard-3</artifactId>    <!-- Name of your project -->\n  <version>1.0-SNAPSHOT</version>       <!-- xx.xx.xx -SNAPSHOT means development -->\n\n  <parent> <!-- Only used for testing - NOT REQUIRED -->\n    <groupId>com.google.cloud.samples</groupId>\n    <artifactId>shared-configuration</artifactId>\n    <version>1.2.0</version>\n  </parent>\n\n  <properties>\n    <!-- [START config] -->\n    <bookshelf.storageType>datastore</bookshelf.storageType>   <!-- datastore or cloudsql -->\n\n    <sql.dbName>bookshelf</sql.dbName>                        <!-- A reasonable default -->\n    <!-- Instance Connection Name - project:region:dbName -->\n    <!-- -Dsql.instanceName=localhost to use a local MySQL server -->\n    <sql.instanceName>DATABASE-connectionName-HERE\n    </sql.instanceName> <!-- See `gcloud sql instances describe [instanceName]` -->\n    <sql.userName>root</sql.userName>                         <!-- A reasonable default -->\n    <sql.password>MYSQL-ROOT-PASSWORD-HERE</sql.password> <!-- -Dsql.password=myRootPassword1234 -->\n\n    <bookshelf.bucket>BUCKET-NAME-HERE</bookshelf.bucket> <!-- eg project-id.appspot.com -->\n    <!-- [END config] -->\n\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <maven.compiler.source>1.8</maven.compiler.source> <!-- REQUIRED -->\n    <maven.compiler.target>1.8</maven.compiler.target> <!-- REQUIRED -->\n    <maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>\n    <maven.compiler.showWarnings>true</maven.compiler.showWarnings>\n    <maven.war.filteringDeploymentDescriptors>true</maven.war.filteringDeploymentDescriptors>\n  </properties>\n\n  <!-- THINGS ONLY USED WHEN RUN LOCALLY -->\n  <profiles>\n    <profile>\n      <id>local</id>\n      <dependencies>\n        <dependency>\n          <groupId>com.google.cloud.sql</groupId>\n          <artifactId>mysql-socket-factory</artifactId>\n          <version>1.6.1</version>\n        </dependency>\n        <dependency>                        <!-- http://dev.mysql.com/doc/connector-j/en/ -->\n          <groupId>mysql</groupId>\n          <artifactId>mysql-connector-java</artifactId>\n          <version>8.0.30</version>  <!-- v5.x.x is Java 7, v6.x.x is Java 8 -->\n        </dependency>\n        <dependency>\n          <groupId>com.google.api-client</groupId>\n          <artifactId>google-api-client-appengine</artifactId>\n          <version>2.0.0</version>\n        </dependency>\n      </dependencies>\n    </profile>\n  </profiles>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.google.appengine</groupId>\n      <artifactId>appengine-api-1.0-sdk</artifactId>\n      <version>2.0.15</version>\n    </dependency>\n\n    <dependency>                        <!-- REQUIRED -->\n      <groupId>javax.servlet</groupId>  <!-- Java Servlet API -->\n      <artifactId>javax.servlet-api</artifactId>\n      <version>4.0.1</version>\n      <scope>provided</scope>           <!-- Provided by the Jetty Servlet engine -->\n    </dependency>\n\n    <dependency>                        <!-- Java Server Pages -->\n      <groupId>javax.servlet</groupId>\n      <artifactId>jsp-api</artifactId>\n      <version>2.0</version>\n    </dependency>\n\n    <dependency>                        <!-- JSP standard tag library -->\n      <groupId>jstl</groupId>\n      <artifactId>jstl</artifactId>\n      <version>1.2</version>\n    </dependency>\n\n    <dependency>                        <!-- Apache Taglibs -->\n      <groupId>taglibs</groupId>\n      <artifactId>standard</artifactId>\n      <version>1.1.2</version>\n    </dependency>\n\n    <dependency>                        <!-- Google Cloud Client Library for Java -->\n      <groupId>com.google.cloud</groupId>\n      <artifactId>google-cloud-storage</artifactId>\n      <version>2.23.0</version>\n    </dependency>\n\n    <dependency>                        <!-- Google Core Libraries for Java -->\n      <groupId>com.google.guava</groupId>\n      <artifactId>guava</artifactId>  <!-- https://github.com/google/guava/wiki -->\n      <version>33.1.0-jre</version>\n      <scope>compile</scope>\n    </dependency>\n\n    <dependency>                        <!-- http://www.joda.org/joda-time/ -->\n      <groupId>joda-time</groupId>\n      <artifactId>joda-time</artifactId>\n      <version>2.12.6</version>\n    </dependency>\n\n    <dependency>                        <!-- http://commons.apache.org/proper/commons-fileupload/ -->\n      <groupId>commons-fileupload</groupId>\n      <artifactId>commons-fileupload</artifactId>\n      <version>1.5</version>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <!-- Optional - for hot reload of the web application when using an IDE Eclipse / IDEA -->\n    <outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes\n    </outputDirectory>\n    <plugins>\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>appengine-maven-plugin</artifactId>\n        <version>2.4.4</version>\n        <configuration>\n          <!-- can be set w/ -DprojectId=myProjectId on command line -->\n          <projectId>GCLOUD_CONFIG</projectId>\n          <version>bookshelf</version>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <version>3.11.0</version>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "bookshelf-standard/3-binary-data/src/main/java/com/example/getstarted/basicactions/CreateBookServlet.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport com.example.getstarted.daos.BookDao;\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.util.CloudStorageHelper;\nimport com.google.common.base.Strings;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport org.apache.commons.fileupload.FileItemIterator;\nimport org.apache.commons.fileupload.FileItemStream;\nimport org.apache.commons.fileupload.FileUploadException;\nimport org.apache.commons.fileupload.servlet.ServletFileUpload;\nimport org.apache.commons.fileupload.util.Streams;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class CreateBookServlet extends HttpServlet {\n\n  // [START setup]\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,\n      IOException {\n    req.setAttribute(\"action\", \"Add\");          // Part of the Header in form.jsp\n    req.setAttribute(\"destination\", \"create\");  // The urlPattern to invoke (this Servlet)\n    req.setAttribute(\"page\", \"form\");           // Tells base.jsp to include form.jsp\n    req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n  }\n  // [END setup]\n\n  // [START formpost]\n  @Override\n  public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,\n      IOException {\n    // [START storageHelper]\n    assert ServletFileUpload.isMultipartContent(req);\n    CloudStorageHelper storageHelper =\n        (CloudStorageHelper) getServletContext().getAttribute(\"storageHelper\");\n\n    String newImageUrl = null;\n    Map<String, String> params = new HashMap<String, String>();\n    try {\n      FileItemIterator iter = new ServletFileUpload().getItemIterator(req);\n      while (iter.hasNext()) {\n        FileItemStream item = iter.next();\n        if (item.isFormField()) {\n          params.put(item.getFieldName(), Streams.asString(item.openStream()));\n        } else if (!Strings.isNullOrEmpty(item.getName())) {\n          newImageUrl = storageHelper.uploadFile(\n              item, getServletContext().getInitParameter(\"bookshelf.bucket\"));\n        }\n      }\n    } catch (FileUploadException e) {\n      throw new IOException(e);\n    }\n\n    // [START bookBuilder]\n    Book book = new Book.Builder()\n        .author(params.get(\"author\"))\n        .description(params.get(\"description\"))\n        .publishedDate(params.get(\"publishedDate\"))\n        .title(params.get(\"title\"))\n        .imageUrl(null == newImageUrl ? params.get(\"imageUrl\") : newImageUrl)\n        .build();\n    // [END bookBuilder]\n    // [END storageHelper]\n\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    try {\n      Long id = dao.createBook(book);\n      resp.sendRedirect(\"/read?id=\" + id.toString());   // read what we just wrote\n    } catch (Exception e) {\n      throw new ServletException(\"Error creating book\", e);\n    }\n  }\n  // [END formpost]\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/3-binary-data/src/main/java/com/example/getstarted/basicactions/DeleteBookServlet.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport com.example.getstarted.daos.BookDao;\nimport java.io.IOException;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class DeleteBookServlet extends HttpServlet {\n\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,\n      IOException {\n    Long id = Long.decode(req.getParameter(\"id\"));\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    try {\n      dao.deleteBook(id);\n      resp.sendRedirect(\"/books\");\n    } catch (Exception e) {\n      throw new ServletException(\"Error deleting book\", e);\n    }\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/3-binary-data/src/main/java/com/example/getstarted/basicactions/ListBookServlet.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport com.example.getstarted.daos.BookDao;\nimport com.example.getstarted.daos.CloudSqlDao;\nimport com.example.getstarted.daos.DatastoreDao;\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.objects.Result;\nimport com.example.getstarted.util.CloudStorageHelper;\nimport com.google.common.base.Strings;\nimport java.io.IOException;\nimport java.sql.SQLException;\nimport java.util.List;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class ListBookServlet extends HttpServlet {\n\n  @Override\n  public void init() throws ServletException {\n    BookDao dao = null;\n\n    // [START storageHelper]\n    CloudStorageHelper storageHelper = new CloudStorageHelper();\n    // [END storageHelper]\n\n    // Creates the DAO based on the Context Parameters\n    String storageType = this.getServletContext().getInitParameter(\"bookshelf.storageType\");\n    switch (storageType) {\n      case \"datastore\":\n        dao = new DatastoreDao();\n        break;\n      case \"cloudsql\":\n        try {\n          // Use this url when using dev appserver, but connecting to Cloud SQL\n          String connect = this.getServletContext().getInitParameter(\"sql.urlRemote\");\n          if (connect.contains(\"localhost\")) {\n            // Use this url when using a local mysql server\n            connect = this.getServletContext().getInitParameter(\"sql.urlLocal\");\n          } else if (System.getProperty(\"com.google.appengine.runtime.version\")\n              .startsWith(\"Google App Engine/\")) {\n            // Use this url when on App Engine, connecting to Cloud SQL.\n            // Uses a special adapter because of the App Engine sandbox.\n            connect = this.getServletContext().getInitParameter(\"sql.urlRemoteGAE\");\n          }\n          dao = new CloudSqlDao(connect);\n        } catch (SQLException e) {\n          throw new ServletException(\"SQL error\", e);\n        }\n        break;\n      default:\n        throw new IllegalStateException(\n            \"Invalid storage type. Check if bookshelf.storageType property is set.\");\n    }\n    this.getServletContext().setAttribute(\"dao\", dao);\n    // [START save_storage]\n    this.getServletContext().setAttribute(\"storageHelper\", storageHelper);\n    this.getServletContext().setAttribute(\n        \"isCloudStorageConfigured\",  // Hide upload when Cloud Storage is not configured.\n        !Strings.isNullOrEmpty(getServletContext().getInitParameter(\"bookshelf.bucket\")));\n    // [END save_storage]\n  }\n\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException,\n      ServletException {\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    String startCursor = req.getParameter(\"cursor\");\n    List<Book> books = null;\n    String endCursor = null;\n    try {\n      Result<Book> result = dao.listBooks(startCursor);\n      books = result.result;\n      endCursor = result.cursor;\n    } catch (Exception e) {\n      throw new ServletException(\"Error listing books\", e);\n    }\n    req.getSession().getServletContext().setAttribute(\"books\", books);\n    StringBuilder bookNames = new StringBuilder();\n    for (Book book : books) {\n      bookNames.append(book.getTitle() + \" \");\n    }\n    req.setAttribute(\"cursor\", endCursor);\n    req.setAttribute(\"page\", \"list\");\n    req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/3-binary-data/src/main/java/com/example/getstarted/basicactions/ReadBookServlet.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport com.example.getstarted.daos.BookDao;\nimport com.example.getstarted.objects.Book;\nimport java.io.IOException;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class ReadBookServlet extends HttpServlet {\n\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException,\n      ServletException {\n    Long id = Long.decode(req.getParameter(\"id\"));\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    try {\n      Book book = dao.readBook(id);\n      req.setAttribute(\"book\", book);\n      req.setAttribute(\"page\", \"view\");\n      req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n    } catch (Exception e) {\n      throw new ServletException(\"Error reading book\", e);\n    }\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/3-binary-data/src/main/java/com/example/getstarted/basicactions/UpdateBookServlet.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport com.example.getstarted.daos.BookDao;\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.util.CloudStorageHelper;\nimport com.google.common.base.Strings;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport org.apache.commons.fileupload.FileItemIterator;\nimport org.apache.commons.fileupload.FileItemStream;\nimport org.apache.commons.fileupload.FileUploadException;\nimport org.apache.commons.fileupload.servlet.ServletFileUpload;\nimport org.apache.commons.fileupload.util.Streams;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class UpdateBookServlet extends HttpServlet {\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,\n      IOException {\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    try {\n      Book book = dao.readBook(Long.decode(req.getParameter(\"id\")));\n      req.setAttribute(\"book\", book);\n      req.setAttribute(\"action\", \"Edit\");\n      req.setAttribute(\"destination\", \"update\");\n      req.setAttribute(\"page\", \"form\");\n      req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n    } catch (Exception e) {\n      throw new ServletException(\"Error loading book for editing\", e);\n    }\n  }\n\n  @Override\n  public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,\n      IOException {\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n\n    // [START storageHelper]\n    assert ServletFileUpload.isMultipartContent(req);\n    CloudStorageHelper storageHelper =\n        (CloudStorageHelper) getServletContext().getAttribute(\"storageHelper\");\n\n    String newImageUrl = null;\n    Map<String, String> params = new HashMap<String, String>();\n    try {\n      FileItemIterator iter = new ServletFileUpload().getItemIterator(req);\n      while (iter.hasNext()) {\n        FileItemStream item = iter.next();\n        if (item.isFormField()) {\n          params.put(item.getFieldName(), Streams.asString(item.openStream()));\n        } else if (!Strings.isNullOrEmpty(item.getName())) {\n          newImageUrl = storageHelper.uploadFile(\n              item, getServletContext().getInitParameter(\"bookshelf.bucket\"));\n        }\n      }\n    } catch (FileUploadException e) {\n      throw new IOException(e);\n    }\n\n    // [START bookBuilder]\n    Book book = new Book.Builder()\n        .author(params.get(\"author\"))\n        .description(params.get(\"description\"))\n        .publishedDate(params.get(\"publishedDate\"))\n        .title(params.get(\"title\"))\n        .imageUrl(null == newImageUrl ? params.get(\"imageUrl\") : newImageUrl)\n        .id(Long.decode(params.get(\"id\")))\n        .build();\n    // [END bookBuilder]\n    // [END storageHelper]\n\n    try {\n      dao.updateBook(book);\n      resp.sendRedirect(\"/read?id=\" + params.get(\"id\"));\n    } catch (Exception e) {\n      throw new ServletException(\"Error updating book\", e);\n    }\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/3-binary-data/src/main/java/com/example/getstarted/daos/BookDao.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.daos;\n\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.objects.Result;\nimport java.sql.SQLException;\n\n// [START example]\npublic interface BookDao {\n  Long createBook(Book book) throws SQLException;\n\n  Book readBook(Long bookId) throws SQLException;\n\n  void updateBook(Book book) throws SQLException;\n\n  void deleteBook(Long bookId) throws SQLException;\n\n  Result<Book> listBooks(String startCursor) throws SQLException;\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/3-binary-data/src/main/java/com/example/getstarted/daos/CloudSqlDao.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.daos;\n\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.objects.Result;\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.ArrayList;\nimport java.util.List;\n\n// [START example]\npublic class CloudSqlDao implements BookDao {\n  // [START constructor]\n  private String sqlUrl;\n\n  /**\n   * A data access object for Bookshelf using a Google Cloud SQL server for storage.\n   */\n  public CloudSqlDao(final String url) throws SQLException {\n\n    sqlUrl = url;\n    final String createTableSql = \"CREATE TABLE IF NOT EXISTS books3 ( id INT NOT NULL \"\n        + \"AUTO_INCREMENT, author VARCHAR(255), createdBy VARCHAR(255), createdById VARCHAR(255), \"\n        + \"description VARCHAR(255), publishedDate VARCHAR(255), title VARCHAR(255), imageUrl \"\n        + \"VARCHAR(255), PRIMARY KEY (id))\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl)) {\n      conn.createStatement().executeUpdate(createTableSql);\n    }\n  }\n  // [END constructor]\n\n  // [START create]\n  @Override\n  public Long createBook(Book book) throws SQLException {\n    final String createBookString = \"INSERT INTO books3 \"\n        + \"(author, createdBy, createdById, description, publishedDate, title, imageUrl) \"\n        + \"VALUES (?, ?, ?, ?, ?, ?, ?)\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl);\n        final PreparedStatement createBookStmt = conn.prepareStatement(createBookString,\n            Statement.RETURN_GENERATED_KEYS)) {\n      createBookStmt.setString(1, book.getAuthor());\n      createBookStmt.setString(2, book.getCreatedBy());\n      createBookStmt.setString(3, book.getCreatedById());\n      createBookStmt.setString(4, book.getDescription());\n      createBookStmt.setString(5, book.getPublishedDate());\n      createBookStmt.setString(6, book.getTitle());\n      createBookStmt.setString(7, book.getImageUrl());\n      createBookStmt.executeUpdate();\n      try (ResultSet keys = createBookStmt.getGeneratedKeys()) {\n        keys.next();\n        return keys.getLong(1);\n      }\n    }\n  }\n  // [END create]\n\n  // [START read]\n  @Override\n  public Book readBook(Long bookId) throws SQLException {\n    final String readBookString = \"SELECT * FROM books3 WHERE id = ?\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl);\n        PreparedStatement readBookStmt = conn.prepareStatement(readBookString)) {\n      readBookStmt.setLong(1, bookId);\n      try (ResultSet keys = readBookStmt.executeQuery()) {\n        keys.next();\n        return new Book.Builder()\n            .author(keys.getString(Book.AUTHOR))\n            .createdBy(keys.getString(Book.CREATED_BY))\n            .createdById(keys.getString(Book.CREATED_BY_ID))\n            .description(keys.getString(Book.DESCRIPTION))\n            .id(keys.getLong(Book.ID))\n            .publishedDate(keys.getString(Book.PUBLISHED_DATE))\n            .title(keys.getString(Book.TITLE))\n            .imageUrl(keys.getString(Book.IMAGE_URL))\n            .build();\n      }\n    }\n  }\n  // [END read]\n\n  // [START update]\n  @Override\n  public void updateBook(Book book) throws SQLException {\n    final String updateBookString = \"UPDATE books3 SET author = ?, createdBy = ?, createdById = ?, \"\n        + \"description = ?, publishedDate = ?, title = ?, imageUrl = ? WHERE id = ?\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl);\n        PreparedStatement updateBookStmt = conn.prepareStatement(updateBookString)) {\n      updateBookStmt.setString(1, book.getAuthor());\n      updateBookStmt.setString(2, book.getCreatedBy());\n      updateBookStmt.setString(3, book.getCreatedById());\n      updateBookStmt.setString(4, book.getDescription());\n      updateBookStmt.setString(5, book.getPublishedDate());\n      updateBookStmt.setString(6, book.getTitle());\n      updateBookStmt.setString(7, book.getImageUrl());\n      updateBookStmt.setLong(8, book.getId());\n      updateBookStmt.executeUpdate();\n    }\n  }\n  // [END update]\n\n  // [START delete]\n  @Override\n  public void deleteBook(Long bookId) throws SQLException {\n    final String deleteBookString = \"DELETE FROM books3 WHERE id = ?\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl);\n        PreparedStatement deleteBookStmt = conn.prepareStatement(deleteBookString)) {\n      deleteBookStmt.setLong(1, bookId);\n      deleteBookStmt.executeUpdate();\n    }\n  }\n  // [END delete]\n\n  // [START listbooks]\n  @Override\n  public Result<Book> listBooks(String cursor) throws SQLException {\n    int offset = 0;\n    if (cursor != null && !cursor.equals(\"\")) {\n      offset = Integer.parseInt(cursor);\n    }\n    final String listBooksString = \"SELECT SQL_CALC_FOUND_ROWS author, createdBy, createdById, \"\n        + \"description, id, publishedDate, title, imageUrl FROM books3 ORDER BY title ASC \"\n        + \"LIMIT 10 OFFSET ?\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl);\n        PreparedStatement listBooksStmt = conn.prepareStatement(listBooksString)) {\n      listBooksStmt.setInt(1, offset);\n      List<Book> resultBooks = new ArrayList<>();\n      try (ResultSet rs = listBooksStmt.executeQuery()) {\n        while (rs.next()) {\n          Book book = new Book.Builder()\n              .author(rs.getString(Book.AUTHOR))\n              .createdBy(rs.getString(Book.CREATED_BY))\n              .createdById(rs.getString(Book.CREATED_BY_ID))\n              .description(rs.getString(Book.DESCRIPTION))\n              .id(rs.getLong(Book.ID))\n              .publishedDate(rs.getString(Book.PUBLISHED_DATE))\n              .title(rs.getString(Book.TITLE))\n              .imageUrl(rs.getString(Book.IMAGE_URL))\n              .build();\n          resultBooks.add(book);\n        }\n      }\n      try (ResultSet rs = conn.createStatement().executeQuery(\"SELECT FOUND_ROWS()\")) {\n        int totalNumRows = 0;\n        if (rs.next()) {\n          totalNumRows = rs.getInt(1);\n        }\n        if (totalNumRows > offset + 10) {\n          return new Result<>(resultBooks, Integer.toString(offset + 10));\n        } else {\n          return new Result<>(resultBooks);\n        }\n      }\n    }\n  }\n  // [END listbooks]\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/3-binary-data/src/main/java/com/example/getstarted/daos/DatastoreDao.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.daos;\n\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.objects.Result;\nimport com.google.appengine.api.datastore.Cursor;\nimport com.google.appengine.api.datastore.DatastoreService;\nimport com.google.appengine.api.datastore.DatastoreServiceFactory;\nimport com.google.appengine.api.datastore.Entity;\nimport com.google.appengine.api.datastore.EntityNotFoundException;\nimport com.google.appengine.api.datastore.FetchOptions;\nimport com.google.appengine.api.datastore.Key;\nimport com.google.appengine.api.datastore.KeyFactory;\nimport com.google.appengine.api.datastore.PreparedQuery;\nimport com.google.appengine.api.datastore.Query;\nimport com.google.appengine.api.datastore.Query.SortDirection;\nimport com.google.appengine.api.datastore.QueryResultIterator;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\n\n// [START example]\npublic class DatastoreDao implements BookDao {\n\n  // [START constructor]\n  private DatastoreService datastore;\n  private static final String BOOK_KIND = \"Book3\";\n\n  public DatastoreDao() {\n    datastore = DatastoreServiceFactory.getDatastoreService(); // Authorized Datastore service\n  }\n  // [END constructor]\n\n  // [START entityToBook]\n  public Book entityToBook(Entity entity) {\n    return new Book.Builder()                                     // Convert to Book form\n        .author((String) entity.getProperty(Book.AUTHOR))\n        .description((String) entity.getProperty(Book.DESCRIPTION))\n        .id(entity.getKey().getId())\n        .publishedDate((String) entity.getProperty(Book.PUBLISHED_DATE))\n        .imageUrl((String) entity.getProperty(Book.IMAGE_URL))\n        .title((String) entity.getProperty(Book.TITLE))\n        .build();\n  }\n  // [END entityToBook]\n\n  // [START create]\n  @Override\n  public Long createBook(Book book) {\n    Entity incBookEntity = new Entity(BOOK_KIND);  // Key will be assigned once written\n    incBookEntity.setProperty(Book.AUTHOR, book.getAuthor());\n    incBookEntity.setProperty(Book.DESCRIPTION, book.getDescription());\n    incBookEntity.setProperty(Book.PUBLISHED_DATE, book.getPublishedDate());\n    incBookEntity.setProperty(Book.TITLE, book.getTitle());\n    incBookEntity.setProperty(Book.IMAGE_URL, book.getImageUrl());\n\n    Key bookKey = datastore.put(incBookEntity); // Save the Entity\n    return bookKey.getId();                     // The ID of the Key\n  }\n  // [END create]\n\n  // [START read]\n  @Override\n  public Book readBook(Long bookId) {\n    try {\n      Entity bookEntity = datastore.get(KeyFactory.createKey(BOOK_KIND, bookId));\n      return entityToBook(bookEntity);\n    } catch (EntityNotFoundException e) {\n      return null;\n    }\n  }\n  // [END read]\n\n  // [START update]\n  @Override\n  public void updateBook(Book book) {\n    Key key = KeyFactory.createKey(BOOK_KIND, book.getId());  // From a book, create a Key\n    Entity entity = new Entity(key);         // Convert Book to an Entity\n    entity.setProperty(Book.AUTHOR, book.getAuthor());\n    entity.setProperty(Book.DESCRIPTION, book.getDescription());\n    entity.setProperty(Book.PUBLISHED_DATE, book.getPublishedDate());\n    entity.setProperty(Book.TITLE, book.getTitle());\n    entity.setProperty(Book.IMAGE_URL, book.getImageUrl());\n\n    datastore.put(entity);                   // Update the Entity\n  }\n  // [END update]\n\n  // [START delete]\n  @Override\n  public void deleteBook(Long bookId) {\n    Key key = KeyFactory.createKey(BOOK_KIND, bookId);        // Create the Key\n    datastore.delete(key);                      // Delete the Entity\n  }\n  // [END delete]\n\n  // [START entitiesToBooks]\n  public List<Book> entitiesToBooks(Iterator<Entity> results) {\n    List<Book> resultBooks = new ArrayList<>();\n    while (results.hasNext()) {  // We still have data\n      resultBooks.add(entityToBook(results.next()));      // Add the Book to the List\n    }\n    return resultBooks;\n  }\n  // [END entitiesToBooks]\n\n  // [START listbooks]\n  @Override\n  public Result<Book> listBooks(String startCursorString) {\n    FetchOptions fetchOptions = FetchOptions.Builder.withLimit(10); // Only show 10 at a time\n    if (startCursorString != null && !startCursorString.equals(\"\")) {\n      fetchOptions.startCursor(Cursor.fromWebSafeString(startCursorString)); // Where we left off\n    }\n    Query query = new Query(BOOK_KIND) // We only care about Books\n        .addSort(Book.TITLE, SortDirection.ASCENDING); // Use default Index \"title\"\n    PreparedQuery preparedQuery = datastore.prepare(query);\n    QueryResultIterator<Entity> results = preparedQuery.asQueryResultIterator(fetchOptions);\n\n    List<Book> resultBooks = entitiesToBooks(results);     // Retrieve and convert Entities\n    Cursor cursor = results.getCursor();              // Where to start next time\n    if (cursor != null && resultBooks.size() == 10) {         // Are we paging? Save Cursor\n      String cursorString = cursor.toWebSafeString();               // Cursors are WebSafe\n      return new Result<>(resultBooks, cursorString);\n    } else {\n      return new Result<>(resultBooks);\n    }\n  }\n  // [END listbooks]\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/3-binary-data/src/main/java/com/example/getstarted/objects/Book.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.objects;\n\n// [START example]\npublic class Book {\n  // [START book]\n  private String title;\n  private String author;\n  private String createdBy;\n  private String createdById;\n  private String publishedDate;\n  private String description;\n  private Long id;\n  private String imageUrl;\n  // [END book]\n  // [START keys]\n  public static final String AUTHOR = \"author\";\n  public static final String CREATED_BY = \"createdBy\";\n  public static final String CREATED_BY_ID = \"createdById\";\n  public static final String DESCRIPTION = \"description\";\n  public static final String ID = \"id\";\n  public static final String PUBLISHED_DATE = \"publishedDate\";\n  public static final String TITLE = \"title\";\n  public static final String IMAGE_URL = \"imageUrl\";\n  // [END keys]\n\n  // [START constructor]\n  // We use a Builder pattern here to simplify and standardize construction of Book objects.\n  private Book(Builder builder) {\n    this.title = builder.title;\n    this.author = builder.author;\n    this.createdBy = builder.createdBy;\n    this.createdById = builder.createdById;\n    this.publishedDate = builder.publishedDate;\n    this.description = builder.description;\n    this.id = builder.id;\n    this.imageUrl = builder.imageUrl;\n  }\n  // [END constructor]\n\n  // [START builder]\n  public static class Builder {\n    private String title;\n    private String author;\n    private String createdBy;\n    private String createdById;\n    private String publishedDate;\n    private String description;\n    private Long id;\n    private String imageUrl;\n\n    public Builder title(String title) {\n      this.title = title;\n      return this;\n    }\n\n    public Builder author(String author) {\n      this.author = author;\n      return this;\n    }\n\n    public Builder createdBy(String createdBy) {\n      this.createdBy = createdBy;\n      return this;\n    }\n\n    public Builder createdById(String createdById) {\n      this.createdById = createdById;\n      return this;\n    }\n\n    public Builder publishedDate(String publishedDate) {\n      this.publishedDate = publishedDate;\n      return this;\n    }\n\n    public Builder description(String description) {\n      this.description = description;\n      return this;\n    }\n\n    public Builder id(Long id) {\n      this.id = id;\n      return this;\n    }\n\n    public Builder imageUrl(String imageUrl) {\n      this.imageUrl = imageUrl;\n      return this;\n    }\n\n    public Book build() {\n      return new Book(this);\n    }\n  }\n\n  public String getTitle() {\n    return title;\n  }\n\n  public void setTitle(String title) {\n    this.title = title;\n  }\n\n  public String getAuthor() {\n    return author;\n  }\n\n  public void setAuthor(String author) {\n    this.author = author;\n  }\n\n  public String getCreatedBy() {\n    return createdBy;\n  }\n\n  public void setCreatedBy(String createdBy) {\n    this.createdBy = createdBy;\n  }\n\n  public String getCreatedById() {\n    return createdById;\n  }\n\n  public void setCreatedById(String createdById) {\n    this.createdById = createdById;\n  }\n\n  public String getPublishedDate() {\n    return publishedDate;\n  }\n\n  public void setPublishedDate(String publishedDate) {\n    this.publishedDate = publishedDate;\n  }\n\n  public String getDescription() {\n    return description;\n  }\n\n  public void setDescription(String description) {\n    this.description = description;\n  }\n\n  public Long getId() {\n    return id;\n  }\n\n  public void setId(Long id) {\n    this.id = id;\n  }\n\n  public String getImageUrl() {\n    return imageUrl;\n  }\n\n  public void setImageUrl(String imageUrl) {\n    this.imageUrl = imageUrl;\n  }\n\n  // [END builder]\n  @Override\n  public String toString() {\n    return\n        \"Title: \" + title + \", Author: \" + author + \", Published date: \" + publishedDate\n        + \", Added by: \" + createdBy;\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/3-binary-data/src/main/java/com/example/getstarted/objects/Result.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.objects;\n\nimport java.util.List;\n\n// [START example]\npublic class Result<K> {\n\n  public String cursor;\n  public List<K> result;\n\n  public Result(List<K> result, String cursor) {\n    this.result = result;\n    this.cursor = cursor;\n  }\n\n  public Result(List<K> result) {\n    this.result = result;\n    this.cursor = null;\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/3-binary-data/src/main/java/com/example/getstarted/util/CloudStorageHelper.java",
    "content": "/*\n * Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.util;\n\nimport com.google.cloud.storage.Acl;\nimport com.google.cloud.storage.Acl.Role;\nimport com.google.cloud.storage.Acl.User;\nimport com.google.cloud.storage.BlobInfo;\nimport com.google.cloud.storage.Storage;\nimport com.google.cloud.storage.StorageOptions;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport javax.servlet.ServletException;\nimport org.apache.commons.fileupload.FileItemStream;\nimport org.joda.time.DateTime;\nimport org.joda.time.DateTimeZone;\nimport org.joda.time.format.DateTimeFormat;\nimport org.joda.time.format.DateTimeFormatter;\n\n// [START example]\npublic class CloudStorageHelper {\n\n  private static Storage storage = null;\n\n  // [START init]\n  static {\n    storage = StorageOptions.getDefaultInstance().getService();\n  }\n  // [END init]\n\n  // [START uploadFile]\n  /**\n   * Uploads a file to Google Cloud Storage to the bucket specified in the BUCKET_NAME\n   * environment variable, appending a timestamp to end of the uploaded filename.\n   */\n  @SuppressWarnings(\"deprecation\")\n  public String uploadFile(FileItemStream fileStream, final String bucketName)\n      throws IOException, ServletException {\n    checkFileExtension(fileStream.getName());\n\n    DateTimeFormatter dtf = DateTimeFormat.forPattern(\"-YYYY-MM-dd-HHmmssSSS\");\n    DateTime dt = DateTime.now(DateTimeZone.UTC);\n    String dtString = dt.toString(dtf);\n    final String fileName = fileStream.getName() + dtString;\n\n    // the inputstream is closed by default, so we don't need to close it here\n    BlobInfo blobInfo =\n        storage.create(\n            BlobInfo\n                .newBuilder(bucketName, fileName)\n                // Modify access list to allow all users with link to read file\n                .setAcl(new ArrayList<>(Arrays.asList(Acl.of(User.ofAllUsers(), Role.READER))))\n                .build(),\n            fileStream.openStream());\n    // return the public download link\n    return blobInfo.getMediaLink();\n  }\n  // [END uploadFile]\n\n  // [START checkFileExtension]\n\n  /**\n   * Checks that the file extension is supported.\n   */\n  private void checkFileExtension(String fileName) throws ServletException {\n    if (fileName != null && !fileName.isEmpty() && fileName.contains(\".\")) {\n      String[] allowedExt = {\".jpg\", \".jpeg\", \".png\", \".gif\"};\n      for (String ext : allowedExt) {\n        if (fileName.endsWith(ext)) {\n          return;\n        }\n      }\n      throw new ServletException(\"file must be an image\");\n    }\n  }\n  // [END checkFileExtension]\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/3-binary-data/src/main/java/com/example/getstarted/util/DatastoreSessionFilter.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.util;\n\nimport com.google.appengine.api.datastore.DatastoreService;\nimport com.google.appengine.api.datastore.DatastoreServiceFactory;\nimport com.google.appengine.api.datastore.Entity;\nimport com.google.appengine.api.datastore.EntityNotFoundException;\nimport com.google.appengine.api.datastore.Key;\nimport com.google.appengine.api.datastore.KeyFactory;\nimport com.google.appengine.api.datastore.Query;\nimport com.google.appengine.api.datastore.Query.FilterOperator;\nimport com.google.appengine.api.datastore.Query.FilterPredicate;\nimport com.google.appengine.api.datastore.Transaction;\nimport com.google.common.collect.FluentIterable;\nimport com.google.common.collect.MapDifference;\nimport com.google.common.collect.Maps;\nimport java.io.IOException;\nimport java.math.BigInteger;\nimport java.security.SecureRandom;\nimport java.util.Enumeration;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.Map;\nimport javax.servlet.Filter;\nimport javax.servlet.FilterChain;\nimport javax.servlet.FilterConfig;\nimport javax.servlet.ServletException;\nimport javax.servlet.ServletRequest;\nimport javax.servlet.ServletResponse;\nimport javax.servlet.http.Cookie;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport javax.servlet.http.HttpSession;\nimport org.joda.time.DateTime;\nimport org.joda.time.DateTimeZone;\nimport org.joda.time.format.DateTimeFormat;\nimport org.joda.time.format.DateTimeFormatter;\n\n// [START init]\npublic class DatastoreSessionFilter implements Filter {\n\n  private static DatastoreService datastore;\n  private static final DateTimeFormatter DTF = DateTimeFormat.forPattern(\"yyyyMMddHHmmssSSS\");\n  private static final String SESSION_KIND = \"SessionVariable\";\n\n  @Override\n  public void init(FilterConfig config) throws ServletException {\n    // initialize local copy of datastore session variables\n\n    datastore = DatastoreServiceFactory.getDatastoreService();\n    // Delete all sessions unmodified for over two days\n    DateTime dt = DateTime.now(DateTimeZone.UTC);\n    Query query = new Query(SESSION_KIND).setFilter(new FilterPredicate(\n            \"lastModified\", FilterOperator.LESS_THAN_OR_EQUAL, dt.minusDays(2).toString(DTF)));\n    Iterator<Entity> results = datastore.prepare(query).asIterator();\n    while (results.hasNext()) {\n      Entity stateEntity = results.next();\n      datastore.delete(stateEntity.getKey());\n    }\n  }\n  // [END init]\n\n  @Override\n  public void doFilter(ServletRequest servletReq, ServletResponse servletResp, FilterChain chain)\n      throws IOException, ServletException {\n    HttpServletRequest req = (HttpServletRequest) servletReq;\n    HttpServletResponse resp = (HttpServletResponse) servletResp;\n\n    // Check if the session cookie is there, if not there, make a session cookie using a unique\n    // identifier.\n    String sessionId = getCookieValue(req, \"bookshelfSessionId\");\n    if (sessionId.equals(\"\")) {\n      String sessionNum = new BigInteger(130, new SecureRandom()).toString(32);\n      Cookie session = new Cookie(\"bookshelfSessionId\", sessionNum);\n      session.setPath(\"/\");\n      resp.addCookie(session);\n    }\n\n    Map<String, String> datastoreMap = loadSessionVariables(req);  // session variables for request\n\n    chain.doFilter(servletReq, servletResp);  // Allow the servlet to process request and response\n\n    HttpSession session = req.getSession();   // Create session map\n    Map<String, String> sessionMap = new HashMap<>();\n    Enumeration<String> attrNames = session.getAttributeNames();\n    while (attrNames.hasMoreElements()) {\n      String attrName = attrNames.nextElement();\n      sessionMap.put(attrName, (String) session.getAttribute(attrName));\n    }\n\n    // Create a diff between the new session variables and the existing session variables\n    // to minimize datastore access\n    MapDifference<String, String> diff = Maps.difference(sessionMap, datastoreMap);\n    Map<String, String> setMap = diff.entriesOnlyOnLeft();\n    Map<String, String> deleteMap = diff.entriesOnlyOnRight();\n\n    // Apply the diff\n    setSessionVariables(sessionId, setMap);\n    deleteSessionVariables(\n        sessionId,\n        FluentIterable.from(deleteMap.keySet()).toArray(String.class));\n  }\n\n  @SuppressWarnings({\"unused\", \"JdkObsolete\"})\n  private String mapToString(Map<String, String> map) {\n    StringBuffer names = new StringBuffer();\n    for (String name : map.keySet()) {\n      names.append(name + \" \");\n    }\n    return names.toString();\n  }\n\n  @Override\n  public void destroy() {\n  }\n\n  protected String getCookieValue(HttpServletRequest req, String cookieName) {\n    Cookie[] cookies = req.getCookies();\n    if (cookies != null) {\n      for (Cookie cookie : cookies) {\n        if (cookie.getName().equals(cookieName)) {\n          return cookie.getValue();\n        }\n      }\n    }\n    return \"\";\n  }\n\n  // [START deleteSessionVariables]\n  /**\n   * Delete a value stored in the project's datastore.\n   *\n   * @param sessionId Request from which the session is extracted.\n   */\n  protected void deleteSessionVariables(String sessionId, String... varNames) {\n    if (sessionId.equals(\"\")) {\n      return;\n    }\n    Key key = KeyFactory.createKey(SESSION_KIND, sessionId);\n    Transaction transaction = datastore.beginTransaction();\n    try {\n      Entity stateEntity = datastore.get(transaction, key);\n      for (String varName : varNames) {\n        stateEntity.removeProperty(varName);\n      }\n      datastore.put(transaction, stateEntity);\n      transaction.commit();\n    } catch (EntityNotFoundException e) {\n      // Ignore - if there's no session, there's nothing to delete.\n    } finally {\n      if (transaction.isActive()) {\n        transaction.rollback();\n      }\n    }\n  }\n  // [END deleteSessionVariables]\n\n  protected void deleteSessionWithValue(String varName, String varValue) {\n    Transaction transaction = datastore.beginTransaction();\n    try {\n      Query query = new Query(SESSION_KIND)\n          .setFilter(new FilterPredicate(varName, FilterOperator.EQUAL, varValue));\n      Iterator<Entity> results = datastore.prepare(transaction, query).asIterator();\n      while (results.hasNext()) {\n        Entity stateEntity = results.next();\n        datastore.delete(transaction, stateEntity.getKey());\n      }\n      transaction.commit();\n    } finally {\n      if (transaction.isActive()) {\n        transaction.rollback();\n      }\n    }\n  }\n\n  // [START setSessionVariables]\n  /**\n   * Stores the state value in each key-value pair in the project's datastore.\n   *\n   * @param sessionId Request from which to extract session.\n   * @param varName the name of the desired session variable\n   * @param varValue the value of the desired session variable\n   */\n  protected void setSessionVariables(String sessionId, Map<String, String> setMap) {\n    if (sessionId.equals(\"\")) {\n      return;\n    }\n    Key key = KeyFactory.createKey(SESSION_KIND, sessionId);\n    Transaction transaction = datastore.beginTransaction();\n    DateTime dt = DateTime.now(DateTimeZone.UTC);\n    dt.toString(DTF);\n    try {\n      Entity stateEntity;\n      try {\n        stateEntity = datastore.get(transaction, key);\n      } catch (EntityNotFoundException e) {\n        stateEntity = new Entity(key);\n      }\n      for (String varName : setMap.keySet()) {\n        stateEntity.setProperty(varName, setMap.get(varName));\n      }\n      stateEntity.setProperty(\"lastModified\", dt.toString(DTF));\n      datastore.put(transaction, stateEntity);\n      transaction.commit();\n    } finally {\n      if (transaction.isActive()) {\n        transaction.rollback();\n      }\n    }\n  }\n  // [END setSessionVariables]\n\n  // [START loadSessionVariables]\n  /**\n   * Take an HttpServletRequest, and copy all of the current session variables over to it\n   *\n   * @param req Request from which to extract session.\n   * @return a map of strings containing all the session variables loaded or an empty map.\n   */\n  protected Map<String, String> loadSessionVariables(HttpServletRequest req)\n      throws ServletException {\n    Map<String, String> datastoreMap = new HashMap<>();\n    String sessionId = getCookieValue(req, \"bookshelfSessionId\");\n    if (sessionId.equals(\"\")) {\n      return datastoreMap;\n    }\n    Key key = KeyFactory.createKey(SESSION_KIND, sessionId);\n    Transaction transaction = datastore.beginTransaction();\n    try {\n      Entity stateEntity = datastore.get(transaction, key);\n      Map<String, Object> properties = stateEntity.getProperties();\n      for (Map.Entry<String, Object> property : properties.entrySet()) {\n        req.getSession().setAttribute(property.getKey(), property.getValue());\n        datastoreMap.put(property.getKey(), (String) property.getValue());\n      }\n      transaction.commit();\n    } catch (EntityNotFoundException e) {\n      // Ignore - if there's no session, there's nothing to delete.\n    } finally {\n      if (transaction.isActive()) {\n        transaction.rollback();\n      }\n    }\n    return datastoreMap;\n  }\n  // [END loadSessionVariables]\n}\n"
  },
  {
    "path": "bookshelf-standard/3-binary-data/src/main/webapp/WEB-INF/appengine-web.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2015 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<appengine-web-app xmlns=\"http://appengine.google.com/ns/1.0\">\n  <threadsafe>true</threadsafe>\n  <use-google-connector-j>true</use-google-connector-j>\n\n  <!-- Configure java.util.logging -->\n  <system-properties>\n    <property name=\"java.util.logging.config.file\" value=\"WEB-INF/logging.properties\"/>\n  </system-properties>\n\n  <static-files>\n    <include path=\"/static/**\" />\n  </static-files>\n\n  <resource-files>\n    <include path=\"/resources/**\" />\n  </resource-files>\n\n  <sessions-enabled>true</sessions-enabled>\n  <async-session-persistence enabled=\"true\" />\n</appengine-web-app>\n"
  },
  {
    "path": "bookshelf-standard/3-binary-data/src/main/webapp/WEB-INF/logging.properties",
    "content": ""
  },
  {
    "path": "bookshelf-standard/3-binary-data/src/main/webapp/WEB-INF/web.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n Copyright 2016 Google Inc.\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n       http://www.apache.org/licenses/LICENSE-2.0\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START webxml] -->\n<web-app xmlns=\"http://java.sun.com/xml/ns/javaee\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xmlns:web=\"http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd\"\n         xsi:schemaLocation=\"http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd\" \n         version=\"2.5\">\n    <!--\n      A web.xml is needed to explicitly set the order in which filters process requests. Any filters\n      not included in web.xml will be loaded after filters listed below.\n    -->\n    <filter>\n      <filter-name>DatastoreSessionFilter</filter-name>\n      <filter-class>com.example.getstarted.util.DatastoreSessionFilter</filter-class>\n    </filter>\n    <filter-mapping>\n      <filter-name>DatastoreSessionFilter</filter-name>\n      <url-pattern>/</url-pattern>\n      <url-pattern>/books</url-pattern>\n      <url-pattern>/books/mine</url-pattern>\n      <url-pattern>/create</url-pattern>\n      <url-pattern>/delete</url-pattern>\n      <url-pattern>/login</url-pattern>\n      <url-pattern>/logout</url-pattern>\n      <url-pattern>/oauth2callback</url-pattern>\n      <url-pattern>/read</url-pattern>\n      <url-pattern>/update</url-pattern>\n    </filter-mapping>\n\n    <servlet>\n      <servlet-name>list</servlet-name>\n      <servlet-class>com.example.getstarted.basicactions.ListBookServlet</servlet-class>\n      <load-on-startup>1</load-on-startup>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>list</servlet-name>\n      <url-pattern>/</url-pattern>\n      <url-pattern>/books</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n      <servlet-name>create</servlet-name>\n      <servlet-class>com.example.getstarted.basicactions.CreateBookServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>create</servlet-name>\n      <url-pattern>/create</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n      <servlet-name>update</servlet-name>\n      <servlet-class>com.example.getstarted.basicactions.UpdateBookServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>update</servlet-name>\n      <url-pattern>/update</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n      <servlet-name>read</servlet-name>\n      <servlet-class>com.example.getstarted.basicactions.ReadBookServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>read</servlet-name>\n      <url-pattern>/read</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n      <servlet-name>delete</servlet-name>\n      <servlet-class>com.example.getstarted.basicactions.DeleteBookServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>delete</servlet-name>\n      <url-pattern>/delete</url-pattern>\n    </servlet-mapping>\n\n    <!-- [START config] -->\n    <context-param>\n        <param-name>bookshelf.storageType</param-name>\n        <param-value>${bookshelf.storageType}</param-value>\n    </context-param>\n\n<!-- [START bucket] -->\n    <context-param>\n        <param-name>bookshelf.bucket</param-name>\n        <param-value>${bookshelf.bucket}</param-value>\n    </context-param>\n<!-- [END bucket] -->\n\n    <context-param>\n        <param-name>sql.urlRemoteGAE</param-name>\n        <param-value>jdbc:google:mysql://${sql.instanceName}/${sql.dbName}?user=${sql.userName}&amp;password=${sql.password}</param-value>\n    </context-param>\n\n    <context-param>\n        <param-name>sql.urlRemote</param-name>\n        <param-value>jdbc:mysql://google/${sql.dbName}?cloudSqlInstance=${sql.instanceName}&amp;socketFactory=com.google.cloud.sql.mysql.SocketFactory&amp;user=${sql.userName}&amp;password=${sql.password}</param-value>\n    </context-param>\n\n    <context-param>\n        <param-name>sql.urlLocal</param-name>\n        <param-value>jdbc:mysql://localhost/${sql.dbName}?user=${sql.userName}&amp;password=${sql.password}&amp;useSSL=false</param-value>\n    </context-param>\n    <!-- [END config] -->\n</web-app>\n<!-- [END webxml] -->\n"
  },
  {
    "path": "bookshelf-standard/3-binary-data/src/main/webapp/base.jsp",
    "content": "<!--\nCopyright 2016 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START base] -->\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\" prefix=\"fn\" %>\n<html lang=\"en\">\n  <head>\n    <title>Bookshelf - Java on Google Cloud Platform</title>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link rel=\"stylesheet\" href=\"//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css\">\n  </head>\n  <body>\n    <div class=\"navbar navbar-default\">\n      <div class=\"container\">\n        <div class=\"navbar-header\">\n          <div class=\"navbar-brand\">Bookshelf</div>\n        </div>\n        <ul class=\"nav navbar-nav\">\n          <li><a href=\"/\">Books</a></li>\n          <c:if test=\"${isAuthConfigured}\"><li><a href=\"/books/mine\">My Books</a></li></c:if>\n        </ul>\n        <p class=\"navbar-text navbar-right\">\n          <c:choose>\n          <c:when test=\"${not empty token}\">\n          <!-- using pageContext requires jsp-api artifact in pom.xml -->\n          <a href=\"/logout\">\n            <c:if test=\"${not empty userImageUrl}\">\n              <img class=\"img-circle\" src=\"${fn:escapeXml(userImageUrl)}\" width=\"24\">\n            </c:if>\n            ${fn:escapeXml(userEmail)}\n          </a>\n          </c:when>\n          <c:when test=\"${isAuthConfigured}\">\n          <a href=\"/login\">Login</a>\n          </c:when>\n          </c:choose>\n        </p>\n      </div>\n    </div>\n    <c:import url=\"/${page}.jsp\" />\n  </body>\n</html>\n<!-- [END base]-->\n"
  },
  {
    "path": "bookshelf-standard/3-binary-data/src/main/webapp/form.jsp",
    "content": "<!--\nCopyright 2016 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START form] -->\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\"%>\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\" prefix=\"fn\"%>\n<div class=\"container\">\n  <h3>\n    <c:out value=\"${action}\" /> book\n  </h3>\n\n  <form method=\"POST\" action=\"${destination}\" enctype=\"multipart/form-data\">\n\n    <div class=\"form-group\">\n      <label for=\"title\">Title</label>\n      <input type=\"text\" name=\"title\" id=\"title\" value=\"${fn:escapeXml(book.title)}\" class=\"form-control\" />\n    </div>\n\n    <div class=\"form-group\">\n      <label for=\"author\">Author</label>\n      <input type=\"text\" name=\"author\" id=\"author\" value=\"${fn:escapeXml(book.author)}\" class=\"form-control\" />\n    </div>\n\n    <div class=\"form-group\">\n      <label for=\"publishedDate\">Date Published</label>\n      <input type=\"text\" name=\"publishedDate\" id=\"publishedDate\" value=\"${fn:escapeXml(book.publishedDate)}\" class=\"form-control\" />\n    </div>\n\n    <div class=\"form-group\">\n      <label for=\"description\">Description</label>\n      <textarea name=\"description\" id=\"description\" class=\"form-control\">${fn:escapeXml(book.description)}</textarea>\n    </div>\n\n    <div class=\"form-group ${isCloudStorageConfigured ? '' : 'hidden'}\">\n      <label for=\"image\">Cover Image</label>\n      <input type=\"file\" name=\"file\" id=\"file\" class=\"form-control\" />\n    </div>\n\n    <div class=\"form-group hidden\">\n      <label for=\"imageUrl\">Cover Image URL</label>\n      <input type=\"hidden\" name=\"id\" value=\"${book.id}\" />\n      <input type=\"text\" name=\"imageUrl\" id=\"imageUrl\" value=\"${fn:escapeXml(book.imageUrl)}\" class=\"form-control\" />\n    </div>\n\n    <button type=\"submit\" class=\"btn btn-success\">Save</button>\n  </form>\n</div>\n<!-- [END form] -->\n"
  },
  {
    "path": "bookshelf-standard/3-binary-data/src/main/webapp/list.jsp",
    "content": "<!--\nCopyright 2016 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START list] -->\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\" prefix=\"fn\" %>\n<div class=\"container\">\n  <h3>Books</h3>\n  <a href=\"/create\" class=\"btn btn-success btn-sm\">\n    <i class=\"glyphicon glyphicon-plus\"></i>\n    Add book\n  </a>\n  <c:choose>\n  <c:when test=\"${empty books}\">\n  <p>No books found</p>\n  </c:when>\n  <c:otherwise>\n  <c:forEach items=\"${books}\" var=\"book\">\n  <div class=\"media\">\n    <a href=\"/read?id=${book.id}\">\n      <div class=\"media-left\">\n        <img alt=\"ahhh\" src=\"${fn:escapeXml(not empty book.imageUrl?book.imageUrl:'http://placekitten.com/g/128/192')}\">\n      </div>\n      <div class=\"media-body\">\n        <h4>${fn:escapeXml(book.title)}</h4>\n        <p>${fn:escapeXml(book.author)}</p>\n      </div>\n    </a>\n  </div>\n  </c:forEach>\n  <c:if test=\"${not empty cursor}\">\n  <nav>\n    <ul class=\"pager\">\n      <li><a href=\"?cursor=${fn:escapeXml(cursor)}\">More</a></li>\n    </ul>\n  </nav>\n  </c:if>\n  </c:otherwise>\n  </c:choose>\n</div>\n<!-- [END list] -->\n"
  },
  {
    "path": "bookshelf-standard/3-binary-data/src/main/webapp/view.jsp",
    "content": "<!--\nCopyright 2016 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START view] -->\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\" prefix=\"fn\" %>\n<div class=\"container\">\n  <h3>Book</h3>\n  <div class=\"btn-group\">\n    <a href=\"/update?id=${book.id}\" class=\"btn btn-primary btn-sm\">\n      <i class=\"glyphicon glyphicon-edit\"></i>\n      Edit book\n    </a>\n    <a href=\"/delete?id=${book.id}\" class=\"btn btn-danger btn-sm\">\n      <i class=\"glyphicon glyphicon-trash\"></i>\n      Delete book\n    </a>\n  </div>\n\n  <div class=\"media\">\n    <div class=\"media-left\">\n      <img class=\"book-image\" src=\"${fn:escapeXml(not empty book.imageUrl?book.imageUrl:'http://placekitten.com/g/128/192')}\">\n    </div>\n    <div class=\"media-body\">\n      <h4 class=\"book-title\">\n        ${fn:escapeXml(book.title)}\n        <small>${fn:escapeXml(book.publishedDate)}</small>\n      </h4>\n      <h5 class=\"book-author\">By ${fn:escapeXml(not empty book.author?book.author:'Unknown')}</h5>\n      <p class=\"book-description\">${fn:escapeXml(book.description)}</p>\n      <small class=\"book-added-by\">Added by\n        ${fn:escapeXml(not empty book.createdBy?book.createdBy:'Anonymous')}</small>\n    </div>\n  </div>\n</div>\n<!-- [END view] -->\n"
  },
  {
    "path": "bookshelf-standard/4-auth/README.md",
    "content": "# Bookshelf App for Java on App Engine Standard Tutorial\n## Auth\n\nContains the code for using Cloud Datastore and Cloud SQL v2.\n\nThis is part of a [Bookshelf tutorial](https://cloud.google.com/java/getting-started/tutorial-app).\n\nMost users can get this running by updating the parameters in `pom.xml`. You'll\nalso need to [create a bucket][create-bucket] in Google Cloud Storage, referred\nto below as `MY-BUCKET`.\n\n[create-bucket]: https://cloud.google.com/storage/docs/creating-buckets\n\n### Running Locally\n\n    mvn -Plocal clean appengine:devserver -Dbookshelf.bucket=MY-BUCKET\n\n**Note**: If you run into an error about `Invalid Credentials`, you may have to run:\n\n    gcloud auth application-default login\n\n### Deploying to App Engine Standard\n\n* In the `pom.xml`, update the [App Engine Maven Plugin](https://cloud.google.com/appengine/docs/standard/java/tools/maven-reference)\nwith your Google Cloud Project Id:\n\n  ```\n  <plugin>\n    <groupId>com.google.cloud.tools</groupId>\n    <artifactId>appengine-maven-plugin</artifactId>\n    <version>2.3.0</version>\n    <configuration>\n      <projectId>GCLOUD_CONFIG</projectId>\n      <version>bookshelf</version>\n    </configuration>\n  </plugin>\n  ```\n\n* Deploy your App\n\n    mvn clean appengine appengine:deploy \\\n        -Dbookshelf.bucket=MY-BUCKET\n\nVisit it at http://bookshelf.<your-project-id>.appspot.com\n"
  },
  {
    "path": "bookshelf-standard/4-auth/jenkins.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2017 Google Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Fail on non-zero return and print command to stdout\nset -xe\n\n# Jenkins provides values for GOOGLE_PROJECT_ID and GOOGLE_VERSION_ID\n\n# Make sure it works on GAE7\n\n# Deploy and run selenium tests\nmvn clean appengine:update verify \\\n  -Pselenium \\\n  -Dappengine.appId=\"${GOOGLE_PROJECT_ID}\" \\\n  -Dappengine.version=\"${GOOGLE_VERSION_ID}\" \\\n  -Dbookshelf.bucket=\"${GCS_BUCKET}\" \\\n  -Dappengine.additionalParams=\"--service_account_json_key_file=${GOOGLE_APPLICATION_CREDENTIALS}\"\n\n\n# Make sure it deploys on GAE8\n\nFILE_PATH=src/main/webapp/WEB-INF/appengine-web.xml\nsed -i'.bak' '/<appengine-web-app/ a <runtime>java8</runtime>' \"${FILE_PATH}\"\n# Restore the backup after we're done\ntrap 'mv \"${FILE_PATH}\"{.bak,}' EXIT\n# Deploy and run selenium tests\nmvn clean appengine:update verify \\\n  -Pselenium \\\n  -Dappengine.appId=\"${GOOGLE_PROJECT_ID}\" \\\n  -Dappengine.version=\"${GOOGLE_VERSION_ID}\" \\\n  -Dbookshelf.bucket=\"${GCS_BUCKET}\" \\\n  -Dappengine.additionalParams=\"--service_account_json_key_file=${GOOGLE_APPLICATION_CREDENTIALS}\"\n\n"
  },
  {
    "path": "bookshelf-standard/4-auth/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\nCopyright 2016 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<project>                               <!-- REQUIRED -->\n\n  <modelVersion>4.0.0</modelVersion>    <!-- REQUIRED -->\n  <packaging>war</packaging>            <!-- REQUIRED -->\n\n  <groupId>com.example.standard.gettingstarted</groupId>\n  <artifactId>bookshelf-standard-4</artifactId>    <!-- Name of your project -->\n  <version>1.0-SNAPSHOT</version>       <!-- xx.xx.xx -SNAPSHOT means development -->\n\n  <parent> <!-- Only used for testing - NOT REQUIRED -->\n    <groupId>com.google.cloud.samples</groupId>\n    <artifactId>shared-configuration</artifactId>\n    <version>1.2.0</version>\n    <relativePath>../</relativePath>\n  </parent>\n\n  <properties>\n    <!-- [START config] -->\n    <bookshelf.storageType>datastore</bookshelf.storageType>   <!-- datastore or cloudsql -->\n\n    <sql.dbName>bookshelf</sql.dbName>                        <!-- A reasonable default -->\n    <!-- Instance Connection Name - project:region:dbName -->\n    <!-- -Dsql.instanceName=localhost to use a local MySQL server -->\n    <sql.instanceName>DATABASE-connectionName-HERE\n    </sql.instanceName> <!-- See `gcloud sql instances describe [instanceName]` -->\n    <sql.userName>root</sql.userName>                         <!-- A reasonable default -->\n    <sql.password>MYSQL-ROOT-PASSWORD-HERE</sql.password> <!-- -Dsql.password=myRootPassword1234 -->\n\n    <bookshelf.bucket>BUCKET-NAME-HERE</bookshelf.bucket> <!-- eg project-id.appspot.com -->\n    <!-- [END config] -->\n\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <maven.compiler.source>1.8</maven.compiler.source> <!-- REQUIRED -->\n    <maven.compiler.target>1.8</maven.compiler.target> <!-- REQUIRED -->\n    <maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>\n    <maven.compiler.showWarnings>true</maven.compiler.showWarnings>\n    <maven.war.filteringDeploymentDescriptors>true</maven.war.filteringDeploymentDescriptors>\n  </properties>\n\n  <!-- THINGS ONLY USED WHEN RUN LOCALLY -->\n  <profiles>\n    <profile>\n      <id>local</id>\n      <dependencies>\n        <dependency>\n          <groupId>com.google.cloud.sql</groupId>\n          <artifactId>mysql-socket-factory</artifactId>\n          <version>1.6.1</version>\n        </dependency>\n        <dependency>                        <!-- http://dev.mysql.com/doc/connector-j/en/ -->\n          <groupId>mysql</groupId>\n          <artifactId>mysql-connector-java</artifactId>\n          <version>8.0.30</version>  <!-- v5.x.x is Java 7, v6.x.x is Java 8 -->\n        </dependency>\n        <dependency>\n          <groupId>com.google.api-client</groupId>\n          <artifactId>google-api-client-appengine</artifactId>\n          <version>2.0.0</version>\n        </dependency>\n      </dependencies>\n    </profile>\n  </profiles>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.google.appengine</groupId>\n      <artifactId>appengine-api-1.0-sdk</artifactId>\n      <version>2.0.15</version>\n    </dependency>\n\n    <dependency>                        <!-- REQUIRED -->\n      <groupId>javax.servlet</groupId>  <!-- Java Servlet API -->\n      <artifactId>javax.servlet-api</artifactId>\n      <version>4.0.1</version>\n      <scope>provided</scope>           <!-- Provided by the Jetty Servlet engine -->\n    </dependency>\n\n    <dependency>                        <!-- Java Server Pages -->\n      <groupId>javax.servlet</groupId>\n      <artifactId>jsp-api</artifactId>\n      <version>2.0</version>\n    </dependency>\n\n    <dependency>                        <!-- JSP standard tag library -->\n      <groupId>jstl</groupId>\n      <artifactId>jstl</artifactId>\n      <version>1.2</version>\n    </dependency>\n\n    <dependency>                        <!-- Apache Taglibs -->\n      <groupId>taglibs</groupId>\n      <artifactId>standard</artifactId>\n      <version>1.1.2</version>\n    </dependency>\n\n    <dependency>                        <!-- Google Cloud Client Library for Java -->\n      <groupId>com.google.cloud</groupId>\n      <artifactId>google-cloud-storage</artifactId>\n      <version>2.23.0</version>\n    </dependency>\n\n    <dependency>                        <!-- Google Core Libraries for Java -->\n      <groupId>com.google.guava</groupId>\n      <artifactId>guava</artifactId>  <!-- https://github.com/google/guava/wiki -->\n      <version>33.1.0-jre</version>\n      <scope>compile</scope>\n    </dependency>\n\n    <dependency>                        <!-- http://www.joda.org/joda-time/ -->\n      <groupId>joda-time</groupId>\n      <artifactId>joda-time</artifactId>\n      <version>2.12.6</version>\n    </dependency>\n\n    <dependency>                        <!-- http://commons.apache.org/proper/commons-fileupload/ -->\n      <groupId>commons-fileupload</groupId>\n      <artifactId>commons-fileupload</artifactId>\n      <version>1.5</version>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <!-- Optional - for hot reload of the web application when using an IDE Eclipse / IDEA -->\n    <outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes\n    </outputDirectory>\n    <plugins>\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>appengine-maven-plugin</artifactId>\n        <version>2.4.4</version>\n        <configuration>\n          <!-- can be set w/ -DprojectId=myProjectId on command line -->\n          <projectId>GCLOUD_CONFIG</projectId>\n          <version>bookshelf</version>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <version>3.11.0</version>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/java/com/example/getstarted/auth/ListByUserFilter.java",
    "content": "/*\n * Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.auth;\n\nimport com.google.appengine.api.users.UserService;\nimport com.google.appengine.api.users.UserServiceFactory;\nimport java.io.IOException;\nimport javax.servlet.Filter;\nimport javax.servlet.FilterChain;\nimport javax.servlet.FilterConfig;\nimport javax.servlet.ServletException;\nimport javax.servlet.ServletRequest;\nimport javax.servlet.ServletResponse;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\npublic class ListByUserFilter implements Filter {\n\n  @Override\n  public void init(FilterConfig config) throws ServletException {\n  }\n\n  @Override\n  public void doFilter(ServletRequest servletReq, ServletResponse servletResp, FilterChain chain)\n      throws IOException, ServletException {\n    HttpServletRequest req = (HttpServletRequest) servletReq;\n    HttpServletResponse resp = (HttpServletResponse) servletResp;\n\n    UserService userService = UserServiceFactory.getUserService();\n    if (userService.isUserLoggedIn()) {\n      chain.doFilter(servletReq, servletResp);\n    } else {\n      req.getSession().setAttribute(\"loginDestination\", \"/books/mine\");\n      resp.sendRedirect(userService.createLoginURL(\"/login\"));\n    }\n  }\n\n  @Override\n  public void destroy() {\n  }\n}\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/java/com/example/getstarted/auth/LoginServlet.java",
    "content": "/*\n * Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.auth;\n\nimport com.google.appengine.api.users.User;\nimport com.google.appengine.api.users.UserService;\nimport com.google.appengine.api.users.UserServiceFactory;\nimport java.io.IOException;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class LoginServlet extends HttpServlet {\n  @Override\n  protected void doGet(HttpServletRequest req, HttpServletResponse resp)\n      throws IOException, ServletException {\n\n    UserService userService = UserServiceFactory.getUserService();\n    if (userService.isUserLoggedIn()) {\n      // Save the relevant profile info and store it in the session.\n      User user = userService.getCurrentUser();\n      req.getSession().setAttribute(\"userEmail\", user.getEmail());\n      req.getSession().setAttribute(\"userId\", user.getUserId());\n\n      String destination = (String) req.getSession().getAttribute(\"loginDestination\");\n      if (destination == null) {\n        destination = \"/books\";\n      }\n\n      resp.sendRedirect(destination);\n    } else {\n      resp.sendRedirect(userService.createLoginURL(\"/login\"));\n    }\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/java/com/example/getstarted/auth/LogoutFilter.java",
    "content": "/*\n * Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.auth;\n\nimport com.google.appengine.api.users.UserService;\nimport com.google.appengine.api.users.UserServiceFactory;\nimport java.io.IOException;\nimport javax.servlet.Filter;\nimport javax.servlet.FilterChain;\nimport javax.servlet.FilterConfig;\nimport javax.servlet.ServletException;\nimport javax.servlet.ServletRequest;\nimport javax.servlet.ServletResponse;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n// [START init]\npublic class LogoutFilter implements Filter {\n  // [END init]\n\n  @Override\n  public void init(FilterConfig config) throws ServletException {\n  }\n\n  @Override\n  public void doFilter(ServletRequest servletReq, ServletResponse servletResp, FilterChain chain)\n      throws IOException, ServletException {\n    HttpServletRequest req = (HttpServletRequest) servletReq;\n    HttpServletResponse resp = (HttpServletResponse) servletResp;\n    String path = req.getRequestURI();\n\n    chain.doFilter(servletReq, servletResp);\n\n    UserService userService = UserServiceFactory.getUserService();\n    if (userService.isUserLoggedIn()) {\n      resp.sendRedirect(userService.createLogoutURL(\"/logout\"));\n    } else if (path.startsWith(\"/logout\")) {\n      resp.sendRedirect(\"/books\");\n    }\n  }\n\n  @Override\n  public void destroy() {\n  }\n}\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/java/com/example/getstarted/auth/LogoutServlet.java",
    "content": "/*\n * Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.auth;\n\nimport java.io.IOException;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport javax.servlet.http.HttpSession;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class LogoutServlet extends HttpServlet {\n\n  @Override\n  protected void doGet(HttpServletRequest req, HttpServletResponse resp)\n      throws IOException, ServletException {\n    // you can also make an authenticated request to logout, but here we choose to\n    // simply delete the session variables for simplicity\n    HttpSession session =  req.getSession(false);\n    if (session != null) {\n      session.invalidate();\n    }\n    // rebuild session\n    req.getSession();\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/java/com/example/getstarted/basicactions/CreateBookServlet.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport com.example.getstarted.daos.BookDao;\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.util.CloudStorageHelper;\nimport com.google.common.base.Strings;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport javax.servlet.http.HttpSession;\nimport org.apache.commons.fileupload.FileItemIterator;\nimport org.apache.commons.fileupload.FileItemStream;\nimport org.apache.commons.fileupload.FileUploadException;\nimport org.apache.commons.fileupload.servlet.ServletFileUpload;\nimport org.apache.commons.fileupload.util.Streams;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class CreateBookServlet extends HttpServlet {\n\n  // [START setup]\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,\n      IOException {\n    req.setAttribute(\"action\", \"Add\");          // Part of the Header in form.jsp\n    req.setAttribute(\"destination\", \"create\");  // The urlPattern to invoke (this Servlet)\n    req.setAttribute(\"page\", \"form\");           // Tells base.jsp to include form.jsp\n    req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n  }\n  // [END setup]\n\n  // [START formpost]\n  @Override\n  public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,\n      IOException {\n    assert ServletFileUpload.isMultipartContent(req);\n    CloudStorageHelper storageHelper =\n        (CloudStorageHelper) getServletContext().getAttribute(\"storageHelper\");\n\n    String newImageUrl = null;\n    Map<String, String> params = new HashMap<String, String>();\n    try {\n      FileItemIterator iter = new ServletFileUpload().getItemIterator(req);\n      while (iter.hasNext()) {\n        FileItemStream item = iter.next();\n        if (item.isFormField()) {\n          params.put(item.getFieldName(), Streams.asString(item.openStream()));\n        } else if (!Strings.isNullOrEmpty(item.getName())) {\n          newImageUrl = storageHelper.uploadFile(\n              item, getServletContext().getInitParameter(\"bookshelf.bucket\"));\n        }\n      }\n    } catch (FileUploadException e) {\n      throw new IOException(e);\n    }\n\n    // [START createdBy]\n    String createdByString = \"\";\n    String createdByIdString = \"\";\n    HttpSession session = req.getSession();\n    if (session.getAttribute(\"userEmail\") != null) { // Does the user have a logged in session?\n      createdByString = (String) session.getAttribute(\"userEmail\");\n      createdByIdString = (String) session.getAttribute(\"userId\");\n    }\n    // [END createdBy]\n\n    // [START bookBuilder]\n    Book book = new Book.Builder()\n        .author(params.get(\"author\"))\n        .description(params.get(\"description\"))\n        .publishedDate(params.get(\"publishedDate\"))\n        .title(params.get(\"title\"))\n        .imageUrl(null == newImageUrl ? params.get(\"imageUrl\") : newImageUrl)\n        // [START auth]\n        .createdBy(createdByString)\n        .createdById(createdByIdString)\n        // [END auth]\n        .build();\n    // [END bookBuilder]\n\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    try {\n      Long id = dao.createBook(book);\n      resp.sendRedirect(\"/read?id=\" + id.toString());   // read what we just wrote\n    } catch (Exception e) {\n      throw new ServletException(\"Error creating book\", e);\n    }\n  }\n  // [END formpost]\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/java/com/example/getstarted/basicactions/DeleteBookServlet.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport com.example.getstarted.daos.BookDao;\nimport java.io.IOException;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class DeleteBookServlet extends HttpServlet {\n\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,\n      IOException {\n    Long id = Long.decode(req.getParameter(\"id\"));\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    try {\n      dao.deleteBook(id);\n      resp.sendRedirect(\"/books\");\n    } catch (Exception e) {\n      throw new ServletException(\"Error deleting book\", e);\n    }\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/java/com/example/getstarted/basicactions/ListBookServlet.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport com.example.getstarted.daos.BookDao;\nimport com.example.getstarted.daos.CloudSqlDao;\nimport com.example.getstarted.daos.DatastoreDao;\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.objects.Result;\nimport com.example.getstarted.util.CloudStorageHelper;\nimport com.google.common.base.Strings;\nimport java.io.IOException;\nimport java.sql.SQLException;\nimport java.util.List;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class ListBookServlet extends HttpServlet {\n\n  @Override\n  public void init() throws ServletException {\n    BookDao dao = null;\n\n    CloudStorageHelper storageHelper = new CloudStorageHelper();\n\n    // Creates the DAO based on the Context Parameters\n    String storageType = this.getServletContext().getInitParameter(\"bookshelf.storageType\");\n    switch (storageType) {\n      case \"datastore\":\n        dao = new DatastoreDao();\n        break;\n      case \"cloudsql\":\n        try {\n          // Use this url when using dev appserver, but connecting to Cloud SQL\n          String connect = this.getServletContext().getInitParameter(\"sql.urlRemote\");\n          if (connect.contains(\"localhost\")) {\n            // Use this url when using a local mysql server\n            connect = this.getServletContext().getInitParameter(\"sql.urlLocal\");\n          } else if (System.getProperty(\"com.google.appengine.runtime.version\")\n              .startsWith(\"Google App Engine/\")) {\n            // Use this url when on App Engine, connecting to Cloud SQL.\n            // Uses a special adapter because of the App Engine sandbox.\n            connect = this.getServletContext().getInitParameter(\"sql.urlRemoteGAE\");\n          }\n          dao = new CloudSqlDao(connect);\n        } catch (SQLException e) {\n          throw new ServletException(\"SQL error\", e);\n        }\n        break;\n      default:\n        throw new IllegalStateException(\n            \"Invalid storage type. Check if bookshelf.storageType property is set.\");\n    }\n    this.getServletContext().setAttribute(\"dao\", dao);\n    this.getServletContext().setAttribute(\"storageHelper\", storageHelper);\n    this.getServletContext().setAttribute(\n        \"isCloudStorageConfigured\",  // Hide upload when Cloud Storage is not configured.\n        !Strings.isNullOrEmpty(getServletContext().getInitParameter(\"bookshelf.bucket\")));\n  }\n\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException,\n      ServletException {\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    String startCursor = req.getParameter(\"cursor\");\n    List<Book> books = null;\n    String endCursor = null;\n    try {\n      Result<Book> result = dao.listBooks(startCursor);\n      books = result.result;\n      endCursor = result.cursor;\n    } catch (Exception e) {\n      throw new ServletException(\"Error listing books\", e);\n    }\n    req.getSession().getServletContext().setAttribute(\"books\", books);\n    StringBuilder bookNames = new StringBuilder();\n    for (Book book : books) {\n      bookNames.append(book.getTitle() + \" \");\n    }\n    req.setAttribute(\"cursor\", endCursor);\n    req.setAttribute(\"page\", \"list\");\n    req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/java/com/example/getstarted/basicactions/ListByUserServlet.java",
    "content": "/*\n * Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport com.example.getstarted.daos.BookDao;\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.objects.Result;\nimport java.io.IOException;\nimport java.util.List;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class ListByUserServlet extends HttpServlet {\n\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException,\n        ServletException {\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    String startCursor = req.getParameter(\"cursor\");\n    List<Book> books = null;\n    String endCursor = null;\n    try {\n      Result<Book> result =\n          dao.listBooksByUser((String) req.getSession().getAttribute(\"userId\"), startCursor);\n      books = result.result;\n      endCursor = result.cursor;\n    } catch (Exception e) {\n      throw new ServletException(\"Error listing books\", e);\n    }\n    req.getSession().getServletContext().setAttribute(\"books\", books);\n    StringBuilder bookNames = new StringBuilder();\n    for (Book book : books) {\n      bookNames.append(book.getTitle() + \" \");\n    }\n    req.getSession().setAttribute(\"cursor\", endCursor);\n    req.getSession().setAttribute(\"page\", \"list\");\n    req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/java/com/example/getstarted/basicactions/ReadBookServlet.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport com.example.getstarted.daos.BookDao;\nimport com.example.getstarted.objects.Book;\nimport java.io.IOException;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class ReadBookServlet extends HttpServlet {\n\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException,\n      ServletException {\n    Long id = Long.decode(req.getParameter(\"id\"));\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    try {\n      Book book = dao.readBook(id);\n      req.setAttribute(\"book\", book);\n      req.setAttribute(\"page\", \"view\");\n      req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n    } catch (Exception e) {\n      throw new ServletException(\"Error reading book\", e);\n    }\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/java/com/example/getstarted/basicactions/UpdateBookServlet.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport com.example.getstarted.daos.BookDao;\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.util.CloudStorageHelper;\nimport com.google.common.base.Strings;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport org.apache.commons.fileupload.FileItemIterator;\nimport org.apache.commons.fileupload.FileItemStream;\nimport org.apache.commons.fileupload.FileUploadException;\nimport org.apache.commons.fileupload.servlet.ServletFileUpload;\nimport org.apache.commons.fileupload.util.Streams;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class UpdateBookServlet extends HttpServlet {\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,\n      IOException {\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    try {\n      Book book = dao.readBook(Long.decode(req.getParameter(\"id\")));\n      req.setAttribute(\"book\", book);\n      req.setAttribute(\"action\", \"Edit\");\n      req.setAttribute(\"destination\", \"update\");\n      req.setAttribute(\"page\", \"form\");\n      req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n    } catch (Exception e) {\n      throw new ServletException(\"Error loading book for editing\", e);\n    }\n  }\n\n  @Override\n  public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,\n      IOException {\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n\n    assert ServletFileUpload.isMultipartContent(req);\n    CloudStorageHelper storageHelper =\n        (CloudStorageHelper) getServletContext().getAttribute(\"storageHelper\");\n\n    String newImageUrl = null;\n    Map<String, String> params = new HashMap<String, String>();\n    try {\n      FileItemIterator iter = new ServletFileUpload().getItemIterator(req);\n      while (iter.hasNext()) {\n        FileItemStream item = iter.next();\n        if (item.isFormField()) {\n          params.put(item.getFieldName(), Streams.asString(item.openStream()));\n        } else if (!Strings.isNullOrEmpty(item.getName())) {\n          newImageUrl = storageHelper.uploadFile(\n              item, getServletContext().getInitParameter(\"bookshelf.bucket\"));\n        }\n      }\n    } catch (FileUploadException e) {\n      throw new IOException(e);\n    }\n\n    try {\n      Book oldBook = dao.readBook(Long.decode(params.get(\"id\")));\n\n      // [START bookBuilder]\n      Book book = new Book.Builder()\n          .author(params.get(\"author\"))\n          .description(params.get(\"description\"))\n          .publishedDate(params.get(\"publishedDate\"))\n          .title(params.get(\"title\"))\n          .imageUrl(null == newImageUrl ? params.get(\"imageUrl\") : newImageUrl)\n          .id(Long.decode(params.get(\"id\")))\n          .createdBy(oldBook.getCreatedBy())\n          .createdById(oldBook.getCreatedById())\n          .build();\n      // [END bookBuilder]\n\n      dao.updateBook(book);\n      resp.sendRedirect(\"/read?id=\" + params.get(\"id\"));\n    } catch (Exception e) {\n      throw new ServletException(\"Error updating book\", e);\n    }\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/java/com/example/getstarted/daos/BookDao.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.daos;\n\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.objects.Result;\nimport java.sql.SQLException;\n\n// [START example]\npublic interface BookDao {\n  Long createBook(Book book) throws SQLException;\n\n  Book readBook(Long bookId) throws SQLException;\n\n  void updateBook(Book book) throws SQLException;\n\n  void deleteBook(Long bookId) throws SQLException;\n\n  Result<Book> listBooks(String startCursor) throws SQLException;\n\n  Result<Book> listBooksByUser(String userId, String startCursor) throws SQLException;\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/java/com/example/getstarted/daos/CloudSqlDao.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.daos;\n\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.objects.Result;\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.ArrayList;\nimport java.util.List;\n\n// [START example]\npublic class CloudSqlDao implements BookDao {\n  // [START constructor]\n  private String sqlUrl;\n\n  /**\n   * A data access object for Bookshelf using a Google Cloud SQL server for storage.\n   */\n  public CloudSqlDao(final String url) throws SQLException {\n\n    sqlUrl = url;\n    final String createTableSql = \"CREATE TABLE IF NOT EXISTS books4 ( id INT NOT NULL \"\n        + \"AUTO_INCREMENT, author VARCHAR(255), createdBy VARCHAR(255), createdById VARCHAR(255), \"\n        + \"description VARCHAR(255), publishedDate VARCHAR(255), title VARCHAR(255), imageUrl \"\n        + \"VARCHAR(255), PRIMARY KEY (id))\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl)) {\n      conn.createStatement().executeUpdate(createTableSql);\n    }\n  }\n  // [END constructor]\n\n  // [START create]\n  @Override\n  public Long createBook(Book book) throws SQLException {\n    final String createBookString = \"INSERT INTO books4 \"\n        + \"(author, createdBy, createdById, description, publishedDate, title, imageUrl) \"\n        + \"VALUES (?, ?, ?, ?, ?, ?, ?)\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl);\n         final PreparedStatement createBookStmt = conn.prepareStatement(createBookString,\n             Statement.RETURN_GENERATED_KEYS)) {\n      createBookStmt.setString(1, book.getAuthor());\n      createBookStmt.setString(2, book.getCreatedBy());\n      createBookStmt.setString(3, book.getCreatedById());\n      createBookStmt.setString(4, book.getDescription());\n      createBookStmt.setString(5, book.getPublishedDate());\n      createBookStmt.setString(6, book.getTitle());\n      createBookStmt.setString(7, book.getImageUrl());\n      createBookStmt.executeUpdate();\n      try (ResultSet keys = createBookStmt.getGeneratedKeys()) {\n        keys.next();\n        return keys.getLong(1);\n      }\n    }\n  }\n  // [END create]\n\n  // [START read]\n  @Override\n  public Book readBook(Long bookId) throws SQLException {\n    final String readBookString = \"SELECT * FROM books4 WHERE id = ?\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl);\n         PreparedStatement readBookStmt = conn.prepareStatement(readBookString)) {\n      readBookStmt.setLong(1, bookId);\n      try (ResultSet keys = readBookStmt.executeQuery()) {\n        keys.next();\n        return new Book.Builder()\n            .author(keys.getString(Book.AUTHOR))\n            .createdBy(keys.getString(Book.CREATED_BY))\n            .createdById(keys.getString(Book.CREATED_BY_ID))\n            .description(keys.getString(Book.DESCRIPTION))\n            .id(keys.getLong(Book.ID))\n            .publishedDate(keys.getString(Book.PUBLISHED_DATE))\n            .title(keys.getString(Book.TITLE))\n            .imageUrl(keys.getString(Book.IMAGE_URL))\n            .build();\n      }\n    }\n  }\n  // [END read]\n\n  // [START update]\n  @Override\n  public void updateBook(Book book) throws SQLException {\n    final String updateBookString = \"UPDATE books4 SET author = ?, createdBy = ?, createdById = ?, \"\n        + \"description = ?, publishedDate = ?, title = ?, imageUrl = ? WHERE id = ?\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl);\n         PreparedStatement updateBookStmt = conn.prepareStatement(updateBookString)) {\n      updateBookStmt.setString(1, book.getAuthor());\n      updateBookStmt.setString(2, book.getCreatedBy());\n      updateBookStmt.setString(3, book.getCreatedById());\n      updateBookStmt.setString(4, book.getDescription());\n      updateBookStmt.setString(5, book.getPublishedDate());\n      updateBookStmt.setString(6, book.getTitle());\n      updateBookStmt.setString(7, book.getImageUrl());\n      updateBookStmt.setLong(8, book.getId());\n      updateBookStmt.executeUpdate();\n    }\n  }\n  // [END update]\n\n  // [START delete]\n  @Override\n  public void deleteBook(Long bookId) throws SQLException {\n    final String deleteBookString = \"DELETE FROM books4 WHERE id = ?\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl);\n         PreparedStatement deleteBookStmt = conn.prepareStatement(deleteBookString)) {\n      deleteBookStmt.setLong(1, bookId);\n      deleteBookStmt.executeUpdate();\n    }\n  }\n  // [END delete]\n\n  // [START listbooks]\n  @Override\n  public Result<Book> listBooks(String cursor) throws SQLException {\n    int offset = 0;\n    if (cursor != null && !cursor.equals(\"\")) {\n      offset = Integer.parseInt(cursor);\n    }\n    final String listBooksString = \"SELECT SQL_CALC_FOUND_ROWS author, createdBy, createdById, \"\n        + \"description, id, publishedDate, title, imageUrl FROM books4 ORDER BY title ASC \"\n        + \"LIMIT 10 OFFSET ?\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl);\n         PreparedStatement listBooksStmt = conn.prepareStatement(listBooksString)) {\n      listBooksStmt.setInt(1, offset);\n      List<Book> resultBooks = new ArrayList<>();\n      try (ResultSet rs = listBooksStmt.executeQuery()) {\n        while (rs.next()) {\n          Book book = new Book.Builder()\n              .author(rs.getString(Book.AUTHOR))\n              .createdBy(rs.getString(Book.CREATED_BY))\n              .createdById(rs.getString(Book.CREATED_BY_ID))\n              .description(rs.getString(Book.DESCRIPTION))\n              .id(rs.getLong(Book.ID))\n              .publishedDate(rs.getString(Book.PUBLISHED_DATE))\n              .title(rs.getString(Book.TITLE))\n              .imageUrl(rs.getString(Book.IMAGE_URL))\n              .build();\n          resultBooks.add(book);\n        }\n      }\n      try (ResultSet rs = conn.createStatement().executeQuery(\"SELECT FOUND_ROWS()\")) {\n        int totalNumRows = 0;\n        if (rs.next()) {\n          totalNumRows = rs.getInt(1);\n        }\n        if (totalNumRows > offset + 10) {\n          return new Result<>(resultBooks, Integer.toString(offset + 10));\n        } else {\n          return new Result<>(resultBooks);\n        }\n      }\n    }\n  }\n  // [END listbooks]\n\n  // [START listbyuser]\n  @Override\n  public Result<Book> listBooksByUser(String userId, String startCursor) throws SQLException {\n    int offset = 0;\n    if (startCursor != null && !startCursor.equals(\"\")) {\n      offset = Integer.parseInt(startCursor);\n    }\n    final String listBooksString = \"SELECT SQL_CALC_FOUND_ROWS author, createdBy, createdById, \"\n        + \"description, id, publishedDate, title, imageUrl FROM books WHERE createdById = ? \"\n        + \"ORDER BY title ASC LIMIT 10 OFFSET ?\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl);\n         PreparedStatement listBooksStmt = conn.prepareStatement(listBooksString)) {\n      listBooksStmt.setString(1, userId);\n      listBooksStmt.setInt(2, offset);\n      List<Book> resultBooks = new ArrayList<>();\n      try (ResultSet rs = listBooksStmt.executeQuery()) {\n        while (rs.next()) {\n          Book book = new Book.Builder()\n              .author(rs.getString(Book.AUTHOR))\n              .createdBy(rs.getString(Book.CREATED_BY))\n              .createdById(rs.getString(Book.CREATED_BY_ID))\n              .description(rs.getString(Book.DESCRIPTION))\n              .id(rs.getLong(Book.ID))\n              .publishedDate(rs.getString(Book.PUBLISHED_DATE))\n              .title(rs.getString(Book.TITLE))\n              .imageUrl(rs.getString(Book.IMAGE_URL))\n              .build();\n          resultBooks.add(book);\n        }\n      }\n      try (ResultSet rs = conn.createStatement().executeQuery(\"SELECT FOUND_ROWS()\")) {\n        int totalNumRows = 0;\n        if (rs.next()) {\n          totalNumRows = rs.getInt(1);\n        }\n        if (totalNumRows > offset + 10) {\n          return new Result<>(resultBooks, Integer.toString(offset + 10));\n        } else {\n          return new Result<>(resultBooks);\n        }\n      }\n    }\n  }\n  // [END listbyuser]\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/java/com/example/getstarted/daos/DatastoreDao.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.daos;\n\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.objects.Result;\nimport com.google.appengine.api.datastore.Cursor;\nimport com.google.appengine.api.datastore.DatastoreService;\nimport com.google.appengine.api.datastore.DatastoreServiceFactory;\nimport com.google.appengine.api.datastore.Entity;\nimport com.google.appengine.api.datastore.EntityNotFoundException;\nimport com.google.appengine.api.datastore.FetchOptions;\nimport com.google.appengine.api.datastore.Key;\nimport com.google.appengine.api.datastore.KeyFactory;\nimport com.google.appengine.api.datastore.PreparedQuery;\nimport com.google.appengine.api.datastore.Query;\nimport com.google.appengine.api.datastore.Query.SortDirection;\nimport com.google.appengine.api.datastore.QueryResultIterator;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\n\n// [START example]\npublic class DatastoreDao implements BookDao {\n\n  // [START constructor]\n  private DatastoreService datastore;\n  private static final String BOOK_KIND = \"Book4\";\n\n  public DatastoreDao() {\n    datastore = DatastoreServiceFactory.getDatastoreService(); // Authorized Datastore service\n  }\n  // [END constructor]\n\n  // [START entityToBook]\n  public Book entityToBook(Entity entity) {\n    return new Book.Builder()                                     // Convert to Book form\n        .author((String) entity.getProperty(Book.AUTHOR))\n        .description((String) entity.getProperty(Book.DESCRIPTION))\n        .id(entity.getKey().getId())\n        .publishedDate((String) entity.getProperty(Book.PUBLISHED_DATE))\n        .imageUrl((String) entity.getProperty(Book.IMAGE_URL))\n        .createdBy((String) entity.getProperty(Book.CREATED_BY))\n        .createdById((String) entity.getProperty(Book.CREATED_BY_ID))\n        .title((String) entity.getProperty(Book.TITLE))\n        .build();\n  }\n  // [END entityToBook]\n\n  // [START create]\n  @Override\n  public Long createBook(Book book) {\n    Entity incBookEntity = new Entity(BOOK_KIND);  // Key will be assigned once written\n    incBookEntity.setProperty(Book.AUTHOR, book.getAuthor());\n    incBookEntity.setProperty(Book.DESCRIPTION, book.getDescription());\n    incBookEntity.setProperty(Book.PUBLISHED_DATE, book.getPublishedDate());\n    incBookEntity.setProperty(Book.TITLE, book.getTitle());\n    incBookEntity.setProperty(Book.IMAGE_URL, book.getImageUrl());\n    incBookEntity.setProperty(Book.CREATED_BY, book.getCreatedBy());\n    incBookEntity.setProperty(Book.CREATED_BY_ID, book.getCreatedById());\n\n    Key bookKey = datastore.put(incBookEntity); // Save the Entity\n    return bookKey.getId();                     // The ID of the Key\n  }\n  // [END create]\n\n  // [START read]\n  @Override\n  public Book readBook(Long bookId) {\n    try {\n      Entity bookEntity = datastore.get(KeyFactory.createKey(BOOK_KIND, bookId));\n      return entityToBook(bookEntity);\n    } catch (EntityNotFoundException e) {\n      return null;\n    }\n  }\n  // [END read]\n\n  // [START update]\n  @Override\n  public void updateBook(Book book) {\n    Key key = KeyFactory.createKey(BOOK_KIND, book.getId());  // From a book, create a Key\n    Entity entity = new Entity(key);         // Convert Book to an Entity\n    entity.setProperty(Book.AUTHOR, book.getAuthor());\n    entity.setProperty(Book.DESCRIPTION, book.getDescription());\n    entity.setProperty(Book.PUBLISHED_DATE, book.getPublishedDate());\n    entity.setProperty(Book.TITLE, book.getTitle());\n    entity.setProperty(Book.IMAGE_URL, book.getImageUrl());\n    entity.setProperty(Book.CREATED_BY, book.getCreatedBy());\n    entity.setProperty(Book.CREATED_BY_ID, book.getCreatedById());\n\n    datastore.put(entity);                   // Update the Entity\n  }\n  // [END update]\n\n  // [START delete]\n  @Override\n  public void deleteBook(Long bookId) {\n    Key key = KeyFactory.createKey(BOOK_KIND, bookId);        // Create the Key\n    datastore.delete(key);                      // Delete the Entity\n  }\n  // [END delete]\n\n  // [START entitiesToBooks]\n  public List<Book> entitiesToBooks(Iterator<Entity> results) {\n    List<Book> resultBooks = new ArrayList<>();\n    while (results.hasNext()) {  // We still have data\n      resultBooks.add(entityToBook(results.next()));      // Add the Book to the List\n    }\n    return resultBooks;\n  }\n  // [END entitiesToBooks]\n\n  // [START listbooks]\n  @Override\n  public Result<Book> listBooks(String startCursorString) {\n    FetchOptions fetchOptions = FetchOptions.Builder.withLimit(10); // Only show 10 at a time\n    if (startCursorString != null && !startCursorString.equals(\"\")) {\n      fetchOptions.startCursor(Cursor.fromWebSafeString(startCursorString)); // Where we left off\n    }\n    Query query = new Query(BOOK_KIND) // We only care about Books\n        .addSort(Book.TITLE, SortDirection.ASCENDING); // Use default Index \"title\"\n    PreparedQuery preparedQuery = datastore.prepare(query);\n    QueryResultIterator<Entity> results = preparedQuery.asQueryResultIterator(fetchOptions);\n\n    List<Book> resultBooks = entitiesToBooks(results);     // Retrieve and convert Entities\n    Cursor cursor = results.getCursor();              // Where to start next time\n    if (cursor != null && resultBooks.size() == 10) {         // Are we paging? Save Cursor\n      String cursorString = cursor.toWebSafeString();               // Cursors are WebSafe\n      return new Result<>(resultBooks, cursorString);\n    } else {\n      return new Result<>(resultBooks);\n    }\n  }\n  // [END listbooks]\n\n  // [START listbyuser]\n  @Override\n  public Result<Book> listBooksByUser(String userId, String startCursorString) {\n    FetchOptions fetchOptions = FetchOptions.Builder.withLimit(10); // Only show 10 at a time\n    if (startCursorString != null && !startCursorString.equals(\"\")) {\n      fetchOptions.startCursor(Cursor.fromWebSafeString(startCursorString)); // Where we left off\n    }\n    Query query = new Query(BOOK_KIND) // We only care about Books\n        // Only for this user\n        .setFilter(new Query.FilterPredicate(\n            Book.CREATED_BY_ID, Query.FilterOperator.EQUAL, userId))\n        // a custom datastore index is required since you are filtering by one property\n        // but ordering by another\n        .addSort(Book.TITLE, SortDirection.ASCENDING);\n    PreparedQuery preparedQuery = datastore.prepare(query);\n    QueryResultIterator<Entity> results = preparedQuery.asQueryResultIterator(fetchOptions);\n\n    List<Book> resultBooks = entitiesToBooks(results);     // Retrieve and convert Entities\n    Cursor cursor = results.getCursor();              // Where to start next time\n    if (cursor != null && resultBooks.size() == 10) {         // Are we paging? Save Cursor\n      String cursorString = cursor.toWebSafeString();               // Cursors are WebSafe\n      return new Result<>(resultBooks, cursorString);\n    } else {\n      return new Result<>(resultBooks);\n    }\n  }\n  // [END listbyuser]\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/java/com/example/getstarted/objects/Book.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.objects;\n\n// [START example]\npublic class Book {\n  // [START book]\n  private String title;\n  private String author;\n  private String createdBy;\n  private String createdById;\n  private String publishedDate;\n  private String description;\n  private Long id;\n  private String imageUrl;\n  // [END book]\n  // [START keys]\n  public static final String AUTHOR = \"author\";\n  public static final String CREATED_BY = \"createdBy\";\n  public static final String CREATED_BY_ID = \"createdById\";\n  public static final String DESCRIPTION = \"description\";\n  public static final String ID = \"id\";\n  public static final String PUBLISHED_DATE = \"publishedDate\";\n  public static final String TITLE = \"title\";\n  public static final String IMAGE_URL = \"imageUrl\";\n  // [END keys]\n\n  // [START constructor]\n  // We use a Builder pattern here to simplify and standardize construction of Book objects.\n  private Book(Builder builder) {\n    this.title = builder.title;\n    this.author = builder.author;\n    this.createdBy = builder.createdBy;\n    this.createdById = builder.createdById;\n    this.publishedDate = builder.publishedDate;\n    this.description = builder.description;\n    this.id = builder.id;\n    this.imageUrl = builder.imageUrl;\n  }\n  // [END constructor]\n\n  // [START builder]\n  public static class Builder {\n    private String title;\n    private String author;\n    private String createdBy;\n    private String createdById;\n    private String publishedDate;\n    private String description;\n    private Long id;\n    private String imageUrl;\n\n    public Builder title(String title) {\n      this.title = title;\n      return this;\n    }\n\n    public Builder author(String author) {\n      this.author = author;\n      return this;\n    }\n\n    public Builder createdBy(String createdBy) {\n      this.createdBy = createdBy;\n      return this;\n    }\n\n    public Builder createdById(String createdById) {\n      this.createdById = createdById;\n      return this;\n    }\n\n    public Builder publishedDate(String publishedDate) {\n      this.publishedDate = publishedDate;\n      return this;\n    }\n\n    public Builder description(String description) {\n      this.description = description;\n      return this;\n    }\n\n    public Builder id(Long id) {\n      this.id = id;\n      return this;\n    }\n\n    public Builder imageUrl(String imageUrl) {\n      this.imageUrl = imageUrl;\n      return this;\n    }\n\n    public Book build() {\n      return new Book(this);\n    }\n  }\n\n  public String getTitle() {\n    return title;\n  }\n\n  public void setTitle(String title) {\n    this.title = title;\n  }\n\n  public String getAuthor() {\n    return author;\n  }\n\n  public void setAuthor(String author) {\n    this.author = author;\n  }\n\n  public String getCreatedBy() {\n    return createdBy;\n  }\n\n  public void setCreatedBy(String createdBy) {\n    this.createdBy = createdBy;\n  }\n\n  public String getCreatedById() {\n    return createdById;\n  }\n\n  public void setCreatedById(String createdById) {\n    this.createdById = createdById;\n  }\n\n  public String getPublishedDate() {\n    return publishedDate;\n  }\n\n  public void setPublishedDate(String publishedDate) {\n    this.publishedDate = publishedDate;\n  }\n\n  public String getDescription() {\n    return description;\n  }\n\n  public void setDescription(String description) {\n    this.description = description;\n  }\n\n  public Long getId() {\n    return id;\n  }\n\n  public void setId(Long id) {\n    this.id = id;\n  }\n\n  public String getImageUrl() {\n    return imageUrl;\n  }\n\n  public void setImageUrl(String imageUrl) {\n    this.imageUrl = imageUrl;\n  }\n\n  // [END builder]\n  @Override\n  public String toString() {\n    return\n        \"Title: \" + title + \", Author: \" + author + \", Published date: \" + publishedDate\n        + \", Added by: \" + createdBy;\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/java/com/example/getstarted/objects/Result.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.objects;\n\nimport java.util.List;\n\n// [START example]\npublic class Result<K> {\n\n  public String cursor;\n  public List<K> result;\n\n  public Result(List<K> result, String cursor) {\n    this.result = result;\n    this.cursor = cursor;\n  }\n\n  public Result(List<K> result) {\n    this.result = result;\n    this.cursor = null;\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/java/com/example/getstarted/util/CloudStorageHelper.java",
    "content": "/*\n * Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.util;\n\nimport com.google.cloud.storage.Acl;\nimport com.google.cloud.storage.Acl.Role;\nimport com.google.cloud.storage.Acl.User;\nimport com.google.cloud.storage.BlobInfo;\nimport com.google.cloud.storage.Storage;\nimport com.google.cloud.storage.StorageOptions;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport javax.servlet.ServletException;\nimport org.apache.commons.fileupload.FileItemStream;\nimport org.joda.time.DateTime;\nimport org.joda.time.DateTimeZone;\nimport org.joda.time.format.DateTimeFormat;\nimport org.joda.time.format.DateTimeFormatter;\n\n// [START example]\npublic class CloudStorageHelper {\n\n  private static Storage storage = null;\n\n  // [START init]\n  static {\n    storage = StorageOptions.getDefaultInstance().getService();\n  }\n  // [END init]\n\n  // [START uploadFile]\n  /**\n   * Uploads a file to Google Cloud Storage to the bucket specified in the BUCKET_NAME\n   * environment variable, appending a timestamp to end of the uploaded filename.\n   */\n  public String uploadFile(FileItemStream fileStream, final String bucketName)\n      throws IOException, ServletException {\n    checkFileExtension(fileStream.getName());\n\n    DateTimeFormatter dtf = DateTimeFormat.forPattern(\"-YYYY-MM-dd-HHmmssSSS\");\n    DateTime dt = DateTime.now(DateTimeZone.UTC);\n    String dtString = dt.toString(dtf);\n    final String fileName = fileStream.getName() + dtString;\n\n    // the inputstream is closed by default, so we don't need to close it here\n    @SuppressWarnings(\"deprecation\")\n    BlobInfo blobInfo =\n        storage.create(\n            BlobInfo\n                .newBuilder(bucketName, fileName)\n                // Modify access list to allow all users with link to read file\n                .setAcl(new ArrayList<>(Arrays.asList(Acl.of(User.ofAllUsers(), Role.READER))))\n                .build(),\n            fileStream.openStream());\n    // return the public download link\n    return blobInfo.getMediaLink();\n  }\n  // [END uploadFile]\n\n  // [START checkFileExtension]\n  /**\n   * Checks that the file extension is supported.\n   */\n  private void checkFileExtension(String fileName) throws ServletException {\n    if (fileName != null && !fileName.isEmpty() && fileName.contains(\".\")) {\n      String[] allowedExt = { \".jpg\", \".jpeg\", \".png\", \".gif\" };\n      for (String ext : allowedExt) {\n        if (fileName.endsWith(ext)) {\n          return;\n        }\n      }\n      throw new ServletException(\"file must be an image\");\n    }\n  }\n  // [END checkFileExtension]\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/java/com/example/getstarted/util/DatastoreSessionFilter.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.util;\n\nimport com.google.appengine.api.datastore.DatastoreService;\nimport com.google.appengine.api.datastore.DatastoreServiceFactory;\nimport com.google.appengine.api.datastore.Entity;\nimport com.google.appengine.api.datastore.EntityNotFoundException;\nimport com.google.appengine.api.datastore.Key;\nimport com.google.appengine.api.datastore.KeyFactory;\nimport com.google.appengine.api.datastore.Query;\nimport com.google.appengine.api.datastore.Query.FilterOperator;\nimport com.google.appengine.api.datastore.Query.FilterPredicate;\nimport com.google.appengine.api.datastore.Transaction;\nimport com.google.common.collect.FluentIterable;\nimport com.google.common.collect.MapDifference;\nimport com.google.common.collect.Maps;\nimport java.io.IOException;\nimport java.math.BigInteger;\nimport java.security.SecureRandom;\nimport java.util.Enumeration;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.Map;\nimport javax.servlet.Filter;\nimport javax.servlet.FilterChain;\nimport javax.servlet.FilterConfig;\nimport javax.servlet.ServletException;\nimport javax.servlet.ServletRequest;\nimport javax.servlet.ServletResponse;\nimport javax.servlet.http.Cookie;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport javax.servlet.http.HttpSession;\nimport org.joda.time.DateTime;\nimport org.joda.time.DateTimeZone;\nimport org.joda.time.format.DateTimeFormat;\nimport org.joda.time.format.DateTimeFormatter;\n\n// [START init]\npublic class DatastoreSessionFilter implements Filter {\n\n  private static DatastoreService datastore;\n  private static final DateTimeFormatter DTF = DateTimeFormat.forPattern(\"yyyyMMddHHmmssSSS\");\n  private static final String SESSION_KIND = \"SessionVariable\";\n\n  @Override\n  public void init(FilterConfig config) throws ServletException {\n    // initialize local copy of datastore session variables\n\n    datastore = DatastoreServiceFactory.getDatastoreService();\n    // Delete all sessions unmodified for over two days\n    DateTime dt = DateTime.now(DateTimeZone.UTC);\n    Query query = new Query(SESSION_KIND).setFilter(new FilterPredicate(\n            \"lastModified\", FilterOperator.LESS_THAN_OR_EQUAL, dt.minusDays(2).toString(DTF)));\n    Iterator<Entity> results = datastore.prepare(query).asIterator();\n    while (results.hasNext()) {\n      Entity stateEntity = results.next();\n      datastore.delete(stateEntity.getKey());\n    }\n  }\n  // [END init]\n\n  @Override\n  public void doFilter(ServletRequest servletReq, ServletResponse servletResp, FilterChain chain)\n      throws IOException, ServletException {\n    HttpServletRequest req = (HttpServletRequest) servletReq;\n    HttpServletResponse resp = (HttpServletResponse) servletResp;\n\n    // Check if the session cookie is there, if not there, make a session cookie using a unique\n    // identifier.\n    String sessionId = getCookieValue(req, \"bookshelfSessionId\");\n    if (sessionId.equals(\"\")) {\n      String sessionNum = new BigInteger(130, new SecureRandom()).toString(32);\n      Cookie session = new Cookie(\"bookshelfSessionId\", sessionNum);\n      session.setPath(\"/\");\n      resp.addCookie(session);\n    }\n\n    Map<String, String> datastoreMap = loadSessionVariables(req);  // session variables for request\n\n    chain.doFilter(servletReq, servletResp);  // Allow the servlet to process request and response\n\n    HttpSession session = req.getSession();   // Create session map\n    Map<String, String> sessionMap = new HashMap<>();\n    Enumeration<String> attrNames = session.getAttributeNames();\n    while (attrNames.hasMoreElements()) {\n      String attrName = attrNames.nextElement();\n      sessionMap.put(attrName, (String) session.getAttribute(attrName));\n    }\n\n    // Create a diff between the new session variables and the existing session variables\n    // to minimize datastore access\n    MapDifference<String, String> diff = Maps.difference(sessionMap, datastoreMap);\n    Map<String, String> setMap = diff.entriesOnlyOnLeft();\n    Map<String, String> deleteMap = diff.entriesOnlyOnRight();\n\n    // Apply the diff\n    setSessionVariables(sessionId, setMap);\n    deleteSessionVariables(\n        sessionId,\n        FluentIterable.from(deleteMap.keySet()).toArray(String.class));\n  }\n\n  @SuppressWarnings({\"unused\", \"JdkObsolete\"})\n  private String mapToString(Map<String, String> map) {\n    StringBuffer names = new StringBuffer();\n    for (String name : map.keySet()) {\n      names.append(name + \" \");\n    }\n    return names.toString();\n  }\n\n  @Override\n  public void destroy() {\n  }\n\n  protected String getCookieValue(HttpServletRequest req, String cookieName) {\n    Cookie[] cookies = req.getCookies();\n    if (cookies != null) {\n      for (Cookie cookie : cookies) {\n        if (cookie.getName().equals(cookieName)) {\n          return cookie.getValue();\n        }\n      }\n    }\n    return \"\";\n  }\n\n  // [START deleteSessionVariables]\n  /**\n   * Delete a value stored in the project's datastore.\n   *\n   * @param sessionId Request from which the session is extracted.\n   */\n  protected void deleteSessionVariables(String sessionId, String... varNames) {\n    if (sessionId.equals(\"\")) {\n      return;\n    }\n    Key key = KeyFactory.createKey(SESSION_KIND, sessionId);\n    Transaction transaction = datastore.beginTransaction();\n    try {\n      Entity stateEntity = datastore.get(transaction, key);\n      for (String varName : varNames) {\n        stateEntity.removeProperty(varName);\n      }\n      datastore.put(transaction, stateEntity);\n      transaction.commit();\n    } catch (EntityNotFoundException e) {\n      // Ignore - if there's no session, there's nothing to delete.\n    } finally {\n      if (transaction.isActive()) {\n        transaction.rollback();\n      }\n    }\n  }\n  // [END deleteSessionVariables]\n\n  protected void deleteSessionWithValue(String varName, String varValue) {\n    Transaction transaction = datastore.beginTransaction();\n    try {\n      Query query = new Query(SESSION_KIND)\n          .setFilter(new FilterPredicate(varName, FilterOperator.EQUAL, varValue));\n      Iterator<Entity> results = datastore.prepare(transaction, query).asIterator();\n      while (results.hasNext()) {\n        Entity stateEntity = results.next();\n        datastore.delete(transaction, stateEntity.getKey());\n      }\n      transaction.commit();\n    } finally {\n      if (transaction.isActive()) {\n        transaction.rollback();\n      }\n    }\n  }\n\n  // [START setSessionVariables]\n  /**\n   * Stores the state value in each key-value pair in the project's datastore.\n   *\n   * @param sessionId Request from which to extract session.\n   * @param varName the name of the desired session variable\n   * @param varValue the value of the desired session variable\n   */\n  protected void setSessionVariables(String sessionId, Map<String, String> setMap) {\n    if (sessionId.equals(\"\")) {\n      return;\n    }\n    Key key = KeyFactory.createKey(SESSION_KIND, sessionId);\n    Transaction transaction = datastore.beginTransaction();\n    DateTime dt = DateTime.now(DateTimeZone.UTC);\n    dt.toString(DTF);\n    try {\n      Entity stateEntity;\n      try {\n        stateEntity = datastore.get(transaction, key);\n      } catch (EntityNotFoundException e) {\n        stateEntity = new Entity(key);\n      }\n      for (String varName : setMap.keySet()) {\n        stateEntity.setProperty(varName, setMap.get(varName));\n      }\n      stateEntity.setProperty(\"lastModified\", dt.toString(DTF));\n      datastore.put(transaction, stateEntity);\n      transaction.commit();\n    } finally {\n      if (transaction.isActive()) {\n        transaction.rollback();\n      }\n    }\n  }\n  // [END setSessionVariables]\n\n  // [START loadSessionVariables]\n  /**\n   * Take an HttpServletRequest, and copy all of the current session variables over to it\n   *\n   * @param req Request from which to extract session.\n   * @return a map of strings containing all the session variables loaded or an empty map.\n   */\n  protected Map<String, String> loadSessionVariables(HttpServletRequest req)\n      throws ServletException {\n    Map<String, String> datastoreMap = new HashMap<>();\n    String sessionId = getCookieValue(req, \"bookshelfSessionId\");\n    if (sessionId.equals(\"\")) {\n      return datastoreMap;\n    }\n    Key key = KeyFactory.createKey(SESSION_KIND, sessionId);\n    Transaction transaction = datastore.beginTransaction();\n    try {\n      Entity stateEntity = datastore.get(transaction, key);\n      Map<String, Object> properties = stateEntity.getProperties();\n      for (Map.Entry<String, Object> property : properties.entrySet()) {\n        req.getSession().setAttribute(property.getKey(), property.getValue());\n        datastoreMap.put(property.getKey(), (String) property.getValue());\n      }\n      transaction.commit();\n    } catch (EntityNotFoundException e) {\n      // Ignore - if there's no session, there's nothing to delete.\n    } finally {\n      if (transaction.isActive()) {\n        transaction.rollback();\n      }\n    }\n    return datastoreMap;\n  }\n  // [END loadSessionVariables]\n}\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/webapp/WEB-INF/appengine-web.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2015 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<appengine-web-app xmlns=\"http://appengine.google.com/ns/1.0\">\n  <threadsafe>true</threadsafe>\n  <use-google-connector-j>true</use-google-connector-j>\n\n  <!-- Configure java.util.logging -->\n  <system-properties>\n    <property name=\"java.util.logging.config.file\" value=\"WEB-INF/logging.properties\"/>\n  </system-properties>\n\n  <static-files>\n    <include path=\"/static/**\" />\n  </static-files>\n\n  <resource-files>\n    <include path=\"/resources/**\" />\n  </resource-files>\n\n  <sessions-enabled>true</sessions-enabled>\n  <async-session-persistence enabled=\"true\" />\n</appengine-web-app>\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/webapp/WEB-INF/datastore-indexes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<datastore-indexes autoGenerate=\"true\">\n    <datastore-index kind=\"Book4\" ancestor=\"false\" source=\"auto\">\n        <property name=\"createdById\" direction=\"asc\"/>\n        <property name=\"title\" direction=\"asc\"/>\n    </datastore-index>\n</datastore-indexes>\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/webapp/WEB-INF/logging.properties",
    "content": ""
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/webapp/WEB-INF/web.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n Copyright 2016 Google Inc.\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n       http://www.apache.org/licenses/LICENSE-2.0\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START webxml] -->\n<web-app xmlns=\"http://java.sun.com/xml/ns/javaee\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xmlns:web=\"http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd\"\n         xsi:schemaLocation=\"http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd\" \n         version=\"2.5\">\n    <!--\n      A web.xml is needed to explicitly set the order in which filters process requests. Any filters\n      not included in web.xml will be loaded after filters listed below.\n    -->\n    <filter>\n      <filter-name>DatastoreSessionFilter</filter-name>\n      <filter-class>com.example.getstarted.util.DatastoreSessionFilter</filter-class>\n    </filter>\n    <filter-mapping>\n      <filter-name>DatastoreSessionFilter</filter-name>\n      <url-pattern>/</url-pattern>\n      <url-pattern>/books</url-pattern>\n      <url-pattern>/books/mine</url-pattern>\n      <url-pattern>/create</url-pattern>\n      <url-pattern>/delete</url-pattern>\n      <url-pattern>/login</url-pattern>\n      <url-pattern>/logout</url-pattern>\n      <url-pattern>/read</url-pattern>\n      <url-pattern>/update</url-pattern>\n    </filter-mapping>\n\n    <filter>\n      <filter-name>LogoutFilter</filter-name>\n      <filter-class>com.example.getstarted.auth.LogoutFilter</filter-class>\n    </filter>\n    <filter-mapping>\n      <filter-name>LogoutFilter</filter-name>\n      <url-pattern>/logout</url-pattern>\n    </filter-mapping>\n\n    <filter>\n      <filter-name>ListByUserFilter</filter-name>\n      <filter-class>com.example.getstarted.auth.ListByUserFilter</filter-class>\n    </filter>\n    <filter-mapping>\n      <filter-name>ListByUserFilter</filter-name>\n      <url-pattern>/books/mine</url-pattern>\n    </filter-mapping>\n\n    <servlet>\n      <servlet-name>list</servlet-name>\n      <servlet-class>com.example.getstarted.basicactions.ListBookServlet</servlet-class>\n      <load-on-startup>1</load-on-startup>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>list</servlet-name>\n      <url-pattern>/</url-pattern>\n      <url-pattern>/books</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n      <servlet-name>create</servlet-name>\n      <servlet-class>com.example.getstarted.basicactions.CreateBookServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>create</servlet-name>\n      <url-pattern>/create</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n      <servlet-name>update</servlet-name>\n      <servlet-class>com.example.getstarted.basicactions.UpdateBookServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>update</servlet-name>\n      <url-pattern>/update</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n      <servlet-name>read</servlet-name>\n      <servlet-class>com.example.getstarted.basicactions.ReadBookServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>read</servlet-name>\n      <url-pattern>/read</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n      <servlet-name>delete</servlet-name>\n      <servlet-class>com.example.getstarted.basicactions.DeleteBookServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>delete</servlet-name>\n      <url-pattern>/delete</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n      <servlet-name>logout</servlet-name>\n      <servlet-class>com.example.getstarted.auth.LogoutServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>logout</servlet-name>\n      <url-pattern>/logout</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n      <servlet-name>login</servlet-name>\n      <servlet-class>com.example.getstarted.auth.LoginServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>login</servlet-name>\n      <url-pattern>/login</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n      <servlet-name>listbyuser</servlet-name>\n      <servlet-class>com.example.getstarted.basicactions.ListByUserServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>listbyuser</servlet-name>\n      <url-pattern>/books/mine</url-pattern>\n    </servlet-mapping>\n\n    <!-- [START config] -->\n    <context-param>\n        <param-name>bookshelf.storageType</param-name>\n        <param-value>${bookshelf.storageType}</param-value>\n    </context-param>\n\n    <context-param>\n        <param-name>bookshelf.bucket</param-name>\n        <param-value>${bookshelf.bucket}</param-value>\n    </context-param>\n\n    <context-param>\n        <param-name>sql.urlRemoteGAE</param-name>\n        <param-value>jdbc:google:mysql://${sql.instanceName}/${sql.dbName}?user=${sql.userName}&amp;password=${sql.password}</param-value>\n    </context-param>\n\n    <context-param>\n        <param-name>sql.urlRemote</param-name>\n        <param-value>jdbc:mysql://google/${sql.dbName}?cloudSqlInstance=${sql.instanceName}&amp;socketFactory=com.google.cloud.sql.mysql.SocketFactory&amp;user=${sql.userName}&amp;password=${sql.password}</param-value>\n    </context-param>\n\n    <context-param>\n        <param-name>sql.urlLocal</param-name>\n        <param-value>jdbc:mysql://localhost/${sql.dbName}?user=${sql.userName}&amp;password=${sql.password}&amp;useSSL=false</param-value>\n    </context-param>\n    <!-- [END config] -->\n</web-app>\n<!-- [END webxml] -->\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/webapp/base.jsp",
    "content": "<!--\nCopyright 2016 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START base] -->\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\" prefix=\"fn\" %>\n<html lang=\"en\">\n  <head>\n    <title>Bookshelf - Java on Google Cloud Platform</title>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link rel=\"stylesheet\" href=\"//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css\">\n  </head>\n  <body>\n    <div class=\"navbar navbar-default\">\n      <div class=\"container\">\n        <div class=\"navbar-header\">\n          <div class=\"navbar-brand\">Bookshelf</div>\n        </div>\n        <ul class=\"nav navbar-nav\">\n          <li><a href=\"/\">Books</a></li>\n          <li><a href=\"/books/mine\">My Books</a></li>\n        </ul>\n        <p class=\"navbar-text navbar-right\">\n          <c:choose>\n          <c:when test=\"${not empty userEmail}\">\n          <!-- using pageContext requires jsp-api artifact in pom.xml -->\n          <a href=\"/logout\">\n            <c:if test=\"${not empty userImageUrl}\">\n              <img class=\"img-circle\" src=\"${fn:escapeXml(userImageUrl)}\" width=\"24\">\n            </c:if>\n            ${fn:escapeXml(userEmail)}\n          </a>\n          </c:when>\n          <c:otherwise>\n          <a href=\"/login\">Login</a>\n          </c:otherwise>\n          </c:choose>\n        </p>\n      </div>\n    </div>\n    <c:import url=\"/${page}.jsp\" />\n  </body>\n</html>\n<!-- [END base]-->\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/webapp/form.jsp",
    "content": "<!--\nCopyright 2016 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START form] -->\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\"%>\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\" prefix=\"fn\"%>\n<div class=\"container\">\n  <h3>\n    <c:out value=\"${action}\" /> book\n  </h3>\n\n  <form method=\"POST\" action=\"${destination}\" enctype=\"multipart/form-data\">\n\n    <div class=\"form-group\">\n      <label for=\"title\">Title</label>\n      <input type=\"text\" name=\"title\" id=\"title\" value=\"${fn:escapeXml(book.title)}\" class=\"form-control\" />\n    </div>\n\n    <div class=\"form-group\">\n      <label for=\"author\">Author</label>\n      <input type=\"text\" name=\"author\" id=\"author\" value=\"${fn:escapeXml(book.author)}\" class=\"form-control\" />\n    </div>\n\n    <div class=\"form-group\">\n      <label for=\"publishedDate\">Date Published</label>\n      <input type=\"text\" name=\"publishedDate\" id=\"publishedDate\" value=\"${fn:escapeXml(book.publishedDate)}\" class=\"form-control\" />\n    </div>\n\n    <div class=\"form-group\">\n      <label for=\"description\">Description</label>\n      <textarea name=\"description\" id=\"description\" class=\"form-control\">${fn:escapeXml(book.description)}</textarea>\n    </div>\n\n    <div class=\"form-group ${isCloudStorageConfigured ? '' : 'hidden'}\">\n      <label for=\"image\">Cover Image</label>\n      <input type=\"file\" name=\"file\" id=\"file\" class=\"form-control\" />\n    </div>\n\n    <div class=\"form-group hidden\">\n      <label for=\"imageUrl\">Cover Image URL</label>\n      <input type=\"hidden\" name=\"id\" value=\"${book.id}\" />\n      <input type=\"text\" name=\"imageUrl\" id=\"imageUrl\" value=\"${fn:escapeXml(book.imageUrl)}\" class=\"form-control\" />\n    </div>\n\n    <button type=\"submit\" class=\"btn btn-success\">Save</button>\n  </form>\n</div>\n<!-- [END form] -->\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/webapp/list.jsp",
    "content": "<!--\nCopyright 2016 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START list] -->\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\" prefix=\"fn\" %>\n<div class=\"container\">\n  <h3>Books</h3>\n  <a href=\"/create\" class=\"btn btn-success btn-sm\">\n    <i class=\"glyphicon glyphicon-plus\"></i>\n    Add book\n  </a>\n  <c:choose>\n  <c:when test=\"${empty books}\">\n  <p>No books found</p>\n  </c:when>\n  <c:otherwise>\n  <c:forEach items=\"${books}\" var=\"book\">\n  <div class=\"media\">\n    <a href=\"/read?id=${book.id}\">\n      <div class=\"media-left\">\n        <img alt=\"ahhh\" src=\"${fn:escapeXml(not empty book.imageUrl?book.imageUrl:'http://placekitten.com/g/128/192')}\">\n      </div>\n      <div class=\"media-body\">\n        <h4>${fn:escapeXml(book.title)}</h4>\n        <p>${fn:escapeXml(book.author)}</p>\n      </div>\n    </a>\n  </div>\n  </c:forEach>\n  <c:if test=\"${not empty cursor}\">\n  <nav>\n    <ul class=\"pager\">\n      <li><a href=\"?cursor=${fn:escapeXml(cursor)}\">More</a></li>\n    </ul>\n  </nav>\n  </c:if>\n  </c:otherwise>\n  </c:choose>\n</div>\n<!-- [END list] -->\n"
  },
  {
    "path": "bookshelf-standard/4-auth/src/main/webapp/view.jsp",
    "content": "<!--\nCopyright 2016 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START view] -->\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\" prefix=\"fn\" %>\n<div class=\"container\">\n  <h3>Book</h3>\n  <div class=\"btn-group\">\n    <a href=\"/update?id=${book.id}\" class=\"btn btn-primary btn-sm\">\n      <i class=\"glyphicon glyphicon-edit\"></i>\n      Edit book\n    </a>\n    <a href=\"/delete?id=${book.id}\" class=\"btn btn-danger btn-sm\">\n      <i class=\"glyphicon glyphicon-trash\"></i>\n      Delete book\n    </a>\n  </div>\n\n  <div class=\"media\">\n    <div class=\"media-left\">\n      <img class=\"book-image\" src=\"${fn:escapeXml(not empty book.imageUrl?book.imageUrl:'http://placekitten.com/g/128/192')}\">\n    </div>\n    <div class=\"media-body\">\n      <h4 class=\"book-title\">\n        ${fn:escapeXml(book.title)}\n        <small>${fn:escapeXml(book.publishedDate)}</small>\n      </h4>\n      <h5 class=\"book-author\">By ${fn:escapeXml(not empty book.author?book.author:'Unknown')}</h5>\n      <p class=\"book-description\">${fn:escapeXml(book.description)}</p>\n      <small class=\"book-added-by\">Added by\n        ${fn:escapeXml(not empty book.createdBy?book.createdBy:'Anonymous')}</small>\n    </div>\n  </div>\n</div>\n<!-- [END view] -->\n"
  },
  {
    "path": "bookshelf-standard/5-logging/README.md",
    "content": "# Bookshelf App for Java on App Engine Standard Tutorial\n## Logging\n\nContains the code for using Cloud Datastore and Cloud SQL v2.\n\nThis is part of a [Bookshelf tutorial](https://cloud.google.com/java/getting-started/tutorial-app).\n\nMost users can get this running by updating the parameters in `pom.xml`. You'll\nalso need to [create a bucket][create-bucket] in Google Cloud Storage, referred\nto below as `MY-BUCKET`.\n\n[create-bucket]: https://cloud.google.com/storage/docs/creating-buckets\n\n### Running Locally\n\n    mvn -Plocal clean appengine:devserver -Dbookshelf.bucket=MY-BUCKET\n\n**Note**: If you run into an error about `Invalid Credentials`, you may have to run:\n\n    gcloud auth application-default login\n\n### Deploying to App Engine Standard\n\n* In the `pom.xml`, update the [App Engine Maven Plugin](https://cloud.google.com/appengine/docs/standard/java/tools/maven-reference)\nwith your Google Cloud Project Id:\n\n  ```\n  <plugin>\n    <groupId>com.google.cloud.tools</groupId>\n    <artifactId>appengine-maven-plugin</artifactId>\n    <version>2.3.0</version>\n    <configuration>\n      <projectId>GCLOUD_CONFIG</projectId>\n      <version>bookshelf</version>\n    </configuration>\n  </plugin>\n  ```\n\n* Deploy your App\n\n    mvn package appengine:deploy \\\n        -Dbookshelf.bucket=MY-BUCKET\n\nVisit it at http://bookshelf.<your-project-id>.appspot.com\n"
  },
  {
    "path": "bookshelf-standard/5-logging/jenkins.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2017 Google Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Fail on non-zero return and print command to stdout\nset -xe\n\n# Jenkins provides values for GOOGLE_PROJECT_ID and GOOGLE_VERSION_ID\n\n# Make sure it works on GAE7\n\n# Deploy and run selenium tests\nmvn clean appengine:update verify \\\n  -Pselenium \\\n  -Dappengine.appId=\"${GOOGLE_PROJECT_ID}\" \\\n  -Dappengine.version=\"${GOOGLE_VERSION_ID}\" \\\n  -Dbookshelf.bucket=\"${GCS_BUCKET}\" \\\n  -Dappengine.additionalParams=\"--service_account_json_key_file=${GOOGLE_APPLICATION_CREDENTIALS}\"\n\n\n# Make sure it deploys on GAE8\n\nFILE_PATH=src/main/webapp/WEB-INF/appengine-web.xml\nsed -i'.bak' '/<appengine-web-app/ a <runtime>java8</runtime>' \"${FILE_PATH}\"\n# Restore the backup after we're done\ntrap 'mv \"${FILE_PATH}\"{.bak,}' EXIT\n# Deploy and run selenium tests\nmvn clean appengine:update verify \\\n  -Pselenium \\\n  -Dappengine.appId=\"${GOOGLE_PROJECT_ID}\" \\\n  -Dappengine.version=\"${GOOGLE_VERSION_ID}\" \\\n  -Dbookshelf.bucket=\"${GCS_BUCKET}\" \\\n  -Dappengine.additionalParams=\"--service_account_json_key_file=${GOOGLE_APPLICATION_CREDENTIALS}\"\n\n"
  },
  {
    "path": "bookshelf-standard/5-logging/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\nCopyright 2016 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<project>                               <!-- REQUIRED -->\n\n  <modelVersion>4.0.0</modelVersion>    <!-- REQUIRED -->\n  <packaging>war</packaging>            <!-- REQUIRED -->\n\n  <groupId>com.example.standard.gettingstarted</groupId>\n  <artifactId>bookshelf-standard-5</artifactId>    <!-- Name of your project -->\n  <version>1.0-SNAPSHOT</version>       <!-- xx.xx.xx -SNAPSHOT means development -->\n\n  <parent> <!-- Only used for testing - NOT REQUIRED -->\n    <groupId>com.google.cloud.samples</groupId>\n    <artifactId>shared-configuration</artifactId>\n    <version>1.2.0</version>\n    <relativePath>../</relativePath>\n  </parent>\n\n  <properties>\n    <!-- [START config] -->\n    <bookshelf.storageType>datastore</bookshelf.storageType>   <!-- datastore or cloudsql -->\n\n    <sql.dbName>bookshelf</sql.dbName>                        <!-- A reasonable default -->\n    <!-- Instance Connection Name - project:region:dbName -->\n    <!-- -Dsql.instanceName=localhost to use a local MySQL server -->\n    <sql.instanceName>DATABASE-connectionName-HERE\n    </sql.instanceName> <!-- See `gcloud sql instances describe [instanceName]` -->\n    <sql.userName>root</sql.userName>                         <!-- A reasonable default -->\n    <sql.password>MYSQL-ROOT-PASSWORD-HERE</sql.password> <!-- -Dsql.password=myRootPassword1234 -->\n\n    <bookshelf.bucket>BUCKET-NAME-HERE</bookshelf.bucket> <!-- eg project-id.appspot.com -->\n    <!-- [END config] -->\n\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <maven.compiler.source>1.8</maven.compiler.source> <!-- REQUIRED -->\n    <maven.compiler.target>1.8</maven.compiler.target> <!-- REQUIRED -->\n    <maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>\n    <maven.compiler.showWarnings>true</maven.compiler.showWarnings>\n    <maven.war.filteringDeploymentDescriptors>true</maven.war.filteringDeploymentDescriptors>\n  </properties>\n\n  <!-- THINGS ONLY USED WHEN RUN LOCALLY -->\n  <profiles>\n    <profile>\n      <id>local</id>\n      <dependencies>\n        <dependency>\n          <groupId>com.google.cloud.sql</groupId>\n          <artifactId>mysql-socket-factory</artifactId>\n          <version>1.6.1</version>\n        </dependency>\n        <dependency>                        <!-- http://dev.mysql.com/doc/connector-j/en/ -->\n          <groupId>mysql</groupId>\n          <artifactId>mysql-connector-java</artifactId>\n          <version>8.0.30</version>  <!-- v5.x.x is Java 7, v6.x.x is Java 8 -->\n        </dependency>\n        <dependency>\n          <groupId>com.google.api-client</groupId>\n          <artifactId>google-api-client-appengine</artifactId>\n          <version>2.0.0</version>\n        </dependency>\n      </dependencies>\n    </profile>\n  </profiles>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.google.appengine</groupId>\n      <artifactId>appengine-api-1.0-sdk</artifactId>\n      <version>2.0.15</version>\n    </dependency>\n\n    <dependency>                        <!-- REQUIRED -->\n      <groupId>javax.servlet</groupId>  <!-- Java Servlet API -->\n      <artifactId>javax.servlet-api</artifactId>\n      <version>4.0.1</version>\n      <scope>provided</scope>           <!-- Provided by the Jetty Servlet engine -->\n    </dependency>\n\n    <dependency>                        <!-- Java Server Pages -->\n      <groupId>javax.servlet</groupId>\n      <artifactId>jsp-api</artifactId>\n      <version>2.0</version>\n    </dependency>\n\n    <dependency>                        <!-- JSP standard tag library -->\n      <groupId>jstl</groupId>\n      <artifactId>jstl</artifactId>\n      <version>1.2</version>\n    </dependency>\n\n    <dependency>                        <!-- Apache Taglibs -->\n      <groupId>taglibs</groupId>\n      <artifactId>standard</artifactId>\n      <version>1.1.2</version>\n    </dependency>\n\n    <dependency>                        <!-- Google Cloud Client Library for Java -->\n      <groupId>com.google.cloud</groupId>\n      <artifactId>google-cloud-storage</artifactId>\n      <version>2.23.0</version>\n    </dependency>\n\n    <dependency>                        <!-- Google Core Libraries for Java -->\n      <groupId>com.google.guava</groupId>\n      <artifactId>guava</artifactId>  <!-- https://github.com/google/guava/wiki -->\n      <version>33.1.0-jre</version>\n      <scope>compile</scope>\n    </dependency>\n\n    <dependency>                        <!-- http://www.joda.org/joda-time/ -->\n      <groupId>joda-time</groupId>\n      <artifactId>joda-time</artifactId>\n      <version>2.12.6</version>\n    </dependency>\n\n    <dependency>                        <!-- http://commons.apache.org/proper/commons-fileupload/ -->\n      <groupId>commons-fileupload</groupId>\n      <artifactId>commons-fileupload</artifactId>\n      <version>1.5</version>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <!-- Optional - for hot reload of the web application when using an IDE Eclipse / IDEA -->\n    <outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes\n    </outputDirectory>\n    <plugins>\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>appengine-maven-plugin</artifactId>\n        <version>2.4.4</version>\n        <configuration>\n          <!-- can be set w/ -DprojectId=myProjectId on command line -->\n          <projectId>GCLOUD_CONFIG</projectId>\n          <version>bookshelf</version>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <version>3.11.0</version>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/java/com/example/getstarted/auth/ListByUserFilter.java",
    "content": "/*\n * Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.auth;\n\nimport com.google.appengine.api.users.UserService;\nimport com.google.appengine.api.users.UserServiceFactory;\nimport java.io.IOException;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport javax.servlet.Filter;\nimport javax.servlet.FilterChain;\nimport javax.servlet.FilterConfig;\nimport javax.servlet.ServletException;\nimport javax.servlet.ServletRequest;\nimport javax.servlet.ServletResponse;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\npublic class ListByUserFilter implements Filter {\n\n  // [START createLogger]\n  private static final Logger logger = Logger.getLogger(ListByUserFilter.class.getName());\n  // [END createLogger]\n\n  @Override\n  public void init(FilterConfig config) throws ServletException {\n  }\n\n  @Override\n  public void doFilter(ServletRequest servletReq, ServletResponse servletResp, FilterChain chain)\n      throws IOException, ServletException {\n    HttpServletRequest req = (HttpServletRequest) servletReq;\n    HttpServletResponse resp = (HttpServletResponse) servletResp;\n\n    // [START logStuff]\n    String instanceId = System.getenv().containsKey(\"GAE_MODULE_INSTANCE\")\n        ? System.getenv(\"GAE_MODULE_INSTANCE\") : \"-1\";\n    logger.log(\n        Level.INFO,\n        \"ListByUserFilter processing new request for path: \" + req.getRequestURI()\n        + \" and instance: \" + instanceId);\n    // [END logStuff]\n\n    UserService userService = UserServiceFactory.getUserService();\n    if (userService.isUserLoggedIn()) {\n      chain.doFilter(servletReq, servletResp);\n    } else {\n      logger.log(Level.INFO, \"Not logged in, setting loginDestination to /books/mine\");\n      req.getSession().setAttribute(\"loginDestination\", \"/books/mine\");\n      resp.sendRedirect(userService.createLoginURL(\"/login\"));\n    }\n  }\n\n  @Override\n  public void destroy() {\n    logger.log(Level.INFO, \"ListByUserFilter is de-initializing\");\n  }\n}\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/java/com/example/getstarted/auth/LoginServlet.java",
    "content": "/*\n * Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.auth;\n\nimport com.google.appengine.api.users.User;\nimport com.google.appengine.api.users.UserService;\nimport com.google.appengine.api.users.UserServiceFactory;\nimport java.io.IOException;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class LoginServlet extends HttpServlet {\n\n  private Logger logger = Logger.getLogger(this.getClass().getName());\n\n  @Override\n  protected void doGet(HttpServletRequest req, HttpServletResponse resp)\n      throws IOException, ServletException {\n\n    UserService userService = UserServiceFactory.getUserService();\n    if (userService.isUserLoggedIn()) {\n      // Save the relevant profile info and store it in the session.\n      User user = userService.getCurrentUser();\n      req.getSession().setAttribute(\"userEmail\", user.getEmail());\n      req.getSession().setAttribute(\"userId\", user.getUserId());\n\n      String destination = (String) req.getSession().getAttribute(\"loginDestination\");\n      if (destination == null) {\n        destination = \"/books\";\n      }\n\n      logger.log(Level.INFO, \"logging destination \" + destination);\n      resp.sendRedirect(destination);\n    } else {\n      resp.sendRedirect(userService.createLoginURL(\"/login\"));\n      logger.log(Level.INFO, \"logging destination /login\");\n    }\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/java/com/example/getstarted/auth/LogoutFilter.java",
    "content": "/*\n * Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.auth;\n\nimport com.google.appengine.api.users.UserService;\nimport com.google.appengine.api.users.UserServiceFactory;\nimport java.io.IOException;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport javax.servlet.Filter;\nimport javax.servlet.FilterChain;\nimport javax.servlet.FilterConfig;\nimport javax.servlet.ServletException;\nimport javax.servlet.ServletRequest;\nimport javax.servlet.ServletResponse;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n// [START init]\npublic class LogoutFilter implements Filter {\n\n  private static final Logger logger = Logger.getLogger(ListByUserFilter.class.getName());\n  // [END init]\n\n  @Override\n  public void init(FilterConfig config) throws ServletException {\n  }\n\n  @Override\n  public void doFilter(ServletRequest servletReq, ServletResponse servletResp, FilterChain chain)\n      throws IOException, ServletException {\n    HttpServletRequest req = (HttpServletRequest) servletReq;\n    HttpServletResponse resp = (HttpServletResponse) servletResp;\n    String path = req.getRequestURI();\n\n    chain.doFilter(servletReq, servletResp);\n\n    UserService userService = UserServiceFactory.getUserService();\n    if (userService.isUserLoggedIn()) {\n      resp.sendRedirect(userService.createLogoutURL(\"/logout\"));\n    } else if (path.startsWith(\"/logout\")) {\n      resp.sendRedirect(\"/books\");\n    }\n  }\n\n  @Override\n  public void destroy() {\n    logger.log(Level.INFO, \"destroy called in LogoutFilter\");\n  }\n}\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/java/com/example/getstarted/auth/LogoutServlet.java",
    "content": "/*\n * Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.auth;\n\nimport java.io.IOException;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport javax.servlet.http.HttpSession;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class LogoutServlet extends HttpServlet {\n\n  @Override\n  protected void doGet(HttpServletRequest req, HttpServletResponse resp)\n      throws IOException, ServletException {\n    // you can also make an authenticated request to logout, but here we choose to\n    // simply delete the session variables for simplicity\n    HttpSession session =  req.getSession(false);\n    if (session != null) {\n      session.invalidate();\n    }\n    // rebuild session\n    req.getSession();\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/java/com/example/getstarted/basicactions/CreateBookServlet.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport com.example.getstarted.daos.BookDao;\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.util.CloudStorageHelper;\nimport com.google.common.base.Strings;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport javax.servlet.http.HttpSession;\nimport org.apache.commons.fileupload.FileItemIterator;\nimport org.apache.commons.fileupload.FileItemStream;\nimport org.apache.commons.fileupload.FileUploadException;\nimport org.apache.commons.fileupload.servlet.ServletFileUpload;\nimport org.apache.commons.fileupload.util.Streams;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class CreateBookServlet extends HttpServlet {\n\n  private static final Logger logger = Logger.getLogger(CreateBookServlet.class.getName());\n\n  // [START setup]\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,\n      IOException {\n    req.setAttribute(\"action\", \"Add\");          // Part of the Header in form.jsp\n    req.setAttribute(\"destination\", \"create\");  // The urlPattern to invoke (this Servlet)\n    req.setAttribute(\"page\", \"form\");           // Tells base.jsp to include form.jsp\n    req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n  }\n  // [END setup]\n\n  // [START formpost]\n  @Override\n  public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,\n      IOException {\n    assert ServletFileUpload.isMultipartContent(req);\n    CloudStorageHelper storageHelper =\n        (CloudStorageHelper) getServletContext().getAttribute(\"storageHelper\");\n\n    String newImageUrl = null;\n    Map<String, String> params = new HashMap<String, String>();\n    try {\n      FileItemIterator iter = new ServletFileUpload().getItemIterator(req);\n      while (iter.hasNext()) {\n        FileItemStream item = iter.next();\n        if (item.isFormField()) {\n          params.put(item.getFieldName(), Streams.asString(item.openStream()));\n        } else if (!Strings.isNullOrEmpty(item.getName())) {\n          newImageUrl = storageHelper.uploadFile(\n              item, getServletContext().getInitParameter(\"bookshelf.bucket\"));\n        }\n      }\n    } catch (FileUploadException e) {\n      throw new IOException(e);\n    }\n\n    String createdByString = \"\";\n    String createdByIdString = \"\";\n    HttpSession session = req.getSession();\n    if (session.getAttribute(\"userEmail\") != null) { // Does the user have a logged in session?\n      createdByString = (String) session.getAttribute(\"userEmail\");\n      createdByIdString = (String) session.getAttribute(\"userId\");\n    }\n\n    Book book = new Book.Builder()\n        .author(params.get(\"author\"))\n        .description(params.get(\"description\"))\n        .publishedDate(params.get(\"publishedDate\"))\n        .title(params.get(\"title\"))\n        .imageUrl(null == newImageUrl ? params.get(\"imageUrl\") : newImageUrl)\n        .createdBy(createdByString)\n        .createdById(createdByIdString)\n        .build();\n\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    try {\n      Long id = dao.createBook(book);\n      logger.log(Level.INFO, \"Created book {0}\", book);\n      resp.sendRedirect(\"/read?id=\" + id.toString());   // read what we just wrote\n    } catch (Exception e) {\n      throw new ServletException(\"Error creating book\", e);\n    }\n  }\n  // [END formpost]\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/java/com/example/getstarted/basicactions/DeleteBookServlet.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport com.example.getstarted.daos.BookDao;\nimport java.io.IOException;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class DeleteBookServlet extends HttpServlet {\n\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,\n      IOException {\n    Long id = Long.decode(req.getParameter(\"id\"));\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    try {\n      dao.deleteBook(id);\n      resp.sendRedirect(\"/books\");\n    } catch (Exception e) {\n      throw new ServletException(\"Error deleting book\", e);\n    }\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/java/com/example/getstarted/basicactions/ListBookServlet.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport com.example.getstarted.daos.BookDao;\nimport com.example.getstarted.daos.CloudSqlDao;\nimport com.example.getstarted.daos.DatastoreDao;\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.objects.Result;\nimport com.example.getstarted.util.CloudStorageHelper;\nimport com.google.common.base.Strings;\nimport java.io.IOException;\nimport java.sql.SQLException;\nimport java.util.List;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n// [START example]\n// a url pattern of \"\" makes this servlet the root servlet\n@SuppressWarnings(\"serial\")\npublic class ListBookServlet extends HttpServlet {\n\n  private static final Logger logger = Logger.getLogger(ListBookServlet.class.getName());\n\n  @Override\n  public void init() throws ServletException {\n    BookDao dao = null;\n\n    CloudStorageHelper storageHelper = new CloudStorageHelper();\n\n    // Creates the DAO based on the Context Parameters\n    String storageType = this.getServletContext().getInitParameter(\"bookshelf.storageType\");\n    switch (storageType) {\n      case \"datastore\":\n        dao = new DatastoreDao();\n        break;\n      case \"cloudsql\":\n        try {\n          // Use this url when using dev appserver, but connecting to Cloud SQL\n          String connect = this.getServletContext().getInitParameter(\"sql.urlRemote\");\n          if (connect.contains(\"localhost\")) {\n            // Use this url when using a local mysql server\n            connect = this.getServletContext().getInitParameter(\"sql.urlLocal\");\n          } else if (System.getProperty(\"com.google.appengine.runtime.version\")\n              .startsWith(\"Google App Engine/\")) {\n            // Use this url when on App Engine, connecting to Cloud SQL.\n            // Uses a special adapter because of the App Engine sandbox.\n            connect = this.getServletContext().getInitParameter(\"sql.urlRemoteGAE\");\n          }\n          dao = new CloudSqlDao(connect);\n        } catch (SQLException e) {\n          throw new ServletException(\"SQL error\", e);\n        }\n        break;\n      default:\n        throw new IllegalStateException(\n            \"Invalid storage type. Check if bookshelf.storageType property is set.\");\n    }\n    this.getServletContext().setAttribute(\"dao\", dao);\n    this.getServletContext().setAttribute(\"storageHelper\", storageHelper);\n    this.getServletContext().setAttribute(\n        \"isCloudStorageConfigured\",  // Hide upload when Cloud Storage is not configured.\n        !Strings.isNullOrEmpty(getServletContext().getInitParameter(\"bookshelf.bucket\")));\n  }\n\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException,\n      ServletException {\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    String startCursor = req.getParameter(\"cursor\");\n    List<Book> books = null;\n    String endCursor = null;\n    try {\n      Result<Book> result = dao.listBooks(startCursor);\n      logger.log(Level.INFO, \"Retrieved list of all books\");\n      books = result.result;\n      endCursor = result.cursor;\n    } catch (Exception e) {\n      throw new ServletException(\"Error listing books\", e);\n    }\n    req.getSession().getServletContext().setAttribute(\"books\", books);\n    StringBuilder bookNames = new StringBuilder();\n    for (Book book : books) {\n      bookNames.append(book.getTitle() + \" \");\n    }\n    logger.log(Level.INFO, \"Loaded books: \" + bookNames.toString());\n    req.setAttribute(\"cursor\", endCursor);\n    req.setAttribute(\"page\", \"list\");\n    req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/java/com/example/getstarted/basicactions/ListByUserServlet.java",
    "content": "/*\n * Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport com.example.getstarted.daos.BookDao;\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.objects.Result;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class ListByUserServlet extends HttpServlet {\n\n  private static final Logger logger = Logger.getLogger(ListByUserServlet.class.getName());\n\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException,\n        ServletException {\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    String startCursor = req.getParameter(\"cursor\");\n    List<Book> books = null;\n    String endCursor = null;\n    try {\n      Result<Book> result =\n          dao.listBooksByUser((String) req.getSession().getAttribute(\"userId\"), startCursor);\n      books = result.result;\n      endCursor = result.cursor;\n    } catch (Exception e) {\n      throw new ServletException(\"Error listing books\", e);\n    }\n    req.getSession().getServletContext().setAttribute(\"books\", books);\n    StringBuilder bookNames = new StringBuilder();\n    for (Book book : books) {\n      bookNames.append(book.getTitle() + \" \");\n    }\n    logger.log(Level.INFO, \"Loaded books: \" + bookNames.toString()\n        + \" for user \" + (String) req.getSession().getAttribute(\"userId\"));\n    req.getSession().setAttribute(\"cursor\", endCursor);\n    req.getSession().setAttribute(\"page\", \"list\");\n    req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/java/com/example/getstarted/basicactions/ReadBookServlet.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport com.example.getstarted.daos.BookDao;\nimport com.example.getstarted.objects.Book;\nimport java.io.IOException;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class ReadBookServlet extends HttpServlet {\n\n  // [START init]\n  private final Logger logger = Logger.getLogger(ReadBookServlet.class.getName());\n  // [END init]\n\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException,\n      ServletException {\n    Long id = Long.decode(req.getParameter(\"id\"));\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    try {\n      Book book = dao.readBook(id);\n      // [START log]\n      logger.log(Level.INFO, \"Read book with id {0}\", id);\n      // [END log]\n      req.setAttribute(\"book\", book);\n      req.setAttribute(\"page\", \"view\");\n      req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n    } catch (Exception e) {\n      throw new ServletException(\"Error reading book\", e);\n    }\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/java/com/example/getstarted/basicactions/UpdateBookServlet.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport com.example.getstarted.daos.BookDao;\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.util.CloudStorageHelper;\nimport com.google.common.base.Strings;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport org.apache.commons.fileupload.FileItemIterator;\nimport org.apache.commons.fileupload.FileItemStream;\nimport org.apache.commons.fileupload.FileUploadException;\nimport org.apache.commons.fileupload.servlet.ServletFileUpload;\nimport org.apache.commons.fileupload.util.Streams;\n\n// [START example]\n@SuppressWarnings(\"serial\")\npublic class UpdateBookServlet extends HttpServlet {\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,\n      IOException {\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n    try {\n      Book book = dao.readBook(Long.decode(req.getParameter(\"id\")));\n      req.setAttribute(\"book\", book);\n      req.setAttribute(\"action\", \"Edit\");\n      req.setAttribute(\"destination\", \"update\");\n      req.setAttribute(\"page\", \"form\");\n      req.getRequestDispatcher(\"/base.jsp\").forward(req, resp);\n    } catch (Exception e) {\n      throw new ServletException(\"Error loading book for editing\", e);\n    }\n  }\n\n  @Override\n  public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,\n      IOException {\n    BookDao dao = (BookDao) this.getServletContext().getAttribute(\"dao\");\n\n    assert ServletFileUpload.isMultipartContent(req);\n    CloudStorageHelper storageHelper =\n        (CloudStorageHelper) getServletContext().getAttribute(\"storageHelper\");\n\n    String newImageUrl = null;\n    Map<String, String> params = new HashMap<String, String>();\n    try {\n      FileItemIterator iter = new ServletFileUpload().getItemIterator(req);\n      while (iter.hasNext()) {\n        FileItemStream item = iter.next();\n        if (item.isFormField()) {\n          params.put(item.getFieldName(), Streams.asString(item.openStream()));\n        } else if (!Strings.isNullOrEmpty(item.getName())) {\n          newImageUrl = storageHelper.uploadFile(\n              item, getServletContext().getInitParameter(\"bookshelf.bucket\"));\n        }\n      }\n    } catch (FileUploadException e) {\n      throw new IOException(e);\n    }\n\n    try {\n      Book oldBook = dao.readBook(Long.decode(params.get(\"id\")));\n\n      Book book = new Book.Builder()\n          .author(params.get(\"author\"))\n          .description(params.get(\"description\"))\n          .publishedDate(params.get(\"publishedDate\"))\n          .title(params.get(\"title\"))\n          .imageUrl(null == newImageUrl ? params.get(\"imageUrl\") : newImageUrl)\n          .id(Long.decode(params.get(\"id\")))\n          .createdBy(oldBook.getCreatedBy())\n          .createdById(oldBook.getCreatedById())\n          .build();\n\n      dao.updateBook(book);\n      resp.sendRedirect(\"/read?id=\" + params.get(\"id\"));\n    } catch (Exception e) {\n      throw new ServletException(\"Error updating book\", e);\n    }\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/java/com/example/getstarted/daos/BookDao.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.daos;\n\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.objects.Result;\nimport java.sql.SQLException;\n\n// [START example]\npublic interface BookDao {\n  Long createBook(Book book) throws SQLException;\n\n  Book readBook(Long bookId) throws SQLException;\n\n  void updateBook(Book book) throws SQLException;\n\n  void deleteBook(Long bookId) throws SQLException;\n\n  Result<Book> listBooks(String startCursor) throws SQLException;\n\n  Result<Book> listBooksByUser(String userId, String startCursor) throws SQLException;\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/java/com/example/getstarted/daos/CloudSqlDao.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.daos;\n\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.objects.Result;\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.ArrayList;\nimport java.util.List;\n\n// [START example]\npublic class CloudSqlDao implements BookDao {\n  // [START constructor]\n  private String sqlUrl;\n\n  /**\n   * A data access object for Bookshelf using a Google Cloud SQL server for storage.\n   */\n  public CloudSqlDao(final String url) throws SQLException {\n\n    sqlUrl = url;\n    final String createTableSql = \"CREATE TABLE IF NOT EXISTS books5 ( id INT NOT NULL \"\n        + \"AUTO_INCREMENT, author VARCHAR(255), createdBy VARCHAR(255), createdById VARCHAR(255), \"\n        + \"description VARCHAR(255), publishedDate VARCHAR(255), title VARCHAR(255), imageUrl \"\n        + \"VARCHAR(255), PRIMARY KEY (id))\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl)) {\n      conn.createStatement().executeUpdate(createTableSql);\n    }\n  }\n  // [END constructor]\n\n  // [START create]\n  @Override\n  public Long createBook(Book book) throws SQLException {\n    final String createBookString = \"INSERT INTO books5 \"\n        + \"(author, createdBy, createdById, description, publishedDate, title, imageUrl) \"\n        + \"VALUES (?, ?, ?, ?, ?, ?, ?)\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl);\n         final PreparedStatement createBookStmt = conn.prepareStatement(createBookString,\n             Statement.RETURN_GENERATED_KEYS)) {\n      createBookStmt.setString(1, book.getAuthor());\n      createBookStmt.setString(2, book.getCreatedBy());\n      createBookStmt.setString(3, book.getCreatedById());\n      createBookStmt.setString(4, book.getDescription());\n      createBookStmt.setString(5, book.getPublishedDate());\n      createBookStmt.setString(6, book.getTitle());\n      createBookStmt.setString(7, book.getImageUrl());\n      createBookStmt.executeUpdate();\n      try (ResultSet keys = createBookStmt.getGeneratedKeys()) {\n        keys.next();\n        return keys.getLong(1);\n      }\n    }\n  }\n  // [END create]\n\n  // [START read]\n  @Override\n  public Book readBook(Long bookId) throws SQLException {\n    final String readBookString = \"SELECT * FROM books5 WHERE id = ?\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl);\n         PreparedStatement readBookStmt = conn.prepareStatement(readBookString)) {\n      readBookStmt.setLong(1, bookId);\n      try (ResultSet keys = readBookStmt.executeQuery()) {\n        keys.next();\n        return new Book.Builder()\n            .author(keys.getString(Book.AUTHOR))\n            .createdBy(keys.getString(Book.CREATED_BY))\n            .createdById(keys.getString(Book.CREATED_BY_ID))\n            .description(keys.getString(Book.DESCRIPTION))\n            .id(keys.getLong(Book.ID))\n            .publishedDate(keys.getString(Book.PUBLISHED_DATE))\n            .title(keys.getString(Book.TITLE))\n            .imageUrl(keys.getString(Book.IMAGE_URL))\n            .build();\n      }\n    }\n  }\n  // [END read]\n\n  // [START update]\n  @Override\n  public void updateBook(Book book) throws SQLException {\n    final String updateBookString = \"UPDATE books5 SET author = ?, createdBy = ?, createdById = ?, \"\n        + \"description = ?, publishedDate = ?, title = ?, imageUrl = ? WHERE id = ?\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl);\n         PreparedStatement updateBookStmt = conn.prepareStatement(updateBookString)) {\n      updateBookStmt.setString(1, book.getAuthor());\n      updateBookStmt.setString(2, book.getCreatedBy());\n      updateBookStmt.setString(3, book.getCreatedById());\n      updateBookStmt.setString(4, book.getDescription());\n      updateBookStmt.setString(5, book.getPublishedDate());\n      updateBookStmt.setString(6, book.getTitle());\n      updateBookStmt.setString(7, book.getImageUrl());\n      updateBookStmt.setLong(8, book.getId());\n      updateBookStmt.executeUpdate();\n    }\n  }\n  // [END update]\n\n  // [START delete]\n  @Override\n  public void deleteBook(Long bookId) throws SQLException {\n    final String deleteBookString = \"DELETE FROM books5 WHERE id = ?\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl);\n         PreparedStatement deleteBookStmt = conn.prepareStatement(deleteBookString)) {\n      deleteBookStmt.setLong(1, bookId);\n      deleteBookStmt.executeUpdate();\n    }\n  }\n  // [END delete]\n\n  // [START listbooks]\n  @Override\n  public Result<Book> listBooks(String cursor) throws SQLException {\n    int offset = 0;\n    if (cursor != null && !cursor.equals(\"\")) {\n      offset = Integer.parseInt(cursor);\n    }\n    final String listBooksString = \"SELECT SQL_CALC_FOUND_ROWS author, createdBy, createdById, \"\n        + \"description, id, publishedDate, title, imageUrl FROM books5 ORDER BY title ASC \"\n        + \"LIMIT 10 OFFSET ?\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl);\n         PreparedStatement listBooksStmt = conn.prepareStatement(listBooksString)) {\n      listBooksStmt.setInt(1, offset);\n      List<Book> resultBooks = new ArrayList<>();\n      try (ResultSet rs = listBooksStmt.executeQuery()) {\n        while (rs.next()) {\n          Book book = new Book.Builder()\n              .author(rs.getString(Book.AUTHOR))\n              .createdBy(rs.getString(Book.CREATED_BY))\n              .createdById(rs.getString(Book.CREATED_BY_ID))\n              .description(rs.getString(Book.DESCRIPTION))\n              .id(rs.getLong(Book.ID))\n              .publishedDate(rs.getString(Book.PUBLISHED_DATE))\n              .title(rs.getString(Book.TITLE))\n              .imageUrl(rs.getString(Book.IMAGE_URL))\n              .build();\n          resultBooks.add(book);\n        }\n      }\n      try (ResultSet rs = conn.createStatement().executeQuery(\"SELECT FOUND_ROWS()\")) {\n        int totalNumRows = 0;\n        if (rs.next()) {\n          totalNumRows = rs.getInt(1);\n        }\n        if (totalNumRows > offset + 10) {\n          return new Result<>(resultBooks, Integer.toString(offset + 10));\n        } else {\n          return new Result<>(resultBooks);\n        }\n      }\n    }\n  }\n  // [END listbooks]\n\n  // [START listbyuser]\n  @Override\n  public Result<Book> listBooksByUser(String userId, String startCursor) throws SQLException {\n    int offset = 0;\n    if (startCursor != null && !startCursor.equals(\"\")) {\n      offset = Integer.parseInt(startCursor);\n    }\n    final String listBooksString = \"SELECT SQL_CALC_FOUND_ROWS author, createdBy, createdById, \"\n        + \"description, id, publishedDate, title, imageUrl FROM books WHERE createdById = ? \"\n        + \"ORDER BY title ASC LIMIT 10 OFFSET ?\";\n    try (Connection conn = DriverManager.getConnection(sqlUrl);\n         PreparedStatement listBooksStmt = conn.prepareStatement(listBooksString)) {\n      listBooksStmt.setString(1, userId);\n      listBooksStmt.setInt(2, offset);\n      List<Book> resultBooks = new ArrayList<>();\n      try (ResultSet rs = listBooksStmt.executeQuery()) {\n        while (rs.next()) {\n          Book book = new Book.Builder()\n              .author(rs.getString(Book.AUTHOR))\n              .createdBy(rs.getString(Book.CREATED_BY))\n              .createdById(rs.getString(Book.CREATED_BY_ID))\n              .description(rs.getString(Book.DESCRIPTION))\n              .id(rs.getLong(Book.ID))\n              .publishedDate(rs.getString(Book.PUBLISHED_DATE))\n              .title(rs.getString(Book.TITLE))\n              .imageUrl(rs.getString(Book.IMAGE_URL))\n              .build();\n          resultBooks.add(book);\n        }\n      }\n      try (ResultSet rs = conn.createStatement().executeQuery(\"SELECT FOUND_ROWS()\")) {\n        int totalNumRows = 0;\n        if (rs.next()) {\n          totalNumRows = rs.getInt(1);\n        }\n        if (totalNumRows > offset + 10) {\n          return new Result<>(resultBooks, Integer.toString(offset + 10));\n        } else {\n          return new Result<>(resultBooks);\n        }\n      }\n    }\n  }\n  // [END listbyuser]\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/java/com/example/getstarted/daos/DatastoreDao.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.daos;\n\nimport com.example.getstarted.objects.Book;\nimport com.example.getstarted.objects.Result;\nimport com.google.appengine.api.datastore.Cursor;\nimport com.google.appengine.api.datastore.DatastoreService;\nimport com.google.appengine.api.datastore.DatastoreServiceFactory;\nimport com.google.appengine.api.datastore.Entity;\nimport com.google.appengine.api.datastore.EntityNotFoundException;\nimport com.google.appengine.api.datastore.FetchOptions;\nimport com.google.appengine.api.datastore.Key;\nimport com.google.appengine.api.datastore.KeyFactory;\nimport com.google.appengine.api.datastore.PreparedQuery;\nimport com.google.appengine.api.datastore.Query;\nimport com.google.appengine.api.datastore.Query.SortDirection;\nimport com.google.appengine.api.datastore.QueryResultIterator;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\n\n// [START example]\npublic class DatastoreDao implements BookDao {\n\n  // [START constructor]\n  private DatastoreService datastore;\n  private static final String BOOK_KIND = \"Book5\";\n\n  public DatastoreDao() {\n    datastore = DatastoreServiceFactory.getDatastoreService(); // Authorized Datastore service\n  }\n  // [END constructor]\n\n  // [START entityToBook]\n  public Book entityToBook(Entity entity) {\n    return new Book.Builder()                                     // Convert to Book form\n        .author((String) entity.getProperty(Book.AUTHOR))\n        .description((String) entity.getProperty(Book.DESCRIPTION))\n        .id(entity.getKey().getId())\n        .publishedDate((String) entity.getProperty(Book.PUBLISHED_DATE))\n        .imageUrl((String) entity.getProperty(Book.IMAGE_URL))\n        .createdBy((String) entity.getProperty(Book.CREATED_BY))\n        .createdById((String) entity.getProperty(Book.CREATED_BY_ID))\n        .title((String) entity.getProperty(Book.TITLE))\n        .build();\n  }\n  // [END entityToBook]\n\n  // [START create]\n  @Override\n  public Long createBook(Book book) {\n    Entity incBookEntity = new Entity(BOOK_KIND);  // Key will be assigned once written\n    incBookEntity.setProperty(Book.AUTHOR, book.getAuthor());\n    incBookEntity.setProperty(Book.DESCRIPTION, book.getDescription());\n    incBookEntity.setProperty(Book.PUBLISHED_DATE, book.getPublishedDate());\n    incBookEntity.setProperty(Book.TITLE, book.getTitle());\n    incBookEntity.setProperty(Book.IMAGE_URL, book.getImageUrl());\n    incBookEntity.setProperty(Book.CREATED_BY, book.getCreatedBy());\n    incBookEntity.setProperty(Book.CREATED_BY_ID, book.getCreatedById());\n\n    Key bookKey = datastore.put(incBookEntity); // Save the Entity\n    return bookKey.getId();                     // The ID of the Key\n  }\n  // [END create]\n\n  // [START read]\n  @Override\n  public Book readBook(Long bookId) {\n    try {\n      Entity bookEntity = datastore.get(KeyFactory.createKey(BOOK_KIND, bookId));\n      return entityToBook(bookEntity);\n    } catch (EntityNotFoundException e) {\n      return null;\n    }\n  }\n  // [END read]\n\n  // [START update]\n  @Override\n  public void updateBook(Book book) {\n    Key key = KeyFactory.createKey(BOOK_KIND, book.getId());  // From a book, create a Key\n    Entity entity = new Entity(key);         // Convert Book to an Entity\n    entity.setProperty(Book.AUTHOR, book.getAuthor());\n    entity.setProperty(Book.DESCRIPTION, book.getDescription());\n    entity.setProperty(Book.PUBLISHED_DATE, book.getPublishedDate());\n    entity.setProperty(Book.TITLE, book.getTitle());\n    entity.setProperty(Book.IMAGE_URL, book.getImageUrl());\n    entity.setProperty(Book.CREATED_BY, book.getCreatedBy());\n    entity.setProperty(Book.CREATED_BY_ID, book.getCreatedById());\n\n    datastore.put(entity);                   // Update the Entity\n  }\n  // [END update]\n\n  // [START delete]\n  @Override\n  public void deleteBook(Long bookId) {\n    Key key = KeyFactory.createKey(BOOK_KIND, bookId);        // Create the Key\n    datastore.delete(key);                      // Delete the Entity\n  }\n  // [END delete]\n\n  // [START entitiesToBooks]\n  public List<Book> entitiesToBooks(Iterator<Entity> results) {\n    List<Book> resultBooks = new ArrayList<>();\n    while (results.hasNext()) {  // We still have data\n      resultBooks.add(entityToBook(results.next()));      // Add the Book to the List\n    }\n    return resultBooks;\n  }\n  // [END entitiesToBooks]\n\n  // [START listbooks]\n  @Override\n  public Result<Book> listBooks(String startCursorString) {\n    FetchOptions fetchOptions = FetchOptions.Builder.withLimit(10); // Only show 10 at a time\n    if (startCursorString != null && !startCursorString.equals(\"\")) {\n      fetchOptions.startCursor(Cursor.fromWebSafeString(startCursorString)); // Where we left off\n    }\n    Query query = new Query(BOOK_KIND) // We only care about Books\n        .addSort(Book.TITLE, SortDirection.ASCENDING); // Use default Index \"title\"\n    PreparedQuery preparedQuery = datastore.prepare(query);\n    QueryResultIterator<Entity> results = preparedQuery.asQueryResultIterator(fetchOptions);\n\n    List<Book> resultBooks = entitiesToBooks(results);     // Retrieve and convert Entities\n    Cursor cursor = results.getCursor();              // Where to start next time\n    if (cursor != null && resultBooks.size() == 10) {         // Are we paging? Save Cursor\n      String cursorString = cursor.toWebSafeString();               // Cursors are WebSafe\n      return new Result<>(resultBooks, cursorString);\n    } else {\n      return new Result<>(resultBooks);\n    }\n  }\n  // [END listbooks]\n\n  // [START listbyuser]\n  @Override\n  public Result<Book> listBooksByUser(String userId, String startCursorString) {\n    FetchOptions fetchOptions = FetchOptions.Builder.withLimit(10); // Only show 10 at a time\n    if (startCursorString != null && !startCursorString.equals(\"\")) {\n      fetchOptions.startCursor(Cursor.fromWebSafeString(startCursorString)); // Where we left off\n    }\n    Query query = new Query(BOOK_KIND) // We only care about Books\n        // Only for this user\n        .setFilter(new Query.FilterPredicate(\n            Book.CREATED_BY_ID, Query.FilterOperator.EQUAL, userId))\n        // a custom datastore index is required since you are filtering by one property\n        // but ordering by another\n        .addSort(Book.TITLE, SortDirection.ASCENDING);\n    PreparedQuery preparedQuery = datastore.prepare(query);\n    QueryResultIterator<Entity> results = preparedQuery.asQueryResultIterator(fetchOptions);\n\n    List<Book> resultBooks = entitiesToBooks(results);     // Retrieve and convert Entities\n    Cursor cursor = results.getCursor();              // Where to start next time\n    if (cursor != null && resultBooks.size() == 10) {         // Are we paging? Save Cursor\n      String cursorString = cursor.toWebSafeString();               // Cursors are WebSafe\n      return new Result<>(resultBooks, cursorString);\n    } else {\n      return new Result<>(resultBooks);\n    }\n  }\n  // [END listbyuser]\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/java/com/example/getstarted/objects/Book.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.objects;\n\n// [START example]\npublic class Book {\n  // [START book]\n  private String title;\n  private String author;\n  private String createdBy;\n  private String createdById;\n  private String publishedDate;\n  private String description;\n  private Long id;\n  private String imageUrl;\n  // [END book]\n  // [START keys]\n  public static final String AUTHOR = \"author\";\n  public static final String CREATED_BY = \"createdBy\";\n  public static final String CREATED_BY_ID = \"createdById\";\n  public static final String DESCRIPTION = \"description\";\n  public static final String ID = \"id\";\n  public static final String PUBLISHED_DATE = \"publishedDate\";\n  public static final String TITLE = \"title\";\n  public static final String IMAGE_URL = \"imageUrl\";\n  // [END keys]\n\n  // [START constructor]\n  // We use a Builder pattern here to simplify and standardize construction of Book objects.\n  private Book(Builder builder) {\n    this.title = builder.title;\n    this.author = builder.author;\n    this.createdBy = builder.createdBy;\n    this.createdById = builder.createdById;\n    this.publishedDate = builder.publishedDate;\n    this.description = builder.description;\n    this.id = builder.id;\n    this.imageUrl = builder.imageUrl;\n  }\n  // [END constructor]\n\n  // [START builder]\n  public static class Builder {\n    private String title;\n    private String author;\n    private String createdBy;\n    private String createdById;\n    private String publishedDate;\n    private String description;\n    private Long id;\n    private String imageUrl;\n\n    public Builder title(String title) {\n      this.title = title;\n      return this;\n    }\n\n    public Builder author(String author) {\n      this.author = author;\n      return this;\n    }\n\n    public Builder createdBy(String createdBy) {\n      this.createdBy = createdBy;\n      return this;\n    }\n\n    public Builder createdById(String createdById) {\n      this.createdById = createdById;\n      return this;\n    }\n\n    public Builder publishedDate(String publishedDate) {\n      this.publishedDate = publishedDate;\n      return this;\n    }\n\n    public Builder description(String description) {\n      this.description = description;\n      return this;\n    }\n\n    public Builder id(Long id) {\n      this.id = id;\n      return this;\n    }\n\n    public Builder imageUrl(String imageUrl) {\n      this.imageUrl = imageUrl;\n      return this;\n    }\n\n    public Book build() {\n      return new Book(this);\n    }\n  }\n\n  public String getTitle() {\n    return title;\n  }\n\n  public void setTitle(String title) {\n    this.title = title;\n  }\n\n  public String getAuthor() {\n    return author;\n  }\n\n  public void setAuthor(String author) {\n    this.author = author;\n  }\n\n  public String getCreatedBy() {\n    return createdBy;\n  }\n\n  public void setCreatedBy(String createdBy) {\n    this.createdBy = createdBy;\n  }\n\n  public String getCreatedById() {\n    return createdById;\n  }\n\n  public void setCreatedById(String createdById) {\n    this.createdById = createdById;\n  }\n\n  public String getPublishedDate() {\n    return publishedDate;\n  }\n\n  public void setPublishedDate(String publishedDate) {\n    this.publishedDate = publishedDate;\n  }\n\n  public String getDescription() {\n    return description;\n  }\n\n  public void setDescription(String description) {\n    this.description = description;\n  }\n\n  public Long getId() {\n    return id;\n  }\n\n  public void setId(Long id) {\n    this.id = id;\n  }\n\n  public String getImageUrl() {\n    return imageUrl;\n  }\n\n  public void setImageUrl(String imageUrl) {\n    this.imageUrl = imageUrl;\n  }\n\n  // [END builder]\n  @Override\n  public String toString() {\n    return\n        \"Title: \" + title + \", Author: \" + author + \", Published date: \" + publishedDate\n        + \", Added by: \" + createdBy;\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/java/com/example/getstarted/objects/Result.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.objects;\n\nimport java.util.List;\n\n// [START example]\npublic class Result<K> {\n\n  public String cursor;\n  public List<K> result;\n\n  public Result(List<K> result, String cursor) {\n    this.result = result;\n    this.cursor = cursor;\n  }\n\n  public Result(List<K> result) {\n    this.result = result;\n    this.cursor = null;\n  }\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/java/com/example/getstarted/util/CloudStorageHelper.java",
    "content": "/*\n * Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.util;\n\nimport com.google.cloud.storage.Acl;\nimport com.google.cloud.storage.Acl.Role;\nimport com.google.cloud.storage.Acl.User;\nimport com.google.cloud.storage.BlobInfo;\nimport com.google.cloud.storage.Storage;\nimport com.google.cloud.storage.StorageOptions;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport javax.servlet.ServletException;\nimport org.apache.commons.fileupload.FileItemStream;\nimport org.joda.time.DateTime;\nimport org.joda.time.DateTimeZone;\nimport org.joda.time.format.DateTimeFormat;\nimport org.joda.time.format.DateTimeFormatter;\n\n// [START example]\npublic class CloudStorageHelper {\n\n  private final Logger logger = Logger.getLogger(CloudStorageHelper.class.getName());\n  private static Storage storage = null;\n\n  // [START init]\n  static {\n    storage = StorageOptions.getDefaultInstance().getService();\n  }\n  // [END init]\n\n  // [START uploadFile]\n  /**\n   * Uploads a file to Google Cloud Storage to the bucket specified in the BUCKET_NAME\n   * environment variable, appending a timestamp to end of the uploaded filename.\n   */\n  public String uploadFile(FileItemStream fileStream, final String bucketName)\n      throws IOException, ServletException {\n    checkFileExtension(fileStream.getName());\n\n    DateTimeFormatter dtf = DateTimeFormat.forPattern(\"-YYYY-MM-dd-HHmmssSSS\");\n    DateTime dt = DateTime.now(DateTimeZone.UTC);\n    String dtString = dt.toString(dtf);\n    final String fileName = fileStream.getName() + dtString;\n\n    // the inputstream is closed by default, so we don't need to close it here\n    @SuppressWarnings(\"deprecation\")\n    BlobInfo blobInfo =\n        storage.create(\n            BlobInfo\n                .newBuilder(bucketName, fileName)\n                // Modify access list to allow all users with link to read file\n                .setAcl(new ArrayList<>(Arrays.asList(Acl.of(User.ofAllUsers(), Role.READER))))\n                .build(),\n            fileStream.openStream());\n    logger.log(Level.INFO, \"Uploaded file {0} as {1}\", new Object[]{\n        fileStream.getName(), fileName});\n    // return the public download link\n    return blobInfo.getMediaLink();\n  }\n  // [END uploadFile]\n\n  // [START checkFileExtension]\n  /**\n   * Checks that the file extension is supported.\n   */\n  private void checkFileExtension(String fileName) throws ServletException {\n    if (fileName != null && !fileName.isEmpty() && fileName.contains(\".\")) {\n      String[] allowedExt = { \".jpg\", \".jpeg\", \".png\", \".gif\" };\n      for (String ext : allowedExt) {\n        if (fileName.endsWith(ext)) {\n          return;\n        }\n      }\n      throw new ServletException(\"file must be an image\");\n    }\n  }\n  // [END checkFileExtension]\n}\n// [END example]\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/java/com/example/getstarted/util/DatastoreSessionFilter.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.util;\n\nimport com.google.appengine.api.datastore.DatastoreService;\nimport com.google.appengine.api.datastore.DatastoreServiceFactory;\nimport com.google.appengine.api.datastore.Entity;\nimport com.google.appengine.api.datastore.EntityNotFoundException;\nimport com.google.appengine.api.datastore.Key;\nimport com.google.appengine.api.datastore.KeyFactory;\nimport com.google.appengine.api.datastore.Query;\nimport com.google.appengine.api.datastore.Query.FilterOperator;\nimport com.google.appengine.api.datastore.Query.FilterPredicate;\nimport com.google.appengine.api.datastore.Transaction;\nimport com.google.common.collect.FluentIterable;\nimport com.google.common.collect.MapDifference;\nimport com.google.common.collect.Maps;\nimport java.io.IOException;\nimport java.math.BigInteger;\nimport java.security.SecureRandom;\nimport java.util.Enumeration;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.Map;\nimport javax.servlet.Filter;\nimport javax.servlet.FilterChain;\nimport javax.servlet.FilterConfig;\nimport javax.servlet.ServletException;\nimport javax.servlet.ServletRequest;\nimport javax.servlet.ServletResponse;\nimport javax.servlet.http.Cookie;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport javax.servlet.http.HttpSession;\nimport org.joda.time.DateTime;\nimport org.joda.time.DateTimeZone;\nimport org.joda.time.format.DateTimeFormat;\nimport org.joda.time.format.DateTimeFormatter;\n\n// [START init]\npublic class DatastoreSessionFilter implements Filter {\n\n  private static DatastoreService datastore;\n  private static final DateTimeFormatter DTF = DateTimeFormat.forPattern(\"yyyyMMddHHmmssSSS\");\n  private static final String SESSION_KIND = \"SessionVariable\";\n\n  @Override\n  public void init(FilterConfig config) throws ServletException {\n    // initialize local copy of datastore session variables\n\n    datastore = DatastoreServiceFactory.getDatastoreService();\n    // Delete all sessions unmodified for over two days\n    DateTime dt = DateTime.now(DateTimeZone.UTC);\n    Query query = new Query(SESSION_KIND).setFilter(new FilterPredicate(\n            \"lastModified\", FilterOperator.LESS_THAN_OR_EQUAL, dt.minusDays(2).toString(DTF)));\n    Iterator<Entity> results = datastore.prepare(query).asIterator();\n    while (results.hasNext()) {\n      Entity stateEntity = results.next();\n      datastore.delete(stateEntity.getKey());\n    }\n  }\n  // [END init]\n\n  @Override\n  public void doFilter(ServletRequest servletReq, ServletResponse servletResp, FilterChain chain)\n      throws IOException, ServletException {\n    HttpServletRequest req = (HttpServletRequest) servletReq;\n    HttpServletResponse resp = (HttpServletResponse) servletResp;\n\n    // Check if the session cookie is there, if not there, make a session cookie using a unique\n    // identifier.\n    String sessionId = getCookieValue(req, \"bookshelfSessionId\");\n    if (sessionId.equals(\"\")) {\n      String sessionNum = new BigInteger(130, new SecureRandom()).toString(32);\n      Cookie session = new Cookie(\"bookshelfSessionId\", sessionNum);\n      session.setPath(\"/\");\n      resp.addCookie(session);\n    }\n\n    Map<String, String> datastoreMap = loadSessionVariables(req);  // session variables for request\n\n    chain.doFilter(servletReq, servletResp);  // Allow the servlet to process request and response\n\n    HttpSession session = req.getSession();   // Create session map\n    Map<String, String> sessionMap = new HashMap<>();\n    Enumeration<String> attrNames = session.getAttributeNames();\n    while (attrNames.hasMoreElements()) {\n      String attrName = attrNames.nextElement();\n      sessionMap.put(attrName, (String) session.getAttribute(attrName));\n    }\n\n    // Create a diff between the new session variables and the existing session variables\n    // to minimize datastore access\n    MapDifference<String, String> diff = Maps.difference(sessionMap, datastoreMap);\n    Map<String, String> setMap = diff.entriesOnlyOnLeft();\n    Map<String, String> deleteMap = diff.entriesOnlyOnRight();\n\n    // Apply the diff\n    setSessionVariables(sessionId, setMap);\n    deleteSessionVariables(\n        sessionId,\n        FluentIterable.from(deleteMap.keySet()).toArray(String.class));\n  }\n\n  @SuppressWarnings({\"unused\", \"JdkObsolete\"})\n  private String mapToString(Map<String, String> map) {\n    StringBuffer names = new StringBuffer();\n    for (String name : map.keySet()) {\n      names.append(name + \" \");\n    }\n    return names.toString();\n  }\n\n  @Override\n  public void destroy() {\n  }\n\n  protected String getCookieValue(HttpServletRequest req, String cookieName) {\n    Cookie[] cookies = req.getCookies();\n    if (cookies != null) {\n      for (Cookie cookie : cookies) {\n        if (cookie.getName().equals(cookieName)) {\n          return cookie.getValue();\n        }\n      }\n    }\n    return \"\";\n  }\n\n  // [START deleteSessionVariables]\n  /**\n   * Delete a value stored in the project's datastore.\n   *\n   * @param sessionId Request from which the session is extracted.\n   */\n  protected void deleteSessionVariables(String sessionId, String... varNames) {\n    if (sessionId.equals(\"\")) {\n      return;\n    }\n    Key key = KeyFactory.createKey(SESSION_KIND, sessionId);\n    Transaction transaction = datastore.beginTransaction();\n    try {\n      Entity stateEntity = datastore.get(transaction, key);\n      for (String varName : varNames) {\n        stateEntity.removeProperty(varName);\n      }\n      datastore.put(transaction, stateEntity);\n      transaction.commit();\n    } catch (EntityNotFoundException e) {\n      // Ignore - if there's no session, there's nothing to delete.\n    } finally {\n      if (transaction.isActive()) {\n        transaction.rollback();\n      }\n    }\n  }\n  // [END deleteSessionVariables]\n\n  protected void deleteSessionWithValue(String varName, String varValue) {\n    Transaction transaction = datastore.beginTransaction();\n    try {\n      Query query = new Query(SESSION_KIND)\n          .setFilter(new FilterPredicate(varName, FilterOperator.EQUAL, varValue));\n      Iterator<Entity> results = datastore.prepare(transaction, query).asIterator();\n      while (results.hasNext()) {\n        Entity stateEntity = results.next();\n        datastore.delete(transaction, stateEntity.getKey());\n      }\n      transaction.commit();\n    } finally {\n      if (transaction.isActive()) {\n        transaction.rollback();\n      }\n    }\n  }\n\n  // [START setSessionVariables]\n  /**\n   * Stores the state value in each key-value pair in the project's datastore.\n   *\n   * @param sessionId Request from which to extract session.\n   * @param varName the name of the desired session variable\n   * @param varValue the value of the desired session variable\n   */\n  protected void setSessionVariables(String sessionId, Map<String, String> setMap) {\n    if (sessionId.equals(\"\")) {\n      return;\n    }\n    Key key = KeyFactory.createKey(SESSION_KIND, sessionId);\n    Transaction transaction = datastore.beginTransaction();\n    DateTime dt = DateTime.now(DateTimeZone.UTC);\n    dt.toString(DTF);\n    try {\n      Entity stateEntity;\n      try {\n        stateEntity = datastore.get(transaction, key);\n      } catch (EntityNotFoundException e) {\n        stateEntity = new Entity(key);\n      }\n      for (String varName : setMap.keySet()) {\n        stateEntity.setProperty(varName, setMap.get(varName));\n      }\n      stateEntity.setProperty(\"lastModified\", dt.toString(DTF));\n      datastore.put(transaction, stateEntity);\n      transaction.commit();\n    } finally {\n      if (transaction.isActive()) {\n        transaction.rollback();\n      }\n    }\n  }\n  // [END setSessionVariables]\n\n  // [START loadSessionVariables]\n  /**\n   * Take an HttpServletRequest, and copy all of the current session variables over to it\n   *\n   * @param req Request from which to extract session.\n   * @return a map of strings containing all the session variables loaded or an empty map.\n   */\n  protected Map<String, String> loadSessionVariables(HttpServletRequest req)\n      throws ServletException {\n    Map<String, String> datastoreMap = new HashMap<>();\n    String sessionId = getCookieValue(req, \"bookshelfSessionId\");\n    if (sessionId.equals(\"\")) {\n      return datastoreMap;\n    }\n    Key key = KeyFactory.createKey(SESSION_KIND, sessionId);\n    Transaction transaction = datastore.beginTransaction();\n    try {\n      Entity stateEntity = datastore.get(transaction, key);\n      Map<String, Object> properties = stateEntity.getProperties();\n      for (Map.Entry<String, Object> property : properties.entrySet()) {\n        req.getSession().setAttribute(property.getKey(), property.getValue());\n        datastoreMap.put(property.getKey(), (String) property.getValue());\n      }\n      transaction.commit();\n    } catch (EntityNotFoundException e) {\n      // Ignore - if there's no session, there's nothing to delete.\n    } finally {\n      if (transaction.isActive()) {\n        transaction.rollback();\n      }\n    }\n    return datastoreMap;\n  }\n  // [END loadSessionVariables]\n}\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/webapp/WEB-INF/appengine-web.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright 2015 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<appengine-web-app xmlns=\"http://appengine.google.com/ns/1.0\">\n  <threadsafe>true</threadsafe>\n  <use-google-connector-j>true</use-google-connector-j>\n\n  <!-- Configure java.util.logging -->\n  <system-properties>\n    <property name=\"java.util.logging.config.file\" value=\"WEB-INF/logging.properties\"/>\n  </system-properties>\n\n  <static-files>\n    <include path=\"/static/**\" />\n  </static-files>\n\n  <resource-files>\n    <include path=\"/resources/**\" />\n  </resource-files>\n\n  <sessions-enabled>true</sessions-enabled>\n  <async-session-persistence enabled=\"true\" />\n</appengine-web-app>\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/webapp/WEB-INF/datastore-indexes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<datastore-indexes autoGenerate=\"true\">\n    <datastore-index kind=\"Book5\" ancestor=\"false\" source=\"auto\">\n        <property name=\"createdById\" direction=\"asc\"/>\n        <property name=\"title\" direction=\"asc\"/>\n    </datastore-index>\n</datastore-indexes>\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/webapp/WEB-INF/logging.properties",
    "content": "# A default java.util.logging configuration.\n# (All App Engine logging is through java.util.logging by default).\n\n# Set the default logging level for all loggers to INFO\n.level = INFO\nhandlers=java.util.logging.ConsoleHandler\njava.util.logging.ConsoleHandler.level=FINEST\njava.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter\njava.util.logging.SimpleFormatter.format = [%1$tc] %4$s: %2$s - %5$s %6$s%n\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/webapp/WEB-INF/web.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n Copyright 2016 Google Inc.\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n       http://www.apache.org/licenses/LICENSE-2.0\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START webxml] -->\n<web-app xmlns=\"http://java.sun.com/xml/ns/javaee\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xmlns:web=\"http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd\"\n         xsi:schemaLocation=\"http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd\" \n         version=\"2.5\">\n    <!--\n      A web.xml is needed to explicitly set the order in which filters process requests. Any filters\n      not included in web.xml will be loaded after filters listed below.\n    -->\n    <filter>\n      <filter-name>DatastoreSessionFilter</filter-name>\n      <filter-class>com.example.getstarted.util.DatastoreSessionFilter</filter-class>\n    </filter>\n    <filter-mapping>\n      <filter-name>DatastoreSessionFilter</filter-name>\n      <url-pattern>/</url-pattern>\n      <url-pattern>/books</url-pattern>\n      <url-pattern>/books/mine</url-pattern>\n      <url-pattern>/create</url-pattern>\n      <url-pattern>/delete</url-pattern>\n      <url-pattern>/login</url-pattern>\n      <url-pattern>/logout</url-pattern>\n      <url-pattern>/read</url-pattern>\n      <url-pattern>/update</url-pattern>\n    </filter-mapping>\n\n    <filter>\n      <filter-name>LogoutFilter</filter-name>\n      <filter-class>com.example.getstarted.auth.LogoutFilter</filter-class>\n    </filter>\n    <filter-mapping>\n      <filter-name>LogoutFilter</filter-name>\n      <url-pattern>/logout</url-pattern>\n    </filter-mapping>\n\n    <filter>\n      <filter-name>ListByUserFilter</filter-name>\n      <filter-class>com.example.getstarted.auth.ListByUserFilter</filter-class>\n    </filter>\n    <filter-mapping>\n      <filter-name>ListByUserFilter</filter-name>\n      <url-pattern>/books/mine</url-pattern>\n    </filter-mapping>\n\n    <servlet>\n      <servlet-name>list</servlet-name>\n      <servlet-class>com.example.getstarted.basicactions.ListBookServlet</servlet-class>\n      <load-on-startup>1</load-on-startup>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>list</servlet-name>\n      <url-pattern>/</url-pattern>\n      <url-pattern>/books</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n      <servlet-name>create</servlet-name>\n      <servlet-class>com.example.getstarted.basicactions.CreateBookServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>create</servlet-name>\n      <url-pattern>/create</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n      <servlet-name>update</servlet-name>\n      <servlet-class>com.example.getstarted.basicactions.UpdateBookServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>update</servlet-name>\n      <url-pattern>/update</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n      <servlet-name>read</servlet-name>\n      <servlet-class>com.example.getstarted.basicactions.ReadBookServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>read</servlet-name>\n      <url-pattern>/read</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n      <servlet-name>delete</servlet-name>\n      <servlet-class>com.example.getstarted.basicactions.DeleteBookServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>delete</servlet-name>\n      <url-pattern>/delete</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n      <servlet-name>logout</servlet-name>\n      <servlet-class>com.example.getstarted.auth.LogoutServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>logout</servlet-name>\n      <url-pattern>/logout</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n      <servlet-name>login</servlet-name>\n      <servlet-class>com.example.getstarted.auth.LoginServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>login</servlet-name>\n      <url-pattern>/login</url-pattern>\n    </servlet-mapping>\n\n    <servlet>\n      <servlet-name>listbyuser</servlet-name>\n      <servlet-class>com.example.getstarted.basicactions.ListByUserServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n      <servlet-name>listbyuser</servlet-name>\n      <url-pattern>/books/mine</url-pattern>\n    </servlet-mapping>\n\n    <!-- [START config] -->\n    <context-param>\n        <param-name>bookshelf.storageType</param-name>\n        <param-value>${bookshelf.storageType}</param-value>\n    </context-param>\n\n    <context-param>\n        <param-name>bookshelf.bucket</param-name>\n        <param-value>${bookshelf.bucket}</param-value>\n    </context-param>\n\n    <context-param>\n        <param-name>sql.urlRemoteGAE</param-name>\n        <param-value>jdbc:google:mysql://${sql.instanceName}/${sql.dbName}?user=${sql.userName}&amp;password=${sql.password}</param-value>\n    </context-param>\n\n    <context-param>\n        <param-name>sql.urlRemote</param-name>\n        <param-value>jdbc:mysql://google/${sql.dbName}?cloudSqlInstance=${sql.instanceName}&amp;socketFactory=com.google.cloud.sql.mysql.SocketFactory&amp;user=${sql.userName}&amp;password=${sql.password}</param-value>\n    </context-param>\n\n    <context-param>\n        <param-name>sql.urlLocal</param-name>\n        <param-value>jdbc:mysql://localhost/${sql.dbName}?user=${sql.userName}&amp;password=${sql.password}&amp;useSSL=false</param-value>\n    </context-param>\n    <!-- [END config] -->\n</web-app>\n<!-- [END webxml] -->\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/webapp/base.jsp",
    "content": "<!--\nCopyright 2016 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START base] -->\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\" prefix=\"fn\" %>\n<html lang=\"en\">\n  <head>\n    <title>Bookshelf - Java on Google Cloud Platform</title>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link rel=\"stylesheet\" href=\"//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css\">\n  </head>\n  <body>\n    <div class=\"navbar navbar-default\">\n      <div class=\"container\">\n        <div class=\"navbar-header\">\n          <div class=\"navbar-brand\">Bookshelf</div>\n        </div>\n        <ul class=\"nav navbar-nav\">\n          <li><a href=\"/\">Books</a></li>\n          <li><a href=\"/books/mine\">My Books</a></li>\n        </ul>\n        <p class=\"navbar-text navbar-right\">\n          <c:choose>\n          <c:when test=\"${not empty userEmail}\">\n          <!-- using pageContext requires jsp-api artifact in pom.xml -->\n          <a href=\"/logout\">\n            <c:if test=\"${not empty userImageUrl}\">\n              <img class=\"img-circle\" src=\"${fn:escapeXml(userImageUrl)}\" width=\"24\">\n            </c:if>\n            ${fn:escapeXml(userEmail)}\n          </a>\n          </c:when>\n          <c:otherwise>\n          <a href=\"/login\">Login</a>\n          </c:otherwise>\n          </c:choose>\n        </p>\n      </div>\n    </div>\n    <c:import url=\"/${page}.jsp\" />\n  </body>\n</html>\n<!-- [END base]-->\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/webapp/form.jsp",
    "content": "<!--\nCopyright 2016 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START form] -->\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\"%>\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\" prefix=\"fn\"%>\n<div class=\"container\">\n  <h3>\n    <c:out value=\"${action}\" /> book\n  </h3>\n\n  <form method=\"POST\" action=\"${destination}\" enctype=\"multipart/form-data\">\n\n    <div class=\"form-group\">\n      <label for=\"title\">Title</label>\n      <input type=\"text\" name=\"title\" id=\"title\" value=\"${fn:escapeXml(book.title)}\" class=\"form-control\" />\n    </div>\n\n    <div class=\"form-group\">\n      <label for=\"author\">Author</label>\n      <input type=\"text\" name=\"author\" id=\"author\" value=\"${fn:escapeXml(book.author)}\" class=\"form-control\" />\n    </div>\n\n    <div class=\"form-group\">\n      <label for=\"publishedDate\">Date Published</label>\n      <input type=\"text\" name=\"publishedDate\" id=\"publishedDate\" value=\"${fn:escapeXml(book.publishedDate)}\" class=\"form-control\" />\n    </div>\n\n    <div class=\"form-group\">\n      <label for=\"description\">Description</label>\n      <textarea name=\"description\" id=\"description\" class=\"form-control\">${fn:escapeXml(book.description)}</textarea>\n    </div>\n\n    <div class=\"form-group ${isCloudStorageConfigured ? '' : 'hidden'}\">\n      <label for=\"image\">Cover Image</label>\n      <input type=\"file\" name=\"file\" id=\"file\" class=\"form-control\" />\n    </div>\n\n    <div class=\"form-group hidden\">\n      <label for=\"imageUrl\">Cover Image URL</label>\n      <input type=\"hidden\" name=\"id\" value=\"${book.id}\" />\n      <input type=\"text\" name=\"imageUrl\" id=\"imageUrl\" value=\"${fn:escapeXml(book.imageUrl)}\" class=\"form-control\" />\n    </div>\n\n    <button type=\"submit\" class=\"btn btn-success\">Save</button>\n  </form>\n</div>\n<!-- [END form] -->\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/webapp/list.jsp",
    "content": "<!--\nCopyright 2016 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START list] -->\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\" prefix=\"fn\" %>\n<div class=\"container\">\n  <h3>Books</h3>\n  <a href=\"/create\" class=\"btn btn-success btn-sm\">\n    <i class=\"glyphicon glyphicon-plus\"></i>\n    Add book\n  </a>\n  <c:choose>\n  <c:when test=\"${empty books}\">\n  <p>No books found</p>\n  </c:when>\n  <c:otherwise>\n  <c:forEach items=\"${books}\" var=\"book\">\n  <div class=\"media\">\n    <a href=\"/read?id=${book.id}\">\n      <div class=\"media-left\">\n        <img alt=\"ahhh\" src=\"${fn:escapeXml(not empty book.imageUrl?book.imageUrl:'http://placekitten.com/g/128/192')}\">\n      </div>\n      <div class=\"media-body\">\n        <h4>${fn:escapeXml(book.title)}</h4>\n        <p>${fn:escapeXml(book.author)}</p>\n      </div>\n    </a>\n  </div>\n  </c:forEach>\n  <c:if test=\"${not empty cursor}\">\n  <nav>\n    <ul class=\"pager\">\n      <li><a href=\"?cursor=${fn:escapeXml(cursor)}\">More</a></li>\n    </ul>\n  </nav>\n  </c:if>\n  </c:otherwise>\n  </c:choose>\n</div>\n<!-- [END list] -->\n"
  },
  {
    "path": "bookshelf-standard/5-logging/src/main/webapp/view.jsp",
    "content": "<!--\nCopyright 2016 Google Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START view] -->\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n<%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\" prefix=\"fn\" %>\n<div class=\"container\">\n  <h3>Book</h3>\n  <div class=\"btn-group\">\n    <a href=\"/update?id=${book.id}\" class=\"btn btn-primary btn-sm\">\n      <i class=\"glyphicon glyphicon-edit\"></i>\n      Edit book\n    </a>\n    <a href=\"/delete?id=${book.id}\" class=\"btn btn-danger btn-sm\">\n      <i class=\"glyphicon glyphicon-trash\"></i>\n      Delete book\n    </a>\n  </div>\n\n  <div class=\"media\">\n    <div class=\"media-left\">\n      <img class=\"book-image\" src=\"${fn:escapeXml(not empty book.imageUrl?book.imageUrl:'http://placekitten.com/g/128/192')}\">\n    </div>\n    <div class=\"media-body\">\n      <h4 class=\"book-title\">\n        ${fn:escapeXml(book.title)}\n        <small>${fn:escapeXml(book.publishedDate)}</small>\n      </h4>\n      <h5 class=\"book-author\">By ${fn:escapeXml(not empty book.author?book.author:'Unknown')}</h5>\n      <p class=\"book-description\">${fn:escapeXml(book.description)}</p>\n      <small class=\"book-added-by\">Added by\n        ${fn:escapeXml(not empty book.createdBy?book.createdBy:'Anonymous')}</small>\n    </div>\n  </div>\n</div>\n<!-- [END view] -->\n"
  },
  {
    "path": "codecov.yml",
    "content": "# Copyright 2016 Google Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ncodecov:\n  branch: main\n\ncomment:\n  branches:\n  - main\n"
  },
  {
    "path": "gce/README.md",
    "content": "# Getting Started with Java - Google Compute Engine\n\nSee the [Bookshelf tutorial][tutorial] for help getting started with Google App Engine (Standard), Google Cloud\nFirestore, and more.\n\nYou'll need to [create a bucket][create-bucket] in Google Cloud Storage,\nreferred to below as `MY-BUCKET`. You'll also need to create an OAuth2 client\nand secret, and edit `pom.xml` with its values.\n\n### Running Locally\n\n    mvn clean jetty:run-war -DprojectID=YOUR-PROJECT-ID\n\n### Deploying to Compute Engine\n\n* Initialize the [Google Cloud SDK][cloud_sdk]\n\n        gcloud init\n\n* In the `makeProject` script update the `BUCKET` environment variable\n  with your bucket name.\n\n* Deploy your App\n\n        ./makeProject gce\n\n* To tear down the App, use\n\n        ./makeProject down\n        \n### Deploying to Compute Engine with horizontal scaling\n\n* Initialize Google Cloud SDK and `makeProject` as above.\n\n* Deploy your App\n\n        ./makeProject gce-many\n\n* To tear down the App, use\n\n        ./makeProject down-many\n\n[tutorial]: https://cloud.google.com/java/getting-started/tutorial-app\n[create-bucket]: https://cloud.google.com/storage/docs/creating-buckets#storage-create-bucket-console\n[cloud_sdk]: https://cloud.google.com/sdk/\n"
  },
  {
    "path": "gce/config/base/etc/java-util-logging.properties",
    "content": "# Copyright 2019 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# A default java.util.logging configuration.\n#\n\nhandlers=java.util.logging.ConsoleHandler,java.util.logging.FileHandler\n\njava.util.logging.FileHandler.level=INFO\n#java.util.logging.FileHandler.formatter=\n#java.util.logging.FileHandler.pattern=/opt/jetty/logs/app.%g.log.json\njava.util.logging.FileHandler.limit=104857600\njava.util.logging.FileHandler.count=3\n\n# Set the default logging level for all loggers to INFO\n.level=INFO\n\n# Override root level\ncom.foo.level=FINE\n\n# Override parent's level\ncom.foo.bar.level=SEVERE\n"
  },
  {
    "path": "gce/config/base/modules/gce.mod",
    "content": "# Copyright 2019 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# GCE Module\n#\n\n[depend]\nresources\nserver\n\n[optional]\n\n[ini-template]\n\n## Google Defaults\njetty.httpConfig.outputAggregationSize=32768\njetty.httpConfig.headerCacheSize=512\n\njetty.httpConfig.sendServerVersion=true\njetty.httpConfig.sendDateHeader=false\n\n#gae.httpPort=80\n#gae.httpsPort=443\n"
  },
  {
    "path": "gce/config/base/resources/jetty-logging.properties",
    "content": "# Copyright 2019 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Direct Jetty logging to JavaUtilLog\n# see etc/java-util-logging.properties\norg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.JavaUtilLog\n"
  },
  {
    "path": "gce/makeProject",
    "content": "#!/bin/bash\n#\n#    Copyright 2016 Google, Inc.\n#\n#    Licensed under the Apache License, Version 2.0 (the \"License\");\n#    you may not use this file except in compliance with the License.\n#    You may obtain a copy of the License at\n#\n#        http://www.apache.org/licenses/LICENSE-2.0\n#\n#    Unless required by applicable law or agreed to in writing, software\n#    distributed under the License is distributed on an \"AS IS\" BASIS,\n#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#    See the License for the specific language governing permissions and\n#    limitations under the License.\n\nset -ex\n\n# [START getting_started_useful]\nBUCKET=\n\nREGION=us-central1\nZONE=$REGION-f\n\nGROUP=frontend-group\nTEMPLATE=$GROUP-tmpl\nMACHINE_TYPE=g1-small\nIMAGE_FAMILY=debian-9\nIMAGE_PROJECT=debian-cloud\nSTARTUP_SCRIPT=scripts/startup-script.sh\nSCOPES=\"datastore,userinfo-email,logging-write,storage-full,cloud-platform\"\nTAGS=http-server\n\nMIN_INSTANCES=1\nMAX_INSTANCES=10\nTARGET_UTILIZATION=0.6\n# [END getting_started_useful]\n\nSERVICE=frontend-web-service\nWAR=getting-started-gce-1.0-SNAPSHOT.war\n\nfunction print_usage() {\n  echo \"Usage: ${0} gce | down | gce-many | down-many\"\n  echo \"\"\n  echo \"This command is useful as a place to let you easily move back and forth between running or\"\n  echo \"deploying the hello-world app.  You may add all your configuration here, so you don't need\"\n  echo \"to change them in every version of this application.\"\n  echo \"\"\n  echo \"gce      - mvn package; gcloud storage cp; gcloud compute instances create ...; - deploys to Compute Engine\"\n  echo \"down     - tears down a single instance group\"\n  echo \"gce-many - deploys a managed instance group\"\n  echo \"down-many- tears down a managed instance group\"\n}\n\nif [ $# = 0 ]; then\n  print_usage\n  exit\nfi\n\nCOMMAND=$1\n\ncase $COMMAND in\n  # usage flags\n  --help|-help|-h)\n    print_usage\n    exit\n    ;;\n\nrun)\n  set -v\n  mvn -Plocal clean jetty:run-exploded\n  ;;\n\ndeploy)\n  set -v\n  mvn clean gcloud:deploy\n  ;;\n\ngce)\n  set -v\n# [START getting_started_gce_single]\n  mvn clean package\n\n  gcloud storage cp --recursive target/${WAR} config/base gs://${BUCKET}/gce/\n\n  gcloud compute firewall-rules create allow-http-hello-world \\\n    --allow tcp:80 \\\n    --source-ranges 0.0.0.0/0 \\\n    --target-tags ${TAGS} \\\n    --description \"Allow port 80 access to instances tagged with ${TAGS}\"\n\n  gcloud compute instances create my-app-instance \\\n    --machine-type=${MACHINE_TYPE} \\\n    --scopes=${SCOPES} \\\n    --metadata-from-file startup-script=${STARTUP_SCRIPT} \\\n    --zone=${ZONE} \\\n    --tags=${TAGS} \\\n    --image-family=${IMAGE_FAMILY} \\\n    --image-project=${IMAGE_PROJECT} \\\n    --metadata BUCKET=${BUCKET}\n# [END getting_started_gce_single]\n  ;;\n\ndown)\n  set -v\n  gcloud compute instances delete my-app-instance --zone=${ZONE} --quiet\n  gcloud compute firewall-rules delete allow-http-hello-world --quiet\n  ;;\n\ngce-many)\n  set -v +e\n#\n# Instance group setup\n#\n  mvn clean package\n\n# First we have to create an instance template.\n# This template will be used by the instance group\n# to create new instances.\n\n# [START getting_started_create_template]\n  gcloud compute instance-templates create ${TEMPLATE} \\\n    --image-family=${IMAGE_FAMILY} \\\n    --image-project=${IMAGE_PROJECT} \\\n    --machine-type=${MACHINE_TYPE} \\\n    --scopes=${SCOPES} \\\n    --metadata-from-file startup-script=${STARTUP_SCRIPT} \\\n    --tags ${TAGS} \\\n    --metadata BUCKET=${BUCKET}\n# [END getting_started_create_template]\n\n# Add a firewall rule so that we can connect directly to\n# the compute instances in the group.\n  gcloud compute firewall-rules create allow-http-hello-world \\\n    --allow tcp:80 \\\n    --source-ranges 0.0.0.0/0 \\\n    --target-tags ${TAGS} \\\n    --description \"Allow port 80 access to instances tagged with ${TAGS}\"\n\n# Create the managed instance group.\n\n# [START getting_started_create_group]\n  gcloud compute instance-groups managed \\\n    create ${GROUP} \\\n    --base-instance-name ${GROUP} \\\n    --size ${MIN_INSTANCES} \\\n    --template ${TEMPLATE} \\\n    --zone ${ZONE}\n# [END getting_started_create_group]\n\n#\n# Load Balancer Setup\n#\n\n# A complete HTTP load balancer is structured as follows:\n#\n# 1) A global forwarding rule directs incoming requests to a target HTTP proxy.\n# 2) The target HTTP proxy checks each request against a URL map to determine the\n#    appropriate backend service for the request.\n# 3) The backend service directs each request to an appropriate backend based on\n#    serving capacity, zone, and instance health of its attached backends. The\n#    health of each backend instance is verified using either a health check.\n#\n# We'll create these resources in reverse order:\n# service, health check, backend service, url map, proxy.\n\n# Create a health check\n# The load balancer will use this check to keep track of which instances to send traffic to.\n# Note that health checks will not cause the load balancer to shutdown any instances.\n\n# [START getting_started_create_health_check]\n  gcloud compute http-health-checks create ah-health-check \\\n    --request-path /_ah/health \\\n    --port 80\n# [END getting_started_create_health_check]\n\n# Create a backend service, associate it with the health check and instance group.\n# The backend service serves as a target for load balancing.\n\n# [START getting_started_create_backend_service]\n  gcloud compute backend-services create $SERVICE \\\n    --http-health-checks ah-health-check --global\n# [END getting_started_create_backend_service]\n\n# [START getting_started_add_backend_service]\n  gcloud compute backend-services add-backend $SERVICE \\\n    --instance-group $GROUP \\\n    --instance-group-zone $ZONE \\\n    --global\n# [END getting_started_add_backend_service]\n\n# Create a URL map and web Proxy. The URL map will send all requests to the\n# backend service defined above.\n\n# [START getting_started_create_url_map]\n  gcloud compute url-maps create $SERVICE-map \\\n    --default-service $SERVICE\n# [END getting_started_create_url_map]\n\n# [START getting_started_create_http_proxy]\n  gcloud compute target-http-proxies create $SERVICE-proxy \\\n    --url-map $SERVICE-map\n# [END getting_started_create_http_proxy]\n\n# Create a global forwarding rule to send all traffic to our proxy\n\n# [START getting_started_create_forwarding_rule]\n  gcloud compute forwarding-rules create $SERVICE-http-rule \\\n    --global \\\n    --target-http-proxy $SERVICE-proxy \\\n    --ports 80\n# [END getting_started_create_forwarding_rule]\n\n#\n# Autoscaler configuration\n#\n# [START getting_started_set_autoscaling]\n  gcloud compute instance-groups managed set-autoscaling \\\n    $GROUP \\\n    --max-num-replicas $MAX_INSTANCES \\\n    --target-load-balancing-utilization $TARGET_UTILIZATION \\\n    --zone $ZONE\n# [END getting_started_set_autoscaling]\n\n  ;;\n\ndown-many)\n  set -v +e\n# [START getting_started_stop_gce]\n  gcloud compute instance-groups managed stop-autoscaling $GROUP --zone $ZONE --quiet\n  gcloud compute forwarding-rules delete $SERVICE-http-rule --global --quiet\n  gcloud compute target-http-proxies delete $SERVICE-proxy --quiet\n  gcloud compute url-maps delete $SERVICE-map --quiet\n  gcloud compute backend-services delete $SERVICE --global --quiet\n  gcloud compute http-health-checks delete ah-health-check --quiet\n  gcloud compute instance-groups managed delete $GROUP --zone $ZONE --quiet\n  gcloud compute firewall-rules delete allow-http-hello-world --quiet\n  gcloud compute instance-templates delete $TEMPLATE --quiet\n# [END getting_started_stop_gce]\n  ;;\nesac\nset +v\n"
  },
  {
    "path": "gce/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\nCopyright 2019 Google LLC\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<project>\n\n  <modelVersion>4.0.0</modelVersion>\n  <packaging>war</packaging>\n\n  <groupId>com.google.gce.demos</groupId>\n  <artifactId>getting-started-gce</artifactId>\n  <version>1.0-SNAPSHOT</version>\n\n  <parent>\n    <groupId>com.google.cloud.samples</groupId>\n    <artifactId>shared-configuration</artifactId>\n    <version>1.2.0</version>\n  </parent>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>\n    <maven.compiler.showWarnings>true</maven.compiler.showWarnings>\n    <maven.compiler.failOnWarning>true</maven.compiler.failOnWarning>\n    <java.version>1.8</java.version>\n    <maven.compiler.source>1.8</maven.compiler.source>\n    <maven.compiler.target>1.8</maven.compiler.target>\n    <maven.war.filteringDeploymentDescriptors>true</maven.war.filteringDeploymentDescriptors>\n  </properties>\n\n  <profiles>\n    <profile>\n      <id>local</id>\n      <properties>\n        <callback.method>http</callback.method>\n        <callback.host>localhost:8080</callback.host>\n      </properties>\n    </profile>\n  </profiles>\n\n  <dependencies>\n    <!-- START [dependencies] -->\n    <dependency>\n      <groupId>javax.servlet</groupId>\n      <artifactId>javax.servlet-api</artifactId>\n      <version>4.0.1</version>\n    </dependency>\n\n    <dependency>\n      <groupId>javax.servlet</groupId>\n      <artifactId>jsp-api</artifactId>\n      <version>2.0</version>\n    </dependency>\n    <!-- END [dependencies] -->\n\n    <!-- Test dependencies -->\n    <dependency>\n      <groupId>junit</groupId>\n      <artifactId>junit</artifactId>\n      <version>4.13.2</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>com.google.guava</groupId>\n      <artifactId>guava</artifactId>\n      <version>33.1.0-jre</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.seleniumhq.selenium</groupId>\n      <artifactId>selenium-server</artifactId>\n      <version>4.0.0-alpha-2</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.seleniumhq.selenium</groupId>\n      <artifactId>selenium-chrome-driver</artifactId>\n      <version>4.10.0</version>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.eclipse.jetty</groupId>\n        <artifactId>jetty-maven-plugin</artifactId>\n        <version>9.4.47.v20220610</version>\n        <executions>\n          <execution>\n            <id>start-selenium</id>\n            <phase>pre-integration-test</phase>\n            <goals>\n              <goal>start</goal>\n            </goals>\n            <configuration>\n            </configuration>\n          </execution>\n          <execution>\n            <id>stop-selenium</id>\n            <phase>post-integration-test</phase>\n            <goals>\n              <goal>stop</goal>\n            </goals>\n          </execution>\n        </executions>\n        <configuration>\n          <stopKey>stopPlzKThxBai</stopKey>\n          <stopPort>9283</stopPort>\n          <stopWait>60</stopWait>\n          <webApp>\n            <overrideDescriptor>${project.build.directory}/${project.build.finalName}/WEB-INF/web.xml</overrideDescriptor>\n          </webApp>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>appengine-maven-plugin</artifactId>\n        <version>2.4.4</version>\n        <configuration>\n          <!-- can be set w/ -DprojectId=myProjectId on command line -->\n          <projectId>GCLOUD_CONFIG</projectId>\n          <!-- set the GAE version or use \"GCLOUD_CONFIG\" for an autogenerated GAE version -->\n          <version>GCLOUD_CONFIG</version>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "gce/scripts/startup-script.sh",
    "content": "#! /bin/bash\n# Copyright 2019 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# [START script]\nset -e\nset -v\n\n# Talk to the metadata server to get the project id\nPROJECTID=$(curl -s \"http://metadata.google.internal/computeMetadata/v1/project/project-id\" -H \"Metadata-Flavor: Google\")\n\necho \"Project ID: ${PROJECTID}\"\n\n# Install dependencies from apt\napt-get install -yq openjdk-11-jdk git maven\n\nmvn --version\n\n# Jetty Setup\nmkdir -p /opt/jetty/temp\nmkdir -p /var/log/jetty\n\n# Get Jetty\ncurl -L https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-distribution/9.4.13.v20181111/jetty-distribution-9.4.13.v20181111.tar.gz -o jetty9.tgz\ntar xf jetty9.tgz  --strip-components=1 -C /opt/jetty\n\n# Add a Jetty User\nuseradd --user-group --shell /bin/false --home-dir /opt/jetty/temp jetty\n\ncd /opt/jetty\n# Add running as \"jetty\"\njava -jar /opt/jetty/start.jar --add-to-startd=setuid\ncd /\n\n# Clone the source repository.\ngit clone https://github.com/GoogleCloudPlatform/getting-started-java\ncd getting-started-java/gce\n\n# Build the .war file and rename.\n# very important - by renaming the war to root.war, it will run as the root servlet.\nmvn clean package -q\nmv target/getting-started-gce-1.0-SNAPSHOT.war /opt/jetty/webapps/root.war\n\n# Make sure \"jetty\" owns everything.\nchown --recursive jetty /opt/jetty\n\n# Configure the default paths for the Jetty service\ncp /opt/jetty/bin/jetty.sh /etc/init.d/jetty\necho \"JETTY_HOME=/opt/jetty\" > /etc/default/jetty\n{\n  echo \"JETTY_BASE=/opt/jetty\"\n  echo \"TMPDIR=/opt/jetty/temp\"\n  echo \"JAVA_OPTIONS=-Djetty.http.port=80\"\n  echo \"JETTY_LOGS=/var/log/jetty\"\n} >> /etc/default/jetty\n\n# Reload daemon to pick up new service\nsystemctl daemon-reload\n\n# Install logging monitor. The monitor will automatically pickup logs sent to syslog.\ncurl -sSO https://dl.google.com/cloudagents/add-logging-agent-repo.sh\nsudo bash add-logging-agent-repo.sh --also-install\n\nservice google-fluentd restart &\n\nservice jetty start\nservice jetty check\n\necho \"Startup Complete\"\n# [END script]\n"
  },
  {
    "path": "gce/src/main/appengine/app.yaml",
    "content": "# Copyright 2019 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#       http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# [START runtime]\nruntime: java\nenv: flex\n\nhandlers:\n- url: /.*\n  script: this field is required, but ignored\n\n# [START env_variables]\nenv_variables:    # Logging options\n  JAVA_OPTS: >-\n    -D.level=INFO\n# [END env_variables]\n# [END runtime]\n\nruntime_config:   # Optional\n  jdk: openjdk8\n  server: jetty9\n"
  },
  {
    "path": "gce/src/main/java/com/example/getstarted/basicactions/HelloworldController.java",
    "content": "/*\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport java.io.IOException;\nimport javax.servlet.annotation.WebServlet;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n@WebServlet(value = \"/\")\npublic class HelloworldController extends HttpServlet {\n\n  @Override\n  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {\n    resp.getWriter().write(\"Hello world - GCE!\");\n    resp.setStatus(HttpServletResponse.SC_OK);\n  }\n}\n"
  },
  {
    "path": "gce/src/main/webapp/WEB-INF/web.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n Copyright 2019 Google LLC\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n       http://www.apache.org/licenses/LICENSE-2.0\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n-->\n<!-- [START webxml] -->\n<web-app version=\"3.0\">\n    <!--\n      A web.xml is needed to explicitly set the order in which filters process requests. Any filters\n      not included in web.xml will be loaded after filters listed below.\n    -->\n    <!-- [START welcome] -->\n    <welcome-file-list>\n        <welcome-file>/</welcome-file>\n    </welcome-file-list>\n    <!-- [END welcome] -->\n    <!-- [START config] -->\n    <!-- [END config] -->\n</web-app>\n<!-- [END webxml] -->\n"
  },
  {
    "path": "gce/src/test/java/com/example/getstarted/basicactions/UserJourneyTestIT.java",
    "content": "/*\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.example.getstarted.basicactions;\n\nimport static org.junit.Assert.assertTrue;\n\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.BeforeClass;\nimport org.junit.Ignore;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.chrome.ChromeDriverService;\nimport org.openqa.selenium.chrome.ChromeOptions;\nimport org.openqa.selenium.remote.RemoteWebDriver;\nimport org.openqa.selenium.remote.service.DriverService;\n\n@RunWith(JUnit4.class)\n@SuppressWarnings(\"checkstyle:AbbreviationAsWordInName\")\npublic class UserJourneyTestIT {\n  private static DriverService service;\n  private WebDriver driver;\n\n  @BeforeClass\n  public static void setupClass() throws Exception {\n    service = ChromeDriverService.createDefaultService();\n    service.start();\n  }\n\n  @Before\n  public void setup() {\n    driver = new RemoteWebDriver(service.getUrl(), new ChromeOptions());\n  }\n\n  @After\n  public void tearDown() {\n    driver.quit();\n  }\n\n  @Test\n  @Ignore(\"b/138123046\")\n  public void userJourney() {\n    driver.get(\"http://localhost:8080\");\n\n    try {\n      assertTrue(driver.getPageSource().contains(\"Hello world - GCE!\"));\n    } catch (Exception e) {\n      System.err.println(driver.getPageSource());\n      throw e;\n    }\n  }\n}\n"
  },
  {
    "path": "helloworld-jsp/README.md",
    "content": "# Java Server Pages based Hello World app\n\n## Requirements\n* [Apache Maven](http://maven.apache.org) 3.3.9 or greater\n* [Google Cloud SDK](https://cloud.google.com/sdk/)\n* `gcloud components install app-engine-java`\n* `gcloud components update`\n\n## Setup\n\nUse either:\n\n* `gcloud init`\n* `gcloud beta auth application-default login`\n\nSet your project, the plugins in this example are configured to use this value from gcloud\n\n* `gcloud config set project <YOUR_PROJECT_NAME>`\n\nWe support building with [Maven](http://maven.apache.org/), [Gradle](https://gradle.org), and [IntelliJ IDEA](https://cloud.google.com/tools/intellij/docs/).\nThe samples have files to support both Maven and Gradle.  To use the IDE plugins, see the documentation pages above.\n\n## Maven\n[Using Maven and the App Engine Plugin](https://cloud.google.com/appengine/docs/flexible/java/using-maven)\n& [Maven Plugin Goals and Parameters](https://cloud.google.com/appengine/docs/flexible/java/maven-reference)\n### Running locally\n\n    $ mvn jetty:run-exploded\n\n### Deploying\n\n* In the `pom.xml`, update the [App Engine Maven Plugin](https://cloud.google.com/appengine/docs/standard/java/tools/maven-reference)\nwith your Google Cloud Project Id:\n\n  ```\n  <plugin>\n    <groupId>com.google.cloud.tools</groupId>\n    <artifactId>appengine-maven-plugin</artifactId>\n    <version>2.3.0</version>\n    <configuration>\n      <projectId>GCLOUD_CONFIG</projectId>\n      <version>GCLOUD_CONFIG</version>\n    </configuration>\n  </plugin>\n  ```\n  **Note:** `GCLOUD_CONFIG` is a special version for autogenerating an App Engine\n  version. Change this field to specify a specific version name.\n\n* Deploy your App  \n  ```\n    $ mvn package appengine:deploy\n  ```\n\n## Gradle\n[Using Gradle and the App Engine Plugin](https://cloud.google.com/appengine/docs/flexible/java/using-gradle)\n& [Gradle Tasks and Parameters](https://cloud.google.com/appengine/docs/flexible/java/gradle-reference)\n### Running locally\n\n    $ gradle jettyRun\n\n### Deploying\n\n    $ gradle appengineDeploy\n"
  },
  {
    "path": "helloworld-jsp/build.gradle",
    "content": "// Copyright 2016 Google Inc.\n//\n//  Licensed under the Apache License, Version 2.0 (the \"License\");\n//  you may not use this file except in compliance with the License.\n//  You may obtain a copy of the License at\n//\n//        http://www.apache.org/licenses/LICENSE-2.0\n//\n//  Unless required by applicable law or agreed to in writing, software\n//  distributed under the License is distributed on an \"AS IS\" BASIS,\n//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//  See the License for the specific language governing permissions and\n//  limitations under the License.\n// [START gradle]\nbuildscript {      // Configuration for building\n  repositories {\n    jcenter()      // Bintray's repository - a fast Maven Central mirror & more\n    mavenCentral()\n  }\n  dependencies {\n    classpath 'com.google.cloud.tools:appengine-gradle-plugin:2.5.0'\n    classpath 'org.akhikhl.gretty:gretty:+'\n  }\n}\n\nrepositories {   // repositories for JARs you access in your code\n  maven {\n    url 'https://maven-central.storage.googleapis.com'             // Google's mirror of Maven Central\n  }\n\n//maven {\n//  url 'https://oss.sonatype.org/content/repositories/snapshots' // SNAPSHOT repository if needed\n//}\n\n  jcenter()\n  mavenCentral()\n}\n\napply plugin: 'java'\napply plugin: 'war'\napply plugin: 'org.akhikhl.gretty'\napply plugin: 'com.google.cloud.tools.appengine'\n\ndependencies {\n  providedCompile 'javax.servlet:javax.servlet-api:4.0.1'\n  providedCompile 'com.google.appengine:appengine:+'\n// Add your dependencies here.\n\n}\n\n// [START gretty]\ngretty {\n    httpPort = 8080\n    contextPath = '/'\n    servletContainer = 'jetty9'  // What App Engine Flexible uses\n}\n// [END gretty]\n\n// [START model]\nappengine {\n\n  deploy {   // deploy configuration\n    stopPreviousVersion = true  // default - stop the current version\n    promote = true              // default - & make this the current version\n    projectId = 'GCLOUD_CONFIG' // delegate to project in gcloud config\n    version = 'GCLOUD_CONFIG'   // delegate to gcloud to generate a version\n  }\n}\n// [END model]\n\ngroup = 'com.example.appengine.helloworld-jsp'   // Generated output GroupId\nversion = '1.0-SNAPSHOT'          // Version in generated output\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n// [END gradle]\n"
  },
  {
    "path": "helloworld-jsp/eclipse-launch-profiles/AppEngineDeploy.launch",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<launchConfiguration type=\"org.eclipse.m2e.Maven2LaunchConfigurationType\">\n<booleanAttribute key=\"M2_DEBUG_OUTPUT\" value=\"false\"/>\n<stringAttribute key=\"M2_GOALS\" value=\"appengine:deploy\"/>\n<booleanAttribute key=\"M2_NON_RECURSIVE\" value=\"false\"/>\n<booleanAttribute key=\"M2_OFFLINE\" value=\"false\"/>\n<stringAttribute key=\"M2_PROFILES\" value=\"\"/>\n<listAttribute key=\"M2_PROPERTIES\"/>\n<stringAttribute key=\"M2_RUNTIME\" value=\"EMBEDDED\"/>\n<booleanAttribute key=\"M2_SKIP_TESTS\" value=\"false\"/>\n<intAttribute key=\"M2_THREADS\" value=\"1\"/>\n<booleanAttribute key=\"M2_UPDATE_SNAPSHOTS\" value=\"false\"/>\n<stringAttribute key=\"M2_USER_SETTINGS\" value=\"\"/>\n<booleanAttribute key=\"M2_WORKSPACE_RESOLUTION\" value=\"false\"/>\n<booleanAttribute key=\"org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD\" value=\"true\"/>\n<stringAttribute key=\"org.eclipse.jdt.launching.WORKING_DIRECTORY\" value=\"${project_loc}\"/>\n</launchConfiguration>\n"
  },
  {
    "path": "helloworld-jsp/eclipse-launch-profiles/AppEngineRun.launch",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<launchConfiguration type=\"org.eclipse.m2e.Maven2LaunchConfigurationType\">\n<booleanAttribute key=\"M2_DEBUG_OUTPUT\" value=\"false\"/>\n<stringAttribute key=\"M2_GOALS\" value=\"jetty:run-exploded\"/>\n<booleanAttribute key=\"M2_NON_RECURSIVE\" value=\"false\"/>\n<booleanAttribute key=\"M2_OFFLINE\" value=\"false\"/>\n<stringAttribute key=\"M2_PROFILES\" value=\"\"/>\n<listAttribute key=\"M2_PROPERTIES\"/>\n<stringAttribute key=\"M2_RUNTIME\" value=\"EMBEDDED\"/>\n<booleanAttribute key=\"M2_SKIP_TESTS\" value=\"false\"/>\n<intAttribute key=\"M2_THREADS\" value=\"1\"/>\n<booleanAttribute key=\"M2_UPDATE_SNAPSHOTS\" value=\"false\"/>\n<stringAttribute key=\"M2_USER_SETTINGS\" value=\"\"/>\n<booleanAttribute key=\"M2_WORKSPACE_RESOLUTION\" value=\"false\"/>\n<booleanAttribute key=\"org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD\" value=\"true\"/>\n<stringAttribute key=\"org.eclipse.jdt.launching.WORKING_DIRECTORY\" value=\"${project_loc}\"/>\n</launchConfiguration>\n"
  },
  {
    "path": "helloworld-jsp/gradle/wrapper/gradle-wrapper.properties",
    "content": "#Wed Sep 07 11:48:19 PDT 2016\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.0-bin.zip\n"
  },
  {
    "path": "helloworld-jsp/gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [[ \"$(uname)\" == \"Darwin\" ]] && [[ \"$HOME\" == \"$PWD\" ]]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "helloworld-jsp/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windows variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "helloworld-jsp/nbactions.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n~ Copyright (c) 2013 Google Inc.\n~\n~ Licensed under the Apache License, Version 2.0 (the \"License\"); you\n~ may not use this file except in compliance with the License. You may\n~ obtain a copy of the License at\n~\n~     http://www.apache.org/licenses/LICENSE-2.0\n~\n~ Unless required by applicable law or agreed to in writing, software\n~ distributed under the License is distributed on an \"AS IS\" BASIS,\n~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n~ implied. See the License for the specific language governing\n~ permissions and limitations under the License.\n-->\n\n<actions>\n  <action>\n    <actionName>CUSTOM-appengine:gcloud</actionName>\n    <displayName>appengine:run</displayName>\n    <goals>\n      <goal>jetty:run-exploded</goal>\n    </goals>\n  </action>\n  <action>\n    <actionName>CUSTOM-appengine:gcloud</actionName>\n    <displayName>appengine:deploy</displayName>\n    <goals>\n      <goal>appengine:deploy</goal>\n    </goals>\n  </action>\n</actions>\n"
  },
  {
    "path": "helloworld-jsp/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n~ Copyright (c) 2013 Google Inc.\n~\n~ Licensed under the Apache License, Version 2.0 (the \"License\"); you\n~ may not use this file except in compliance with the License. You may\n~ obtain a copy of the License at\n~\n~     http://www.apache.org/licenses/LICENSE-2.0\n~\n~ Unless required by applicable law or agreed to in writing, software\n~ distributed under the License is distributed on an \"AS IS\" BASIS,\n~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n~ implied. See the License for the specific language governing\n~ permissions and limitations under the License.\n-->\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\n  <modelVersion>4.0.0</modelVersion>\n  <packaging>war</packaging>\n  <version>1.0-SNAPSHOT</version>\n\n  <groupId>com.google.flex.demos</groupId>\n  <artifactId>helloworld-jsp</artifactId>\n\n  <properties>\n    <maven.compiler.source>1.8</maven.compiler.source> <!-- REQUIRED -->\n    <maven.compiler.target>1.8</maven.compiler.target> <!-- REQUIRED -->\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n\n    <failOnMissingWebXml>false</failOnMissingWebXml> <!-- REQUIRED -->\n    <appengine.maven.plugin>2.3.0</appengine.maven.plugin>\n    <jetty.maven.plugin>9.4.51.v20230217</jetty.maven.plugin>\n  </properties>\n\n  <!-- Parent POM defines common plugins and properties. -->\n  <parent>\n    <groupId>com.google.cloud.samples</groupId>\n    <artifactId>shared-configuration</artifactId>\n    <version>1.2.0</version>\n  </parent>\n\n  <dependencies>\n    <dependency>\n      <groupId>javax.servlet</groupId>\n      <artifactId>javax.servlet-api</artifactId>\n      <version>4.0.1</version>\n      <type>jar</type>\n      <scope>provided</scope>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <!-- For hot reload of the web application when using an IDE Eclipse / IDEA -->\n    <outputDirectory>target/${project.artifactId}-${project.version}/WEB-INF/classes</outputDirectory>\n    <plugins>\n\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>appengine-maven-plugin</artifactId>\n        <version>2.4.4</version>\n        <configuration>\n          <!-- can be set w/ -DprojectId=myProjectId on command line -->\n          <projectId>GCLOUD_CONFIG</projectId>\n          <!-- set the GAE version or use \"GCLOUD_CONFIG\" for an autogenerated GAE version -->\n          <version>GCLOUD_CONFIG</version>\n          <!-- dev appserver configuration (standard environment only) -->\n          <!--\n          <devserver.host>127.0.0.1</devserver.port>\n          <devserver.port>8080</devserver.port>\n          -->\n          <!-- deploy configuration -->\n<!--  Defaults\n          <deploy.promote>true</deploy.promote>\n          <deploy.stopPreviousVersion>true</deploy.stopPreviousVersion>\n -->\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>org.eclipse.jetty</groupId>\n        <artifactId>jetty-maven-plugin</artifactId>\n        <version>${jetty.maven.plugin}</version>\n      </plugin>\n    </plugins>\n  </build>\n\n</project>\n"
  },
  {
    "path": "helloworld-jsp/src/main/appengine/app.yaml",
    "content": "# [START_EXCLUDE]\n#Copyright 2015 Google Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#       http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# [END_EXCLUDE]\nruntime: java\nenv: flex\n\nruntime_config:  # Optional\n  jdk: openjdk8\n  server: jetty9\n\nhandlers:\n- url: /.*\n  script: this field is required, but ignored\n\nmanual_scaling:\n  instances: 1\n\nbeta_settings:\n  java_quickstart: true\n"
  },
  {
    "path": "helloworld-jsp/src/main/java/org/example/appengine/hello/HelloInfo.java",
    "content": "/* Copyright 2016 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *       http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.example.appengine.hello;\n\n// [START example]\n/**\n * Generate some simple information.\n */\npublic class HelloInfo {\n\n  public static String getInfo() {\n    return \"Version: \" + System.getProperty(\"java.version\")\n          + \" OS: \" + System.getProperty(\"os.name\")\n          + \" User: \" + System.getProperty(\"user.name\");\n  }\n}\n// [END example]\n"
  },
  {
    "path": "helloworld-jsp/src/main/webapp/WEB-INF/logging.properties",
    "content": "# A default java.util.logging configuration.\n# (All App Engine logging is through java.util.logging by default).\n#\n# To use this configuration, copy it into your application's WEB-INF\n# folder and add the following to your appengine-web.xml:\n# \n# <system-properties>\n#   <property name=\"java.util.logging.config.file\" value=\"WEB-INF/logging.properties\"/>\n# </system-properties>\n#\n\n# Set the default logging level for all loggers to WARNING\n.level = WARNING\n"
  },
  {
    "path": "helloworld-jsp/src/main/webapp/WEB-INF/web.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- [START_EXCLUDE] -->\n<!-- \n  ~ Copyright (c) 2016 Google Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\"); you\n  ~ may not use this file except in compliance with the License. You may\n  ~ obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n  ~ implied. See the License for the specific language governing\n  ~ permissions and limitations under the License.\n \n --><!-- [END_EXCLUDE] -->\n\n<web-app xmlns=\"http://java.sun.com/xml/ns/javaee\" id=\"WebApp_ID\" version=\"3.0\">\n  \n  <welcome-file-list>\n    <welcome-file>hello.jsp</welcome-file>\n  </welcome-file-list>\n</web-app>\n"
  },
  {
    "path": "helloworld-jsp/src/main/webapp/hello.jsp",
    "content": "<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %>\n<%@ page import=\"org.example.appengine.hello.HelloInfo\" %>\n<!-- [START_EXCLUDE] -->\n<%--\n  ~ Copyright (c) 2016 Google Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\"); you\n  ~ may not use this file except in compliance with the License. You may\n  ~ obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n  ~ implied. See the License for the specific language governing\n  ~ permissions and limitations under the License.\n  --%>\n<!-- [END_EXCLUDE] -->\n<!DOCTYPE html>\n<html>\n<head>\n  <link href='//fonts.googleapis.com/css?family=Marmelad' rel='stylesheet' type='text/css'>\n</head>\n<body>\n  <h2>Hello!</h2>\n\n  <p>This is <%= HelloInfo.getInfo() %>.</p>\n</body>\n</html>\n"
  },
  {
    "path": "mvnw.cmd",
    "content": "@REM ----------------------------------------------------------------------------\r\n@REM Licensed to the Apache Software Foundation (ASF) under one\r\n@REM or more contributor license agreements.  See the NOTICE file\r\n@REM distributed with this work for additional information\r\n@REM regarding copyright ownership.  The ASF licenses this file\r\n@REM to you under the Apache License, Version 2.0 (the\r\n@REM \"License\"); you may not use this file except in compliance\r\n@REM with the License.  You may obtain a copy of the License at\r\n@REM\r\n@REM    http://www.apache.org/licenses/LICENSE-2.0\r\n@REM\r\n@REM Unless required by applicable law or agreed to in writing,\r\n@REM software distributed under the License is distributed on an\r\n@REM \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\r\n@REM KIND, either express or implied.  See the License for the\r\n@REM specific language governing permissions and limitations\r\n@REM under the License.\r\n@REM ----------------------------------------------------------------------------\r\n\r\n@REM ----------------------------------------------------------------------------\r\n@REM Maven2 Start Up Batch script\r\n@REM\r\n@REM Required ENV vars:\r\n@REM JAVA_HOME - location of a JDK home dir\r\n@REM\r\n@REM Optional ENV vars\r\n@REM M2_HOME - location of maven2's installed home dir\r\n@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands\r\n@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending\r\n@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven\r\n@REM     e.g. to debug Maven itself, use\r\n@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\r\n@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files\r\n@REM ----------------------------------------------------------------------------\r\n\r\n@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'\r\n@echo off\r\n@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'\r\n@if \"%MAVEN_BATCH_ECHO%\" == \"on\"  echo %MAVEN_BATCH_ECHO%\r\n\r\n@REM set %HOME% to equivalent of $HOME\r\nif \"%HOME%\" == \"\" (set \"HOME=%HOMEDRIVE%%HOMEPATH%\")\r\n\r\n@REM Execute a user defined script before this one\r\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPre\r\n@REM check for pre script, once with legacy .bat ending and once with .cmd ending\r\nif exist \"%HOME%\\mavenrc_pre.bat\" call \"%HOME%\\mavenrc_pre.bat\"\r\nif exist \"%HOME%\\mavenrc_pre.cmd\" call \"%HOME%\\mavenrc_pre.cmd\"\r\n:skipRcPre\r\n\r\n@setlocal\r\n\r\nset ERROR_CODE=0\r\n\r\n@REM To isolate internal variables from possible post scripts, we use another setlocal\r\n@setlocal\r\n\r\n@REM ==== START VALIDATION ====\r\nif not \"%JAVA_HOME%\" == \"\" goto OkJHome\r\n\r\necho.\r\necho Error: JAVA_HOME not found in your environment. >&2\r\necho Please set the JAVA_HOME variable in your environment to match the >&2\r\necho location of your Java installation. >&2\r\necho.\r\ngoto error\r\n\r\n:OkJHome\r\nif exist \"%JAVA_HOME%\\bin\\java.exe\" goto init\r\n\r\necho.\r\necho Error: JAVA_HOME is set to an invalid directory. >&2\r\necho JAVA_HOME = \"%JAVA_HOME%\" >&2\r\necho Please set the JAVA_HOME variable in your environment to match the >&2\r\necho location of your Java installation. >&2\r\necho.\r\ngoto error\r\n\r\n@REM ==== END VALIDATION ====\r\n\r\n:init\r\n\r\nset MAVEN_CMD_LINE_ARGS=%MAVEN_CONFIG% %*\r\n\r\n@REM Find the project base dir, i.e. the directory that contains the folder \".mvn\".\r\n@REM Fallback to current working directory if not found.\r\n\r\nset MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%\r\nIF NOT \"%MAVEN_PROJECTBASEDIR%\"==\"\" goto endDetectBaseDir\r\n\r\nset EXEC_DIR=%CD%\r\nset WDIR=%EXEC_DIR%\r\n:findBaseDir\r\nIF EXIST \"%WDIR%\"\\.mvn goto baseDirFound\r\ncd ..\r\nIF \"%WDIR%\"==\"%CD%\" goto baseDirNotFound\r\nset WDIR=%CD%\r\ngoto findBaseDir\r\n\r\n:baseDirFound\r\nset MAVEN_PROJECTBASEDIR=%WDIR%\r\ncd \"%EXEC_DIR%\"\r\ngoto endDetectBaseDir\r\n\r\n:baseDirNotFound\r\nset MAVEN_PROJECTBASEDIR=%EXEC_DIR%\r\ncd \"%EXEC_DIR%\"\r\n\r\n:endDetectBaseDir\r\n\r\nIF NOT EXIST \"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\" goto endReadAdditionalConfig\r\n\r\n@setlocal EnableExtensions EnableDelayedExpansion\r\nfor /F \"usebackq delims=\" %%a in (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a\r\n@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%\r\n\r\n:endReadAdditionalConfig\r\n\r\nSET MAVEN_JAVA_EXE=\"%JAVA_HOME%\\bin\\java.exe\"\r\n\nset WRAPPER_JAR=\"\"%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.jar\"\"\nset WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\r\n\r\n# avoid using MAVEN_CMD_LINE_ARGS below since that would loose parameter escaping in %*\r\n%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% \"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%\" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*\r\nif ERRORLEVEL 1 goto error\r\ngoto end\r\n\r\n:error\r\nset ERROR_CODE=1\r\n\r\n:end\r\n@endlocal & set ERROR_CODE=%ERROR_CODE%\r\n\r\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPost\r\n@REM check for post script, once with legacy .bat ending and once with .cmd ending\r\nif exist \"%HOME%\\mavenrc_post.bat\" call \"%HOME%\\mavenrc_post.bat\"\r\nif exist \"%HOME%\\mavenrc_post.cmd\" call \"%HOME%\\mavenrc_post.cmd\"\r\n:skipRcPost\r\n\r\n@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'\r\nif \"%MAVEN_BATCH_PAUSE%\" == \"on\" pause\r\n\r\nif \"%MAVEN_TERMINATE_CMD%\" == \"on\" exit %ERROR_CODE%\r\n\r\nexit /B %ERROR_CODE%\r\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"extends\": [\n    \"config:base\"\n  ],\n  \"packageRules\": [\n    {\n      \"packagePatterns\": [ \"^com.google.appengine:\" ],\n      \"groupName\": \"AppEngine packages\"\n    },\n    {\n      \"packagePatterns\": [ \"^io.grpc:grpc-\" ],\n      \"groupName\": \"gRPC packages\"\n    },\n    {\n      \"packagePatterns\": [ \"^io.vertx:vertex-\" ],\n      \"groupName\": \"Vertx packages\"\n    },\n    {\n      \"packagePatterns\": [ \"^org.jetbrains.kotlin:\" ],\n      \"groupName\": \"Kotlin packages\"\n    },\n    {\n      \"packagePatterns\": [ \"^org.springframework.boot:\" ],\n      \"groupName\": \"Spring packages\"\n    },\n    {\n      \"packagePatterns\": [ \"^io.opencensus:\" ],\n      \"groupName\": \"Opencensus packages\"\n    }\n  ],\n  \"labels\": [\n    \"automerge\"\n  ],\n  \"prConcurrentLimit\": 0,\n  \"rebaseWhen\": \"never\",\n   \"dependencyDashboard\": true,\n   \"dependencyDashboardLabels\": [\n    \"type: process\"\n  ],\n  \"constraints\": {\n    \"java\": \"1.8\"\n  }\n}\n"
  }
]