diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1253c5d4574..0e99c9f3b32 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -74,6 +74,7 @@ /dialogflow-cx @GoogleCloudPlatform/java-samples-reviewers @yoshi-approver @GoogleCloudPlatform/cloud-samples-reviewers /discoveryengine @GoogleCloudPlatform/java-samples-reviewers @yoshi-approver @GoogleCloudPlatform/cloud-samples-reviewers /document-ai @GoogleCloudPlatform/java-samples-reviewers @yoshi-approver @GoogleCloudPlatform/cloud-samples-reviewers +/genai @GoogleCloudPlatform/java-samples-reviewers @yoshi-approver @GoogleCloudPlatform/cloud-samples-reviewers /jobs @GoogleCloudPlatform/java-samples-reviewers @yoshi-approver @GoogleCloudPlatform/cloud-samples-reviewers /language @GoogleCloudPlatform/java-samples-reviewers @yoshi-approver @GoogleCloudPlatform/cloud-samples-reviewers /mediatranslation @GoogleCloudPlatform/java-samples-reviewers @yoshi-approver @GoogleCloudPlatform/cloud-samples-reviewers diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 4ee98eb7f77..cbc450cd79a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,18 +2,23 @@ Fixes # -Note: Before submitting a pull request, please open an issue for discussion if you are not associated with Google. - ## Checklist -- [ ] I have followed [Sample Format Guide](https://github.com/GoogleCloudPlatform/java-docs-samples/blob/main/SAMPLE_FORMAT.md) -- [ ] `pom.xml` parent set to latest `shared-configuration` -- [ ] Appropriate changes to README are included in PR -- [ ] These samples need a new **API enabled** in testing projects to pass (let us know which ones) -- [ ] These samples need a new/updated **env vars** in testing projects set to pass (let us know which ones) +### Testing + +- [ ] **I have tested this change on a live environment and verified it works as intended.** - [ ] **Tests** pass: `mvn clean verify` **required** - [ ] **Lint** passes: `mvn -P lint checkstyle:check` **required** - [ ] **Static Analysis**: `mvn -P lint clean compile pmd:cpd-check spotbugs:check` **advisory only** + +### Compliance & Style + +- [ ] I have followed [Sample Format Guide](https://github.com/GoogleCloudPlatform/java-docs-samples/blob/main/SAMPLE_FORMAT.md) +- [ ] `pom.xml` parent set to latest `shared-configuration` +- [ ] Appropriate changes to README are included in PR - [ ] This sample adds a new sample directory, and I updated the [CODEOWNERS file](https://github.com/GoogleCloudPlatform/java-docs-samples/blob/main/.github/CODEOWNERS) with the codeowners for this sample -- [ ] This sample adds a new **Product API**, and I updated the [Blunderbuss issue/PR auto-assigner](https://github.com/GoogleCloudPlatform/java-docs-samples/blob/main/.github/blunderbuss.yml) with the codeowners for this sample +- [ ] This sample adds a new **Product API**, and I updated the [Blunderbuss issue/PR auto-assigner](https://github.com/GoogleCloudPlatform/java-docs-samples/blob/main/.github/blunderbuss.yml) with the codeowners for this sample + +### Post-Approval Actions + - [ ] Please **merge** this PR for me once it is approved diff --git a/.github/header-checker-lint.yml b/.github/header-checker-lint.yml index c102d85403e..3e996590747 100644 --- a/.github/header-checker-lint.yml +++ b/.github/header-checker-lint.yml @@ -38,5 +38,9 @@ sourceFileExtensions: - 'yml' ignoreFiles: - '.github/auto-label.yaml' + - '.github/auto-approve.yml' + - '.github/renovate.json5' + - '.github/snippet-bot.yml' + - '.github/stale.yml' - '.github/sync-repo-settings.yaml' ignoreLicenseYear: true diff --git a/.github/stale.yml b/.github/stale.yml index 53abb2646b2..98dfb508bf1 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -11,8 +11,6 @@ exemptLabels: - type: process - type: feature request - type: docs - - flakybot: issue - - flakybot: flaky - :rotating_light: # Label to use when marking an issue as stale diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml index ee480eb997c..c6e15f9c4f8 100644 --- a/.github/sync-repo-settings.yaml +++ b/.github/sync-repo-settings.yaml @@ -7,8 +7,6 @@ branchProtectionRules: requiredStatusCheckContexts: - 'cla/google' - 'header-check' - - 'Kokoro CI - Java 11' - - 'Kokoro CI - Java 17' - 'Kokoro CI - Lint' requiredApprovingReviewCount: 1 requiresCodeOwnerReviews: true @@ -16,9 +14,5 @@ branchProtectionRules: permissionRules: - team: java-samples-reviewers permission: push - - team: yoshi-java - permission: push - - team: yoshi-approver-team - permission: push - team: devrel-java-admin permission: admin diff --git a/.kokoro/tests/run_tests.sh b/.kokoro/tests/run_tests.sh index 07143aaccf6..bd3433b1220 100755 --- a/.kokoro/tests/run_tests.sh +++ b/.kokoro/tests/run_tests.sh @@ -45,7 +45,7 @@ fi if [[ "$SCRIPT_DEBUG" != "true" ]]; then # Update `gcloud` and log versioning for debugging apt update && apt -y upgrade google-cloud-sdk - + echo "********** GIT INFO ***********" git version echo "********** GCLOUD INFO ***********" @@ -67,7 +67,10 @@ if [[ "$SCRIPT_DEBUG" != "true" ]]; then # For Cloud Run filesystem sample export FILESTORE_IP_ADDRESS=$(gcloud secrets versions access latest --secret fs-app) export MNT_DIR=$PWD/run/filesystem - + # For Model Armor tests + export MA_FOLDER_ID=695279264361 + export MA_ORG_ID=951890214235 + SECRET_FILES=("java-docs-samples-service-account.json" \ "java-aiplatform-samples-secrets.txt" \ "java-automl-samples-secrets.txt" \ @@ -88,7 +91,7 @@ if [[ "$SCRIPT_DEBUG" != "true" ]]; then # create secret dir mkdir -p "${KOKORO_GFILE_DIR}/secrets" - + for SECRET in "${SECRET_FILES[@]}"; do # grab latest version of secret gcloud secrets versions access latest --secret="${SECRET%.*}" > "${KOKORO_GFILE_DIR}/secrets/$SECRET" @@ -166,7 +169,7 @@ test_prog="$PWD/.kokoro/tests/run_test_java.sh" git config --global --add safe.directory $PWD -# Use btlr to run all the tests in each folder +# Use btlr to run all the tests in each folder echo "btlr" "${btlr_args[@]}" -- "${test_prog}" btlr "${btlr_args[@]}" -- "${test_prog}" diff --git a/README.md b/README.md index 00992f88088..d1549b01053 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,5 @@ # Google Cloud Platform Java Samples -[![Build Status][java-11-badge]][java-11-link] [![Build -Status][java-17-badge]][java-17-link] [![Build Status][java-21-badge]][java-21-link] - Open in Cloud Shell @@ -80,17 +77,5 @@ Apache header: [ADC]: https://developers.google.com/identity/protocols/application-default-credentials [auth_command]: https://cloud.google.com/sdk/gcloud/reference/beta/auth/application-default/login -[java-11-badge]: -https://storage.googleapis.com/cloud-devrel-kokoro-resources/java/badges/java-docs-samples-11.svg -[java-11-link]: -https://storage.googleapis.com/cloud-devrel-kokoro-resources/java/badges/java-docs-samples-11.html -[java-17-badge]: -https://storage.googleapis.com/cloud-devrel-kokoro-resources/java/badges/java-docs-samples-17.svg -[java-17-link]: -https://storage.googleapis.com/cloud-devrel-kokoro-resources/java/badges/java-docs-samples-17.html -[java-21-badge]: -https://storage.googleapis.com/cloud-devrel-kokoro-resources/java/badges/java-docs-samples-21.svg -[java-21-link]: -https://storage.googleapis.com/cloud-devrel-kokoro-resources/java/badges/java-docs-samples-21.html Java is a registered trademark of Oracle and/or its affiliates. diff --git a/aiplatform/src/main/java/aiplatform/batchpredict/CreateBatchPredictionGeminiBigqueryJobSample.java b/aiplatform/src/main/java/aiplatform/batchpredict/CreateBatchPredictionGeminiBigqueryJobSample.java index 886ce9e1481..eb988eefa1c 100644 --- a/aiplatform/src/main/java/aiplatform/batchpredict/CreateBatchPredictionGeminiBigqueryJobSample.java +++ b/aiplatform/src/main/java/aiplatform/batchpredict/CreateBatchPredictionGeminiBigqueryJobSample.java @@ -66,7 +66,7 @@ public static BatchPredictionJob createBatchPredictionGeminiBigqueryJobSample( String modelName = String.format( "projects/%s/locations/%s/publishers/google/models/%s", - project, location, "gemini-2.0-flash-001"); + project, location, "gemini-2.5-flash"); BatchPredictionJob batchPredictionJob = BatchPredictionJob.newBuilder() diff --git a/aiplatform/src/main/java/aiplatform/batchpredict/CreateBatchPredictionGeminiJobSample.java b/aiplatform/src/main/java/aiplatform/batchpredict/CreateBatchPredictionGeminiJobSample.java index 2d081403698..1ad1f09f003 100644 --- a/aiplatform/src/main/java/aiplatform/batchpredict/CreateBatchPredictionGeminiJobSample.java +++ b/aiplatform/src/main/java/aiplatform/batchpredict/CreateBatchPredictionGeminiJobSample.java @@ -72,7 +72,7 @@ public static BatchPredictionJob createBatchPredictionGeminiJobSample( String modelName = String.format( "projects/%s/locations/%s/publishers/google/models/%s", - project, location, "gemini-2.0-flash-001"); + project, location, "gemini-2.5-flash"); BatchPredictionJob batchPredictionJob = BatchPredictionJob.newBuilder() diff --git a/appengine-java11-bundled-services/datastore/pom.xml b/appengine-java11-bundled-services/datastore/pom.xml index 10435652aa7..d80007dc410 100644 --- a/appengine-java11-bundled-services/datastore/pom.xml +++ b/appengine-java11-bundled-services/datastore/pom.xml @@ -126,6 +126,11 @@ 1.4.0 test + + jstl + jstl + 1.2 + diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbook.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbook.java deleted file mode 120000 index 59b8bfc0057..00000000000 --- a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbook.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/appengine/AbstractGuestbook.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbook.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbook.java new file mode 100644 index 00000000000..f9c2aa4043d --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbook.java @@ -0,0 +1,74 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.example.time.Clock; +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.users.User; +import com.google.appengine.api.users.UserService; +import com.google.appengine.api.users.UserServiceFactory; +import com.google.common.collect.ImmutableList; +import java.util.Date; +import java.util.List; + +/** + * A log of notes left by users. + * + *

This is meant to be subclassed to demonstrate different storage structures in Datastore. + */ +abstract class AbstractGuestbook { + + private final DatastoreService datastore; + private final UserService userService; + private final Clock clock; + + AbstractGuestbook(Clock clock) { + this.datastore = DatastoreServiceFactory.getDatastoreService(); + this.userService = UserServiceFactory.getUserService(); + this.clock = clock; + } + + /** + * Appends a new greeting to the guestbook and returns the {@link Entity} that was created. + **/ + public Greeting appendGreeting(String content) { + return Greeting.create( + createGreeting(datastore, userService.getCurrentUser(), Date.from(clock.now()), content)); + } + + /** + * Write a greeting to Datastore. + */ + protected abstract Entity createGreeting( + DatastoreService datastore, User user, Date date, String content); + + /** + * Return a list of the most recent greetings. + */ + public List listGreetings() { + return listGreetingEntities(datastore).stream() + .map(Greeting::create) + .collect(ImmutableList.toImmutableList()); + } + + /** + * Return a list of the most recent greetings. + */ + protected abstract List listGreetingEntities(DatastoreService datastore); +} diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbookServlet.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbookServlet.java deleted file mode 120000 index a91e7e09658..00000000000 --- a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbookServlet.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/appengine/AbstractGuestbookServlet.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbookServlet.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbookServlet.java new file mode 100644 index 00000000000..d9a0d518b92 --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbookServlet.java @@ -0,0 +1,58 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +abstract class AbstractGuestbookServlet extends HttpServlet { + + private final AbstractGuestbook guestbook; + + public AbstractGuestbookServlet(AbstractGuestbook guestbook) { + this.guestbook = guestbook; + } + + private void renderGuestbook(HttpServletRequest req, HttpServletResponse resp) + throws IOException, ServletException { + resp.setContentType("text/html"); + resp.setCharacterEncoding("UTF-8"); + req.setAttribute("greetings", guestbook.listGreetings()); + req.getRequestDispatcher("/guestbook.jsp").forward(req, resp); + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws IOException, ServletException { + renderGuestbook(req, resp); + } + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) + throws IOException, ServletException { + String content = req.getParameter("content"); + if (content == null || content.isEmpty()) { + resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "missing content"); + return; + } + guestbook.appendGreeting(content); + renderGuestbook(req, resp); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/Greeting.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/Greeting.java deleted file mode 120000 index 9c59e2047b8..00000000000 --- a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/Greeting.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/appengine/Greeting.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/Greeting.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/Greeting.java new file mode 100644 index 00000000000..ff45e508205 --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/Greeting.java @@ -0,0 +1,42 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.users.User; +import com.google.auto.value.AutoValue; +import java.time.Instant; +import java.util.Date; +import javax.annotation.Nullable; + +@AutoValue +public abstract class Greeting { + + static Greeting create(Entity entity) { + User user = (User) entity.getProperty("user"); + Instant date = ((Date) entity.getProperty("date")).toInstant(); + String content = (String) entity.getProperty("content"); + return new AutoValue_Greeting(user, date, content); + } + + @Nullable + public abstract User getUser(); + + public abstract Instant getDate(); + + public abstract String getContent(); +} diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/Guestbook.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/Guestbook.java deleted file mode 120000 index 2e3d8e4b664..00000000000 --- a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/Guestbook.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/appengine/Guestbook.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/Guestbook.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/Guestbook.java new file mode 100644 index 00000000000..ebacbf540ae --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/Guestbook.java @@ -0,0 +1,59 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.example.time.Clock; +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.users.User; +import java.util.Date; +import java.util.List; + +/** + * A log of notes left by users. + * + *

This demonstrates the use of Google Cloud Datastore using the App Engine APIs. See the documentation for more + * information. + */ +class Guestbook extends AbstractGuestbook { + + Guestbook(Clock clock) { + super(clock); + } + + @Override + protected Entity createGreeting( + DatastoreService datastore, User user, Date date, String content) { + // No parent key specified, so Greeting is a root entity. + Entity greeting = new Entity("Greeting"); + greeting.setProperty("user", user); + greeting.setProperty("date", date); + greeting.setProperty("content", content); + + datastore.put(greeting); + return greeting; + } + + @Override + protected List listGreetingEntities(DatastoreService datastore) { + Query query = new Query("Greeting").addSort("date", Query.SortDirection.DESCENDING); + return datastore.prepare(query).asList(FetchOptions.Builder.withLimit(10)); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookServlet.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookServlet.java deleted file mode 120000 index 752427a8057..00000000000 --- a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookServlet.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/appengine/GuestbookServlet.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookServlet.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookServlet.java new file mode 100644 index 00000000000..019e5a4210c --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookServlet.java @@ -0,0 +1,26 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.example.time.SystemClock; + +public class GuestbookServlet extends AbstractGuestbookServlet { + + public GuestbookServlet() { + super(new Guestbook(new SystemClock())); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrong.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrong.java deleted file mode 120000 index fef89e36ba8..00000000000 --- a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrong.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/appengine/GuestbookStrong.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrong.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrong.java new file mode 100644 index 00000000000..a3c1bc08743 --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrong.java @@ -0,0 +1,71 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.example.time.Clock; +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.KeyFactory; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.users.User; +import java.util.Date; +import java.util.List; + +/** + * A log of notes left by users. + * + *

This demonstrates the use of Google Cloud Datastore using the App Engine APIs. See the documentation for more + * information. + */ +class GuestbookStrong extends AbstractGuestbook { + + private final String guestbookName; + + GuestbookStrong(String guestbookName, Clock clock) { + super(clock); + this.guestbookName = guestbookName; + } + + @Override + protected Entity createGreeting( + DatastoreService datastore, User user, Date date, String content) { + // String guestbookName = "my guestbook"; -- Set elsewhere (injected to the constructor). + Key guestbookKey = KeyFactory.createKey("Guestbook", guestbookName); + + // Place greeting in the same entity group as guestbook. + Entity greeting = new Entity("Greeting", guestbookKey); + greeting.setProperty("user", user); + greeting.setProperty("date", date); + greeting.setProperty("content", content); + + datastore.put(greeting); + return greeting; + } + + @Override + protected List listGreetingEntities(DatastoreService datastore) { + Key guestbookKey = KeyFactory.createKey("Guestbook", guestbookName); + Query query = + new Query("Greeting", guestbookKey) + .setAncestor(guestbookKey) + .addSort("date", Query.SortDirection.DESCENDING); + return datastore.prepare(query).asList(FetchOptions.Builder.withLimit(10)); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrongServlet.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrongServlet.java deleted file mode 120000 index f2d53752473..00000000000 --- a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrongServlet.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/appengine/GuestbookStrongServlet.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrongServlet.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrongServlet.java new file mode 100644 index 00000000000..2861c88d991 --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrongServlet.java @@ -0,0 +1,28 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.example.time.SystemClock; + +public class GuestbookStrongServlet extends AbstractGuestbookServlet { + + public static final String GUESTBOOK_ID = "my guestbook"; + + public GuestbookStrongServlet() { + super(new GuestbookStrong(GUESTBOOK_ID, new SystemClock())); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/ListPeopleServlet.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/ListPeopleServlet.java deleted file mode 120000 index fc42fbc3dcd..00000000000 --- a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/ListPeopleServlet.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/appengine/ListPeopleServlet.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/ListPeopleServlet.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/ListPeopleServlet.java new file mode 100644 index 00000000000..96725a70566 --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/ListPeopleServlet.java @@ -0,0 +1,90 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.google.appengine.api.datastore.Cursor; +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.PreparedQuery; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.SortDirection; +import com.google.appengine.api.datastore.QueryResultList; +import com.google.common.html.HtmlEscapers; +import java.io.IOException; +import java.io.PrintWriter; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class ListPeopleServlet extends HttpServlet { + + static final int PAGE_SIZE = 15; + private final DatastoreService datastore; + + public ListPeopleServlet() { + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + FetchOptions fetchOptions = FetchOptions.Builder.withLimit(PAGE_SIZE); + + // If this servlet is passed a cursor parameter, let's use it. + String startCursor = req.getParameter("cursor"); + if (startCursor != null) { + fetchOptions.startCursor(Cursor.fromWebSafeString(startCursor)); + } + + Query q = new Query("Person").addSort("name", SortDirection.ASCENDING); + PreparedQuery pq = datastore.prepare(q); + + QueryResultList results; + try { + results = pq.asQueryResultList(fetchOptions); + } catch (IllegalArgumentException e) { + // IllegalArgumentException happens when an invalid cursor is used. + // A user could have manually entered a bad cursor in the URL or there + // may have been an internal implementation detail change in App Engine. + // Redirect to the page without the cursor parameter to show something + // rather than an error. + resp.sendRedirect("/people"); + return; + } + + resp.setContentType("text/html"); + resp.setCharacterEncoding("UTF-8"); + PrintWriter w = resp.getWriter(); + w.println(""); + w.println(""); + w.println("Cloud Datastore Cursor Sample"); + w.println("

"); + + String cursorString = results.getCursor().toWebSafeString(); + + // This servlet lives at '/people'. + w.println("Next page"); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/ProjectionServlet.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/ProjectionServlet.java deleted file mode 120000 index ceb105b6acf..00000000000 --- a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/ProjectionServlet.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/appengine/ProjectionServlet.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/ProjectionServlet.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/ProjectionServlet.java new file mode 100644 index 00000000000..d062f9d6d17 --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/ProjectionServlet.java @@ -0,0 +1,78 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.KeyFactory; +import com.google.appengine.api.datastore.PropertyProjection; +import com.google.appengine.api.datastore.Query; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Date; +import java.util.List; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Servlet to demonstrate use of Datastore projection queries. + * + *

See the + * documentation + * for using Datastore projection queries from the Google App Engine standard environment. + */ +@SuppressWarnings("serial") +public class ProjectionServlet extends HttpServlet { + + private static final String GUESTBOOK_ID = GuestbookStrongServlet.GUESTBOOK_ID; + private final DatastoreService datastore; + + public ProjectionServlet() { + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + resp.setContentType("text/plain"); + resp.setCharacterEncoding("UTF-8"); + PrintWriter out = resp.getWriter(); + out.printf("Latest entries from guestbook: \n"); + + Key guestbookKey = KeyFactory.createKey("Guestbook", GUESTBOOK_ID); + Query query = new Query("Greeting", guestbookKey); + addGuestbookProjections(query); + printGuestbookEntries(datastore, query, out); + } + + private void addGuestbookProjections(Query query) { + query.addProjection(new PropertyProjection("content", String.class)); + query.addProjection(new PropertyProjection("date", Date.class)); + } + + private void printGuestbookEntries(DatastoreService datastore, Query query, PrintWriter out) { + List guests = datastore.prepare(query).asList(FetchOptions.Builder.withLimit(5)); + for (Entity guest : guests) { + String content = (String) guest.getProperty("content"); + Date stamp = (Date) guest.getProperty("date"); + out.printf("Message %s posted on %s.\n", content, stamp.toString()); + } + } +} diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/StartupServlet.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/StartupServlet.java deleted file mode 120000 index eae75ba9016..00000000000 --- a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/StartupServlet.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/appengine/StartupServlet.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/StartupServlet.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/StartupServlet.java new file mode 100644 index 00000000000..10877ac5af3 --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/StartupServlet.java @@ -0,0 +1,120 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.EntityNotFoundException; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.KeyFactory; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * A startup handler to populate the datastore with example entities. + */ +public class StartupServlet extends HttpServlet { + + static final String IS_POPULATED_ENTITY = "IsPopulated"; + static final String IS_POPULATED_KEY_NAME = "is-populated"; + + private static final String PERSON_ENTITY = "Person"; + private static final String NAME_PROPERTY = "name"; + private static final ImmutableList US_PRESIDENTS = + ImmutableList.builder() + .add("George Washington") + .add("John Adams") + .add("Thomas Jefferson") + .add("James Madison") + .add("James Monroe") + .add("John Quincy Adams") + .add("Andrew Jackson") + .add("Martin Van Buren") + .add("William Henry Harrison") + .add("John Tyler") + .add("James K. Polk") + .add("Zachary Taylor") + .add("Millard Fillmore") + .add("Franklin Pierce") + .add("James Buchanan") + .add("Abraham Lincoln") + .add("Andrew Johnson") + .add("Ulysses S. Grant") + .add("Rutherford B. Hayes") + .add("James A. Garfield") + .add("Chester A. Arthur") + .add("Grover Cleveland") + .add("Benjamin Harrison") + .add("Grover Cleveland") + .add("William McKinley") + .add("Theodore Roosevelt") + .add("William Howard Taft") + .add("Woodrow Wilson") + .add("Warren G. Harding") + .add("Calvin Coolidge") + .add("Herbert Hoover") + .add("Franklin D. Roosevelt") + .add("Harry S. Truman") + .add("Dwight D. Eisenhower") + .add("John F. Kennedy") + .add("Lyndon B. Johnson") + .add("Richard Nixon") + .add("Gerald Ford") + .add("Jimmy Carter") + .add("Ronald Reagan") + .add("George H. W. Bush") + .add("Bill Clinton") + .add("George W. Bush") + .add("Barack Obama") + .build(); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/plain"); + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + + Key isPopulatedKey = KeyFactory.createKey(IS_POPULATED_ENTITY, IS_POPULATED_KEY_NAME); + boolean isAlreadyPopulated; + try { + datastore.get(isPopulatedKey); + isAlreadyPopulated = true; + } catch (EntityNotFoundException expected) { + isAlreadyPopulated = false; + } + if (isAlreadyPopulated) { + resp.getWriter().println("ok"); + return; + } + + ImmutableList people = US_PRESIDENTS.stream() + .map(name -> { + Entity person = new Entity(PERSON_ENTITY); + person.setProperty(NAME_PROPERTY, name); + return person; + }) + .collect(ImmutableList.toImmutableList()); + datastore.put(people); + datastore.put(new Entity(isPopulatedKey)); + resp.getWriter().println("ok"); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/StatsServlet.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/StatsServlet.java deleted file mode 120000 index 6502647321e..00000000000 --- a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/StatsServlet.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/appengine/StatsServlet.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/StatsServlet.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/StatsServlet.java new file mode 100644 index 00000000000..18cd68e9674 --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/main/java/com/example/appengine/StatsServlet.java @@ -0,0 +1,45 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.Query; +import java.io.IOException; +import java.io.PrintWriter; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class StatsServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + Entity globalStat = datastore.prepare(new Query("__Stat_Total__")).asSingleEntity(); + Long totalBytes = (Long) globalStat.getProperty("bytes"); + Long totalEntities = (Long) globalStat.getProperty("count"); + + resp.setContentType("text/plain"); + resp.setCharacterEncoding("UTF-8"); + PrintWriter w = resp.getWriter(); + w.printf("%d bytes\n%d entities\n", totalBytes, totalEntities); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/time/Clock.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/time/Clock.java deleted file mode 120000 index 2413e2ddc58..00000000000 --- a/appengine-java11-bundled-services/datastore/src/main/java/com/example/time/Clock.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/time/Clock.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/time/Clock.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/time/Clock.java new file mode 100644 index 00000000000..a703577d7f9 --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/main/java/com/example/time/Clock.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.time; + +import java.time.Instant; + +/** + * Provides the current value of "now." To preserve testability, avoid all other libraries that + * access the system clock (whether {@linkplain System#currentTimeMillis directly} or {@linkplain + * java.time.Instant#now() indirectly}). + * + *

In production, use the {@link SystemClock} implementation to return the "real" system time. In + * tests, either use {@link com.example.time.testing.FakeClock}, or get an instance from a mocking + * framework such as Mockito. + */ +public interface Clock { + + /** + * Returns the current, absolute time according to this clock. + */ + Instant now(); +} diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/time/SystemClock.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/time/SystemClock.java deleted file mode 120000 index 4193c4824ac..00000000000 --- a/appengine-java11-bundled-services/datastore/src/main/java/com/example/time/SystemClock.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/time/SystemClock.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/time/SystemClock.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/time/SystemClock.java new file mode 100644 index 00000000000..dcde41330f6 --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/main/java/com/example/time/SystemClock.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.time; + +import java.time.Instant; + +/** + * Clock implementation that returns the "real" system time. + * + *

This class exists so that we can use a fake implementation for unit testing classes that need + * the current time value. See {@link Clock} for general information about clocks. + */ +public class SystemClock implements Clock { + + /** + * Creates a new instance. All {@code SystemClock} instances function identically. + */ + public SystemClock() { + } + + @Override + public Instant now() { + return Instant.now(); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/time/testing/FakeClock.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/time/testing/FakeClock.java deleted file mode 120000 index b963230e3c4..00000000000 --- a/appengine-java11-bundled-services/datastore/src/main/java/com/example/time/testing/FakeClock.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../../appengine-java8/datastore/src/main/java/com/example/time/testing/FakeClock.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/main/java/com/example/time/testing/FakeClock.java b/appengine-java11-bundled-services/datastore/src/main/java/com/example/time/testing/FakeClock.java new file mode 100644 index 00000000000..a08343e51b6 --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/main/java/com/example/time/testing/FakeClock.java @@ -0,0 +1,180 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.time.testing; + +import com.example.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.util.concurrent.atomic.AtomicLong; + +/** + * A Clock that returns a fixed Instant value as the current clock time. The fixed Instant is + * settable for testing. Test code should hold a reference to the FakeClock, while code under test + * should hold a Clock reference. + * + *

The clock time can be incremented/decremented manually, with {@link #incrementTime} and {@link + * #decrementTime} respectively. + * + *

The clock can also be configured so that the time is incremented whenever {@link #now()} is + * called: see {@link #setAutoIncrementStep}. + */ +public class FakeClock implements Clock { + + private static final Instant DEFAULT_TIME = Instant.ofEpochMilli(1000000000L); + private final long baseTimeMs; + private final AtomicLong fakeNowMs; + private volatile long autoIncrementStepMs; + + /** + * Creates a FakeClock instance initialized to an arbitrary constant. + */ + public FakeClock() { + this(DEFAULT_TIME); + } + + /** + * Creates a FakeClock instance initialized to the given time. + */ + public FakeClock(Instant now) { + baseTimeMs = now.toEpochMilli(); + fakeNowMs = new AtomicLong(baseTimeMs); + } + + /** + * Sets the value of the underlying instance for testing purposes. + * + * @return this + */ + public FakeClock setNow(Instant now) { + fakeNowMs.set(now.toEpochMilli()); + return this; + } + + @Override + public Instant now() { + return getAndAdd(autoIncrementStepMs); + } + + /** + * Returns the current time without applying an auto increment, if configured. The default + * behavior of {@link #now()} is the same as this method. + */ + public Instant peek() { + return Instant.ofEpochMilli(fakeNowMs.get()); + } + + /** + * Reset the given clock back to the base time with which the FakeClock was initially + * constructed. + * + * @return this + */ + public FakeClock resetTime() { + fakeNowMs.set(baseTimeMs); + return this; + } + + /** + * Increments the clock time by the given duration. + * + * @param duration the duration to increment the clock time by + * @return this + */ + public FakeClock incrementTime(Duration duration) { + incrementTime(duration.toMillis()); + return this; + } + + /** + * Increments the clock time by the given duration. + * + * @param durationMs the duration to increment the clock time by, in milliseconds + * @return this + */ + public FakeClock incrementTime(long durationMs) { + fakeNowMs.addAndGet(durationMs); + return this; + } + + /** + * Decrements the clock time by the given duration. + * + * @param duration the duration to decrement the clock time by + * @return this + */ + public FakeClock decrementTime(Duration duration) { + incrementTime(-duration.toMillis()); + return this; + } + + /** + * Decrements the clock time by the given duration. + * + * @param durationMs the duration to decrement the clock time by, in milliseconds + * @return this + */ + public FakeClock decrementTime(long durationMs) { + incrementTime(-durationMs); + return this; + } + + /** + * Sets the increment applied to the clock whenever it is queried. The increment is zero by + * default: the clock is left unchanged when queried. + * + * @param autoIncrementStep the new auto increment duration + * @return this + */ + public FakeClock setAutoIncrementStep(Duration autoIncrementStep) { + setAutoIncrementStep(autoIncrementStep.toMillis()); + return this; + } + + /** + * Sets the increment applied to the clock whenever it is queried. The increment is zero by + * default: the clock is left unchanged when queried. + * + * @param autoIncrementStepMs the new auto increment duration, in milliseconds + * @return this + */ + public FakeClock setAutoIncrementStep(long autoIncrementStepMs) { + this.autoIncrementStepMs = autoIncrementStepMs; + return this; + } + + /** + * Atomically adds the given value to the current time. + * + * @param durationMs the duration to add, in milliseconds + * @return the updated current time + * @see AtomicLong#addAndGet + */ + protected final Instant addAndGet(long durationMs) { + return Instant.ofEpochMilli(fakeNowMs.addAndGet(durationMs)); + } + + /** + * Atomically adds the given value to the current time. + * + * @param durationMs the duration to add, in milliseconds + * @return the previous time + * @see AtomicLong#getAndAdd + */ + protected final Instant getAndAdd(long durationMs) { + return Instant.ofEpochMilli(fakeNowMs.getAndAdd(durationMs)); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/main/webapp/WEB-INF/datastore-indexes.xml b/appengine-java11-bundled-services/datastore/src/main/webapp/WEB-INF/datastore-indexes.xml deleted file mode 120000 index 73f343b36e4..00000000000 --- a/appengine-java11-bundled-services/datastore/src/main/webapp/WEB-INF/datastore-indexes.xml +++ /dev/null @@ -1 +0,0 @@ -../../../../../../appengine-java8/datastore/src/main/webapp/WEB-INF/datastore-indexes.xml \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/main/webapp/WEB-INF/datastore-indexes.xml b/appengine-java11-bundled-services/datastore/src/main/webapp/WEB-INF/datastore-indexes.xml new file mode 100644 index 00000000000..c99175eba3b --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/main/webapp/WEB-INF/datastore-indexes.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + diff --git a/appengine-java11-bundled-services/datastore/src/main/webapp/WEB-INF/web.xml b/appengine-java11-bundled-services/datastore/src/main/webapp/WEB-INF/web.xml deleted file mode 120000 index 6c846cab2a4..00000000000 --- a/appengine-java11-bundled-services/datastore/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1 +0,0 @@ -../../../../../../appengine-java8/datastore/src/main/webapp/WEB-INF/web.xml \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/main/webapp/WEB-INF/web.xml b/appengine-java11-bundled-services/datastore/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..dddc47141c7 --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,98 @@ + + + + + guestbook-strong + com.example.appengine.GuestbookStrongServlet + + + guestbook-strong + / + + + guestbook + com.example.appengine.GuestbookServlet + + + guestbook + /guestbook + + + people + com.example.appengine.ListPeopleServlet + + + people + /people + + + projection + com.example.appengine.ProjectionServlet + + + projection + /projection + + + stats + com.example.appengine.StatsServlet + + + stats + /stats + + + + + startup + com.example.appengine.StartupServlet + + + startup + /_ah/start + + + + + profile + /* + + + CONFIDENTIAL + + + * + + + + + + profile + /stats + + + CONFIDENTIAL + + + admin + + + diff --git a/appengine-java11-bundled-services/datastore/src/main/webapp/guestbook.jsp b/appengine-java11-bundled-services/datastore/src/main/webapp/guestbook.jsp deleted file mode 120000 index dc4c9b4a240..00000000000 --- a/appengine-java11-bundled-services/datastore/src/main/webapp/guestbook.jsp +++ /dev/null @@ -1 +0,0 @@ -../../../../../appengine-java8/datastore/src/main/webapp/guestbook.jsp \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/main/webapp/guestbook.jsp b/appengine-java11-bundled-services/datastore/src/main/webapp/guestbook.jsp new file mode 100644 index 00000000000..2cf678f30ae --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/main/webapp/guestbook.jsp @@ -0,0 +1,45 @@ + + +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> + + + + Guestbook + + +

Latest Greetings

+ +

+
+ Posted: ${greeting.date} +

+
+ +

Add Greeting

+
+

+ + +

+

+ +

+
+ + + diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/EntitiesTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/EntitiesTest.java deleted file mode 120000 index 6acecc301aa..00000000000 --- a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/EntitiesTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/EntitiesTest.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/EntitiesTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/EntitiesTest.java new file mode 100644 index 00000000000..ef1970577b2 --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/EntitiesTest.java @@ -0,0 +1,337 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static org.junit.Assert.fail; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.EmbeddedEntity; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.EntityNotFoundException; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.KeyFactory; +import com.google.appengine.api.datastore.KeyRange; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests to demonstrate App Engine Datastore entities. */ +@RunWith(JUnit4.class) +public class EntitiesTest { + + // Set no eventual consistency, that way queries return all results. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void kindExample_writesEntity() throws Exception { + Entity employee = new Entity("Employee", "asalieri"); + employee.setProperty("firstName", "Antonio"); + employee.setProperty("lastName", "Salieri"); + employee.setProperty("hireDate", new Date()); + employee.setProperty("attendedHrTraining", true); + + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + datastore.put(employee); + + Entity got = datastore.get(employee.getKey()); + assertWithMessage("got.firstName") + .that((String) got.getProperty("firstName")) + .isEqualTo("Antonio"); + assertWithMessage("got.lastName") + .that((String) got.getProperty("lastName")) + .isEqualTo("Salieri"); + assertWithMessage("got.hireDate").that((Date) got.getProperty("hireDate")).isNotNull(); + assertWithMessage("got.attendedHrTraining") + .that((boolean) got.getProperty("attendedHrTraining")) + .isTrue(); + } + + @Test + public void identifiers_keyName_setsKeyName() throws Exception { + Entity employee = new Entity("Employee", "asalieri"); + datastore.put(employee); + + assertWithMessage("key name").that(employee.getKey().getName()).isEqualTo("asalieri"); + } + + @Test + public void identifiers_autoId_setsUnallocatedId() throws Exception { + KeyRange keys = datastore.allocateIds("Employee", 1); + long usedId = keys.getStart().getId(); + + Entity employee = new Entity("Employee"); + datastore.put(employee); + + assertWithMessage("key id").that(employee.getKey().getId()).isNotEqualTo(usedId); + } + + @Test + public void parent_withinEntityConstructor_setsParent() throws Exception { + Entity employee = new Entity("Employee"); + datastore.put(employee); + + Entity address = new Entity("Address", employee.getKey()); + datastore.put(address); + + assertWithMessage("address parent").that(address.getParent()).isEqualTo(employee.getKey()); + } + + @Test + public void parent_withKeyName_setsKeyName() throws Exception { + Entity employee = new Entity("Employee"); + datastore.put(employee); + + Entity address = new Entity("Address", "addr1", employee.getKey()); + datastore.put(address); + + assertWithMessage("address key name").that(address.getKey().getName()).isEqualTo("addr1"); + } + + @Test + public void datastoreServiceFactory_returnsDatastoreService() throws Exception { + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + assertWithMessage("datastore").that(datastore).isNotNull(); + } + + @Test + public void creatingAnEntity_withKeyName_writesEntity() throws Exception { + Entity employee = new Entity("Employee", "asalieri"); + // Set the entity properties. + // ... + datastore.put(employee); + + assertWithMessage("employee key name").that(employee.getKey().getName()).isEqualTo("asalieri"); + } + + private Key writeEmptyEmployee() { + Entity employee = new Entity("Employee"); + // Set the entity properties. + // ... + datastore.put(employee); + return employee.getKey(); + } + + @Test + public void creatingAnEntity_withoutKeyName_writesEntity() throws Exception { + Key employeeKey = writeEmptyEmployee(); + // Key employeeKey = ...; + Entity employee = datastore.get(employeeKey); + + assertWithMessage("retrieved key ID") + .that(employee.getKey().getId()) + .isEqualTo(employeeKey.getId()); + } + + @Test + public void deletingAnEntity_deletesAnEntity() throws Exception { + Entity employee = new Entity("Employee", "asalieri"); + datastore.put(employee); + + Key employeeKey = KeyFactory.createKey("Employee", "asalieri"); + // Key employeeKey = ...; + datastore.delete(employeeKey); + + try { + Entity got = datastore.get(employeeKey); + fail("Expected EntityNotFoundException"); + } catch (EntityNotFoundException expected) { + assertWithMessage("exception key name") + .that(expected.getKey().getName()) + .isEqualTo("asalieri"); + } + } + + @Test + public void repeatedProperties_storesList() throws Exception { + Entity employee = new Entity("Employee"); + ArrayList favoriteFruit = new ArrayList<>(); + favoriteFruit.add("Pear"); + favoriteFruit.add("Apple"); + employee.setProperty("favoriteFruit", favoriteFruit); + datastore.put(employee); + + // Sometime later + employee = datastore.get(employee.getKey()); + @SuppressWarnings("unchecked") // Cast can't verify generic type. + ArrayList retrievedFruits = (ArrayList) employee.getProperty("favoriteFruit"); + + assertThat(retrievedFruits).containsExactlyElementsIn(favoriteFruit).inOrder(); + } + + // CHECKSTYLE.OFF: VariableDeclarationUsageDistance + @SuppressWarnings("VariableDeclarationUsageDistance") + @Test + public void embeddedEntity_fromEmbedded_embedsProperties() throws Exception { + Entity employee = new Entity("Employee"); + // Entity employee = ...; + EmbeddedEntity embeddedContactInfo = new EmbeddedEntity(); + + embeddedContactInfo.setProperty("homeAddress", "123 Fake St, Made, UP 45678"); + embeddedContactInfo.setProperty("phoneNumber", "555-555-5555"); + embeddedContactInfo.setProperty("emailAddress", "test@example.com"); + + employee.setProperty("contactInfo", embeddedContactInfo); + datastore.put(employee); + + Entity gotEmployee = datastore.get(employee.getKey()); + EmbeddedEntity got = (EmbeddedEntity) gotEmployee.getProperty("contactInfo"); + assertWithMessage("got.homeAddress") + .that((String) got.getProperty("homeAddress")) + .isEqualTo("123 Fake St, Made, UP 45678"); + } + // CHECKSTYLE.ON: VariableDeclarationUsageDistance + + private Key putEmployeeWithContactInfo(Entity contactInfo) { + Entity employee = new Entity("Employee"); + // Entity employee = ...; + // Entity contactInfo = ...; + EmbeddedEntity embeddedContactInfo = new EmbeddedEntity(); + + embeddedContactInfo.setKey(contactInfo.getKey()); // Optional, used so we can recover original. + embeddedContactInfo.setPropertiesFrom(contactInfo); + + employee.setProperty("contactInfo", embeddedContactInfo); + datastore.put(employee); + return employee.getKey(); + } + + @Test + public void embeddedEntity_fromExisting_canRecover() throws Exception { + Entity initialContactInfo = new Entity("Contact"); + initialContactInfo.setProperty("homeAddress", "123 Fake St, Made, UP 45678"); + initialContactInfo.setProperty("phoneNumber", "555-555-5555"); + initialContactInfo.setProperty("emailAddress", "test@example.com"); + datastore.put(initialContactInfo); + Key employeeKey = putEmployeeWithContactInfo(initialContactInfo); + + Entity employee = datastore.get(employeeKey); + EmbeddedEntity embeddedContactInfo = (EmbeddedEntity) employee.getProperty("contactInfo"); + + Key infoKey = embeddedContactInfo.getKey(); + Entity contactInfo = new Entity(infoKey); + contactInfo.setPropertiesFrom(embeddedContactInfo); + datastore.put(contactInfo); + + Entity got = datastore.get(infoKey); + assertThat(got.getKey()).isEqualTo(initialContactInfo.getKey()); + assertWithMessage("got.homeAddress") + .that((String) got.getProperty("homeAddress")) + .isEqualTo("123 Fake St, Made, UP 45678"); + } + + @Test + public void batchOperations_putsEntities() { + Entity employee1 = new Entity("Employee"); + Entity employee2 = new Entity("Employee"); + Entity employee3 = new Entity("Employee"); + // [START_EXCLUDE] + employee1.setProperty("firstName", "Bill"); + employee2.setProperty("firstName", "Jane"); + employee3.setProperty("firstName", "Alex"); + // [END_EXCLUDE] + + List employees = Arrays.asList(employee1, employee2, employee3); + datastore.put(employees); + + Map got = + datastore.get(Arrays.asList(employee1.getKey(), employee2.getKey(), employee3.getKey())); + assertWithMessage("employee1.firstName") + .that((String) got.get(employee1.getKey()).getProperty("firstName")) + .isEqualTo("Bill"); + assertWithMessage("employee2.firstName") + .that((String) got.get(employee2.getKey()).getProperty("firstName")) + .isEqualTo("Jane"); + assertWithMessage("employee3.firstName") + .that((String) got.get(employee3.getKey()).getProperty("firstName")) + .isEqualTo("Alex"); + } + + @Test + public void createKey_makesKey() { + Key k1 = KeyFactory.createKey("Person", "GreatGrandpa"); + Key k2 = KeyFactory.createKey("Person", 74219); + + assertThat(k1).isNotNull(); + assertThat(k2).isNotNull(); + } + + @Test + public void keyFactoryBuilder_makeKeyWithParents() { + Key greatKey = KeyFactory.createKey("Person", "GreatGrandpa"); + Key grandKey = KeyFactory.createKey(greatKey, "Person", "Grandpa"); + Key dadKey = KeyFactory.createKey(grandKey, "Person", "Dad"); + Key meKey = KeyFactory.createKey(dadKey, "Person", "Me"); + + Key k = + new KeyFactory.Builder("Person", "GreatGrandpa") + .addChild("Person", "Grandpa") + .addChild("Person", "Dad") + .addChild("Person", "Me") + .getKey(); + + assertThat(k).isEqualTo(meKey); + } + + @Test + public void keyToString_getsPerson() throws Exception { + Entity p = new Entity("Person"); + p.setProperty("relationship", "Me"); + datastore.put(p); + Key k = p.getKey(); + + String personKeyStr = KeyFactory.keyToString(k); + + // Some time later (for example, after using personKeyStr in a link). + Key personKey = KeyFactory.stringToKey(personKeyStr); + Entity person = datastore.get(personKey); + + assertThat(personKey).isEqualTo(k); + assertWithMessage("person.relationship") + .that((String) person.getProperty("relationship")) + .isEqualTo("Me"); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookStrongTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookStrongTest.java deleted file mode 120000 index 3541f8f82bb..00000000000 --- a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookStrongTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/GuestbookStrongTest.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookStrongTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookStrongTest.java new file mode 100644 index 00000000000..ca73e082d82 --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookStrongTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import com.example.time.testing.FakeClock; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import com.google.appengine.tools.development.testing.LocalUserServiceTestConfig; +import java.time.Instant; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for {@link GuestbookStrong}. + */ +@RunWith(JUnit4.class) +public class GuestbookStrongTest { + + private static final Instant FAKE_NOW = Instant.ofEpochMilli(1234567890L); + private static final String GUESTBOOK_ID = "my guestbook"; + + // Set maximum eventual consistency. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(100), + // Make sure there is a user logged in. We enforce this in web.xml. + new LocalUserServiceTestConfig()) + .setEnvIsLoggedIn(true) + .setEnvEmail("test@example.com") + .setEnvAuthDomain("gmail.com"); + + private FakeClock clock; + private GuestbookStrong guestbookUnderTest; + + @Before + public void setUp() throws Exception { + helper.setUp(); + clock = new FakeClock(FAKE_NOW); + guestbookUnderTest = new GuestbookStrong(GUESTBOOK_ID, clock); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void appendGreeting_normalData_setsContentProperty() { + Greeting got = guestbookUnderTest.appendGreeting("Hello, Datastore!"); + + assertWithMessage("content property").that(got.getContent()).isEqualTo("Hello, Datastore!"); + } + + @Test + public void appendGreeting_normalData_setsDateProperty() { + Greeting got = guestbookUnderTest.appendGreeting("Hello, Datastore!"); + + assertWithMessage("date property").that(got.getDate()).isEqualTo(FAKE_NOW); + } + + @Test + public void listGreetings_maximumEventualConsistency_returnsAllGreetings() { + // Arrange + guestbookUnderTest.appendGreeting("Hello, Datastore!"); + guestbookUnderTest.appendGreeting("Hello, Eventual Consistency!"); + guestbookUnderTest.appendGreeting("Hello, World!"); + + // Act + List got = guestbookUnderTest.listGreetings(); + + // Assert + // Since we use an ancestor query, all greetings should be available. + assertThat(got).hasSize(3); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookTest.java deleted file mode 120000 index 015e621216b..00000000000 --- a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/GuestbookTest.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookTest.java new file mode 100644 index 00000000000..e41beb98ca9 --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookTest.java @@ -0,0 +1,127 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import com.example.time.testing.FakeClock; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.dev.HighRepJobPolicy; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import com.google.appengine.tools.development.testing.LocalUserServiceTestConfig; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for {@link Guestbook}. + */ +@RunWith(JUnit4.class) +public class GuestbookTest { + + private static final class CustomHighRepJobPolicy implements HighRepJobPolicy { + + static int newJobCounter = 0; + static int existingJobCounter = 0; + + @Override + public boolean shouldApplyNewJob(Key entityGroup) { + // Every other new job fails to apply. + return newJobCounter++ % 2 == 0; + } + + @Override + public boolean shouldRollForwardExistingJob(Key entityGroup) { + // Existing jobs always apply after every Get and every Query. + return true; + } + } + + // Set custom, deterministic, eventual consistency. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + new LocalDatastoreServiceTestConfig() + .setAlternateHighRepJobPolicyClass(CustomHighRepJobPolicy.class), + // Make sure there is a user logged in. We enforce this in web.xml. + new LocalUserServiceTestConfig()) + .setEnvIsLoggedIn(true) + .setEnvEmail("test@example.com") + .setEnvAuthDomain("gmail.com"); + + private FakeClock clock; + private Guestbook guestbookUnderTest; + + @Before + public void setUp() throws Exception { + helper.setUp(); + CustomHighRepJobPolicy.newJobCounter = 0; + CustomHighRepJobPolicy.existingJobCounter = 0; + clock = new FakeClock(); + guestbookUnderTest = new Guestbook(clock); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void appendGreeting_normalData_setsContentProperty() { + Greeting got = guestbookUnderTest.appendGreeting("Hello, Datastore!"); + + assertWithMessage("content property").that(got.getContent()).isEqualTo("Hello, Datastore!"); + } + + @Test + public void listGreetings_eventualConsistency_returnsPartialGreetings() { + // Arrange + guestbookUnderTest.appendGreeting("Hello, Datastore!"); + guestbookUnderTest.appendGreeting("Hello, Eventual Consistency!"); + guestbookUnderTest.appendGreeting("Hello, World!"); + guestbookUnderTest.appendGreeting("Güten Tag!"); + + // Act + List got = guestbookUnderTest.listGreetings(); + + // The first time we query we should half of the results due to the fact that we simulate + // eventual consistency by applying every other write. + assertThat(got).hasSize(2); + } + + @Test + public void listGreetings_groomedDatastore_returnsAllGreetings() { + // Arrange + guestbookUnderTest.appendGreeting("Hello, Datastore!"); + guestbookUnderTest.appendGreeting("Hello, Eventual Consistency!"); + guestbookUnderTest.appendGreeting("Hello, World!"); + + // Act + guestbookUnderTest.listGreetings(); + // Second global query sees both Entities because we "groom" (attempt to + // apply unapplied jobs) after every query. + List got = guestbookUnderTest.listGreetings(); + + assertThat(got).hasSize(3); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/IndexesTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/IndexesTest.java deleted file mode 120000 index 2530a9646f2..00000000000 --- a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/IndexesTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/IndexesTest.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/IndexesTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/IndexesTest.java new file mode 100644 index 00000000000..b23e6a2e310 --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/IndexesTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertWithMessage; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.KeyFactory; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.Filter; +import com.google.appengine.api.datastore.Query.FilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests to demonstrate App Engine Datastore queries. + */ +@RunWith(JUnit4.class) +public class IndexesTest { + + // Set no eventual consistency, that way queries return all results. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void propertyFilterExample_returnsMatchingEntities() throws Exception { + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + + Key acmeKey = KeyFactory.createKey("Company", "Acme"); + + Entity tom = new Entity("Person", "Tom", acmeKey); + tom.setProperty("name", "Tom"); + tom.setProperty("age", 32); + datastore.put(tom); + + Entity lucy = new Entity("Person", "Lucy", acmeKey); + lucy.setProperty("name", "Lucy"); + lucy.setUnindexedProperty("age", 29); + datastore.put(lucy); + + Filter ageFilter = new FilterPredicate("age", FilterOperator.GREATER_THAN, 25); + + Query q = new Query("Person").setAncestor(acmeKey).setFilter(ageFilter); + + // Returns tom but not lucy, because her age is unindexed + List results = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults()); + + assertWithMessage("query results").that(results).containsExactly(tom); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ListPeopleServletTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ListPeopleServletTest.java deleted file mode 120000 index e32bdc25149..00000000000 --- a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ListPeopleServletTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/ListPeopleServletTest.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ListPeopleServletTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ListPeopleServletTest.java new file mode 100644 index 00000000000..b2dc94cafed --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ListPeopleServletTest.java @@ -0,0 +1,165 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.PreparedQuery; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.SortDirection; +import com.google.appengine.api.datastore.QueryResultList; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import com.google.common.collect.ImmutableList; +import java.io.PrintWriter; +import java.io.StringWriter; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link ListPeopleServlet}. + */ +@RunWith(JUnit4.class) +public class ListPeopleServletTest { + + private static final ImmutableList TEST_NAMES = + // Keep in alphabetical order, so this is the same as the query order. + ImmutableList.builder() + .add("Alpha") + .add("Bravo") + .add("Charlie") + .add("Delta") + .add("Echo") + .add("Foxtrot") + .add("Golf") + .add("Hotel") + .add("India") + .add("Juliett") + .add("Kilo") + .add("Lima") + .add("Mike") + .add("November") + .add("Oscar") + .add("Papa") + .add("Quebec") + .add("Romeo") + .add("Sierra") + .add("Tango") + .build(); + + // Set no eventual consistency, that way queries return all results. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + @Mock + private HttpServletRequest mockRequest; + @Mock + private HttpServletResponse mockResponse; + private StringWriter responseWriter; + private DatastoreService datastore; + + private ListPeopleServlet servletUnderTest; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + + // Add test data. + ImmutableList.Builder people = ImmutableList.builder(); + for (String name : TEST_NAMES) { + people.add(createPerson(name)); + } + datastore.put(people.build()); + + // Set up a fake HTTP response. + responseWriter = new StringWriter(); + when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter)); + + servletUnderTest = new ListPeopleServlet(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + private Entity createPerson(String name) { + Entity person = new Entity("Person"); + person.setProperty("name", name); + return person; + } + + @Test + public void doGet_noCursor_writesNames() throws Exception { + servletUnderTest.doGet(mockRequest, mockResponse); + + String response = responseWriter.toString(); + for (int i = 0; i < ListPeopleServlet.PAGE_SIZE; i++) { + assertWithMessage("ListPeopleServlet response").that(response).contains(TEST_NAMES.get(i)); + } + } + + private String getFirstCursor() { + Query q = new Query("Person").addSort("name", SortDirection.ASCENDING); + PreparedQuery pq = datastore.prepare(q); + FetchOptions fetchOptions = FetchOptions.Builder.withLimit(ListPeopleServlet.PAGE_SIZE); + QueryResultList results = pq.asQueryResultList(fetchOptions); + return results.getCursor().toWebSafeString(); + } + + @Test + public void doGet_withValidCursor_writesNames() throws Exception { + when(mockRequest.getParameter("cursor")).thenReturn(getFirstCursor()); + + servletUnderTest.doGet(mockRequest, mockResponse); + + String response = responseWriter.toString(); + int i = 0; + while (i + ListPeopleServlet.PAGE_SIZE < TEST_NAMES.size() && i < ListPeopleServlet.PAGE_SIZE) { + assertWithMessage("ListPeopleServlet response") + .that(response) + .contains(TEST_NAMES.get(i + ListPeopleServlet.PAGE_SIZE)); + i++; + } + } + + @Test + public void doGet_withInvalidCursor_writesRedirect() throws Exception { + when(mockRequest.getParameter("cursor")).thenReturn("ThisCursorIsTotallyInvalid"); + servletUnderTest.doGet(mockRequest, mockResponse); + verify(mockResponse).sendRedirect("/people"); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataEntityGroupTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataEntityGroupTest.java deleted file mode 120000 index 97be437f7c1..00000000000 --- a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataEntityGroupTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/MetadataEntityGroupTest.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataEntityGroupTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataEntityGroupTest.java new file mode 100644 index 00000000000..df40be429a1 --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataEntityGroupTest.java @@ -0,0 +1,160 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entities; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.EntityNotFoundException; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.PreparedQuery; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Transaction; +import com.google.appengine.api.memcache.MemcacheService; +import com.google.appengine.api.memcache.MemcacheServiceFactory; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalMemcacheServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.io.PrintWriter; +import java.io.Serializable; +import java.io.StringWriter; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests to demonstrate App Engine Datastore entity group metadata. + */ +@RunWith(JUnit4.class) +public class MetadataEntityGroupTest { + + // Set no eventual consistency, that way queries return all results. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + new LocalDatastoreServiceTestConfig().setDefaultHighRepJobPolicyUnappliedJobPercentage(0), + new LocalMemcacheServiceTestConfig()); + + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + private static long getEntityGroupVersion(DatastoreService ds, Transaction tx, Key entityKey) { + try { + return Entities.getVersionProperty(ds.get(tx, Entities.createEntityGroupKey(entityKey))); + } catch (EntityNotFoundException e) { + // No entity group information, return a value strictly smaller than any + // possible version + return 0; + } + } + + private static void printEntityGroupVersions(DatastoreService ds, PrintWriter writer) { + Entity entity1 = new Entity("Simple"); + Key key1 = ds.put(entity1); + Key entityGroupKey = Entities.createEntityGroupKey(key1); + + // Print entity1's entity group version + writer.println("version " + getEntityGroupVersion(ds, null, key1)); + + // Write to a different entity group + Entity entity2 = new Entity("Simple"); + ds.put(entity2); + + // Will print the same version, as entity1's entity group has not changed + writer.println("version " + getEntityGroupVersion(ds, null, key1)); + + // Change entity1's entity group by adding a new child entity + Entity entity3 = new Entity("Simple", entity1.getKey()); + ds.put(entity3); + + // Will print a higher version, as entity1's entity group has changed + writer.println("version " + getEntityGroupVersion(ds, null, key1)); + } + + @Test + public void printEntityGroupVersions_printsVersions() throws Exception { + StringWriter responseWriter = new StringWriter(); + printEntityGroupVersions(datastore, new PrintWriter(responseWriter)); + assertThat(responseWriter.toString()).contains("version"); + } + + // A simple class for tracking consistent entity group counts. + private static class EntityGroupCount implements Serializable { + + long version; // Version of the entity group whose count we are tracking + int count; + + EntityGroupCount(long version, int count) { + this.version = version; + this.count = count; + } + + // Display count of entities in an entity group, with consistent caching + void showEntityGroupCount( + DatastoreService ds, MemcacheService cache, PrintWriter writer, Key entityGroupKey) { + EntityGroupCount egCount = (EntityGroupCount) cache.get(entityGroupKey); + // Reuses getEntityGroupVersion method from the previous example. + if (egCount != null && egCount.version == getEntityGroupVersion(ds, null, entityGroupKey)) { + // Cached value matched current entity group version, use that + writer.println(egCount.count + " entities (cached)"); + } else { + // Need to actually count entities. Using a transaction to get a consistent count + // and entity group version. + Transaction tx = ds.beginTransaction(); + PreparedQuery pq = ds.prepare(tx, new Query(entityGroupKey)); + int count = pq.countEntities(FetchOptions.Builder.withLimit(5000)); + cache.put( + entityGroupKey, + new EntityGroupCount(getEntityGroupVersion(ds, tx, entityGroupKey), count)); + tx.rollback(); + writer.println(count + " entities"); + } + } + } + + @Test + public void entityGroupCount_printsCount() throws Exception { + StringWriter responseWriter = new StringWriter(); + MemcacheService cache = MemcacheServiceFactory.getMemcacheService(); + Entity entity1 = new Entity("Simple"); + Key key1 = datastore.put(entity1); + Key entityGroupKey = Entities.createEntityGroupKey(key1); + + EntityGroupCount groupCount = new EntityGroupCount(0, 0); + groupCount.showEntityGroupCount( + datastore, cache, new PrintWriter(responseWriter), entityGroupKey); + + assertThat(responseWriter.toString()).contains(" entities"); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataKindsTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataKindsTest.java deleted file mode 120000 index aedd248bc2b..00000000000 --- a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataKindsTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/MetadataKindsTest.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataKindsTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataKindsTest.java new file mode 100644 index 00000000000..d479e82c470 --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataKindsTest.java @@ -0,0 +1,120 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entities; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.CompositeFilterOperator; +import com.google.appengine.api.datastore.Query.Filter; +import com.google.appengine.api.datastore.Query.FilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests to demonstrate App Engine Datastore kinds metadata. + */ +@RunWith(JUnit4.class) +public class MetadataKindsTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google + // .com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private StringWriter responseWriter; + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + responseWriter = new StringWriter(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + void printLowercaseKinds(DatastoreService ds, PrintWriter writer) { + + // Start with unrestricted kind query + Query q = new Query(Entities.KIND_METADATA_KIND); + + List subFils = new ArrayList<>(); + + // Limit to lowercase initial letters + subFils.add( + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + FilterOperator.GREATER_THAN_OR_EQUAL, + Entities.createKindKey("a"))); + + String endChar = Character.toString((char) ('z' + 1)); // Character after 'z' + + subFils.add( + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + FilterOperator.LESS_THAN, + Entities.createKindKey(endChar))); + + q.setFilter(CompositeFilterOperator.and(subFils)); + + // Print heading + writer.println("Lowercase kinds:"); + + // Print query results + for (Entity e : ds.prepare(q).asIterable()) { + writer.println(" " + e.getKey().getName()); + } + } + + @Test + public void printLowercaseKinds_printsKinds() throws Exception { + datastore.put(new Entity("alpha")); + datastore.put(new Entity("beta")); + datastore.put(new Entity("NotIncluded")); + datastore.put(new Entity("zed")); + + printLowercaseKinds(datastore, new PrintWriter(responseWriter)); + + String response = responseWriter.toString(); + assertThat(response).contains("alpha"); + assertThat(response).contains("beta"); + assertThat(response).contains("zed"); + assertThat(response).doesNotContain("NotIncluded"); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataNamespacesTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataNamespacesTest.java deleted file mode 120000 index 02eea767d46..00000000000 --- a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataNamespacesTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/MetadataNamespacesTest.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataNamespacesTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataNamespacesTest.java new file mode 100644 index 00000000000..bf24b77e56d --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataNamespacesTest.java @@ -0,0 +1,150 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.appengine.api.NamespaceManager; +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entities; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.CompositeFilterOperator; +import com.google.appengine.api.datastore.Query.Filter; +import com.google.appengine.api.datastore.Query.FilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests to demonstrate App Engine Datastore namespaces metadata. + */ +@RunWith(JUnit4.class) +public class MetadataNamespacesTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google + // .com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private StringWriter responseWriter; + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + responseWriter = new StringWriter(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + void printAllNamespaces(DatastoreService ds, PrintWriter writer) { + Query q = new Query(Entities.NAMESPACE_METADATA_KIND); + + for (Entity e : ds.prepare(q).asIterable()) { + // A nonzero numeric id denotes the default namespace; + // see Namespace Queries, below + if (e.getKey().getId() != 0) { + writer.println(""); + } else { + writer.println(e.getKey().getName()); + } + } + } + + @Test + public void printAllNamespaces_printsNamespaces() throws Exception { + datastore.put(new Entity("Simple")); + NamespaceManager.set("another-namespace"); + datastore.put(new Entity("Simple")); + + printAllNamespaces(datastore, new PrintWriter(responseWriter)); + + String response = responseWriter.toString(); + assertThat(response).contains(""); + assertThat(response).contains("another-namespace"); + } + + List getNamespaces(DatastoreService ds, String start, String end) { + + // Start with unrestricted namespace query + Query q = new Query(Entities.NAMESPACE_METADATA_KIND); + List subFilters = new ArrayList<>(); + // Limit to specified range, if any + if (start != null) { + subFilters.add( + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + FilterOperator.GREATER_THAN_OR_EQUAL, + Entities.createNamespaceKey(start))); + } + if (end != null) { + subFilters.add( + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + FilterOperator.LESS_THAN_OR_EQUAL, + Entities.createNamespaceKey(end))); + } + + if (!subFilters.isEmpty()) { + if (subFilters.size() == 1) { + q.setFilter(subFilters.get(0)); + } else { + q.setFilter(CompositeFilterOperator.and(subFilters)); + } + } + + // Build list of query results + return com.google.common.collect.Streams.stream(ds.prepare(q).asIterable()) + .map(e -> Entities.getNamespaceFromNamespaceKey(e.getKey())) + .collect(java.util.stream.Collectors.toList()); + } + + @Test + public void getNamespaces_returnsNamespaces() throws Exception { + NamespaceManager.set("alpha"); + datastore.put(new Entity("Simple")); + NamespaceManager.set("bravo"); + datastore.put(new Entity("Simple")); + NamespaceManager.set("charlie"); + datastore.put(new Entity("Simple")); + NamespaceManager.set("zed"); + datastore.put(new Entity("Simple")); + + List results = getNamespaces(datastore, "bravo", "echo"); + + assertThat(results).containsExactly("bravo", "charlie"); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataPropertiesTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataPropertiesTest.java deleted file mode 120000 index d94057bf260..00000000000 --- a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataPropertiesTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/MetadataPropertiesTest.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataPropertiesTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataPropertiesTest.java new file mode 100644 index 00000000000..5334a4f4493 --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/MetadataPropertiesTest.java @@ -0,0 +1,227 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entities; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.CompositeFilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; +import com.google.appengine.api.datastore.Query.SortDirection; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests to demonstrate App Engine Datastore properties metadata. + */ +@RunWith(JUnit4.class) +public class MetadataPropertiesTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google + // .com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private StringWriter responseWriter; + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + responseWriter = new StringWriter(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + void printProperties(DatastoreService ds, PrintWriter writer) { + + // Create unrestricted keys-only property query + Query q = new Query(Entities.PROPERTY_METADATA_KIND).setKeysOnly(); + + // Print query results + for (Entity e : ds.prepare(q).asIterable()) { + writer.println(e.getKey().getParent().getName() + ": " + e.getKey().getName()); + } + } + + @Test + public void printProperties_printsProperties() throws Exception { + Entity a = new Entity("Widget"); + a.setProperty("combobulators", 2); + a.setProperty("oscillatorState", "harmonzing"); + Entity b = new Entity("Ship"); + b.setProperty("sails", 2); + b.setProperty("captain", "Blackbeard"); + Entity c = new Entity("Ship"); + c.setProperty("captain", "Redbeard"); + c.setProperty("motor", "outboard"); + datastore.put(Arrays.asList(a, b, c)); + + printProperties(datastore, new PrintWriter(responseWriter)); + + String response = responseWriter.toString(); + assertThat(response).contains("Widget: combobulators"); + assertThat(response).contains("Widget: oscillatorState"); + assertThat(response).contains("Ship: sails"); + assertThat(response).contains("Ship: captain"); + assertThat(response).contains("Ship: motor"); + } + + void printPropertyRange(DatastoreService ds, PrintWriter writer) { + + // Start with unrestricted keys-only property query + Query q = new Query(Entities.PROPERTY_METADATA_KIND).setKeysOnly(); + + // Limit range + q.setFilter( + CompositeFilterOperator.and( + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + Query.FilterOperator.GREATER_THAN_OR_EQUAL, + Entities.createPropertyKey("Employee", "salary")), + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + Query.FilterOperator.LESS_THAN_OR_EQUAL, + Entities.createPropertyKey("Manager", "salary")))); + q.addSort(Entity.KEY_RESERVED_PROPERTY, SortDirection.ASCENDING); + + // Print query results + for (Entity e : ds.prepare(q).asIterable()) { + writer.println(e.getKey().getParent().getName() + ": " + e.getKey().getName()); + } + } + + @Test + public void printPropertyRange_printsProperties() throws Exception { + Entity account = new Entity("Account"); + account.setProperty("balance", "10.30"); + account.setProperty("company", "General Company"); + Entity employee = new Entity("Employee"); + employee.setProperty("name", "John Doe"); + employee.setProperty("ssn", "987-65-4321"); + Entity invoice = new Entity("Invoice"); + invoice.setProperty("date", new Date()); + invoice.setProperty("amount", "99.98"); + Entity manager = new Entity("Manager"); + manager.setProperty("name", "Jane Doe"); + manager.setProperty("title", "Technical Director"); + Entity product = new Entity("Product"); + product.setProperty("description", "Widget to re-ionize an oscillator"); + product.setProperty("price", "19.97"); + datastore.put(Arrays.asList(account, employee, invoice, manager, product)); + + printPropertyRange(datastore, new PrintWriter(responseWriter)); + + String response = responseWriter.toString(); + assertThat(response) + .isEqualTo("Employee: ssn\nInvoice: amount\nInvoice: date\nManager: name\n"); + } + + List propertiesOfKind(DatastoreService ds, String kind) { + + // Start with unrestricted keys-only property query + Query q = new Query(Entities.PROPERTY_METADATA_KIND).setKeysOnly(); + + // Limit to specified kind + q.setAncestor(Entities.createKindKey(kind)); + + // Initialize result list + ArrayList results = new ArrayList<>(); + + //Build list of query results + for (Entity e : ds.prepare(q).asIterable()) { + results.add(e.getKey().getName()); + } + + // Return result list + return results; + } + + @Test + public void propertiesOfKind_returnsProperties() throws Exception { + Entity a = new Entity("Alpha"); + a.setProperty("beta", 12); + a.setProperty("charlie", "misc."); + Entity b = new Entity("Alpha"); + b.setProperty("charlie", "assorted"); + b.setProperty("delta", new Date()); + Entity c = new Entity("Charlie"); + c.setProperty("charlie", "some"); + c.setProperty("echo", new Date()); + datastore.put(Arrays.asList(a, b, c)); + + List properties = propertiesOfKind(datastore, "Alpha"); + + assertThat(properties).containsExactly("beta", "charlie", "delta"); + } + + Collection representationsOfProperty(DatastoreService ds, String kind, String property) { + + // Start with unrestricted non-keys-only property query + Query q = new Query(Entities.PROPERTY_METADATA_KIND); + + // Limit to specified kind and property + q.setFilter( + new FilterPredicate( + "__key__", Query.FilterOperator.EQUAL, Entities.createPropertyKey(kind, property))); + + // Get query result + Entity propInfo = ds.prepare(q).asSingleEntity(); + + // Return collection of property representations + return (Collection) propInfo.getProperty("property_representation"); + } + + @Test + public void representationsOfProperty_returnsRepresentations() throws Exception { + Entity a = new Entity("Alpha"); + a.setProperty("beta", 12); + Entity b = new Entity("Alpha"); + b.setProperty("beta", true); + Entity c = new Entity("Alpha"); + c.setProperty("beta", new Date()); + datastore.put(Arrays.asList(a, b, c)); + + Collection results = representationsOfProperty(datastore, "Alpha", "beta"); + + assertThat(results).containsExactly("INT64", "BOOLEAN"); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionServletTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionServletTest.java deleted file mode 120000 index f442793b9c6..00000000000 --- a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionServletTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/ProjectionServletTest.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionServletTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionServletTest.java new file mode 100644 index 00000000000..42e5574da0e --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionServletTest.java @@ -0,0 +1,97 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.Mockito.when; + +import com.example.time.testing.FakeClock; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.io.PrintWriter; +import java.io.StringWriter; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link ProjectionServlet}. + */ +@RunWith(JUnit4.class) +public class ProjectionServletTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig()); + + @Mock + private HttpServletRequest mockRequest; + @Mock + private HttpServletResponse mockResponse; + private StringWriter responseWriter; + private ProjectionServlet servletUnderTest; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + helper.setUp(); + + // Set up a fake HTTP response. + responseWriter = new StringWriter(); + when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter)); + + servletUnderTest = new ProjectionServlet(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void doGet_emptyDatastore_writesNoGreetings() throws Exception { + servletUnderTest.doGet(mockRequest, mockResponse); + + assertWithMessage("ProjectionServlet response") + .that(responseWriter.toString()) + .doesNotContain("Message"); + } + + @Test + public void doGet_manyGreetings_writesLatestGreetings() throws Exception { + // Arrange + GuestbookStrong guestbook = + new GuestbookStrong(GuestbookStrongServlet.GUESTBOOK_ID, new FakeClock()); + guestbook.appendGreeting("Hello."); + guestbook.appendGreeting("Güten Tag!"); + guestbook.appendGreeting("Hi."); + guestbook.appendGreeting("Hola."); + + // Act + servletUnderTest.doGet(mockRequest, mockResponse); + String output = responseWriter.toString(); + + assertWithMessage("ProjectionServlet response").that(output).contains("Message Hello."); + assertWithMessage("ProjectionServlet response").that(output).contains("Message Güten Tag!"); + assertWithMessage("ProjectionServlet response").that(output).contains("Message Hola."); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionTest.java deleted file mode 120000 index 99cd3ef139a..00000000000 --- a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/ProjectionTest.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionTest.java new file mode 100644 index 00000000000..85ee162c055 --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.PropertyProjection; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests to demonstrate App Engine Datastore projection queries. */ +@RunWith(JUnit4.class) +public class ProjectionTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google + // .com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private DatastoreService datastore; + + @Before + public void setUp() throws Exception { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void projectionQuery_grouping_filtersDuplicates() { + putTestData("some duplicate", 0L); + putTestData("some duplicate", 0L); + putTestData("too big", 1L); + + Query q = new Query("TestKind"); + q.addProjection(new PropertyProjection("A", String.class)); + q.addProjection(new PropertyProjection("B", Long.class)); + q.setDistinct(true); + q.setFilter(Query.FilterOperator.LESS_THAN.of("B", 1L)); + q.addSort("B", Query.SortDirection.DESCENDING); + q.addSort("A"); + + List entities = datastore.prepare(q).asList(FetchOptions.Builder.withLimit(5)); + assertThat(entities).hasSize(1); + Entity entity = entities.get(0); + assertWithMessage("entity.A") + .that((String) entity.getProperty("A")) + .isEqualTo("some duplicate"); + assertWithMessage("entity.B").that((long) entity.getProperty("B")).isEqualTo(0L); + } + + private void putTestData(String a, long b) { + Entity entity = new Entity("TestKind"); + entity.setProperty("A", a); + entity.setProperty("B", b); + datastore.put(entity); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/QueriesTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/QueriesTest.java deleted file mode 120000 index 2b4c7b4c7fc..00000000000 --- a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/QueriesTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/QueriesTest.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/QueriesTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/QueriesTest.java new file mode 100644 index 00000000000..8635532539c --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/QueriesTest.java @@ -0,0 +1,798 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static org.junit.Assert.fail; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.PreparedQuery; +import com.google.appengine.api.datastore.PreparedQuery.TooManyResultsException; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.CompositeFilter; +import com.google.appengine.api.datastore.Query.CompositeFilterOperator; +import com.google.appengine.api.datastore.Query.Filter; +import com.google.appengine.api.datastore.Query.FilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; +import com.google.appengine.api.datastore.Query.SortDirection; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import com.google.common.collect.ImmutableList; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests to demonstrate App Engine Datastore queries. */ +@RunWith(JUnit4.class) +public class QueriesTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google + // .com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void propertyFilterExample_returnsMatchingEntities() throws Exception { + // Arrange + Entity p1 = new Entity("Person"); + p1.setProperty("height", 120); + Entity p2 = new Entity("Person"); + p2.setProperty("height", 180); + Entity p3 = new Entity("Person"); + p3.setProperty("height", 160); + datastore.put(ImmutableList.of(p1, p2, p3)); + + // Act + long minHeight = 160; + Filter propertyFilter = + new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight); + Query q = new Query("Person").setFilter(propertyFilter); + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(p2, p3); + } + + @Test + public void keyFilterExample_returnsMatchingEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + Entity b = new Entity("Person", "b"); + Entity c = new Entity("Person", "c"); + Entity aa = new Entity("Person", "aa", b.getKey()); + Entity bb = new Entity("Person", "bb", b.getKey()); + Entity aaa = new Entity("Person", "aaa", bb.getKey()); + Entity bbb = new Entity("Person", "bbb", bb.getKey()); + datastore.put(ImmutableList.of(a, b, c, aa, bb, aaa, bbb)); + + // Act + Key lastSeenKey = bb.getKey(); + Filter keyFilter = + new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey); + Query q = new Query("Person").setFilter(keyFilter); + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results") + .that(results) + .containsExactly( + aaa, // Ancestor path "b/bb/aaa" is greater than "b/bb". + bbb, // Ancestor path "b/bb/bbb" is greater than "b/bb". + c); // Key name identifier "c" is greater than b. + } + + @Test + public void keyFilterExample_kindless_returnsMatchingEntities() throws Exception { + // Arrange + Entity a = new Entity("Child", "a"); + Entity b = new Entity("Child", "b"); + Entity c = new Entity("Child", "c"); + Entity aa = new Entity("Child", "aa", b.getKey()); + Entity bb = new Entity("Child", "bb", b.getKey()); + Entity aaa = new Entity("Child", "aaa", bb.getKey()); + Entity bbb = new Entity("Child", "bbb", bb.getKey()); + Entity adult = new Entity("Adult", "a"); + Entity zooAnimal = new Entity("ZooAnimal", "a"); + datastore.put(ImmutableList.of(a, b, c, aa, bb, aaa, bbb, adult, zooAnimal)); + + // Act + Key lastSeenKey = bb.getKey(); + Filter keyFilter = + new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey); + Query q = new Query().setFilter(keyFilter); + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results") + .that(results) + .containsExactly( + aaa, // Ancestor path "b/bb/aaa" is greater than "b/bb". + bbb, // Ancestor path "b/bb/bbb" is greater than "b/bb". + zooAnimal, // Kind "ZooAnimal" is greater than "Child" + c); // Key name identifier "c" is greater than b. + } + + @Test + public void ancestorFilterExample_returnsMatchingEntities() throws Exception { + Entity a = new Entity("Person", "a"); + Entity b = new Entity("Person", "b"); + Entity aa = new Entity("Person", "aa", a.getKey()); + Entity ab = new Entity("Person", "ab", a.getKey()); + Entity bb = new Entity("Person", "bb", b.getKey()); + datastore.put(ImmutableList.of(a, b, aa, ab, bb)); + + Key ancestorKey = a.getKey(); + Query q = new Query("Person").setAncestor(ancestorKey); + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(a, aa, ab); + } + + @Test + public void ancestorQueryExample_returnsMatchingEntities() throws Exception { + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + + Entity tom = new Entity("Person", "Tom"); + Key tomKey = tom.getKey(); + datastore.put(tom); + + Entity weddingPhoto = new Entity("Photo", tomKey); + weddingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/wedding_photo.jpg"); + + Entity babyPhoto = new Entity("Photo", tomKey); + babyPhoto.setProperty("imageURL", "http://domain.com/some/path/to/baby_photo.jpg"); + + Entity dancePhoto = new Entity("Photo", tomKey); + dancePhoto.setProperty("imageURL", "http://domain.com/some/path/to/dance_photo.jpg"); + + Entity campingPhoto = new Entity("Photo"); + campingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/camping_photo.jpg"); + + List photoList = Arrays.asList(weddingPhoto, babyPhoto, dancePhoto, campingPhoto); + datastore.put(photoList); + + Query photoQuery = new Query("Photo").setAncestor(tomKey); + + // This returns weddingPhoto, babyPhoto, and dancePhoto, + // but not campingPhoto, because tom is not an ancestor + List results = + datastore.prepare(photoQuery).asList(FetchOptions.Builder.withDefaults()); + + assertWithMessage("query results") + .that(results) + .containsExactly(weddingPhoto, babyPhoto, dancePhoto); + } + + @Test + public void ancestorQueryExample_kindlessKeyFilter_returnsMatchingEntities() throws Exception { + // Arrange + Entity a = new Entity("Grandparent", "a"); + Entity b = new Entity("Grandparent", "b"); + Entity c = new Entity("Grandparent", "c"); + Entity aa = new Entity("Parent", "aa", a.getKey()); + Entity ba = new Entity("Parent", "ba", b.getKey()); + Entity bb = new Entity("Parent", "bb", b.getKey()); + Entity bc = new Entity("Parent", "bc", b.getKey()); + Entity cc = new Entity("Parent", "cc", c.getKey()); + Entity aaa = new Entity("Child", "aaa", aa.getKey()); + Entity bbb = new Entity("Child", "bbb", bb.getKey()); + datastore.put(ImmutableList.of(a, b, c, aa, ba, bb, bc, cc, aaa, bbb)); + + // Act + Key ancestorKey = b.getKey(); + Key lastSeenKey = bb.getKey(); + Filter keyFilter = + new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey); + Query q = new Query().setAncestor(ancestorKey).setFilter(keyFilter); + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(bc, bbb); + } + + @Test + public void ancestorQueryExample_kindlessKeyFilterFull_returnsMatchingEntities() + throws Exception { + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + + Entity tom = new Entity("Person", "Tom"); + Key tomKey = tom.getKey(); + datastore.put(tom); + + Entity weddingPhoto = new Entity("Photo", tomKey); + weddingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/wedding_photo.jpg"); + + Entity weddingVideo = new Entity("Video", tomKey); + weddingVideo.setProperty("videoURL", "http://domain.com/some/path/to/wedding_video.avi"); + + List mediaList = Arrays.asList(weddingPhoto, weddingVideo); + datastore.put(mediaList); + + // By default, ancestor queries include the specified ancestor itself. + // The following filter excludes the ancestor from the query results. + Filter keyFilter = + new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, tomKey); + + Query mediaQuery = new Query().setAncestor(tomKey).setFilter(keyFilter); + + // Returns both weddingPhoto and weddingVideo, + // even though they are of different entity kinds + List results = + datastore.prepare(mediaQuery).asList(FetchOptions.Builder.withDefaults()); + + assertWithMessage("query result keys") + .that(results) + .containsExactly(weddingPhoto, weddingVideo); + } + + @Test + public void keysOnlyExample_returnsMatchingEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + Entity b = new Entity("Building", "b"); + Entity c = new Entity("Person", "c"); + datastore.put(ImmutableList.of(a, b, c)); + + Query q = new Query("Person").setKeysOnly(); + + // Assert + List results = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(a, c); + } + + @Test + public void sortOrderExample_returnsSortedEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + a.setProperty("lastName", "Alpha"); + a.setProperty("height", 100); + Entity b = new Entity("Person", "b"); + b.setProperty("lastName", "Bravo"); + b.setProperty("height", 200); + Entity c = new Entity("Person", "c"); + c.setProperty("lastName", "Charlie"); + c.setProperty("height", 300); + datastore.put(ImmutableList.of(a, b, c)); + + // Act + // Order alphabetically by last name: + Query q1 = new Query("Person").addSort("lastName", SortDirection.ASCENDING); + + // Order by height, tallest to shortest: + Query q2 = new Query("Person").addSort("height", SortDirection.DESCENDING); + + // Assert + List lastNameResults = + datastore.prepare(q1.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("last name query results") + .that(lastNameResults) + .containsExactly(a, b, c) + .inOrder(); + List heightResults = + datastore.prepare(q2.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("height query results") + .that(heightResults) + .containsExactly(c, b, a) + .inOrder(); + } + + @Test + public void sortOrderExample_multipleSortOrders_returnsSortedEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + a.setProperty("lastName", "Alpha"); + a.setProperty("height", 100); + Entity b1 = new Entity("Person", "b1"); + b1.setProperty("lastName", "Bravo"); + b1.setProperty("height", 150); + Entity b2 = new Entity("Person", "b2"); + b2.setProperty("lastName", "Bravo"); + b2.setProperty("height", 200); + Entity c = new Entity("Person", "c"); + c.setProperty("lastName", "Charlie"); + c.setProperty("height", 300); + datastore.put(ImmutableList.of(a, b1, b2, c)); + + // Act + Query q = + new Query("Person") + .addSort("lastName", SortDirection.ASCENDING) + .addSort("height", SortDirection.DESCENDING); + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(a, b2, b1, c).inOrder(); + } + + @Test + public void queryInterface_multipleFilters_printsMatchedEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + a.setProperty("firstName", "Alph"); + a.setProperty("lastName", "Alpha"); + a.setProperty("height", 60); + Entity b = new Entity("Person", "b"); + b.setProperty("firstName", "Bee"); + b.setProperty("lastName", "Bravo"); + b.setProperty("height", 70); + Entity c = new Entity("Person", "c"); + c.setProperty("firstName", "Charles"); + c.setProperty("lastName", "Charlie"); + c.setProperty("height", 100); + datastore.put(ImmutableList.of(a, b, c)); + + StringWriter buf = new StringWriter(); + PrintWriter out = new PrintWriter(buf); + long minHeight = 60; + long maxHeight = 72; + + // Act + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + + Filter heightMinFilter = + new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight); + + Filter heightMaxFilter = + new FilterPredicate("height", FilterOperator.LESS_THAN_OR_EQUAL, maxHeight); + + // Use CompositeFilter to combine multiple filters + CompositeFilter heightRangeFilter = + CompositeFilterOperator.and(heightMinFilter, heightMaxFilter); + + // Use class Query to assemble a query + Query q = new Query("Person").setFilter(heightRangeFilter); + + // Use PreparedQuery interface to retrieve results + PreparedQuery pq = datastore.prepare(q); + + for (Entity result : pq.asIterable()) { + String firstName = (String) result.getProperty("firstName"); + String lastName = (String) result.getProperty("lastName"); + Long height = (Long) result.getProperty("height"); + + out.println(firstName + " " + lastName + ", " + height + " inches tall"); + } + + // Assert + assertThat(buf.toString()).contains("Alph Alpha, 60 inches tall"); + assertThat(buf.toString()).contains("Bee Bravo, 70 inches tall"); + assertThat(buf.toString()).doesNotContain("Charlie"); + } + + @Test + public void queryInterface_orFilter_printsMatchedEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + a.setProperty("height", 100); + Entity b = new Entity("Person", "b"); + b.setProperty("height", 150); + Entity c = new Entity("Person", "c"); + c.setProperty("height", 200); + datastore.put(ImmutableList.of(a, b, c)); + + StringWriter buf = new StringWriter(); + PrintWriter out = new PrintWriter(buf); + long minHeight = 125; + long maxHeight = 175; + + // Act + Filter tooShortFilter = new FilterPredicate("height", FilterOperator.LESS_THAN, minHeight); + + Filter tooTallFilter = new FilterPredicate("height", FilterOperator.GREATER_THAN, maxHeight); + + Filter heightOutOfRangeFilter = CompositeFilterOperator.or(tooShortFilter, tooTallFilter); + + Query q = new Query("Person").setFilter(heightOutOfRangeFilter); + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(a, c); + } + + @Test + public void queryRestrictions_compositeFilter_returnsMatchedEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + a.setProperty("birthYear", 1930); + Entity b = new Entity("Person", "b"); + b.setProperty("birthYear", 1960); + Entity c = new Entity("Person", "c"); + c.setProperty("birthYear", 1990); + datastore.put(ImmutableList.of(a, b, c)); + + // Act + long minBirthYear = 1940; + long maxBirthYear = 1980; + Filter birthYearMinFilter = + new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear); + + Filter birthYearMaxFilter = + new FilterPredicate("birthYear", FilterOperator.LESS_THAN_OR_EQUAL, maxBirthYear); + + Filter birthYearRangeFilter = + CompositeFilterOperator.and(birthYearMinFilter, birthYearMaxFilter); + + Query q = new Query("Person").setFilter(birthYearRangeFilter); + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(b); + } + + @Test + public void queryRestrictions_compositeFilter_isInvalid() throws Exception { + long minBirthYear = 1940; + long maxHeight = 200; + Filter birthYearMinFilter = + new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear); + + Filter heightMaxFilter = + new FilterPredicate("height", FilterOperator.LESS_THAN_OR_EQUAL, maxHeight); + + Filter invalidFilter = CompositeFilterOperator.and(birthYearMinFilter, heightMaxFilter); + + Query q = new Query("Person").setFilter(invalidFilter); + + // Note: The local devserver behavior is different than the production + // version of Cloud Datastore, so there aren't any assertions we can make + // in this test. The query appears to work with the local test runner, + // but will fail in production. + } + + @Test + public void queryRestrictions_compositeEqualFilter_returnsMatchedEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + a.setProperty("birthYear", 1930); + a.setProperty("city", "Somewhere"); + a.setProperty("lastName", "Someone"); + Entity b = new Entity("Person", "b"); + b.setProperty("birthYear", 1960); + b.setProperty("city", "Somewhere"); + b.setProperty("lastName", "Someone"); + Entity c = new Entity("Person", "c"); + c.setProperty("birthYear", 1990); + c.setProperty("city", "Somewhere"); + c.setProperty("lastName", "Someone"); + Entity d = new Entity("Person", "d"); + d.setProperty("birthYear", 1960); + d.setProperty("city", "Nowhere"); + d.setProperty("lastName", "Someone"); + Entity e = new Entity("Person", "e"); + e.setProperty("birthYear", 1960); + e.setProperty("city", "Somewhere"); + e.setProperty("lastName", "Noone"); + datastore.put(ImmutableList.of(a, b, c, d, e)); + long minBirthYear = 1940; + long maxBirthYear = 1980; + String targetCity = "Somewhere"; + String targetLastName = "Someone"; + + Filter lastNameFilter = new FilterPredicate("lastName", FilterOperator.EQUAL, targetLastName); + + Filter cityFilter = new FilterPredicate("city", FilterOperator.EQUAL, targetCity); + + Filter birthYearMinFilter = + new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear); + + Filter birthYearMaxFilter = + new FilterPredicate("birthYear", FilterOperator.LESS_THAN_OR_EQUAL, maxBirthYear); + + Filter validFilter = + CompositeFilterOperator.and( + lastNameFilter, cityFilter, birthYearMinFilter, birthYearMaxFilter); + + Query q = new Query("Person").setFilter(validFilter); + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(b); + } + + @Test + public void queryRestrictions_inequalitySortedFirst_returnsMatchedEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + a.setProperty("birthYear", 1930); + a.setProperty("lastName", "Someone"); + Entity b = new Entity("Person", "b"); + b.setProperty("birthYear", 1990); + b.setProperty("lastName", "Bravo"); + Entity c = new Entity("Person", "c"); + c.setProperty("birthYear", 1960); + c.setProperty("lastName", "Charlie"); + Entity d = new Entity("Person", "d"); + d.setProperty("birthYear", 1960); + d.setProperty("lastName", "Delta"); + datastore.put(ImmutableList.of(a, b, c, d)); + long minBirthYear = 1940; + + Filter birthYearMinFilter = + new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear); + + Query q = + new Query("Person") + .setFilter(birthYearMinFilter) + .addSort("birthYear", SortDirection.ASCENDING) + .addSort("lastName", SortDirection.ASCENDING); + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(c, d, b).inOrder(); + } + + @Test + public void queryRestrictions_missingSortOnInequality_isInvalid() throws Exception { + long minBirthYear = 1940; + Filter birthYearMinFilter = + new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear); + + // Not valid. Missing sort on birthYear. + Query q = + new Query("Person") + .setFilter(birthYearMinFilter) + .addSort("lastName", SortDirection.ASCENDING); + + // Note: The local devserver behavior is different than the production + // version of Cloud Datastore, so there aren't any assertions we can make + // in this test. The query appears to work with the local test runner, + // but will fail in production. + } + + @Test + public void queryRestrictions_sortWrongOrderOnInequality_isInvalid() throws Exception { + long minBirthYear = 1940; + Filter birthYearMinFilter = + new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear); + + // Not valid. Sort on birthYear needs to be first. + Query q = + new Query("Person") + .setFilter(birthYearMinFilter) + .addSort("lastName", SortDirection.ASCENDING) + .addSort("birthYear", SortDirection.ASCENDING); + + // Note: The local devserver behavior is different than the production + // version of Cloud Datastore, so there aren't any assertions we can make + // in this test. The query appears to work with the local test runner, + // but will fail in production. + } + + @Test + public void queryRestrictions_surprisingMultipleValuesAllMustMatch_returnsNoEntities() + throws Exception { + Entity a = new Entity("Widget", "a"); + List xs = Arrays.asList(1L, 2L); + a.setProperty("x", xs); + datastore.put(a); + + Query q = + new Query("Widget") + .setFilter( + CompositeFilterOperator.and( + new FilterPredicate("x", FilterOperator.GREATER_THAN, 1), + new FilterPredicate("x", FilterOperator.LESS_THAN, 2))); + + // Entity "a" will not match because no individual value matches all filters. + // See the documentation for more details: + // https://cloud.google.com/appengine/docs/java/datastore/query-restrictions + // #properties_with_multiple_values_can_behave_in_surprising_ways + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).isEmpty(); + } + + @Test + public void queryRestrictions_surprisingMultipleValuesEquals_returnsMatchedEntities() + throws Exception { + Entity a = new Entity("Widget", "a"); + a.setProperty("x", ImmutableList.of(1L, 2L)); + Entity b = new Entity("Widget", "b"); + b.setProperty("x", ImmutableList.of(1L, 3L)); + Entity c = new Entity("Widget", "c"); + c.setProperty("x", ImmutableList.of(-6L, 2L)); + Entity d = new Entity("Widget", "d"); + d.setProperty("x", ImmutableList.of(-6L, 4L)); + Entity e = new Entity("Widget", "e"); + e.setProperty("x", ImmutableList.of(1L, 2L, 3L)); + datastore.put(ImmutableList.of(a, b, c, d, e)); + + Query q = + new Query("Widget") + .setFilter( + CompositeFilterOperator.and( + new FilterPredicate("x", FilterOperator.EQUAL, 1), + new FilterPredicate("x", FilterOperator.EQUAL, 2))); + + // Only "a" and "e" have both 1 and 2 in the "x" array-valued property. + // See the documentation for more details: + // https://cloud.google.com/appengine/docs/java/datastore/query-restrictions + // #properties_with_multiple_values_can_behave_in_surprising_ways + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(a, e); + } + + @Test + public void queryRestrictions_surprisingMultipleValuesNotEquals_returnsMatchedEntities() + throws Exception { + Entity a = new Entity("Widget", "a"); + a.setProperty("x", ImmutableList.of(1L, 2L)); + Entity b = new Entity("Widget", "b"); + b.setProperty("x", ImmutableList.of(1L, 3L)); + Entity c = new Entity("Widget", "c"); + c.setProperty("x", ImmutableList.of(-6L, 2L)); + Entity d = new Entity("Widget", "d"); + d.setProperty("x", ImmutableList.of(-6L, 4L)); + Entity e = new Entity("Widget", "e"); + e.setProperty("x", ImmutableList.of(1L)); + datastore.put(ImmutableList.of(a, b, c, d, e)); + + Query q = new Query("Widget").setFilter(new FilterPredicate("x", FilterOperator.NOT_EQUAL, 1)); + + // The query matches any entity that has a some value other than 1. Only + // entity "e" is not matched. See the documentation for more details: + // https://cloud.google.com/appengine/docs/java/datastore/query-restrictions + // #properties_with_multiple_values_can_behave_in_surprising_ways + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(a, b, c, d); + } + + @Test + public void queryRestrictions_surprisingMultipleValuesTwoNotEquals_returnsMatchedEntities() + throws Exception { + Entity a = new Entity("Widget", "a"); + a.setProperty("x", ImmutableList.of(1L, 2L)); + Entity b = new Entity("Widget", "b"); + b.setProperty("x", ImmutableList.of(1L, 2L, 3L)); + datastore.put(ImmutableList.of(a, b)); + + Query q = + new Query("Widget") + .setFilter( + CompositeFilterOperator.and( + new FilterPredicate("x", FilterOperator.NOT_EQUAL, 1), + new FilterPredicate("x", FilterOperator.NOT_EQUAL, 2))); + + // The two NOT_EQUAL filters in the query become like the combination of queries: + // x < 1 OR (x > 1 AND x < 2) OR x > 2 + // + // Only "b" has some value which matches the "x > 2" portion of this query. + // + // See the documentation for more details: + // https://cloud.google.com/appengine/docs/java/datastore/query-restrictions + // #properties_with_multiple_values_can_behave_in_surprising_ways + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(b); + } + + private Entity retrievePersonWithLastName(String targetLastName) { + Query q = + new Query("Person") + .setFilter(new FilterPredicate("lastName", FilterOperator.EQUAL, targetLastName)); + + PreparedQuery pq = datastore.prepare(q); + Entity result = pq.asSingleEntity(); + return result; + } + + @Test + public void singleRetrievalExample_singleEntity_returnsEntity() throws Exception { + Entity a = new Entity("Person", "a"); + a.setProperty("lastName", "Johnson"); + Entity b = new Entity("Person", "b"); + b.setProperty("lastName", "Smith"); + datastore.put(ImmutableList.of(a, b)); + + Entity result = retrievePersonWithLastName("Johnson"); + + assertWithMessage("result") + .that(result) + .isEqualTo(a); // Note: Entity.equals() only checks the Key. + } + + @Test + public void singleRetrievalExample_multitpleEntities_throwsException() throws Exception { + Entity a = new Entity("Person", "a"); + a.setProperty("lastName", "Johnson"); + Entity b = new Entity("Person", "b"); + b.setProperty("lastName", "Johnson"); + datastore.put(ImmutableList.of(a, b)); + + try { + Entity result = retrievePersonWithLastName("Johnson"); + fail("Expected TooManyResultsException"); + } catch (TooManyResultsException expected) { + // TooManyResultsException does not provide addition details. + } + } + + private List getTallestPeople() { + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + + Query q = new Query("Person").addSort("height", SortDirection.DESCENDING); + + PreparedQuery pq = datastore.prepare(q); + return pq.asList(FetchOptions.Builder.withLimit(5)); + } + + @Test + public void queryLimitExample_returnsLimitedEntities() throws Exception { + Entity a = new Entity("Person", "a"); + a.setProperty("height", 200); + Entity b = new Entity("Person", "b"); + b.setProperty("height", 199); + Entity c = new Entity("Person", "c"); + c.setProperty("height", 201); + Entity d = new Entity("Person", "d"); + d.setProperty("height", 198); + Entity e = new Entity("Person", "e"); + e.setProperty("height", 202); + Entity f = new Entity("Person", "f"); + f.setProperty("height", 197); + Entity g = new Entity("Person", "g"); + g.setProperty("height", 203); + Entity h = new Entity("Person", "h"); + h.setProperty("height", 196); + datastore.put(ImmutableList.of(a, b, c, d, e, f, g, h)); + + List results = getTallestPeople(); + + assertWithMessage("results").that(results).containsExactly(g, e, c, a, b).inOrder(); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ReadPolicyTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ReadPolicyTest.java deleted file mode 120000 index 84a38cfa093..00000000000 --- a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ReadPolicyTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/ReadPolicyTest.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ReadPolicyTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ReadPolicyTest.java new file mode 100644 index 00000000000..f8a02b3828a --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/ReadPolicyTest.java @@ -0,0 +1,113 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertWithMessage; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceConfig; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.ReadPolicy; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for {@link ReadPolicy}. + */ +@RunWith(JUnit4.class) +public class ReadPolicyTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set 100% eventual consistency, so we can test with other job policies. + // https://cloud.google + // .com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(100)); + + @Before + public void setUp() { + helper.setUp(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void readPolicy_eventual_returnsNoResults() { + double deadline = 5.0; + + // Construct a read policy for eventual consistency + ReadPolicy policy = new ReadPolicy(ReadPolicy.Consistency.EVENTUAL); + + // Set the read policy + DatastoreServiceConfig eventuallyConsistentConfig = + DatastoreServiceConfig.Builder.withReadPolicy(policy); + + // Set the call deadline + DatastoreServiceConfig deadlineConfig = DatastoreServiceConfig.Builder.withDeadline(deadline); + + // Set both the read policy and the call deadline + DatastoreServiceConfig datastoreConfig = + DatastoreServiceConfig.Builder.withReadPolicy(policy).deadline(deadline); + + // Get Datastore service with the given configuration + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(datastoreConfig); + + Entity parent = new Entity("Person", "a"); + Entity child = new Entity("Person", "b", parent.getKey()); + datastore.put(ImmutableList.of(parent, child)); + + // Even though we are using an ancestor query, the policy is set to + // eventual, so we should get eventually-consistent results. Since the + // local data store test config is set to 100% unapplied jobs, there + // should be no results. + Query q = new Query("Person").setAncestor(parent.getKey()); + List results = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).isEmpty(); + } + + @Test + public void readPolicy_strong_returnsAllResults() { + double deadline = 5.0; + ReadPolicy policy = new ReadPolicy(ReadPolicy.Consistency.STRONG); + DatastoreServiceConfig datastoreConfig = + DatastoreServiceConfig.Builder.withReadPolicy(policy).deadline(deadline); + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(datastoreConfig); + + Entity parent = new Entity("Person", "a"); + Entity child = new Entity("Person", "b", parent.getKey()); + datastore.put(ImmutableList.of(parent, child)); + + Query q = new Query("Person").setAncestor(parent.getKey()); + List results = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).hasSize(2); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/StartupServletTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/StartupServletTest.java deleted file mode 120000 index cc898f78bbb..00000000000 --- a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/StartupServletTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/StartupServletTest.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/StartupServletTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/StartupServletTest.java new file mode 100644 index 00000000000..2da82842484 --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/StartupServletTest.java @@ -0,0 +1,108 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.Mockito.when; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.Filter; +import com.google.appengine.api.datastore.Query.FilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.io.PrintWriter; +import java.io.StringWriter; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link StartupServlet}. + */ +@RunWith(JUnit4.class) +public class StartupServletTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google + // .com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + @Mock + private HttpServletRequest mockRequest; + @Mock + private HttpServletResponse mockResponse; + private StringWriter responseWriter; + private DatastoreService datastore; + + private StartupServlet servletUnderTest; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + + // Set up a fake HTTP response. + responseWriter = new StringWriter(); + when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter)); + + servletUnderTest = new StartupServlet(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void doGet_emptyDatastore_writesOkay() throws Exception { + servletUnderTest.doGet(mockRequest, mockResponse); + assertWithMessage("StartupServlet response").that(responseWriter.toString()).isEqualTo("ok\n"); + } + + @Test + public void doGet_emptyDatastore_writesPresidents() throws Exception { + servletUnderTest.doGet(mockRequest, mockResponse); + + Filter nameFilter = new FilterPredicate("name", FilterOperator.EQUAL, "George Washington"); + Query q = new Query("Person").setFilter(nameFilter); + Entity result = datastore.prepare(q).asSingleEntity(); + assertWithMessage("name").that(result.getProperty("name")).isEqualTo("George Washington"); + } + + @Test + public void doGet_alreadyPopulated_writesOkay() throws Exception { + datastore.put( + new Entity(StartupServlet.IS_POPULATED_ENTITY, StartupServlet.IS_POPULATED_KEY_NAME)); + servletUnderTest.doGet(mockRequest, mockResponse); + assertWithMessage("StartupServlet response").that(responseWriter.toString()).isEqualTo("ok\n"); + } +} diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/TransactionsTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/TransactionsTest.java deleted file mode 120000 index 8c3e828ead3..00000000000 --- a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/TransactionsTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/TransactionsTest.java \ No newline at end of file diff --git a/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/TransactionsTest.java b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/TransactionsTest.java new file mode 100644 index 00000000000..0e9df0e093c --- /dev/null +++ b/appengine-java11-bundled-services/datastore/src/test/java/com/example/appengine/TransactionsTest.java @@ -0,0 +1,293 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertWithMessage; +import static org.junit.Assert.fail; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.EntityNotFoundException; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.KeyFactory; +import com.google.appengine.api.datastore.PreparedQuery; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Transaction; +import com.google.appengine.api.datastore.TransactionOptions; +import com.google.appengine.api.taskqueue.Queue; +import com.google.appengine.api.taskqueue.QueueFactory; +import com.google.appengine.api.taskqueue.TaskOptions; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.util.ConcurrentModificationException; +import java.util.Date; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests to demonstrate App Engine Datastore transactions. + */ +@RunWith(JUnit4.class) +public class TransactionsTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Use High Rep job policy to allow cross group transactions in tests. + new LocalDatastoreServiceTestConfig().setApplyAllHighRepJobPolicy()); + + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @After + public void tearDown() { + // Clean up any dangling transactions. + Transaction txn = datastore.getCurrentTransaction(null); + if (txn != null && txn.isActive()) { + txn.rollback(); + } + helper.tearDown(); + } + + @Test + public void usingTransactions() throws Exception { + Entity joe = new Entity("Employee", "Joe"); + datastore.put(joe); + + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + Transaction txn = datastore.beginTransaction(); + try { + Key employeeKey = KeyFactory.createKey("Employee", "Joe"); + Entity employee = datastore.get(employeeKey); + employee.setProperty("vacationDays", 10); + + datastore.put(txn, employee); + + txn.commit(); + } finally { + if (txn.isActive()) { + txn.rollback(); + } + } + } + + @Test + public void entityGroups() throws Exception { + try { + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + Entity person = new Entity("Person", "tom"); + datastore.put(person); + + // Transactions on root entities + Transaction txn = datastore.beginTransaction(); + + Entity tom = datastore.get(person.getKey()); + tom.setProperty("age", 40); + datastore.put(txn, tom); + txn.commit(); + + // Transactions on child entities + txn = datastore.beginTransaction(); + tom = datastore.get(person.getKey()); + Entity photo = new Entity("Photo", tom.getKey()); + + // Create a Photo that is a child of the Person entity named "tom" + photo.setProperty("photoUrl", "http://domain.com/path/to/photo.jpg"); + datastore.put(txn, photo); + txn.commit(); + + // Transactions on entities in different entity groups + txn = datastore.beginTransaction(); + tom = datastore.get(person.getKey()); + Entity photoNotaChild = new Entity("Photo"); + photoNotaChild.setProperty("photoUrl", "http://domain.com/path/to/photo.jpg"); + datastore.put(txn, photoNotaChild); + + // Throws IllegalArgumentException because the Person entity + // and the Photo entity belong to different entity groups. + txn.commit(); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + // We expect to get an exception that complains that we don't have a XG-transaction. + } + } + + @Test + @SuppressWarnings("checkstyle:AbbreviationAsWordInName") + public void creatingAnEntityInASpecificEntityGroup() throws Exception { + String boardName = "my-message-board"; + + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + + String messageTitle = "Some Title"; + String messageText = "Some message."; + Date postDate = new Date(); + + Key messageBoardKey = KeyFactory.createKey("MessageBoard", boardName); + + Entity message = new Entity("Message", messageBoardKey); + message.setProperty("message_title", messageTitle); + message.setProperty("message_text", messageText); + message.setProperty("post_date", postDate); + + Transaction txn = datastore.beginTransaction(); + datastore.put(txn, message); + + txn.commit(); + } + + @Test + public void crossGroupTransactions() throws Exception { + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + TransactionOptions options = TransactionOptions.Builder.withXG(true); + Transaction txn = datastore.beginTransaction(options); + + Entity a = new Entity("A"); + a.setProperty("a", 22); + datastore.put(txn, a); + + Entity b = new Entity("B"); + b.setProperty("b", 11); + datastore.put(txn, b); + + txn.commit(); + } + + @Test + public void usesForTransactions_relativeUpdates() throws Exception { + String boardName = "my-message-board"; + Entity b = new Entity("MessageBoard", boardName); + b.setProperty("count", 41); + datastore.put(b); + + int retries = 3; + while (true) { + Transaction txn = datastore.beginTransaction(); + try { + Key boardKey = KeyFactory.createKey("MessageBoard", boardName); + Entity messageBoard = datastore.get(boardKey); + + long count = (Long) messageBoard.getProperty("count"); + ++count; + messageBoard.setProperty("count", count); + datastore.put(txn, messageBoard); + + txn.commit(); + break; + } catch (ConcurrentModificationException e) { + if (retries == 0) { + throw e; + } + // Allow retry to occur + --retries; + } finally { + if (txn.isActive()) { + txn.rollback(); + } + } + } + + b = datastore.get(KeyFactory.createKey("MessageBoard", boardName)); + assertWithMessage("board.count").that((long) b.getProperty("count")).isEqualTo(42L); + } + + private Entity fetchOrCreate(String boardName) { + Transaction txn = datastore.beginTransaction(); + Entity messageBoard; + Key boardKey; + try { + boardKey = KeyFactory.createKey("MessageBoard", boardName); + messageBoard = datastore.get(boardKey); + } catch (EntityNotFoundException e) { + messageBoard = new Entity("MessageBoard", boardName); + messageBoard.setProperty("count", 0L); + boardKey = datastore.put(txn, messageBoard); + } + txn.commit(); + + return messageBoard; + } + + @Test + public void usesForTransactions_fetchOrCreate_fetchesExisting() throws Exception { + Entity b = new Entity("MessageBoard", "my-message-board"); + b.setProperty("count", 7); + datastore.put(b); + + Entity board = fetchOrCreate("my-message-board"); + + assertWithMessage("board.count").that((long) board.getProperty("count")).isEqualTo(7L); + } + + @Test + public void usesForTransactions_fetchOrCreate_createsNew() throws Exception { + Entity board = fetchOrCreate("my-message-board"); + assertWithMessage("board.count").that((long) board.getProperty("count")).isEqualTo(0L); + } + + @Test + public void usesForTransactions_readSnapshot() throws Exception { + String boardName = "my-message-board"; + Entity b = new Entity("MessageBoard", boardName); + b.setProperty("count", 13); + datastore.put(b); + + DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); + + // Display information about a message board and its first 10 messages. + Key boardKey = KeyFactory.createKey("MessageBoard", boardName); + + Transaction txn = datastore.beginTransaction(); + + Entity messageBoard = datastore.get(boardKey); + long count = (Long) messageBoard.getProperty("count"); + + Query q = new Query("Message", boardKey); + + // This is an ancestor query. + PreparedQuery pq = datastore.prepare(txn, q); + List messages = pq.asList(FetchOptions.Builder.withLimit(10)); + + txn.commit(); + + assertWithMessage("board.count").that(count).isEqualTo(13L); + } + + @Test + public void transactionalTaskEnqueuing() throws Exception { + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + Queue queue = QueueFactory.getDefaultQueue(); + Transaction txn = datastore.beginTransaction(); + // ... + + queue.add(txn, TaskOptions.Builder.withUrl("/path/to/handler")); + + // ... + + txn.commit(); + } +} diff --git a/appengine-java17-bundled-services/datastore/pom.xml b/appengine-java17-bundled-services/datastore/pom.xml index 21340039277..950596dbdef 100644 --- a/appengine-java17-bundled-services/datastore/pom.xml +++ b/appengine-java17-bundled-services/datastore/pom.xml @@ -126,6 +126,11 @@ 1.4.0 test + + jstl + jstl + 1.2 + diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbook.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbook.java deleted file mode 120000 index 59b8bfc0057..00000000000 --- a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbook.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/appengine/AbstractGuestbook.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbook.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbook.java new file mode 100644 index 00000000000..a7b46bc4727 --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbook.java @@ -0,0 +1,76 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.example.time.Clock; +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.users.User; +import com.google.appengine.api.users.UserService; +import com.google.appengine.api.users.UserServiceFactory; +import com.google.common.collect.ImmutableList; +import java.util.Date; +import java.util.List; + +/** + * A log of notes left by users. + * + *

This is meant to be subclassed to demonstrate different storage structures in Datastore. + */ +abstract class AbstractGuestbook { + + private final DatastoreService datastore; + private final UserService userService; + private final Clock clock; + + AbstractGuestbook(Clock clock) { + this.datastore = DatastoreServiceFactory.getDatastoreService(); + this.userService = UserServiceFactory.getUserService(); + this.clock = clock; + } + + /** + * Appends a new greeting to the guestbook and returns the {@link Entity} that was created. + **/ + public Greeting appendGreeting(String content) { + return Greeting.create( + createGreeting(datastore, userService.getCurrentUser(), Date.from(clock.now()), content)); + } + + /** + * Write a greeting to Datastore. + */ + protected abstract Entity createGreeting( + DatastoreService datastore, User user, Date date, String content); + + /** + * Return a list of the most recent greetings. + */ + public List listGreetings() { + ImmutableList.Builder greetings = ImmutableList.builder(); + for (Entity entity : listGreetingEntities(datastore)) { + greetings.add(Greeting.create(entity)); + } + return greetings.build(); + } + + /** + * Return a list of the most recent greetings. + */ + protected abstract List listGreetingEntities(DatastoreService datastore); +} diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbookServlet.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbookServlet.java deleted file mode 120000 index a91e7e09658..00000000000 --- a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbookServlet.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/appengine/AbstractGuestbookServlet.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbookServlet.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbookServlet.java new file mode 100644 index 00000000000..9ad8ec0f93e --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/AbstractGuestbookServlet.java @@ -0,0 +1,58 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +abstract class AbstractGuestbookServlet extends HttpServlet { + + private final AbstractGuestbook guestbook; + + public AbstractGuestbookServlet(AbstractGuestbook guestbook) { + this.guestbook = guestbook; + } + + private void renderGuestbook(HttpServletRequest req, HttpServletResponse resp) + throws IOException, ServletException { + resp.setContentType("text/html"); + resp.setCharacterEncoding("UTF-8"); + req.setAttribute("greetings", guestbook.listGreetings()); + req.getRequestDispatcher("/guestbook.jsp").forward(req, resp); + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws IOException, ServletException { + renderGuestbook(req, resp); + } + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) + throws IOException, ServletException { + String content = req.getParameter("content"); + if (content == null || content.isEmpty()) { + resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "missing content"); + return; + } + guestbook.appendGreeting(content); + resp.sendRedirect(req.getRequestURI()); + } +} diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/Greeting.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/Greeting.java deleted file mode 120000 index 9c59e2047b8..00000000000 --- a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/Greeting.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/appengine/Greeting.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/Greeting.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/Greeting.java new file mode 100644 index 00000000000..ff45e508205 --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/Greeting.java @@ -0,0 +1,42 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.users.User; +import com.google.auto.value.AutoValue; +import java.time.Instant; +import java.util.Date; +import javax.annotation.Nullable; + +@AutoValue +public abstract class Greeting { + + static Greeting create(Entity entity) { + User user = (User) entity.getProperty("user"); + Instant date = ((Date) entity.getProperty("date")).toInstant(); + String content = (String) entity.getProperty("content"); + return new AutoValue_Greeting(user, date, content); + } + + @Nullable + public abstract User getUser(); + + public abstract Instant getDate(); + + public abstract String getContent(); +} diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/Guestbook.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/Guestbook.java deleted file mode 120000 index 2e3d8e4b664..00000000000 --- a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/Guestbook.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/appengine/Guestbook.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/Guestbook.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/Guestbook.java new file mode 100644 index 00000000000..ebacbf540ae --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/Guestbook.java @@ -0,0 +1,59 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.example.time.Clock; +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.users.User; +import java.util.Date; +import java.util.List; + +/** + * A log of notes left by users. + * + *

This demonstrates the use of Google Cloud Datastore using the App Engine APIs. See the documentation for more + * information. + */ +class Guestbook extends AbstractGuestbook { + + Guestbook(Clock clock) { + super(clock); + } + + @Override + protected Entity createGreeting( + DatastoreService datastore, User user, Date date, String content) { + // No parent key specified, so Greeting is a root entity. + Entity greeting = new Entity("Greeting"); + greeting.setProperty("user", user); + greeting.setProperty("date", date); + greeting.setProperty("content", content); + + datastore.put(greeting); + return greeting; + } + + @Override + protected List listGreetingEntities(DatastoreService datastore) { + Query query = new Query("Greeting").addSort("date", Query.SortDirection.DESCENDING); + return datastore.prepare(query).asList(FetchOptions.Builder.withLimit(10)); + } +} diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookServlet.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookServlet.java deleted file mode 120000 index 752427a8057..00000000000 --- a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookServlet.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/appengine/GuestbookServlet.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookServlet.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookServlet.java new file mode 100644 index 00000000000..019e5a4210c --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookServlet.java @@ -0,0 +1,26 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.example.time.SystemClock; + +public class GuestbookServlet extends AbstractGuestbookServlet { + + public GuestbookServlet() { + super(new Guestbook(new SystemClock())); + } +} diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrong.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrong.java deleted file mode 120000 index fef89e36ba8..00000000000 --- a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrong.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/appengine/GuestbookStrong.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrong.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrong.java new file mode 100644 index 00000000000..a3c1bc08743 --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrong.java @@ -0,0 +1,71 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.example.time.Clock; +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.KeyFactory; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.users.User; +import java.util.Date; +import java.util.List; + +/** + * A log of notes left by users. + * + *

This demonstrates the use of Google Cloud Datastore using the App Engine APIs. See the documentation for more + * information. + */ +class GuestbookStrong extends AbstractGuestbook { + + private final String guestbookName; + + GuestbookStrong(String guestbookName, Clock clock) { + super(clock); + this.guestbookName = guestbookName; + } + + @Override + protected Entity createGreeting( + DatastoreService datastore, User user, Date date, String content) { + // String guestbookName = "my guestbook"; -- Set elsewhere (injected to the constructor). + Key guestbookKey = KeyFactory.createKey("Guestbook", guestbookName); + + // Place greeting in the same entity group as guestbook. + Entity greeting = new Entity("Greeting", guestbookKey); + greeting.setProperty("user", user); + greeting.setProperty("date", date); + greeting.setProperty("content", content); + + datastore.put(greeting); + return greeting; + } + + @Override + protected List listGreetingEntities(DatastoreService datastore) { + Key guestbookKey = KeyFactory.createKey("Guestbook", guestbookName); + Query query = + new Query("Greeting", guestbookKey) + .setAncestor(guestbookKey) + .addSort("date", Query.SortDirection.DESCENDING); + return datastore.prepare(query).asList(FetchOptions.Builder.withLimit(10)); + } +} diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrongServlet.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrongServlet.java deleted file mode 120000 index f2d53752473..00000000000 --- a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrongServlet.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/appengine/GuestbookStrongServlet.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrongServlet.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrongServlet.java new file mode 100644 index 00000000000..2861c88d991 --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/GuestbookStrongServlet.java @@ -0,0 +1,28 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.example.time.SystemClock; + +public class GuestbookStrongServlet extends AbstractGuestbookServlet { + + public static final String GUESTBOOK_ID = "my guestbook"; + + public GuestbookStrongServlet() { + super(new GuestbookStrong(GUESTBOOK_ID, new SystemClock())); + } +} diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/ListPeopleServlet.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/ListPeopleServlet.java deleted file mode 120000 index fc42fbc3dcd..00000000000 --- a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/ListPeopleServlet.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/appengine/ListPeopleServlet.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/ListPeopleServlet.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/ListPeopleServlet.java new file mode 100644 index 00000000000..11656f22e94 --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/ListPeopleServlet.java @@ -0,0 +1,91 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + + +import com.google.appengine.api.datastore.Cursor; +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.PreparedQuery; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.SortDirection; +import com.google.appengine.api.datastore.QueryResultList; +import java.io.IOException; +import java.io.PrintWriter; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class ListPeopleServlet extends HttpServlet { + + static final int PAGE_SIZE = 15; + private final DatastoreService datastore; + + public ListPeopleServlet() { + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + FetchOptions fetchOptions = FetchOptions.Builder.withLimit(PAGE_SIZE); + + // If this servlet is passed a cursor parameter, let's use it. + String startCursor = req.getParameter("cursor"); + if (startCursor != null) { + fetchOptions.startCursor(Cursor.fromWebSafeString(startCursor)); + } + + Query q = new Query("Person").addSort("name", SortDirection.ASCENDING); + PreparedQuery pq = datastore.prepare(q); + + QueryResultList results; + try { + results = pq.asQueryResultList(fetchOptions); + } catch (IllegalArgumentException e) { + // IllegalArgumentException happens when an invalid cursor is used. + // A user could have manually entered a bad cursor in the URL or there + // may have been an internal implementation detail change in App Engine. + // Redirect to the page without the cursor parameter to show something + // rather than an error. + resp.sendRedirect("/people"); + return; + } + + resp.setContentType("text/html"); + resp.setCharacterEncoding("UTF-8"); + PrintWriter w = resp.getWriter(); + w.println(""); + w.println(""); + w.println("Cloud Datastore Cursor Sample"); + w.println("

    "); + for (Entity entity : results) { + w.println("
  • " + entity.getProperty("name") + "
  • "); + } + w.println("
"); + + String cursorString = results.getCursor().toWebSafeString(); + + // This servlet lives at '/people'. + if (results.size() == PAGE_SIZE) { + w.println("Next page"); + } + } +} diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/ProjectionServlet.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/ProjectionServlet.java deleted file mode 120000 index ceb105b6acf..00000000000 --- a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/ProjectionServlet.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/appengine/ProjectionServlet.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/ProjectionServlet.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/ProjectionServlet.java new file mode 100644 index 00000000000..d062f9d6d17 --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/ProjectionServlet.java @@ -0,0 +1,78 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.KeyFactory; +import com.google.appengine.api.datastore.PropertyProjection; +import com.google.appengine.api.datastore.Query; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Date; +import java.util.List; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Servlet to demonstrate use of Datastore projection queries. + * + *

See the + * documentation + * for using Datastore projection queries from the Google App Engine standard environment. + */ +@SuppressWarnings("serial") +public class ProjectionServlet extends HttpServlet { + + private static final String GUESTBOOK_ID = GuestbookStrongServlet.GUESTBOOK_ID; + private final DatastoreService datastore; + + public ProjectionServlet() { + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + resp.setContentType("text/plain"); + resp.setCharacterEncoding("UTF-8"); + PrintWriter out = resp.getWriter(); + out.printf("Latest entries from guestbook: \n"); + + Key guestbookKey = KeyFactory.createKey("Guestbook", GUESTBOOK_ID); + Query query = new Query("Greeting", guestbookKey); + addGuestbookProjections(query); + printGuestbookEntries(datastore, query, out); + } + + private void addGuestbookProjections(Query query) { + query.addProjection(new PropertyProjection("content", String.class)); + query.addProjection(new PropertyProjection("date", Date.class)); + } + + private void printGuestbookEntries(DatastoreService datastore, Query query, PrintWriter out) { + List guests = datastore.prepare(query).asList(FetchOptions.Builder.withLimit(5)); + for (Entity guest : guests) { + String content = (String) guest.getProperty("content"); + Date stamp = (Date) guest.getProperty("date"); + out.printf("Message %s posted on %s.\n", content, stamp.toString()); + } + } +} diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/StartupServlet.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/StartupServlet.java deleted file mode 120000 index eae75ba9016..00000000000 --- a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/StartupServlet.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/appengine/StartupServlet.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/StartupServlet.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/StartupServlet.java new file mode 100644 index 00000000000..9f4d2d06970 --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/StartupServlet.java @@ -0,0 +1,119 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.EntityNotFoundException; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.KeyFactory; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * A startup handler to populate the datastore with example entities. + */ +public class StartupServlet extends HttpServlet { + + static final String IS_POPULATED_ENTITY = "IsPopulated"; + static final String IS_POPULATED_KEY_NAME = "is-populated"; + + private static final String PERSON_ENTITY = "Person"; + private static final String NAME_PROPERTY = "name"; + private static final ImmutableList US_PRESIDENTS = + ImmutableList.builder() + .add("George Washington") + .add("John Adams") + .add("Thomas Jefferson") + .add("James Madison") + .add("James Monroe") + .add("John Quincy Adams") + .add("Andrew Jackson") + .add("Martin Van Buren") + .add("William Henry Harrison") + .add("John Tyler") + .add("James K. Polk") + .add("Zachary Taylor") + .add("Millard Fillmore") + .add("Franklin Pierce") + .add("James Buchanan") + .add("Abraham Lincoln") + .add("Andrew Johnson") + .add("Ulysses S. Grant") + .add("Rutherford B. Hayes") + .add("James A. Garfield") + .add("Chester A. Arthur") + .add("Grover Cleveland") + .add("Benjamin Harrison") + .add("Grover Cleveland") + .add("William McKinley") + .add("Theodore Roosevelt") + .add("William Howard Taft") + .add("Woodrow Wilson") + .add("Warren G. Harding") + .add("Calvin Coolidge") + .add("Herbert Hoover") + .add("Franklin D. Roosevelt") + .add("Harry S. Truman") + .add("Dwight D. Eisenhower") + .add("John F. Kennedy") + .add("Lyndon B. Johnson") + .add("Richard Nixon") + .add("Gerald Ford") + .add("Jimmy Carter") + .add("Ronald Reagan") + .add("George H. W. Bush") + .add("Bill Clinton") + .add("George W. Bush") + .add("Barack Obama") + .build(); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/plain"); + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + + Key isPopulatedKey = KeyFactory.createKey(IS_POPULATED_ENTITY, IS_POPULATED_KEY_NAME); + boolean isAlreadyPopulated; + try { + datastore.get(isPopulatedKey); + isAlreadyPopulated = true; + } catch (EntityNotFoundException expected) { + isAlreadyPopulated = false; + } + if (isAlreadyPopulated) { + resp.getWriter().println("ok"); + return; + } + + ImmutableList.Builder people = ImmutableList.builder(); + for (String name : US_PRESIDENTS) { + Entity person = new Entity(PERSON_ENTITY); + person.setProperty(NAME_PROPERTY, name); + people.add(person); + } + datastore.put(people.build()); + datastore.put(new Entity(isPopulatedKey)); + resp.getWriter().println("ok"); + } +} diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/StatsServlet.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/StatsServlet.java deleted file mode 120000 index 6502647321e..00000000000 --- a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/StatsServlet.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/appengine/StatsServlet.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/StatsServlet.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/StatsServlet.java new file mode 100644 index 00000000000..18cd68e9674 --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/main/java/com/example/appengine/StatsServlet.java @@ -0,0 +1,45 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.Query; +import java.io.IOException; +import java.io.PrintWriter; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class StatsServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + Entity globalStat = datastore.prepare(new Query("__Stat_Total__")).asSingleEntity(); + Long totalBytes = (Long) globalStat.getProperty("bytes"); + Long totalEntities = (Long) globalStat.getProperty("count"); + + resp.setContentType("text/plain"); + resp.setCharacterEncoding("UTF-8"); + PrintWriter w = resp.getWriter(); + w.printf("%d bytes\n%d entities\n", totalBytes, totalEntities); + } +} diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/time/Clock.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/time/Clock.java deleted file mode 120000 index 2413e2ddc58..00000000000 --- a/appengine-java17-bundled-services/datastore/src/main/java/com/example/time/Clock.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/time/Clock.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/time/Clock.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/time/Clock.java new file mode 100644 index 00000000000..a703577d7f9 --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/main/java/com/example/time/Clock.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.time; + +import java.time.Instant; + +/** + * Provides the current value of "now." To preserve testability, avoid all other libraries that + * access the system clock (whether {@linkplain System#currentTimeMillis directly} or {@linkplain + * java.time.Instant#now() indirectly}). + * + *

In production, use the {@link SystemClock} implementation to return the "real" system time. In + * tests, either use {@link com.example.time.testing.FakeClock}, or get an instance from a mocking + * framework such as Mockito. + */ +public interface Clock { + + /** + * Returns the current, absolute time according to this clock. + */ + Instant now(); +} diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/time/SystemClock.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/time/SystemClock.java deleted file mode 120000 index 4193c4824ac..00000000000 --- a/appengine-java17-bundled-services/datastore/src/main/java/com/example/time/SystemClock.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/main/java/com/example/time/SystemClock.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/time/SystemClock.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/time/SystemClock.java new file mode 100644 index 00000000000..dcde41330f6 --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/main/java/com/example/time/SystemClock.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.time; + +import java.time.Instant; + +/** + * Clock implementation that returns the "real" system time. + * + *

This class exists so that we can use a fake implementation for unit testing classes that need + * the current time value. See {@link Clock} for general information about clocks. + */ +public class SystemClock implements Clock { + + /** + * Creates a new instance. All {@code SystemClock} instances function identically. + */ + public SystemClock() { + } + + @Override + public Instant now() { + return Instant.now(); + } +} diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/time/testing/FakeClock.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/time/testing/FakeClock.java deleted file mode 120000 index b963230e3c4..00000000000 --- a/appengine-java17-bundled-services/datastore/src/main/java/com/example/time/testing/FakeClock.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../../appengine-java8/datastore/src/main/java/com/example/time/testing/FakeClock.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/main/java/com/example/time/testing/FakeClock.java b/appengine-java17-bundled-services/datastore/src/main/java/com/example/time/testing/FakeClock.java new file mode 100644 index 00000000000..a08343e51b6 --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/main/java/com/example/time/testing/FakeClock.java @@ -0,0 +1,180 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.time.testing; + +import com.example.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.util.concurrent.atomic.AtomicLong; + +/** + * A Clock that returns a fixed Instant value as the current clock time. The fixed Instant is + * settable for testing. Test code should hold a reference to the FakeClock, while code under test + * should hold a Clock reference. + * + *

The clock time can be incremented/decremented manually, with {@link #incrementTime} and {@link + * #decrementTime} respectively. + * + *

The clock can also be configured so that the time is incremented whenever {@link #now()} is + * called: see {@link #setAutoIncrementStep}. + */ +public class FakeClock implements Clock { + + private static final Instant DEFAULT_TIME = Instant.ofEpochMilli(1000000000L); + private final long baseTimeMs; + private final AtomicLong fakeNowMs; + private volatile long autoIncrementStepMs; + + /** + * Creates a FakeClock instance initialized to an arbitrary constant. + */ + public FakeClock() { + this(DEFAULT_TIME); + } + + /** + * Creates a FakeClock instance initialized to the given time. + */ + public FakeClock(Instant now) { + baseTimeMs = now.toEpochMilli(); + fakeNowMs = new AtomicLong(baseTimeMs); + } + + /** + * Sets the value of the underlying instance for testing purposes. + * + * @return this + */ + public FakeClock setNow(Instant now) { + fakeNowMs.set(now.toEpochMilli()); + return this; + } + + @Override + public Instant now() { + return getAndAdd(autoIncrementStepMs); + } + + /** + * Returns the current time without applying an auto increment, if configured. The default + * behavior of {@link #now()} is the same as this method. + */ + public Instant peek() { + return Instant.ofEpochMilli(fakeNowMs.get()); + } + + /** + * Reset the given clock back to the base time with which the FakeClock was initially + * constructed. + * + * @return this + */ + public FakeClock resetTime() { + fakeNowMs.set(baseTimeMs); + return this; + } + + /** + * Increments the clock time by the given duration. + * + * @param duration the duration to increment the clock time by + * @return this + */ + public FakeClock incrementTime(Duration duration) { + incrementTime(duration.toMillis()); + return this; + } + + /** + * Increments the clock time by the given duration. + * + * @param durationMs the duration to increment the clock time by, in milliseconds + * @return this + */ + public FakeClock incrementTime(long durationMs) { + fakeNowMs.addAndGet(durationMs); + return this; + } + + /** + * Decrements the clock time by the given duration. + * + * @param duration the duration to decrement the clock time by + * @return this + */ + public FakeClock decrementTime(Duration duration) { + incrementTime(-duration.toMillis()); + return this; + } + + /** + * Decrements the clock time by the given duration. + * + * @param durationMs the duration to decrement the clock time by, in milliseconds + * @return this + */ + public FakeClock decrementTime(long durationMs) { + incrementTime(-durationMs); + return this; + } + + /** + * Sets the increment applied to the clock whenever it is queried. The increment is zero by + * default: the clock is left unchanged when queried. + * + * @param autoIncrementStep the new auto increment duration + * @return this + */ + public FakeClock setAutoIncrementStep(Duration autoIncrementStep) { + setAutoIncrementStep(autoIncrementStep.toMillis()); + return this; + } + + /** + * Sets the increment applied to the clock whenever it is queried. The increment is zero by + * default: the clock is left unchanged when queried. + * + * @param autoIncrementStepMs the new auto increment duration, in milliseconds + * @return this + */ + public FakeClock setAutoIncrementStep(long autoIncrementStepMs) { + this.autoIncrementStepMs = autoIncrementStepMs; + return this; + } + + /** + * Atomically adds the given value to the current time. + * + * @param durationMs the duration to add, in milliseconds + * @return the updated current time + * @see AtomicLong#addAndGet + */ + protected final Instant addAndGet(long durationMs) { + return Instant.ofEpochMilli(fakeNowMs.addAndGet(durationMs)); + } + + /** + * Atomically adds the given value to the current time. + * + * @param durationMs the duration to add, in milliseconds + * @return the previous time + * @see AtomicLong#getAndAdd + */ + protected final Instant getAndAdd(long durationMs) { + return Instant.ofEpochMilli(fakeNowMs.getAndAdd(durationMs)); + } +} diff --git a/appengine-java17-bundled-services/datastore/src/main/webapp/WEB-INF/datastore-indexes.xml b/appengine-java17-bundled-services/datastore/src/main/webapp/WEB-INF/datastore-indexes.xml deleted file mode 120000 index 73f343b36e4..00000000000 --- a/appengine-java17-bundled-services/datastore/src/main/webapp/WEB-INF/datastore-indexes.xml +++ /dev/null @@ -1 +0,0 @@ -../../../../../../appengine-java8/datastore/src/main/webapp/WEB-INF/datastore-indexes.xml \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/main/webapp/WEB-INF/datastore-indexes.xml b/appengine-java17-bundled-services/datastore/src/main/webapp/WEB-INF/datastore-indexes.xml new file mode 100644 index 00000000000..c99175eba3b --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/main/webapp/WEB-INF/datastore-indexes.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + diff --git a/appengine-java17-bundled-services/datastore/src/main/webapp/WEB-INF/web.xml b/appengine-java17-bundled-services/datastore/src/main/webapp/WEB-INF/web.xml deleted file mode 120000 index 6c846cab2a4..00000000000 --- a/appengine-java17-bundled-services/datastore/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1 +0,0 @@ -../../../../../../appengine-java8/datastore/src/main/webapp/WEB-INF/web.xml \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/main/webapp/WEB-INF/web.xml b/appengine-java17-bundled-services/datastore/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..dddc47141c7 --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,98 @@ + + + + + guestbook-strong + com.example.appengine.GuestbookStrongServlet + + + guestbook-strong + / + + + guestbook + com.example.appengine.GuestbookServlet + + + guestbook + /guestbook + + + people + com.example.appengine.ListPeopleServlet + + + people + /people + + + projection + com.example.appengine.ProjectionServlet + + + projection + /projection + + + stats + com.example.appengine.StatsServlet + + + stats + /stats + + + + + startup + com.example.appengine.StartupServlet + + + startup + /_ah/start + + + + + profile + /* + + + CONFIDENTIAL + + + * + + + + + + profile + /stats + + + CONFIDENTIAL + + + admin + + + diff --git a/appengine-java17-bundled-services/datastore/src/main/webapp/guestbook.jsp b/appengine-java17-bundled-services/datastore/src/main/webapp/guestbook.jsp deleted file mode 120000 index dc4c9b4a240..00000000000 --- a/appengine-java17-bundled-services/datastore/src/main/webapp/guestbook.jsp +++ /dev/null @@ -1 +0,0 @@ -../../../../../appengine-java8/datastore/src/main/webapp/guestbook.jsp \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/main/webapp/guestbook.jsp b/appengine-java17-bundled-services/datastore/src/main/webapp/guestbook.jsp new file mode 100644 index 00000000000..2cf678f30ae --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/main/webapp/guestbook.jsp @@ -0,0 +1,45 @@ + + +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> + + + + Guestbook + + +

Latest Greetings

+ +

+
+ Posted: ${greeting.date} +

+
+ +

Add Greeting

+
+

+ + +

+

+ +

+
+ + + diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/EntitiesTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/EntitiesTest.java deleted file mode 120000 index 6acecc301aa..00000000000 --- a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/EntitiesTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/EntitiesTest.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/EntitiesTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/EntitiesTest.java new file mode 100644 index 00000000000..ef1970577b2 --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/EntitiesTest.java @@ -0,0 +1,337 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static org.junit.Assert.fail; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.EmbeddedEntity; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.EntityNotFoundException; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.KeyFactory; +import com.google.appengine.api.datastore.KeyRange; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests to demonstrate App Engine Datastore entities. */ +@RunWith(JUnit4.class) +public class EntitiesTest { + + // Set no eventual consistency, that way queries return all results. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void kindExample_writesEntity() throws Exception { + Entity employee = new Entity("Employee", "asalieri"); + employee.setProperty("firstName", "Antonio"); + employee.setProperty("lastName", "Salieri"); + employee.setProperty("hireDate", new Date()); + employee.setProperty("attendedHrTraining", true); + + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + datastore.put(employee); + + Entity got = datastore.get(employee.getKey()); + assertWithMessage("got.firstName") + .that((String) got.getProperty("firstName")) + .isEqualTo("Antonio"); + assertWithMessage("got.lastName") + .that((String) got.getProperty("lastName")) + .isEqualTo("Salieri"); + assertWithMessage("got.hireDate").that((Date) got.getProperty("hireDate")).isNotNull(); + assertWithMessage("got.attendedHrTraining") + .that((boolean) got.getProperty("attendedHrTraining")) + .isTrue(); + } + + @Test + public void identifiers_keyName_setsKeyName() throws Exception { + Entity employee = new Entity("Employee", "asalieri"); + datastore.put(employee); + + assertWithMessage("key name").that(employee.getKey().getName()).isEqualTo("asalieri"); + } + + @Test + public void identifiers_autoId_setsUnallocatedId() throws Exception { + KeyRange keys = datastore.allocateIds("Employee", 1); + long usedId = keys.getStart().getId(); + + Entity employee = new Entity("Employee"); + datastore.put(employee); + + assertWithMessage("key id").that(employee.getKey().getId()).isNotEqualTo(usedId); + } + + @Test + public void parent_withinEntityConstructor_setsParent() throws Exception { + Entity employee = new Entity("Employee"); + datastore.put(employee); + + Entity address = new Entity("Address", employee.getKey()); + datastore.put(address); + + assertWithMessage("address parent").that(address.getParent()).isEqualTo(employee.getKey()); + } + + @Test + public void parent_withKeyName_setsKeyName() throws Exception { + Entity employee = new Entity("Employee"); + datastore.put(employee); + + Entity address = new Entity("Address", "addr1", employee.getKey()); + datastore.put(address); + + assertWithMessage("address key name").that(address.getKey().getName()).isEqualTo("addr1"); + } + + @Test + public void datastoreServiceFactory_returnsDatastoreService() throws Exception { + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + assertWithMessage("datastore").that(datastore).isNotNull(); + } + + @Test + public void creatingAnEntity_withKeyName_writesEntity() throws Exception { + Entity employee = new Entity("Employee", "asalieri"); + // Set the entity properties. + // ... + datastore.put(employee); + + assertWithMessage("employee key name").that(employee.getKey().getName()).isEqualTo("asalieri"); + } + + private Key writeEmptyEmployee() { + Entity employee = new Entity("Employee"); + // Set the entity properties. + // ... + datastore.put(employee); + return employee.getKey(); + } + + @Test + public void creatingAnEntity_withoutKeyName_writesEntity() throws Exception { + Key employeeKey = writeEmptyEmployee(); + // Key employeeKey = ...; + Entity employee = datastore.get(employeeKey); + + assertWithMessage("retrieved key ID") + .that(employee.getKey().getId()) + .isEqualTo(employeeKey.getId()); + } + + @Test + public void deletingAnEntity_deletesAnEntity() throws Exception { + Entity employee = new Entity("Employee", "asalieri"); + datastore.put(employee); + + Key employeeKey = KeyFactory.createKey("Employee", "asalieri"); + // Key employeeKey = ...; + datastore.delete(employeeKey); + + try { + Entity got = datastore.get(employeeKey); + fail("Expected EntityNotFoundException"); + } catch (EntityNotFoundException expected) { + assertWithMessage("exception key name") + .that(expected.getKey().getName()) + .isEqualTo("asalieri"); + } + } + + @Test + public void repeatedProperties_storesList() throws Exception { + Entity employee = new Entity("Employee"); + ArrayList favoriteFruit = new ArrayList<>(); + favoriteFruit.add("Pear"); + favoriteFruit.add("Apple"); + employee.setProperty("favoriteFruit", favoriteFruit); + datastore.put(employee); + + // Sometime later + employee = datastore.get(employee.getKey()); + @SuppressWarnings("unchecked") // Cast can't verify generic type. + ArrayList retrievedFruits = (ArrayList) employee.getProperty("favoriteFruit"); + + assertThat(retrievedFruits).containsExactlyElementsIn(favoriteFruit).inOrder(); + } + + // CHECKSTYLE.OFF: VariableDeclarationUsageDistance + @SuppressWarnings("VariableDeclarationUsageDistance") + @Test + public void embeddedEntity_fromEmbedded_embedsProperties() throws Exception { + Entity employee = new Entity("Employee"); + // Entity employee = ...; + EmbeddedEntity embeddedContactInfo = new EmbeddedEntity(); + + embeddedContactInfo.setProperty("homeAddress", "123 Fake St, Made, UP 45678"); + embeddedContactInfo.setProperty("phoneNumber", "555-555-5555"); + embeddedContactInfo.setProperty("emailAddress", "test@example.com"); + + employee.setProperty("contactInfo", embeddedContactInfo); + datastore.put(employee); + + Entity gotEmployee = datastore.get(employee.getKey()); + EmbeddedEntity got = (EmbeddedEntity) gotEmployee.getProperty("contactInfo"); + assertWithMessage("got.homeAddress") + .that((String) got.getProperty("homeAddress")) + .isEqualTo("123 Fake St, Made, UP 45678"); + } + // CHECKSTYLE.ON: VariableDeclarationUsageDistance + + private Key putEmployeeWithContactInfo(Entity contactInfo) { + Entity employee = new Entity("Employee"); + // Entity employee = ...; + // Entity contactInfo = ...; + EmbeddedEntity embeddedContactInfo = new EmbeddedEntity(); + + embeddedContactInfo.setKey(contactInfo.getKey()); // Optional, used so we can recover original. + embeddedContactInfo.setPropertiesFrom(contactInfo); + + employee.setProperty("contactInfo", embeddedContactInfo); + datastore.put(employee); + return employee.getKey(); + } + + @Test + public void embeddedEntity_fromExisting_canRecover() throws Exception { + Entity initialContactInfo = new Entity("Contact"); + initialContactInfo.setProperty("homeAddress", "123 Fake St, Made, UP 45678"); + initialContactInfo.setProperty("phoneNumber", "555-555-5555"); + initialContactInfo.setProperty("emailAddress", "test@example.com"); + datastore.put(initialContactInfo); + Key employeeKey = putEmployeeWithContactInfo(initialContactInfo); + + Entity employee = datastore.get(employeeKey); + EmbeddedEntity embeddedContactInfo = (EmbeddedEntity) employee.getProperty("contactInfo"); + + Key infoKey = embeddedContactInfo.getKey(); + Entity contactInfo = new Entity(infoKey); + contactInfo.setPropertiesFrom(embeddedContactInfo); + datastore.put(contactInfo); + + Entity got = datastore.get(infoKey); + assertThat(got.getKey()).isEqualTo(initialContactInfo.getKey()); + assertWithMessage("got.homeAddress") + .that((String) got.getProperty("homeAddress")) + .isEqualTo("123 Fake St, Made, UP 45678"); + } + + @Test + public void batchOperations_putsEntities() { + Entity employee1 = new Entity("Employee"); + Entity employee2 = new Entity("Employee"); + Entity employee3 = new Entity("Employee"); + // [START_EXCLUDE] + employee1.setProperty("firstName", "Bill"); + employee2.setProperty("firstName", "Jane"); + employee3.setProperty("firstName", "Alex"); + // [END_EXCLUDE] + + List employees = Arrays.asList(employee1, employee2, employee3); + datastore.put(employees); + + Map got = + datastore.get(Arrays.asList(employee1.getKey(), employee2.getKey(), employee3.getKey())); + assertWithMessage("employee1.firstName") + .that((String) got.get(employee1.getKey()).getProperty("firstName")) + .isEqualTo("Bill"); + assertWithMessage("employee2.firstName") + .that((String) got.get(employee2.getKey()).getProperty("firstName")) + .isEqualTo("Jane"); + assertWithMessage("employee3.firstName") + .that((String) got.get(employee3.getKey()).getProperty("firstName")) + .isEqualTo("Alex"); + } + + @Test + public void createKey_makesKey() { + Key k1 = KeyFactory.createKey("Person", "GreatGrandpa"); + Key k2 = KeyFactory.createKey("Person", 74219); + + assertThat(k1).isNotNull(); + assertThat(k2).isNotNull(); + } + + @Test + public void keyFactoryBuilder_makeKeyWithParents() { + Key greatKey = KeyFactory.createKey("Person", "GreatGrandpa"); + Key grandKey = KeyFactory.createKey(greatKey, "Person", "Grandpa"); + Key dadKey = KeyFactory.createKey(grandKey, "Person", "Dad"); + Key meKey = KeyFactory.createKey(dadKey, "Person", "Me"); + + Key k = + new KeyFactory.Builder("Person", "GreatGrandpa") + .addChild("Person", "Grandpa") + .addChild("Person", "Dad") + .addChild("Person", "Me") + .getKey(); + + assertThat(k).isEqualTo(meKey); + } + + @Test + public void keyToString_getsPerson() throws Exception { + Entity p = new Entity("Person"); + p.setProperty("relationship", "Me"); + datastore.put(p); + Key k = p.getKey(); + + String personKeyStr = KeyFactory.keyToString(k); + + // Some time later (for example, after using personKeyStr in a link). + Key personKey = KeyFactory.stringToKey(personKeyStr); + Entity person = datastore.get(personKey); + + assertThat(personKey).isEqualTo(k); + assertWithMessage("person.relationship") + .that((String) person.getProperty("relationship")) + .isEqualTo("Me"); + } +} diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookStrongTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookStrongTest.java deleted file mode 120000 index 3541f8f82bb..00000000000 --- a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookStrongTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/GuestbookStrongTest.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookStrongTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookStrongTest.java new file mode 100644 index 00000000000..ca73e082d82 --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookStrongTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import com.example.time.testing.FakeClock; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import com.google.appengine.tools.development.testing.LocalUserServiceTestConfig; +import java.time.Instant; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for {@link GuestbookStrong}. + */ +@RunWith(JUnit4.class) +public class GuestbookStrongTest { + + private static final Instant FAKE_NOW = Instant.ofEpochMilli(1234567890L); + private static final String GUESTBOOK_ID = "my guestbook"; + + // Set maximum eventual consistency. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(100), + // Make sure there is a user logged in. We enforce this in web.xml. + new LocalUserServiceTestConfig()) + .setEnvIsLoggedIn(true) + .setEnvEmail("test@example.com") + .setEnvAuthDomain("gmail.com"); + + private FakeClock clock; + private GuestbookStrong guestbookUnderTest; + + @Before + public void setUp() throws Exception { + helper.setUp(); + clock = new FakeClock(FAKE_NOW); + guestbookUnderTest = new GuestbookStrong(GUESTBOOK_ID, clock); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void appendGreeting_normalData_setsContentProperty() { + Greeting got = guestbookUnderTest.appendGreeting("Hello, Datastore!"); + + assertWithMessage("content property").that(got.getContent()).isEqualTo("Hello, Datastore!"); + } + + @Test + public void appendGreeting_normalData_setsDateProperty() { + Greeting got = guestbookUnderTest.appendGreeting("Hello, Datastore!"); + + assertWithMessage("date property").that(got.getDate()).isEqualTo(FAKE_NOW); + } + + @Test + public void listGreetings_maximumEventualConsistency_returnsAllGreetings() { + // Arrange + guestbookUnderTest.appendGreeting("Hello, Datastore!"); + guestbookUnderTest.appendGreeting("Hello, Eventual Consistency!"); + guestbookUnderTest.appendGreeting("Hello, World!"); + + // Act + List got = guestbookUnderTest.listGreetings(); + + // Assert + // Since we use an ancestor query, all greetings should be available. + assertThat(got).hasSize(3); + } +} diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookTest.java deleted file mode 120000 index 015e621216b..00000000000 --- a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/GuestbookTest.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookTest.java new file mode 100644 index 00000000000..f24f0740a34 --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/GuestbookTest.java @@ -0,0 +1,125 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import com.example.time.testing.FakeClock; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.dev.HighRepJobPolicy; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import com.google.appengine.tools.development.testing.LocalUserServiceTestConfig; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for {@link Guestbook}. + */ +@RunWith(JUnit4.class) +public class GuestbookTest { + + private static final class CustomHighRepJobPolicy implements HighRepJobPolicy { + + static int newJobCounter = 0; + static int existingJobCounter = 0; + + @Override + public boolean shouldApplyNewJob(Key entityGroup) { + // Every other new job fails to apply. + return newJobCounter++ % 2 == 0; + } + + @Override + public boolean shouldRollForwardExistingJob(Key entityGroup) { + // Existing jobs always apply after every Get and every Query. + return true; + } + } + + // Set custom, deterministic, eventual consistency. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + new LocalDatastoreServiceTestConfig() + .setAlternateHighRepJobPolicyClass(CustomHighRepJobPolicy.class), + // Make sure there is a user logged in. We enforce this in web.xml. + new LocalUserServiceTestConfig()) + .setEnvIsLoggedIn(true) + .setEnvEmail("test@example.com") + .setEnvAuthDomain("gmail.com"); + + private FakeClock clock; + private Guestbook guestbookUnderTest; + + @Before + public void setUp() throws Exception { + helper.setUp(); + clock = new FakeClock(); + guestbookUnderTest = new Guestbook(clock); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void appendGreeting_normalData_setsContentProperty() { + Greeting got = guestbookUnderTest.appendGreeting("Hello, Datastore!"); + + assertWithMessage("content property").that(got.getContent()).isEqualTo("Hello, Datastore!"); + } + + @Test + public void listGreetings_eventualConsistency_returnsPartialGreetings() { + // Arrange + guestbookUnderTest.appendGreeting("Hello, Datastore!"); + guestbookUnderTest.appendGreeting("Hello, Eventual Consistency!"); + guestbookUnderTest.appendGreeting("Hello, World!"); + guestbookUnderTest.appendGreeting("Güten Tag!"); + + // Act + List got = guestbookUnderTest.listGreetings(); + + // The first time we query we should half of the results due to the fact that we simulate + // eventual consistency by applying every other write. + assertThat(got).hasSize(2); + } + + @Test + public void listGreetings_groomedDatastore_returnsAllGreetings() { + // Arrange + guestbookUnderTest.appendGreeting("Hello, Datastore!"); + guestbookUnderTest.appendGreeting("Hello, Eventual Consistency!"); + guestbookUnderTest.appendGreeting("Hello, World!"); + + // Act + guestbookUnderTest.listGreetings(); + // Second global query sees both Entities because we "groom" (attempt to + // apply unapplied jobs) after every query. + List got = guestbookUnderTest.listGreetings(); + + assertThat(got).hasSize(3); + } +} diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/IndexesTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/IndexesTest.java deleted file mode 120000 index 2530a9646f2..00000000000 --- a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/IndexesTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/IndexesTest.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/IndexesTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/IndexesTest.java new file mode 100644 index 00000000000..b23e6a2e310 --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/IndexesTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertWithMessage; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.KeyFactory; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.Filter; +import com.google.appengine.api.datastore.Query.FilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests to demonstrate App Engine Datastore queries. + */ +@RunWith(JUnit4.class) +public class IndexesTest { + + // Set no eventual consistency, that way queries return all results. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void propertyFilterExample_returnsMatchingEntities() throws Exception { + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + + Key acmeKey = KeyFactory.createKey("Company", "Acme"); + + Entity tom = new Entity("Person", "Tom", acmeKey); + tom.setProperty("name", "Tom"); + tom.setProperty("age", 32); + datastore.put(tom); + + Entity lucy = new Entity("Person", "Lucy", acmeKey); + lucy.setProperty("name", "Lucy"); + lucy.setUnindexedProperty("age", 29); + datastore.put(lucy); + + Filter ageFilter = new FilterPredicate("age", FilterOperator.GREATER_THAN, 25); + + Query q = new Query("Person").setAncestor(acmeKey).setFilter(ageFilter); + + // Returns tom but not lucy, because her age is unindexed + List results = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults()); + + assertWithMessage("query results").that(results).containsExactly(tom); + } +} diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ListPeopleServletTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ListPeopleServletTest.java deleted file mode 120000 index e32bdc25149..00000000000 --- a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ListPeopleServletTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/ListPeopleServletTest.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ListPeopleServletTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ListPeopleServletTest.java new file mode 100644 index 00000000000..b2dc94cafed --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ListPeopleServletTest.java @@ -0,0 +1,165 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.PreparedQuery; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.SortDirection; +import com.google.appengine.api.datastore.QueryResultList; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import com.google.common.collect.ImmutableList; +import java.io.PrintWriter; +import java.io.StringWriter; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link ListPeopleServlet}. + */ +@RunWith(JUnit4.class) +public class ListPeopleServletTest { + + private static final ImmutableList TEST_NAMES = + // Keep in alphabetical order, so this is the same as the query order. + ImmutableList.builder() + .add("Alpha") + .add("Bravo") + .add("Charlie") + .add("Delta") + .add("Echo") + .add("Foxtrot") + .add("Golf") + .add("Hotel") + .add("India") + .add("Juliett") + .add("Kilo") + .add("Lima") + .add("Mike") + .add("November") + .add("Oscar") + .add("Papa") + .add("Quebec") + .add("Romeo") + .add("Sierra") + .add("Tango") + .build(); + + // Set no eventual consistency, that way queries return all results. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + @Mock + private HttpServletRequest mockRequest; + @Mock + private HttpServletResponse mockResponse; + private StringWriter responseWriter; + private DatastoreService datastore; + + private ListPeopleServlet servletUnderTest; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + + // Add test data. + ImmutableList.Builder people = ImmutableList.builder(); + for (String name : TEST_NAMES) { + people.add(createPerson(name)); + } + datastore.put(people.build()); + + // Set up a fake HTTP response. + responseWriter = new StringWriter(); + when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter)); + + servletUnderTest = new ListPeopleServlet(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + private Entity createPerson(String name) { + Entity person = new Entity("Person"); + person.setProperty("name", name); + return person; + } + + @Test + public void doGet_noCursor_writesNames() throws Exception { + servletUnderTest.doGet(mockRequest, mockResponse); + + String response = responseWriter.toString(); + for (int i = 0; i < ListPeopleServlet.PAGE_SIZE; i++) { + assertWithMessage("ListPeopleServlet response").that(response).contains(TEST_NAMES.get(i)); + } + } + + private String getFirstCursor() { + Query q = new Query("Person").addSort("name", SortDirection.ASCENDING); + PreparedQuery pq = datastore.prepare(q); + FetchOptions fetchOptions = FetchOptions.Builder.withLimit(ListPeopleServlet.PAGE_SIZE); + QueryResultList results = pq.asQueryResultList(fetchOptions); + return results.getCursor().toWebSafeString(); + } + + @Test + public void doGet_withValidCursor_writesNames() throws Exception { + when(mockRequest.getParameter("cursor")).thenReturn(getFirstCursor()); + + servletUnderTest.doGet(mockRequest, mockResponse); + + String response = responseWriter.toString(); + int i = 0; + while (i + ListPeopleServlet.PAGE_SIZE < TEST_NAMES.size() && i < ListPeopleServlet.PAGE_SIZE) { + assertWithMessage("ListPeopleServlet response") + .that(response) + .contains(TEST_NAMES.get(i + ListPeopleServlet.PAGE_SIZE)); + i++; + } + } + + @Test + public void doGet_withInvalidCursor_writesRedirect() throws Exception { + when(mockRequest.getParameter("cursor")).thenReturn("ThisCursorIsTotallyInvalid"); + servletUnderTest.doGet(mockRequest, mockResponse); + verify(mockResponse).sendRedirect("/people"); + } +} diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataEntityGroupTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataEntityGroupTest.java deleted file mode 120000 index 97be437f7c1..00000000000 --- a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataEntityGroupTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/MetadataEntityGroupTest.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataEntityGroupTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataEntityGroupTest.java new file mode 100644 index 00000000000..df40be429a1 --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataEntityGroupTest.java @@ -0,0 +1,160 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entities; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.EntityNotFoundException; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.PreparedQuery; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Transaction; +import com.google.appengine.api.memcache.MemcacheService; +import com.google.appengine.api.memcache.MemcacheServiceFactory; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalMemcacheServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.io.PrintWriter; +import java.io.Serializable; +import java.io.StringWriter; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests to demonstrate App Engine Datastore entity group metadata. + */ +@RunWith(JUnit4.class) +public class MetadataEntityGroupTest { + + // Set no eventual consistency, that way queries return all results. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + new LocalDatastoreServiceTestConfig().setDefaultHighRepJobPolicyUnappliedJobPercentage(0), + new LocalMemcacheServiceTestConfig()); + + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + private static long getEntityGroupVersion(DatastoreService ds, Transaction tx, Key entityKey) { + try { + return Entities.getVersionProperty(ds.get(tx, Entities.createEntityGroupKey(entityKey))); + } catch (EntityNotFoundException e) { + // No entity group information, return a value strictly smaller than any + // possible version + return 0; + } + } + + private static void printEntityGroupVersions(DatastoreService ds, PrintWriter writer) { + Entity entity1 = new Entity("Simple"); + Key key1 = ds.put(entity1); + Key entityGroupKey = Entities.createEntityGroupKey(key1); + + // Print entity1's entity group version + writer.println("version " + getEntityGroupVersion(ds, null, key1)); + + // Write to a different entity group + Entity entity2 = new Entity("Simple"); + ds.put(entity2); + + // Will print the same version, as entity1's entity group has not changed + writer.println("version " + getEntityGroupVersion(ds, null, key1)); + + // Change entity1's entity group by adding a new child entity + Entity entity3 = new Entity("Simple", entity1.getKey()); + ds.put(entity3); + + // Will print a higher version, as entity1's entity group has changed + writer.println("version " + getEntityGroupVersion(ds, null, key1)); + } + + @Test + public void printEntityGroupVersions_printsVersions() throws Exception { + StringWriter responseWriter = new StringWriter(); + printEntityGroupVersions(datastore, new PrintWriter(responseWriter)); + assertThat(responseWriter.toString()).contains("version"); + } + + // A simple class for tracking consistent entity group counts. + private static class EntityGroupCount implements Serializable { + + long version; // Version of the entity group whose count we are tracking + int count; + + EntityGroupCount(long version, int count) { + this.version = version; + this.count = count; + } + + // Display count of entities in an entity group, with consistent caching + void showEntityGroupCount( + DatastoreService ds, MemcacheService cache, PrintWriter writer, Key entityGroupKey) { + EntityGroupCount egCount = (EntityGroupCount) cache.get(entityGroupKey); + // Reuses getEntityGroupVersion method from the previous example. + if (egCount != null && egCount.version == getEntityGroupVersion(ds, null, entityGroupKey)) { + // Cached value matched current entity group version, use that + writer.println(egCount.count + " entities (cached)"); + } else { + // Need to actually count entities. Using a transaction to get a consistent count + // and entity group version. + Transaction tx = ds.beginTransaction(); + PreparedQuery pq = ds.prepare(tx, new Query(entityGroupKey)); + int count = pq.countEntities(FetchOptions.Builder.withLimit(5000)); + cache.put( + entityGroupKey, + new EntityGroupCount(getEntityGroupVersion(ds, tx, entityGroupKey), count)); + tx.rollback(); + writer.println(count + " entities"); + } + } + } + + @Test + public void entityGroupCount_printsCount() throws Exception { + StringWriter responseWriter = new StringWriter(); + MemcacheService cache = MemcacheServiceFactory.getMemcacheService(); + Entity entity1 = new Entity("Simple"); + Key key1 = datastore.put(entity1); + Key entityGroupKey = Entities.createEntityGroupKey(key1); + + EntityGroupCount groupCount = new EntityGroupCount(0, 0); + groupCount.showEntityGroupCount( + datastore, cache, new PrintWriter(responseWriter), entityGroupKey); + + assertThat(responseWriter.toString()).contains(" entities"); + } +} diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataKindsTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataKindsTest.java deleted file mode 120000 index aedd248bc2b..00000000000 --- a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataKindsTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/MetadataKindsTest.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataKindsTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataKindsTest.java new file mode 100644 index 00000000000..d479e82c470 --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataKindsTest.java @@ -0,0 +1,120 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entities; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.CompositeFilterOperator; +import com.google.appengine.api.datastore.Query.Filter; +import com.google.appengine.api.datastore.Query.FilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests to demonstrate App Engine Datastore kinds metadata. + */ +@RunWith(JUnit4.class) +public class MetadataKindsTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google + // .com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private StringWriter responseWriter; + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + responseWriter = new StringWriter(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + void printLowercaseKinds(DatastoreService ds, PrintWriter writer) { + + // Start with unrestricted kind query + Query q = new Query(Entities.KIND_METADATA_KIND); + + List subFils = new ArrayList<>(); + + // Limit to lowercase initial letters + subFils.add( + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + FilterOperator.GREATER_THAN_OR_EQUAL, + Entities.createKindKey("a"))); + + String endChar = Character.toString((char) ('z' + 1)); // Character after 'z' + + subFils.add( + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + FilterOperator.LESS_THAN, + Entities.createKindKey(endChar))); + + q.setFilter(CompositeFilterOperator.and(subFils)); + + // Print heading + writer.println("Lowercase kinds:"); + + // Print query results + for (Entity e : ds.prepare(q).asIterable()) { + writer.println(" " + e.getKey().getName()); + } + } + + @Test + public void printLowercaseKinds_printsKinds() throws Exception { + datastore.put(new Entity("alpha")); + datastore.put(new Entity("beta")); + datastore.put(new Entity("NotIncluded")); + datastore.put(new Entity("zed")); + + printLowercaseKinds(datastore, new PrintWriter(responseWriter)); + + String response = responseWriter.toString(); + assertThat(response).contains("alpha"); + assertThat(response).contains("beta"); + assertThat(response).contains("zed"); + assertThat(response).doesNotContain("NotIncluded"); + } +} diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataNamespacesTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataNamespacesTest.java deleted file mode 120000 index 02eea767d46..00000000000 --- a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataNamespacesTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/MetadataNamespacesTest.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataNamespacesTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataNamespacesTest.java new file mode 100644 index 00000000000..a27c15642ce --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataNamespacesTest.java @@ -0,0 +1,150 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.appengine.api.NamespaceManager; +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entities; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.CompositeFilterOperator; +import com.google.appengine.api.datastore.Query.Filter; +import com.google.appengine.api.datastore.Query.FilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests to demonstrate App Engine Datastore namespaces metadata. + */ +@RunWith(JUnit4.class) +public class MetadataNamespacesTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google + // .com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private StringWriter responseWriter; + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + responseWriter = new StringWriter(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + void printAllNamespaces(DatastoreService ds, PrintWriter writer) { + Query q = new Query(Entities.NAMESPACE_METADATA_KIND); + + for (Entity e : ds.prepare(q).asIterable()) { + // A nonzero numeric id denotes the default namespace; + // see Namespace Queries, below + if (e.getKey().getId() != 0) { + writer.println(""); + } else { + writer.println(e.getKey().getName()); + } + } + } + + @Test + public void printAllNamespaces_printsNamespaces() throws Exception { + datastore.put(new Entity("Simple")); + NamespaceManager.set("another-namespace"); + datastore.put(new Entity("Simple")); + + printAllNamespaces(datastore, new PrintWriter(responseWriter)); + + String response = responseWriter.toString(); + assertThat(response).contains(""); + assertThat(response).contains("another-namespace"); + } + + List getNamespaces(DatastoreService ds, String start, String end) { + + // Start with unrestricted namespace query + Query q = new Query(Entities.NAMESPACE_METADATA_KIND); + List subFilters = new ArrayList<>(); + // Limit to specified range, if any + if (start != null) { + subFilters.add( + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + FilterOperator.GREATER_THAN_OR_EQUAL, + Entities.createNamespaceKey(start))); + } + if (end != null) { + subFilters.add( + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + FilterOperator.LESS_THAN_OR_EQUAL, + Entities.createNamespaceKey(end))); + } + + q.setFilter(CompositeFilterOperator.and(subFilters)); + + // Initialize result list + List results = new ArrayList<>(); + + // Build list of query results + for (Entity e : ds.prepare(q).asIterable()) { + results.add(Entities.getNamespaceFromNamespaceKey(e.getKey())); + } + + // Return result list + return results; + } + + @Test + public void getNamespaces_returnsNamespaces() throws Exception { + NamespaceManager.set("alpha"); + datastore.put(new Entity("Simple")); + NamespaceManager.set("bravo"); + datastore.put(new Entity("Simple")); + NamespaceManager.set("charlie"); + datastore.put(new Entity("Simple")); + NamespaceManager.set("zed"); + datastore.put(new Entity("Simple")); + + List results = getNamespaces(datastore, "bravo", "echo"); + + assertThat(results).containsExactly("bravo", "charlie"); + } +} diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataPropertiesTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataPropertiesTest.java deleted file mode 120000 index d94057bf260..00000000000 --- a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataPropertiesTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/MetadataPropertiesTest.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataPropertiesTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataPropertiesTest.java new file mode 100644 index 00000000000..5334a4f4493 --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/MetadataPropertiesTest.java @@ -0,0 +1,227 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entities; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.CompositeFilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; +import com.google.appengine.api.datastore.Query.SortDirection; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests to demonstrate App Engine Datastore properties metadata. + */ +@RunWith(JUnit4.class) +public class MetadataPropertiesTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google + // .com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private StringWriter responseWriter; + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + responseWriter = new StringWriter(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + void printProperties(DatastoreService ds, PrintWriter writer) { + + // Create unrestricted keys-only property query + Query q = new Query(Entities.PROPERTY_METADATA_KIND).setKeysOnly(); + + // Print query results + for (Entity e : ds.prepare(q).asIterable()) { + writer.println(e.getKey().getParent().getName() + ": " + e.getKey().getName()); + } + } + + @Test + public void printProperties_printsProperties() throws Exception { + Entity a = new Entity("Widget"); + a.setProperty("combobulators", 2); + a.setProperty("oscillatorState", "harmonzing"); + Entity b = new Entity("Ship"); + b.setProperty("sails", 2); + b.setProperty("captain", "Blackbeard"); + Entity c = new Entity("Ship"); + c.setProperty("captain", "Redbeard"); + c.setProperty("motor", "outboard"); + datastore.put(Arrays.asList(a, b, c)); + + printProperties(datastore, new PrintWriter(responseWriter)); + + String response = responseWriter.toString(); + assertThat(response).contains("Widget: combobulators"); + assertThat(response).contains("Widget: oscillatorState"); + assertThat(response).contains("Ship: sails"); + assertThat(response).contains("Ship: captain"); + assertThat(response).contains("Ship: motor"); + } + + void printPropertyRange(DatastoreService ds, PrintWriter writer) { + + // Start with unrestricted keys-only property query + Query q = new Query(Entities.PROPERTY_METADATA_KIND).setKeysOnly(); + + // Limit range + q.setFilter( + CompositeFilterOperator.and( + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + Query.FilterOperator.GREATER_THAN_OR_EQUAL, + Entities.createPropertyKey("Employee", "salary")), + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + Query.FilterOperator.LESS_THAN_OR_EQUAL, + Entities.createPropertyKey("Manager", "salary")))); + q.addSort(Entity.KEY_RESERVED_PROPERTY, SortDirection.ASCENDING); + + // Print query results + for (Entity e : ds.prepare(q).asIterable()) { + writer.println(e.getKey().getParent().getName() + ": " + e.getKey().getName()); + } + } + + @Test + public void printPropertyRange_printsProperties() throws Exception { + Entity account = new Entity("Account"); + account.setProperty("balance", "10.30"); + account.setProperty("company", "General Company"); + Entity employee = new Entity("Employee"); + employee.setProperty("name", "John Doe"); + employee.setProperty("ssn", "987-65-4321"); + Entity invoice = new Entity("Invoice"); + invoice.setProperty("date", new Date()); + invoice.setProperty("amount", "99.98"); + Entity manager = new Entity("Manager"); + manager.setProperty("name", "Jane Doe"); + manager.setProperty("title", "Technical Director"); + Entity product = new Entity("Product"); + product.setProperty("description", "Widget to re-ionize an oscillator"); + product.setProperty("price", "19.97"); + datastore.put(Arrays.asList(account, employee, invoice, manager, product)); + + printPropertyRange(datastore, new PrintWriter(responseWriter)); + + String response = responseWriter.toString(); + assertThat(response) + .isEqualTo("Employee: ssn\nInvoice: amount\nInvoice: date\nManager: name\n"); + } + + List propertiesOfKind(DatastoreService ds, String kind) { + + // Start with unrestricted keys-only property query + Query q = new Query(Entities.PROPERTY_METADATA_KIND).setKeysOnly(); + + // Limit to specified kind + q.setAncestor(Entities.createKindKey(kind)); + + // Initialize result list + ArrayList results = new ArrayList<>(); + + //Build list of query results + for (Entity e : ds.prepare(q).asIterable()) { + results.add(e.getKey().getName()); + } + + // Return result list + return results; + } + + @Test + public void propertiesOfKind_returnsProperties() throws Exception { + Entity a = new Entity("Alpha"); + a.setProperty("beta", 12); + a.setProperty("charlie", "misc."); + Entity b = new Entity("Alpha"); + b.setProperty("charlie", "assorted"); + b.setProperty("delta", new Date()); + Entity c = new Entity("Charlie"); + c.setProperty("charlie", "some"); + c.setProperty("echo", new Date()); + datastore.put(Arrays.asList(a, b, c)); + + List properties = propertiesOfKind(datastore, "Alpha"); + + assertThat(properties).containsExactly("beta", "charlie", "delta"); + } + + Collection representationsOfProperty(DatastoreService ds, String kind, String property) { + + // Start with unrestricted non-keys-only property query + Query q = new Query(Entities.PROPERTY_METADATA_KIND); + + // Limit to specified kind and property + q.setFilter( + new FilterPredicate( + "__key__", Query.FilterOperator.EQUAL, Entities.createPropertyKey(kind, property))); + + // Get query result + Entity propInfo = ds.prepare(q).asSingleEntity(); + + // Return collection of property representations + return (Collection) propInfo.getProperty("property_representation"); + } + + @Test + public void representationsOfProperty_returnsRepresentations() throws Exception { + Entity a = new Entity("Alpha"); + a.setProperty("beta", 12); + Entity b = new Entity("Alpha"); + b.setProperty("beta", true); + Entity c = new Entity("Alpha"); + c.setProperty("beta", new Date()); + datastore.put(Arrays.asList(a, b, c)); + + Collection results = representationsOfProperty(datastore, "Alpha", "beta"); + + assertThat(results).containsExactly("INT64", "BOOLEAN"); + } +} diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionServletTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionServletTest.java deleted file mode 120000 index f442793b9c6..00000000000 --- a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionServletTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/ProjectionServletTest.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionServletTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionServletTest.java new file mode 100644 index 00000000000..42e5574da0e --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionServletTest.java @@ -0,0 +1,97 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.Mockito.when; + +import com.example.time.testing.FakeClock; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.io.PrintWriter; +import java.io.StringWriter; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link ProjectionServlet}. + */ +@RunWith(JUnit4.class) +public class ProjectionServletTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig()); + + @Mock + private HttpServletRequest mockRequest; + @Mock + private HttpServletResponse mockResponse; + private StringWriter responseWriter; + private ProjectionServlet servletUnderTest; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + helper.setUp(); + + // Set up a fake HTTP response. + responseWriter = new StringWriter(); + when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter)); + + servletUnderTest = new ProjectionServlet(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void doGet_emptyDatastore_writesNoGreetings() throws Exception { + servletUnderTest.doGet(mockRequest, mockResponse); + + assertWithMessage("ProjectionServlet response") + .that(responseWriter.toString()) + .doesNotContain("Message"); + } + + @Test + public void doGet_manyGreetings_writesLatestGreetings() throws Exception { + // Arrange + GuestbookStrong guestbook = + new GuestbookStrong(GuestbookStrongServlet.GUESTBOOK_ID, new FakeClock()); + guestbook.appendGreeting("Hello."); + guestbook.appendGreeting("Güten Tag!"); + guestbook.appendGreeting("Hi."); + guestbook.appendGreeting("Hola."); + + // Act + servletUnderTest.doGet(mockRequest, mockResponse); + String output = responseWriter.toString(); + + assertWithMessage("ProjectionServlet response").that(output).contains("Message Hello."); + assertWithMessage("ProjectionServlet response").that(output).contains("Message Güten Tag!"); + assertWithMessage("ProjectionServlet response").that(output).contains("Message Hola."); + } +} diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionTest.java deleted file mode 120000 index 99cd3ef139a..00000000000 --- a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/ProjectionTest.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionTest.java new file mode 100644 index 00000000000..85ee162c055 --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ProjectionTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.PropertyProjection; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests to demonstrate App Engine Datastore projection queries. */ +@RunWith(JUnit4.class) +public class ProjectionTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google + // .com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private DatastoreService datastore; + + @Before + public void setUp() throws Exception { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void projectionQuery_grouping_filtersDuplicates() { + putTestData("some duplicate", 0L); + putTestData("some duplicate", 0L); + putTestData("too big", 1L); + + Query q = new Query("TestKind"); + q.addProjection(new PropertyProjection("A", String.class)); + q.addProjection(new PropertyProjection("B", Long.class)); + q.setDistinct(true); + q.setFilter(Query.FilterOperator.LESS_THAN.of("B", 1L)); + q.addSort("B", Query.SortDirection.DESCENDING); + q.addSort("A"); + + List entities = datastore.prepare(q).asList(FetchOptions.Builder.withLimit(5)); + assertThat(entities).hasSize(1); + Entity entity = entities.get(0); + assertWithMessage("entity.A") + .that((String) entity.getProperty("A")) + .isEqualTo("some duplicate"); + assertWithMessage("entity.B").that((long) entity.getProperty("B")).isEqualTo(0L); + } + + private void putTestData(String a, long b) { + Entity entity = new Entity("TestKind"); + entity.setProperty("A", a); + entity.setProperty("B", b); + datastore.put(entity); + } +} diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/QueriesTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/QueriesTest.java deleted file mode 120000 index 2b4c7b4c7fc..00000000000 --- a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/QueriesTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/QueriesTest.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/QueriesTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/QueriesTest.java new file mode 100644 index 00000000000..8635532539c --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/QueriesTest.java @@ -0,0 +1,798 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static org.junit.Assert.fail; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.PreparedQuery; +import com.google.appengine.api.datastore.PreparedQuery.TooManyResultsException; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.CompositeFilter; +import com.google.appengine.api.datastore.Query.CompositeFilterOperator; +import com.google.appengine.api.datastore.Query.Filter; +import com.google.appengine.api.datastore.Query.FilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; +import com.google.appengine.api.datastore.Query.SortDirection; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import com.google.common.collect.ImmutableList; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests to demonstrate App Engine Datastore queries. */ +@RunWith(JUnit4.class) +public class QueriesTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google + // .com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void propertyFilterExample_returnsMatchingEntities() throws Exception { + // Arrange + Entity p1 = new Entity("Person"); + p1.setProperty("height", 120); + Entity p2 = new Entity("Person"); + p2.setProperty("height", 180); + Entity p3 = new Entity("Person"); + p3.setProperty("height", 160); + datastore.put(ImmutableList.of(p1, p2, p3)); + + // Act + long minHeight = 160; + Filter propertyFilter = + new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight); + Query q = new Query("Person").setFilter(propertyFilter); + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(p2, p3); + } + + @Test + public void keyFilterExample_returnsMatchingEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + Entity b = new Entity("Person", "b"); + Entity c = new Entity("Person", "c"); + Entity aa = new Entity("Person", "aa", b.getKey()); + Entity bb = new Entity("Person", "bb", b.getKey()); + Entity aaa = new Entity("Person", "aaa", bb.getKey()); + Entity bbb = new Entity("Person", "bbb", bb.getKey()); + datastore.put(ImmutableList.of(a, b, c, aa, bb, aaa, bbb)); + + // Act + Key lastSeenKey = bb.getKey(); + Filter keyFilter = + new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey); + Query q = new Query("Person").setFilter(keyFilter); + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results") + .that(results) + .containsExactly( + aaa, // Ancestor path "b/bb/aaa" is greater than "b/bb". + bbb, // Ancestor path "b/bb/bbb" is greater than "b/bb". + c); // Key name identifier "c" is greater than b. + } + + @Test + public void keyFilterExample_kindless_returnsMatchingEntities() throws Exception { + // Arrange + Entity a = new Entity("Child", "a"); + Entity b = new Entity("Child", "b"); + Entity c = new Entity("Child", "c"); + Entity aa = new Entity("Child", "aa", b.getKey()); + Entity bb = new Entity("Child", "bb", b.getKey()); + Entity aaa = new Entity("Child", "aaa", bb.getKey()); + Entity bbb = new Entity("Child", "bbb", bb.getKey()); + Entity adult = new Entity("Adult", "a"); + Entity zooAnimal = new Entity("ZooAnimal", "a"); + datastore.put(ImmutableList.of(a, b, c, aa, bb, aaa, bbb, adult, zooAnimal)); + + // Act + Key lastSeenKey = bb.getKey(); + Filter keyFilter = + new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey); + Query q = new Query().setFilter(keyFilter); + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results") + .that(results) + .containsExactly( + aaa, // Ancestor path "b/bb/aaa" is greater than "b/bb". + bbb, // Ancestor path "b/bb/bbb" is greater than "b/bb". + zooAnimal, // Kind "ZooAnimal" is greater than "Child" + c); // Key name identifier "c" is greater than b. + } + + @Test + public void ancestorFilterExample_returnsMatchingEntities() throws Exception { + Entity a = new Entity("Person", "a"); + Entity b = new Entity("Person", "b"); + Entity aa = new Entity("Person", "aa", a.getKey()); + Entity ab = new Entity("Person", "ab", a.getKey()); + Entity bb = new Entity("Person", "bb", b.getKey()); + datastore.put(ImmutableList.of(a, b, aa, ab, bb)); + + Key ancestorKey = a.getKey(); + Query q = new Query("Person").setAncestor(ancestorKey); + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(a, aa, ab); + } + + @Test + public void ancestorQueryExample_returnsMatchingEntities() throws Exception { + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + + Entity tom = new Entity("Person", "Tom"); + Key tomKey = tom.getKey(); + datastore.put(tom); + + Entity weddingPhoto = new Entity("Photo", tomKey); + weddingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/wedding_photo.jpg"); + + Entity babyPhoto = new Entity("Photo", tomKey); + babyPhoto.setProperty("imageURL", "http://domain.com/some/path/to/baby_photo.jpg"); + + Entity dancePhoto = new Entity("Photo", tomKey); + dancePhoto.setProperty("imageURL", "http://domain.com/some/path/to/dance_photo.jpg"); + + Entity campingPhoto = new Entity("Photo"); + campingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/camping_photo.jpg"); + + List photoList = Arrays.asList(weddingPhoto, babyPhoto, dancePhoto, campingPhoto); + datastore.put(photoList); + + Query photoQuery = new Query("Photo").setAncestor(tomKey); + + // This returns weddingPhoto, babyPhoto, and dancePhoto, + // but not campingPhoto, because tom is not an ancestor + List results = + datastore.prepare(photoQuery).asList(FetchOptions.Builder.withDefaults()); + + assertWithMessage("query results") + .that(results) + .containsExactly(weddingPhoto, babyPhoto, dancePhoto); + } + + @Test + public void ancestorQueryExample_kindlessKeyFilter_returnsMatchingEntities() throws Exception { + // Arrange + Entity a = new Entity("Grandparent", "a"); + Entity b = new Entity("Grandparent", "b"); + Entity c = new Entity("Grandparent", "c"); + Entity aa = new Entity("Parent", "aa", a.getKey()); + Entity ba = new Entity("Parent", "ba", b.getKey()); + Entity bb = new Entity("Parent", "bb", b.getKey()); + Entity bc = new Entity("Parent", "bc", b.getKey()); + Entity cc = new Entity("Parent", "cc", c.getKey()); + Entity aaa = new Entity("Child", "aaa", aa.getKey()); + Entity bbb = new Entity("Child", "bbb", bb.getKey()); + datastore.put(ImmutableList.of(a, b, c, aa, ba, bb, bc, cc, aaa, bbb)); + + // Act + Key ancestorKey = b.getKey(); + Key lastSeenKey = bb.getKey(); + Filter keyFilter = + new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey); + Query q = new Query().setAncestor(ancestorKey).setFilter(keyFilter); + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(bc, bbb); + } + + @Test + public void ancestorQueryExample_kindlessKeyFilterFull_returnsMatchingEntities() + throws Exception { + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + + Entity tom = new Entity("Person", "Tom"); + Key tomKey = tom.getKey(); + datastore.put(tom); + + Entity weddingPhoto = new Entity("Photo", tomKey); + weddingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/wedding_photo.jpg"); + + Entity weddingVideo = new Entity("Video", tomKey); + weddingVideo.setProperty("videoURL", "http://domain.com/some/path/to/wedding_video.avi"); + + List mediaList = Arrays.asList(weddingPhoto, weddingVideo); + datastore.put(mediaList); + + // By default, ancestor queries include the specified ancestor itself. + // The following filter excludes the ancestor from the query results. + Filter keyFilter = + new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, tomKey); + + Query mediaQuery = new Query().setAncestor(tomKey).setFilter(keyFilter); + + // Returns both weddingPhoto and weddingVideo, + // even though they are of different entity kinds + List results = + datastore.prepare(mediaQuery).asList(FetchOptions.Builder.withDefaults()); + + assertWithMessage("query result keys") + .that(results) + .containsExactly(weddingPhoto, weddingVideo); + } + + @Test + public void keysOnlyExample_returnsMatchingEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + Entity b = new Entity("Building", "b"); + Entity c = new Entity("Person", "c"); + datastore.put(ImmutableList.of(a, b, c)); + + Query q = new Query("Person").setKeysOnly(); + + // Assert + List results = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(a, c); + } + + @Test + public void sortOrderExample_returnsSortedEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + a.setProperty("lastName", "Alpha"); + a.setProperty("height", 100); + Entity b = new Entity("Person", "b"); + b.setProperty("lastName", "Bravo"); + b.setProperty("height", 200); + Entity c = new Entity("Person", "c"); + c.setProperty("lastName", "Charlie"); + c.setProperty("height", 300); + datastore.put(ImmutableList.of(a, b, c)); + + // Act + // Order alphabetically by last name: + Query q1 = new Query("Person").addSort("lastName", SortDirection.ASCENDING); + + // Order by height, tallest to shortest: + Query q2 = new Query("Person").addSort("height", SortDirection.DESCENDING); + + // Assert + List lastNameResults = + datastore.prepare(q1.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("last name query results") + .that(lastNameResults) + .containsExactly(a, b, c) + .inOrder(); + List heightResults = + datastore.prepare(q2.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("height query results") + .that(heightResults) + .containsExactly(c, b, a) + .inOrder(); + } + + @Test + public void sortOrderExample_multipleSortOrders_returnsSortedEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + a.setProperty("lastName", "Alpha"); + a.setProperty("height", 100); + Entity b1 = new Entity("Person", "b1"); + b1.setProperty("lastName", "Bravo"); + b1.setProperty("height", 150); + Entity b2 = new Entity("Person", "b2"); + b2.setProperty("lastName", "Bravo"); + b2.setProperty("height", 200); + Entity c = new Entity("Person", "c"); + c.setProperty("lastName", "Charlie"); + c.setProperty("height", 300); + datastore.put(ImmutableList.of(a, b1, b2, c)); + + // Act + Query q = + new Query("Person") + .addSort("lastName", SortDirection.ASCENDING) + .addSort("height", SortDirection.DESCENDING); + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(a, b2, b1, c).inOrder(); + } + + @Test + public void queryInterface_multipleFilters_printsMatchedEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + a.setProperty("firstName", "Alph"); + a.setProperty("lastName", "Alpha"); + a.setProperty("height", 60); + Entity b = new Entity("Person", "b"); + b.setProperty("firstName", "Bee"); + b.setProperty("lastName", "Bravo"); + b.setProperty("height", 70); + Entity c = new Entity("Person", "c"); + c.setProperty("firstName", "Charles"); + c.setProperty("lastName", "Charlie"); + c.setProperty("height", 100); + datastore.put(ImmutableList.of(a, b, c)); + + StringWriter buf = new StringWriter(); + PrintWriter out = new PrintWriter(buf); + long minHeight = 60; + long maxHeight = 72; + + // Act + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + + Filter heightMinFilter = + new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight); + + Filter heightMaxFilter = + new FilterPredicate("height", FilterOperator.LESS_THAN_OR_EQUAL, maxHeight); + + // Use CompositeFilter to combine multiple filters + CompositeFilter heightRangeFilter = + CompositeFilterOperator.and(heightMinFilter, heightMaxFilter); + + // Use class Query to assemble a query + Query q = new Query("Person").setFilter(heightRangeFilter); + + // Use PreparedQuery interface to retrieve results + PreparedQuery pq = datastore.prepare(q); + + for (Entity result : pq.asIterable()) { + String firstName = (String) result.getProperty("firstName"); + String lastName = (String) result.getProperty("lastName"); + Long height = (Long) result.getProperty("height"); + + out.println(firstName + " " + lastName + ", " + height + " inches tall"); + } + + // Assert + assertThat(buf.toString()).contains("Alph Alpha, 60 inches tall"); + assertThat(buf.toString()).contains("Bee Bravo, 70 inches tall"); + assertThat(buf.toString()).doesNotContain("Charlie"); + } + + @Test + public void queryInterface_orFilter_printsMatchedEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + a.setProperty("height", 100); + Entity b = new Entity("Person", "b"); + b.setProperty("height", 150); + Entity c = new Entity("Person", "c"); + c.setProperty("height", 200); + datastore.put(ImmutableList.of(a, b, c)); + + StringWriter buf = new StringWriter(); + PrintWriter out = new PrintWriter(buf); + long minHeight = 125; + long maxHeight = 175; + + // Act + Filter tooShortFilter = new FilterPredicate("height", FilterOperator.LESS_THAN, minHeight); + + Filter tooTallFilter = new FilterPredicate("height", FilterOperator.GREATER_THAN, maxHeight); + + Filter heightOutOfRangeFilter = CompositeFilterOperator.or(tooShortFilter, tooTallFilter); + + Query q = new Query("Person").setFilter(heightOutOfRangeFilter); + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(a, c); + } + + @Test + public void queryRestrictions_compositeFilter_returnsMatchedEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + a.setProperty("birthYear", 1930); + Entity b = new Entity("Person", "b"); + b.setProperty("birthYear", 1960); + Entity c = new Entity("Person", "c"); + c.setProperty("birthYear", 1990); + datastore.put(ImmutableList.of(a, b, c)); + + // Act + long minBirthYear = 1940; + long maxBirthYear = 1980; + Filter birthYearMinFilter = + new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear); + + Filter birthYearMaxFilter = + new FilterPredicate("birthYear", FilterOperator.LESS_THAN_OR_EQUAL, maxBirthYear); + + Filter birthYearRangeFilter = + CompositeFilterOperator.and(birthYearMinFilter, birthYearMaxFilter); + + Query q = new Query("Person").setFilter(birthYearRangeFilter); + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(b); + } + + @Test + public void queryRestrictions_compositeFilter_isInvalid() throws Exception { + long minBirthYear = 1940; + long maxHeight = 200; + Filter birthYearMinFilter = + new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear); + + Filter heightMaxFilter = + new FilterPredicate("height", FilterOperator.LESS_THAN_OR_EQUAL, maxHeight); + + Filter invalidFilter = CompositeFilterOperator.and(birthYearMinFilter, heightMaxFilter); + + Query q = new Query("Person").setFilter(invalidFilter); + + // Note: The local devserver behavior is different than the production + // version of Cloud Datastore, so there aren't any assertions we can make + // in this test. The query appears to work with the local test runner, + // but will fail in production. + } + + @Test + public void queryRestrictions_compositeEqualFilter_returnsMatchedEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + a.setProperty("birthYear", 1930); + a.setProperty("city", "Somewhere"); + a.setProperty("lastName", "Someone"); + Entity b = new Entity("Person", "b"); + b.setProperty("birthYear", 1960); + b.setProperty("city", "Somewhere"); + b.setProperty("lastName", "Someone"); + Entity c = new Entity("Person", "c"); + c.setProperty("birthYear", 1990); + c.setProperty("city", "Somewhere"); + c.setProperty("lastName", "Someone"); + Entity d = new Entity("Person", "d"); + d.setProperty("birthYear", 1960); + d.setProperty("city", "Nowhere"); + d.setProperty("lastName", "Someone"); + Entity e = new Entity("Person", "e"); + e.setProperty("birthYear", 1960); + e.setProperty("city", "Somewhere"); + e.setProperty("lastName", "Noone"); + datastore.put(ImmutableList.of(a, b, c, d, e)); + long minBirthYear = 1940; + long maxBirthYear = 1980; + String targetCity = "Somewhere"; + String targetLastName = "Someone"; + + Filter lastNameFilter = new FilterPredicate("lastName", FilterOperator.EQUAL, targetLastName); + + Filter cityFilter = new FilterPredicate("city", FilterOperator.EQUAL, targetCity); + + Filter birthYearMinFilter = + new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear); + + Filter birthYearMaxFilter = + new FilterPredicate("birthYear", FilterOperator.LESS_THAN_OR_EQUAL, maxBirthYear); + + Filter validFilter = + CompositeFilterOperator.and( + lastNameFilter, cityFilter, birthYearMinFilter, birthYearMaxFilter); + + Query q = new Query("Person").setFilter(validFilter); + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(b); + } + + @Test + public void queryRestrictions_inequalitySortedFirst_returnsMatchedEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + a.setProperty("birthYear", 1930); + a.setProperty("lastName", "Someone"); + Entity b = new Entity("Person", "b"); + b.setProperty("birthYear", 1990); + b.setProperty("lastName", "Bravo"); + Entity c = new Entity("Person", "c"); + c.setProperty("birthYear", 1960); + c.setProperty("lastName", "Charlie"); + Entity d = new Entity("Person", "d"); + d.setProperty("birthYear", 1960); + d.setProperty("lastName", "Delta"); + datastore.put(ImmutableList.of(a, b, c, d)); + long minBirthYear = 1940; + + Filter birthYearMinFilter = + new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear); + + Query q = + new Query("Person") + .setFilter(birthYearMinFilter) + .addSort("birthYear", SortDirection.ASCENDING) + .addSort("lastName", SortDirection.ASCENDING); + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(c, d, b).inOrder(); + } + + @Test + public void queryRestrictions_missingSortOnInequality_isInvalid() throws Exception { + long minBirthYear = 1940; + Filter birthYearMinFilter = + new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear); + + // Not valid. Missing sort on birthYear. + Query q = + new Query("Person") + .setFilter(birthYearMinFilter) + .addSort("lastName", SortDirection.ASCENDING); + + // Note: The local devserver behavior is different than the production + // version of Cloud Datastore, so there aren't any assertions we can make + // in this test. The query appears to work with the local test runner, + // but will fail in production. + } + + @Test + public void queryRestrictions_sortWrongOrderOnInequality_isInvalid() throws Exception { + long minBirthYear = 1940; + Filter birthYearMinFilter = + new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear); + + // Not valid. Sort on birthYear needs to be first. + Query q = + new Query("Person") + .setFilter(birthYearMinFilter) + .addSort("lastName", SortDirection.ASCENDING) + .addSort("birthYear", SortDirection.ASCENDING); + + // Note: The local devserver behavior is different than the production + // version of Cloud Datastore, so there aren't any assertions we can make + // in this test. The query appears to work with the local test runner, + // but will fail in production. + } + + @Test + public void queryRestrictions_surprisingMultipleValuesAllMustMatch_returnsNoEntities() + throws Exception { + Entity a = new Entity("Widget", "a"); + List xs = Arrays.asList(1L, 2L); + a.setProperty("x", xs); + datastore.put(a); + + Query q = + new Query("Widget") + .setFilter( + CompositeFilterOperator.and( + new FilterPredicate("x", FilterOperator.GREATER_THAN, 1), + new FilterPredicate("x", FilterOperator.LESS_THAN, 2))); + + // Entity "a" will not match because no individual value matches all filters. + // See the documentation for more details: + // https://cloud.google.com/appengine/docs/java/datastore/query-restrictions + // #properties_with_multiple_values_can_behave_in_surprising_ways + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).isEmpty(); + } + + @Test + public void queryRestrictions_surprisingMultipleValuesEquals_returnsMatchedEntities() + throws Exception { + Entity a = new Entity("Widget", "a"); + a.setProperty("x", ImmutableList.of(1L, 2L)); + Entity b = new Entity("Widget", "b"); + b.setProperty("x", ImmutableList.of(1L, 3L)); + Entity c = new Entity("Widget", "c"); + c.setProperty("x", ImmutableList.of(-6L, 2L)); + Entity d = new Entity("Widget", "d"); + d.setProperty("x", ImmutableList.of(-6L, 4L)); + Entity e = new Entity("Widget", "e"); + e.setProperty("x", ImmutableList.of(1L, 2L, 3L)); + datastore.put(ImmutableList.of(a, b, c, d, e)); + + Query q = + new Query("Widget") + .setFilter( + CompositeFilterOperator.and( + new FilterPredicate("x", FilterOperator.EQUAL, 1), + new FilterPredicate("x", FilterOperator.EQUAL, 2))); + + // Only "a" and "e" have both 1 and 2 in the "x" array-valued property. + // See the documentation for more details: + // https://cloud.google.com/appengine/docs/java/datastore/query-restrictions + // #properties_with_multiple_values_can_behave_in_surprising_ways + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(a, e); + } + + @Test + public void queryRestrictions_surprisingMultipleValuesNotEquals_returnsMatchedEntities() + throws Exception { + Entity a = new Entity("Widget", "a"); + a.setProperty("x", ImmutableList.of(1L, 2L)); + Entity b = new Entity("Widget", "b"); + b.setProperty("x", ImmutableList.of(1L, 3L)); + Entity c = new Entity("Widget", "c"); + c.setProperty("x", ImmutableList.of(-6L, 2L)); + Entity d = new Entity("Widget", "d"); + d.setProperty("x", ImmutableList.of(-6L, 4L)); + Entity e = new Entity("Widget", "e"); + e.setProperty("x", ImmutableList.of(1L)); + datastore.put(ImmutableList.of(a, b, c, d, e)); + + Query q = new Query("Widget").setFilter(new FilterPredicate("x", FilterOperator.NOT_EQUAL, 1)); + + // The query matches any entity that has a some value other than 1. Only + // entity "e" is not matched. See the documentation for more details: + // https://cloud.google.com/appengine/docs/java/datastore/query-restrictions + // #properties_with_multiple_values_can_behave_in_surprising_ways + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(a, b, c, d); + } + + @Test + public void queryRestrictions_surprisingMultipleValuesTwoNotEquals_returnsMatchedEntities() + throws Exception { + Entity a = new Entity("Widget", "a"); + a.setProperty("x", ImmutableList.of(1L, 2L)); + Entity b = new Entity("Widget", "b"); + b.setProperty("x", ImmutableList.of(1L, 2L, 3L)); + datastore.put(ImmutableList.of(a, b)); + + Query q = + new Query("Widget") + .setFilter( + CompositeFilterOperator.and( + new FilterPredicate("x", FilterOperator.NOT_EQUAL, 1), + new FilterPredicate("x", FilterOperator.NOT_EQUAL, 2))); + + // The two NOT_EQUAL filters in the query become like the combination of queries: + // x < 1 OR (x > 1 AND x < 2) OR x > 2 + // + // Only "b" has some value which matches the "x > 2" portion of this query. + // + // See the documentation for more details: + // https://cloud.google.com/appengine/docs/java/datastore/query-restrictions + // #properties_with_multiple_values_can_behave_in_surprising_ways + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(b); + } + + private Entity retrievePersonWithLastName(String targetLastName) { + Query q = + new Query("Person") + .setFilter(new FilterPredicate("lastName", FilterOperator.EQUAL, targetLastName)); + + PreparedQuery pq = datastore.prepare(q); + Entity result = pq.asSingleEntity(); + return result; + } + + @Test + public void singleRetrievalExample_singleEntity_returnsEntity() throws Exception { + Entity a = new Entity("Person", "a"); + a.setProperty("lastName", "Johnson"); + Entity b = new Entity("Person", "b"); + b.setProperty("lastName", "Smith"); + datastore.put(ImmutableList.of(a, b)); + + Entity result = retrievePersonWithLastName("Johnson"); + + assertWithMessage("result") + .that(result) + .isEqualTo(a); // Note: Entity.equals() only checks the Key. + } + + @Test + public void singleRetrievalExample_multitpleEntities_throwsException() throws Exception { + Entity a = new Entity("Person", "a"); + a.setProperty("lastName", "Johnson"); + Entity b = new Entity("Person", "b"); + b.setProperty("lastName", "Johnson"); + datastore.put(ImmutableList.of(a, b)); + + try { + Entity result = retrievePersonWithLastName("Johnson"); + fail("Expected TooManyResultsException"); + } catch (TooManyResultsException expected) { + // TooManyResultsException does not provide addition details. + } + } + + private List getTallestPeople() { + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + + Query q = new Query("Person").addSort("height", SortDirection.DESCENDING); + + PreparedQuery pq = datastore.prepare(q); + return pq.asList(FetchOptions.Builder.withLimit(5)); + } + + @Test + public void queryLimitExample_returnsLimitedEntities() throws Exception { + Entity a = new Entity("Person", "a"); + a.setProperty("height", 200); + Entity b = new Entity("Person", "b"); + b.setProperty("height", 199); + Entity c = new Entity("Person", "c"); + c.setProperty("height", 201); + Entity d = new Entity("Person", "d"); + d.setProperty("height", 198); + Entity e = new Entity("Person", "e"); + e.setProperty("height", 202); + Entity f = new Entity("Person", "f"); + f.setProperty("height", 197); + Entity g = new Entity("Person", "g"); + g.setProperty("height", 203); + Entity h = new Entity("Person", "h"); + h.setProperty("height", 196); + datastore.put(ImmutableList.of(a, b, c, d, e, f, g, h)); + + List results = getTallestPeople(); + + assertWithMessage("results").that(results).containsExactly(g, e, c, a, b).inOrder(); + } +} diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ReadPolicyTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ReadPolicyTest.java deleted file mode 120000 index 84a38cfa093..00000000000 --- a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ReadPolicyTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/ReadPolicyTest.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ReadPolicyTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ReadPolicyTest.java new file mode 100644 index 00000000000..f8a02b3828a --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/ReadPolicyTest.java @@ -0,0 +1,113 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertWithMessage; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceConfig; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.ReadPolicy; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for {@link ReadPolicy}. + */ +@RunWith(JUnit4.class) +public class ReadPolicyTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set 100% eventual consistency, so we can test with other job policies. + // https://cloud.google + // .com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(100)); + + @Before + public void setUp() { + helper.setUp(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void readPolicy_eventual_returnsNoResults() { + double deadline = 5.0; + + // Construct a read policy for eventual consistency + ReadPolicy policy = new ReadPolicy(ReadPolicy.Consistency.EVENTUAL); + + // Set the read policy + DatastoreServiceConfig eventuallyConsistentConfig = + DatastoreServiceConfig.Builder.withReadPolicy(policy); + + // Set the call deadline + DatastoreServiceConfig deadlineConfig = DatastoreServiceConfig.Builder.withDeadline(deadline); + + // Set both the read policy and the call deadline + DatastoreServiceConfig datastoreConfig = + DatastoreServiceConfig.Builder.withReadPolicy(policy).deadline(deadline); + + // Get Datastore service with the given configuration + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(datastoreConfig); + + Entity parent = new Entity("Person", "a"); + Entity child = new Entity("Person", "b", parent.getKey()); + datastore.put(ImmutableList.of(parent, child)); + + // Even though we are using an ancestor query, the policy is set to + // eventual, so we should get eventually-consistent results. Since the + // local data store test config is set to 100% unapplied jobs, there + // should be no results. + Query q = new Query("Person").setAncestor(parent.getKey()); + List results = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).isEmpty(); + } + + @Test + public void readPolicy_strong_returnsAllResults() { + double deadline = 5.0; + ReadPolicy policy = new ReadPolicy(ReadPolicy.Consistency.STRONG); + DatastoreServiceConfig datastoreConfig = + DatastoreServiceConfig.Builder.withReadPolicy(policy).deadline(deadline); + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(datastoreConfig); + + Entity parent = new Entity("Person", "a"); + Entity child = new Entity("Person", "b", parent.getKey()); + datastore.put(ImmutableList.of(parent, child)); + + Query q = new Query("Person").setAncestor(parent.getKey()); + List results = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).hasSize(2); + } +} diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/StartupServletTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/StartupServletTest.java deleted file mode 120000 index cc898f78bbb..00000000000 --- a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/StartupServletTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/StartupServletTest.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/StartupServletTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/StartupServletTest.java new file mode 100644 index 00000000000..2da82842484 --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/StartupServletTest.java @@ -0,0 +1,108 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.Mockito.when; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.Filter; +import com.google.appengine.api.datastore.Query.FilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.io.PrintWriter; +import java.io.StringWriter; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link StartupServlet}. + */ +@RunWith(JUnit4.class) +public class StartupServletTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google + // .com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + @Mock + private HttpServletRequest mockRequest; + @Mock + private HttpServletResponse mockResponse; + private StringWriter responseWriter; + private DatastoreService datastore; + + private StartupServlet servletUnderTest; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + + // Set up a fake HTTP response. + responseWriter = new StringWriter(); + when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter)); + + servletUnderTest = new StartupServlet(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void doGet_emptyDatastore_writesOkay() throws Exception { + servletUnderTest.doGet(mockRequest, mockResponse); + assertWithMessage("StartupServlet response").that(responseWriter.toString()).isEqualTo("ok\n"); + } + + @Test + public void doGet_emptyDatastore_writesPresidents() throws Exception { + servletUnderTest.doGet(mockRequest, mockResponse); + + Filter nameFilter = new FilterPredicate("name", FilterOperator.EQUAL, "George Washington"); + Query q = new Query("Person").setFilter(nameFilter); + Entity result = datastore.prepare(q).asSingleEntity(); + assertWithMessage("name").that(result.getProperty("name")).isEqualTo("George Washington"); + } + + @Test + public void doGet_alreadyPopulated_writesOkay() throws Exception { + datastore.put( + new Entity(StartupServlet.IS_POPULATED_ENTITY, StartupServlet.IS_POPULATED_KEY_NAME)); + servletUnderTest.doGet(mockRequest, mockResponse); + assertWithMessage("StartupServlet response").that(responseWriter.toString()).isEqualTo("ok\n"); + } +} diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/TransactionsTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/TransactionsTest.java deleted file mode 120000 index 8c3e828ead3..00000000000 --- a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/TransactionsTest.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../appengine-java8/datastore/src/test/java/com/example/appengine/TransactionsTest.java \ No newline at end of file diff --git a/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/TransactionsTest.java b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/TransactionsTest.java new file mode 100644 index 00000000000..0e9df0e093c --- /dev/null +++ b/appengine-java17-bundled-services/datastore/src/test/java/com/example/appengine/TransactionsTest.java @@ -0,0 +1,293 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertWithMessage; +import static org.junit.Assert.fail; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.EntityNotFoundException; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.KeyFactory; +import com.google.appengine.api.datastore.PreparedQuery; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Transaction; +import com.google.appengine.api.datastore.TransactionOptions; +import com.google.appengine.api.taskqueue.Queue; +import com.google.appengine.api.taskqueue.QueueFactory; +import com.google.appengine.api.taskqueue.TaskOptions; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.util.ConcurrentModificationException; +import java.util.Date; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests to demonstrate App Engine Datastore transactions. + */ +@RunWith(JUnit4.class) +public class TransactionsTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Use High Rep job policy to allow cross group transactions in tests. + new LocalDatastoreServiceTestConfig().setApplyAllHighRepJobPolicy()); + + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @After + public void tearDown() { + // Clean up any dangling transactions. + Transaction txn = datastore.getCurrentTransaction(null); + if (txn != null && txn.isActive()) { + txn.rollback(); + } + helper.tearDown(); + } + + @Test + public void usingTransactions() throws Exception { + Entity joe = new Entity("Employee", "Joe"); + datastore.put(joe); + + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + Transaction txn = datastore.beginTransaction(); + try { + Key employeeKey = KeyFactory.createKey("Employee", "Joe"); + Entity employee = datastore.get(employeeKey); + employee.setProperty("vacationDays", 10); + + datastore.put(txn, employee); + + txn.commit(); + } finally { + if (txn.isActive()) { + txn.rollback(); + } + } + } + + @Test + public void entityGroups() throws Exception { + try { + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + Entity person = new Entity("Person", "tom"); + datastore.put(person); + + // Transactions on root entities + Transaction txn = datastore.beginTransaction(); + + Entity tom = datastore.get(person.getKey()); + tom.setProperty("age", 40); + datastore.put(txn, tom); + txn.commit(); + + // Transactions on child entities + txn = datastore.beginTransaction(); + tom = datastore.get(person.getKey()); + Entity photo = new Entity("Photo", tom.getKey()); + + // Create a Photo that is a child of the Person entity named "tom" + photo.setProperty("photoUrl", "http://domain.com/path/to/photo.jpg"); + datastore.put(txn, photo); + txn.commit(); + + // Transactions on entities in different entity groups + txn = datastore.beginTransaction(); + tom = datastore.get(person.getKey()); + Entity photoNotaChild = new Entity("Photo"); + photoNotaChild.setProperty("photoUrl", "http://domain.com/path/to/photo.jpg"); + datastore.put(txn, photoNotaChild); + + // Throws IllegalArgumentException because the Person entity + // and the Photo entity belong to different entity groups. + txn.commit(); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + // We expect to get an exception that complains that we don't have a XG-transaction. + } + } + + @Test + @SuppressWarnings("checkstyle:AbbreviationAsWordInName") + public void creatingAnEntityInASpecificEntityGroup() throws Exception { + String boardName = "my-message-board"; + + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + + String messageTitle = "Some Title"; + String messageText = "Some message."; + Date postDate = new Date(); + + Key messageBoardKey = KeyFactory.createKey("MessageBoard", boardName); + + Entity message = new Entity("Message", messageBoardKey); + message.setProperty("message_title", messageTitle); + message.setProperty("message_text", messageText); + message.setProperty("post_date", postDate); + + Transaction txn = datastore.beginTransaction(); + datastore.put(txn, message); + + txn.commit(); + } + + @Test + public void crossGroupTransactions() throws Exception { + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + TransactionOptions options = TransactionOptions.Builder.withXG(true); + Transaction txn = datastore.beginTransaction(options); + + Entity a = new Entity("A"); + a.setProperty("a", 22); + datastore.put(txn, a); + + Entity b = new Entity("B"); + b.setProperty("b", 11); + datastore.put(txn, b); + + txn.commit(); + } + + @Test + public void usesForTransactions_relativeUpdates() throws Exception { + String boardName = "my-message-board"; + Entity b = new Entity("MessageBoard", boardName); + b.setProperty("count", 41); + datastore.put(b); + + int retries = 3; + while (true) { + Transaction txn = datastore.beginTransaction(); + try { + Key boardKey = KeyFactory.createKey("MessageBoard", boardName); + Entity messageBoard = datastore.get(boardKey); + + long count = (Long) messageBoard.getProperty("count"); + ++count; + messageBoard.setProperty("count", count); + datastore.put(txn, messageBoard); + + txn.commit(); + break; + } catch (ConcurrentModificationException e) { + if (retries == 0) { + throw e; + } + // Allow retry to occur + --retries; + } finally { + if (txn.isActive()) { + txn.rollback(); + } + } + } + + b = datastore.get(KeyFactory.createKey("MessageBoard", boardName)); + assertWithMessage("board.count").that((long) b.getProperty("count")).isEqualTo(42L); + } + + private Entity fetchOrCreate(String boardName) { + Transaction txn = datastore.beginTransaction(); + Entity messageBoard; + Key boardKey; + try { + boardKey = KeyFactory.createKey("MessageBoard", boardName); + messageBoard = datastore.get(boardKey); + } catch (EntityNotFoundException e) { + messageBoard = new Entity("MessageBoard", boardName); + messageBoard.setProperty("count", 0L); + boardKey = datastore.put(txn, messageBoard); + } + txn.commit(); + + return messageBoard; + } + + @Test + public void usesForTransactions_fetchOrCreate_fetchesExisting() throws Exception { + Entity b = new Entity("MessageBoard", "my-message-board"); + b.setProperty("count", 7); + datastore.put(b); + + Entity board = fetchOrCreate("my-message-board"); + + assertWithMessage("board.count").that((long) board.getProperty("count")).isEqualTo(7L); + } + + @Test + public void usesForTransactions_fetchOrCreate_createsNew() throws Exception { + Entity board = fetchOrCreate("my-message-board"); + assertWithMessage("board.count").that((long) board.getProperty("count")).isEqualTo(0L); + } + + @Test + public void usesForTransactions_readSnapshot() throws Exception { + String boardName = "my-message-board"; + Entity b = new Entity("MessageBoard", boardName); + b.setProperty("count", 13); + datastore.put(b); + + DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); + + // Display information about a message board and its first 10 messages. + Key boardKey = KeyFactory.createKey("MessageBoard", boardName); + + Transaction txn = datastore.beginTransaction(); + + Entity messageBoard = datastore.get(boardKey); + long count = (Long) messageBoard.getProperty("count"); + + Query q = new Query("Message", boardKey); + + // This is an ancestor query. + PreparedQuery pq = datastore.prepare(txn, q); + List messages = pq.asList(FetchOptions.Builder.withLimit(10)); + + txn.commit(); + + assertWithMessage("board.count").that(count).isEqualTo(13L); + } + + @Test + public void transactionalTaskEnqueuing() throws Exception { + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + Queue queue = QueueFactory.getDefaultQueue(); + Transaction txn = datastore.beginTransaction(); + // ... + + queue.add(txn, TaskOptions.Builder.withUrl("/path/to/handler")); + + // ... + + txn.commit(); + } +} diff --git a/appengine-java21/ee8/analytics/README.md b/appengine-java21/ee8/analytics/README.md new file mode 100644 index 00000000000..415bd450dbd --- /dev/null +++ b/appengine-java21/ee8/analytics/README.md @@ -0,0 +1,24 @@ +# Google Analytics sample for Google App Engine + + +Open in Cloud Shell + +Integrating App Engine with Google Analytics. + +## Project setup, installation, and configuration + +- Register for [Google Analytics](http://www.google.com/analytics/), create +an application, and get a tracking Id. +- [Find your tracking Id](https://support.google.com/analytics/answer/1008080?hl=en) +and set it as an environment variable in [`appengine-web.xml`](src/main/webapp/WEB-INF/appengine-web.xml). + +## Running locally +This example uses the +[Maven Cloud CLI based plugin](https://cloud.google.com/appengine/docs/java/tools/using-maven). +To run this sample locally: + + $ mvn appengine:run + +## Deploying + + $ mvn clean package appengine:deploy diff --git a/appengine-java21/ee8/analytics/pom.xml b/appengine-java21/ee8/analytics/pom.xml new file mode 100644 index 00000000000..c8d726fa071 --- /dev/null +++ b/appengine-java21/ee8/analytics/pom.xml @@ -0,0 +1,136 @@ + + + 4.0.0 + war + 1.0-SNAPSHOT + com.example.appengine + appengine-analytics-j21 + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + 21 + 21 + 3.0.1 + + + + + + libraries-bom + com.google.cloud + import + pom + 26.28.0 + + + + + + + + com.google.appengine + appengine-api-1.0-sdk + 2.0.23 + + + + jstl + jstl + 1.2 + + + + org.apache.httpcomponents + httpclient + 4.5.14 + + + + javax.servlet + javax.servlet-api + 3.1.0 + jar + provided + + + + + com.google.appengine + appengine-testing + ${appengine.sdk.version} + test + + + com.google.appengine + appengine-api-stubs + 2.0.23 + test + + + + junit + junit + 4.13.2 + test + + + org.mockito + mockito-core + 4.11.0 + test + + + com.google.truth + truth + 1.1.5 + test + + + + + + ${project.build.directory}/${project.build.finalName}/WEB-INF/classes + + + org.apache.maven.plugins + maven-war-plugin + 3.4.0 + + + com.google.cloud.tools + appengine-maven-plugin + 2.5.0 + + GCLOUD_CONFIG + analytics + true + true + + + + + diff --git a/appengine-java21/ee8/analytics/src/main/java/com/example/appengine/analytics/AnalyticsServlet.java b/appengine-java21/ee8/analytics/src/main/java/com/example/appengine/analytics/AnalyticsServlet.java new file mode 100644 index 00000000000..d9e9650acfe --- /dev/null +++ b/appengine-java21/ee8/analytics/src/main/java/com/example/appengine/analytics/AnalyticsServlet.java @@ -0,0 +1,70 @@ +/* + * Copyright 2015 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine.analytics; + +// [START gae_java21_analytics_track] +import com.google.appengine.api.urlfetch.URLFetchService; +import com.google.appengine.api.urlfetch.URLFetchServiceFactory; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.http.client.utils.URIBuilder; + +@SuppressWarnings("serial") +// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required. +@WebServlet( + name = "analytics", + description = "Analytics: Send Analytics Event to Google Analytics", + urlPatterns = "/analytics") +public class AnalyticsServlet extends HttpServlet { + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws IOException, ServletException { + String trackingId = System.getenv("GA_TRACKING_ID"); + URIBuilder builder = new URIBuilder(); + builder + .setScheme("http") + .setHost("www.google-analytics.com") + .setPath("/collect") + .addParameter("v", "1") // API Version. + .addParameter("tid", trackingId) // Tracking ID / Property ID. + // Anonymous Client Identifier. Ideally, this should be a UUID that + // is associated with particular user, device, or browser instance. + .addParameter("cid", "555") + .addParameter("t", "event") // Event hit type. + .addParameter("ec", "example") // Event category. + .addParameter("ea", "test action"); // Event action. + URI uri = null; + try { + uri = builder.build(); + } catch (URISyntaxException e) { + throw new ServletException("Problem building URI", e); + } + URLFetchService fetcher = URLFetchServiceFactory.getURLFetchService(); + URL url = uri.toURL(); + fetcher.fetch(url); + resp.getWriter().println("Event tracked."); + } +} +// [END gae_java21_analytics_track] diff --git a/appengine-java21/ee8/analytics/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java21/ee8/analytics/src/main/webapp/WEB-INF/appengine-web.xml new file mode 100644 index 00000000000..b208f293171 --- /dev/null +++ b/appengine-java21/ee8/analytics/src/main/webapp/WEB-INF/appengine-web.xml @@ -0,0 +1,25 @@ + + + + + + java21 + true + + + + + + + diff --git a/appengine-java21/ee8/analytics/src/main/webapp/WEB-INF/web.xml b/appengine-java21/ee8/analytics/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..8481f9bae83 --- /dev/null +++ b/appengine-java21/ee8/analytics/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,23 @@ + + + + + + + analytics + + diff --git a/appengine-java21/ee8/bigquery/README.md b/appengine-java21/ee8/bigquery/README.md new file mode 100644 index 00000000000..4e557b6e61a --- /dev/null +++ b/appengine-java21/ee8/bigquery/README.md @@ -0,0 +1,65 @@ + +Open in Cloud Shell + +# Google Cloud API Showcase: BigQuery & Cloud Monitoring in App Engine standard environment for Java 21 + +This API Showcase demonstrates how to run an App Engine standard environment application with dependencies on both +[Google BigQuery][bigquery] and [Cloud Monitoring][monitoring]. + +[bigquery]: https://cloud.google.com/bigquery/docs +[monitoring]: https://cloud.google.com/monitoring/docs + +The home page of this application provides a form to initiate a query of public data, in this case StackOverflow +questions tagged with `google-bigquery`. + +The home page also provides a summary view of the metrics that have been logged in the past 30 days. + +## Clone the sample app + +Copy the sample apps to your local machine, and cd to the `appengine-java21/bigquery` directory: + +``` +git clone https://github.com/GoogleCloudPlatform/java-docs-samples +cd appengine-java21/bigquery +``` + +## Setup + +- Make sure [`gcloud`](https://cloud.google.com/sdk/docs/) is installed and initialized: +``` + gcloud init +``` +- If this is the first time you are creating an App Engine project +``` + gcloud app create +``` +- For local development, [set up][set-up] authentication +- Enable [BigQuery][bigquery-api] and [Monitoring][monitoring-api] APIs + +[set-up]: https://cloud.google.com/docs/authentication/getting-started +[bigquery-api]: https://console.cloud.google.com/launcher/details/google/bigquery-json.googleapis.com +[monitoring-api]: https://console.cloud.google.com/launcher/details/google/monitoring.googleapis.com + +## Run locally +Run using shown Maven command. You can then direct your browser to `http://localhost:8080/` to see the most recent query +run (since the app started) and the metrics from the past 30 days. + +``` +mvn appengine:run +``` + +Note: The first time the app is run (or after any metrics definitions have been deleted) it may take up to 5 minutes for +the MetricDescriptors to sync with StackDriver before any results are shown. If you do not see results, please wait a +few moments and try again. + +## Deploy + +- Deploy to App Engine standard environment using the following Maven command. +``` + mvn clean package appengine:deploy +``` +- Direct your browser to `https://.appspot.com`. +- View more in-depth metrics data on the [Cloud Monitoring Dashboard][dashboard] + +[dashboard]: https://pantheon.corp.google.com/monitoring + diff --git a/appengine-java21/ee8/bigquery/pom.xml b/appengine-java21/ee8/bigquery/pom.xml new file mode 100644 index 00000000000..e9c4723bca1 --- /dev/null +++ b/appengine-java21/ee8/bigquery/pom.xml @@ -0,0 +1,138 @@ + + + 4.0.0 + war + 1.0-SNAPSHOT + com.example.appengine + appengine-bigquery-monitoring-j21 + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + 1.8 + 1.8 + + + + + + libraries-bom + com.google.cloud + import + pom + 26.28.0 + + + + + + + com.google.appengine + appengine-api-1.0-sdk + 2.0.23 + + + javax.servlet + javax.servlet-api + 3.1.0 + jar + provided + + + + com.google.cloud + google-cloud-bigquery + + + com.google.cloud + google-cloud-monitoring + + + + + com.google.appengine + appengine-api-stubs + 2.0.23 + test + + + com.google.appengine + appengine-tools-sdk + 2.0.23 + test + + + + junit + junit + 4.13.2 + test + + + org.mockito + mockito-core + 4.11.0 + test + + + com.google.appengine + appengine-testing + 2.0.23 + test + + + com.google.truth + truth + 1.1.5 + test + + + + + ${project.build.directory}/${project.build.finalName}/WEB-INF/classes + + + com.google.cloud.tools + appengine-maven-plugin + 2.5.0 + + GCLOUD_CONFIG + GCLOUD_CONFIG + true + true + + + + + org.apache.maven.plugins + maven-war-plugin + 3.4.0 + + + + + diff --git a/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryHome.java b/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryHome.java new file mode 100644 index 00000000000..91884d9cb72 --- /dev/null +++ b/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryHome.java @@ -0,0 +1,76 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine.bigquerylogging; + +import com.google.cloud.bigquery.FieldValueList; +import com.google.cloud.bigquery.TableResult; +import java.io.IOException; +import java.util.List; + +public class BigQueryHome { + private static BigQueryRunner queryRunner; + + private static BigQueryRunner getQueryRunner() throws IOException { + if (queryRunner == null) { + queryRunner = BigQueryRunner.getInstance(); + } + return queryRunner; + } + + public static String getMostRecentRun() throws IOException { + return convertRunToHtmlTable(BigQueryRunner.getMostRecentRunResult()); + } + + public static String getMetricAverages() throws IOException { + return convertAveragesToHtmlTable(getQueryRunner().getTimeSeriesValues()); + } + + private static String convertRunToHtmlTable(TableResult result) { + if (result == null) { + return ""; + } + + StringBuilder sb = new StringBuilder(); + for (FieldValueList row : result.iterateAll()) { + sb.append(""); + String url = row.get("url").getStringValue(); + addColumn(sb, String.format("%s", url, url)); + addColumn(sb, row.get("view_count").getLongValue()); + sb.append(""); + } + return sb.toString(); + } + + private static String convertAveragesToHtmlTable(List> values) { + + StringBuilder sb = new StringBuilder(); + for (TimeSeriesSummary metric : values) { + sb.append(""); + addColumn(sb, metric.getName()); + addColumn(sb, metric.getValues().size()); + addColumn(sb, metric.getMostRecentRunTime()); + addColumn(sb, metric.getMostRecentValue()); + addColumn(sb, metric.getAverage()); + sb.append(""); + } + return sb.toString(); + } + + private static void addColumn(StringBuilder sb, T content) { + sb.append("").append(content.toString()).append(""); + } +} diff --git a/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryRun.java b/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryRun.java new file mode 100644 index 00000000000..fa83033229e --- /dev/null +++ b/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryRun.java @@ -0,0 +1,44 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine.bigquerylogging; + +import java.io.IOException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet(name = "runQuery BigQuery", value = "/bigquery/run") +public class BigQueryRun extends HttpServlet { + private BigQueryRunner queryRunner; + + public BigQueryRun() throws IOException { + this.queryRunner = BigQueryRunner.getInstance(); + } + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { + try { + queryRunner.runQuery(); + } catch (InterruptedException e) { + resp.sendError( + HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Interrupted while running BigQuery job."); + } + // redirect to home page + resp.sendRedirect("/"); + } +} diff --git a/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryRunner.java b/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryRunner.java new file mode 100644 index 00000000000..0e4702e12ae --- /dev/null +++ b/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryRunner.java @@ -0,0 +1,255 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine.bigquerylogging; + +import com.google.api.Metric; +import com.google.api.MetricDescriptor; +import com.google.cloud.ServiceOptions; +import com.google.cloud.bigquery.BigQuery; +import com.google.cloud.bigquery.BigQueryOptions; +import com.google.cloud.bigquery.Job; +import com.google.cloud.bigquery.JobId; +import com.google.cloud.bigquery.JobInfo; +import com.google.cloud.bigquery.QueryJobConfiguration; +import com.google.cloud.bigquery.TableResult; +import com.google.cloud.monitoring.v3.MetricServiceClient; +import com.google.cloud.monitoring.v3.MetricServiceClient.ListMetricDescriptorsPagedResponse; +import com.google.cloud.monitoring.v3.MetricServiceClient.ListTimeSeriesPagedResponse; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.monitoring.v3.CreateMetricDescriptorRequest; +import com.google.monitoring.v3.CreateTimeSeriesRequest; +import com.google.monitoring.v3.ListMetricDescriptorsRequest; +import com.google.monitoring.v3.ListTimeSeriesRequest; +import com.google.monitoring.v3.Point; +import com.google.monitoring.v3.TimeInterval; +import com.google.monitoring.v3.TimeSeries; +import com.google.monitoring.v3.TypedValue; +import com.google.protobuf.util.Timestamps; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +public class BigQueryRunner { + private static final String CUSTOM_METRIC_FILTER = + "metric.type = starts_with(\"custom.googleapis.com/\")"; + private static BigQueryRunner instance; + + private static final MetricDescriptor QUERY_DURATION_METRIC = + MetricDescriptor.newBuilder() + .setName("custom.googleapis.com/queryDuration") + .setType("custom.googleapis.com/queryDuration") + .setDisplayName("queryDuration") + .setDescription("Time it took a query to run.") + .setMetricKind(MetricDescriptor.MetricKind.GAUGE) + .setValueType(MetricDescriptor.ValueType.INT64) + .build(); + private static final MetricDescriptor ROWS_RETURNED_METRIC = + MetricDescriptor.newBuilder() + .setName("custom.googleapis.com/rowsReturned") + .setType("custom.googleapis.com/rowsReturned") + .setDisplayName("rowsReturned") + .setDescription("Total rows returned by the query result.") + .setMetricKind(MetricDescriptor.MetricKind.GAUGE) + .setValueType(MetricDescriptor.ValueType.INT64) + .build(); + private static final Set REQUIRED_METRICS = + ImmutableSet.of(QUERY_DURATION_METRIC, ROWS_RETURNED_METRIC); + + private static TableResult mostRecentRunResult; + private static Set existingMetrics = Sets.newHashSet(); + + private final MetricServiceClient client; + private final BigQuery bigquery; + private final String projectName; + private PrintStream os; + + // Retrieve a singleton instance + public static synchronized BigQueryRunner getInstance() throws IOException { + if (instance == null) { + instance = new BigQueryRunner(); + } + return instance; + } + + private BigQueryRunner() throws IOException { + this( + MetricServiceClient.create(), + BigQueryOptions.getDefaultInstance().getService(), + System.out); + } + + BigQueryRunner(MetricServiceClient metricsClient, BigQuery bigquery, PrintStream os) { + client = metricsClient; + this.os = os; + this.projectName = String.format("projects/%s", ServiceOptions.getDefaultProjectId()); + this.bigquery = bigquery; + } + + public static TableResult getMostRecentRunResult() { + return mostRecentRunResult; + } + + public void runQuery() throws InterruptedException { + QueryJobConfiguration queryConfig = + QueryJobConfiguration.newBuilder( + "SELECT " + + "CONCAT('https://stackoverflow.com/questions/', CAST(id as STRING)) as url, " + + "view_count " + + "FROM `bigquery-public-data.stackoverflow.posts_questions` " + + "WHERE tags like '%google-bigquery%' " + + "ORDER BY favorite_count DESC LIMIT 10") + // Use standard SQL syntax for queries. + // See: https://cloud.google.com/bigquery/sql-reference/ + .setUseLegacySql(false) + .build(); + + List timeSeriesList = new ArrayList<>(); + + long queryStartTime = System.currentTimeMillis(); + + // Create a job ID so that we can safely retry. + JobId jobId = JobId.of(UUID.randomUUID().toString()); + Job queryJob = bigquery.create(JobInfo.newBuilder(queryConfig).setJobId(jobId).build()); + + // Wait for the query to complete. + queryJob = queryJob.waitFor(); + + // Check for errors + if (queryJob == null) { + throw new RuntimeException("Job no longer exists"); + } else if (queryJob.getStatus().getError() != null) { + // You can also look at queryJob.getStatus().getExecutionErrors() for all + // errors, not just the latest one. + throw new RuntimeException(queryJob.getStatus().getError().toString()); + } + + // Log the result metrics. + TableResult result = queryJob.getQueryResults(); + + long queryEndTime = System.currentTimeMillis(); + // Add query duration metric. + timeSeriesList.add(prepareMetric(QUERY_DURATION_METRIC, queryEndTime - queryStartTime)); + + // Add rows returned metric. + timeSeriesList.add(prepareMetric(ROWS_RETURNED_METRIC, result.getTotalRows())); + + // Prepares the time series request + CreateTimeSeriesRequest request = + CreateTimeSeriesRequest.newBuilder() + .setName(projectName) + .addAllTimeSeries(timeSeriesList) + .build(); + + createMetricsIfNeeded(); + client.createTimeSeries(request); + os.println("Done writing metrics."); + + mostRecentRunResult = result; + } + + // Returns a metric time series with a single int64 data point. + private TimeSeries prepareMetric(MetricDescriptor requiredMetric, long metricValue) { + TimeInterval interval = + TimeInterval.newBuilder() + .setEndTime(Timestamps.fromMillis(System.currentTimeMillis())) + .build(); + TypedValue value = TypedValue.newBuilder().setInt64Value(metricValue).build(); + + Point point = Point.newBuilder().setInterval(interval).setValue(value).build(); + + List pointList = Lists.newArrayList(); + pointList.add(point); + + Metric metric = Metric.newBuilder().setType(requiredMetric.getName()).build(); + + return TimeSeries.newBuilder().setMetric(metric).addAllPoints(pointList).build(); + } + + public List> getTimeSeriesValues() { + List> summaries = Lists.newArrayList(); + createMetricsIfNeeded(); + for (MetricDescriptor metric : REQUIRED_METRICS) { + ListTimeSeriesRequest listTimeSeriesRequest = + ListTimeSeriesRequest.newBuilder() + .setName(projectName) + .setFilter(String.format("metric.type = \"%s\"", metric.getType())) + .setInterval( + TimeInterval.newBuilder() + .setStartTime( + Timestamps.subtract( + Timestamps.fromMillis(System.currentTimeMillis()), + com.google.protobuf.Duration.newBuilder() + .setSeconds(60L * 60L * 24L * 30L) // 30 days ago + .build())) + .setEndTime(Timestamps.fromMillis(System.currentTimeMillis())) + .build()) + .build(); + try { + ListTimeSeriesPagedResponse listTimeSeriesResponse = + client.listTimeSeries(listTimeSeriesRequest); + ArrayList timeSeries = Lists.newArrayList(listTimeSeriesResponse.iterateAll()); + summaries.addAll( + timeSeries.stream() + .map(TimeSeriesSummary::fromTimeSeries) + .collect(Collectors.toList())); + } catch (RuntimeException ex) { + os.println("MetricDescriptors not yet synced. Please try again in a moment."); + } + } + return summaries; + } + + private void createMetricsIfNeeded() { + // If all required metrics already exist, no need to make service calls. + if (REQUIRED_METRICS.stream() + .map(MetricDescriptor::getDisplayName) + .allMatch(existingMetrics::contains)) { + return; + } + ListMetricDescriptorsRequest listMetricsRequest = + ListMetricDescriptorsRequest.newBuilder() + .setName(projectName) + .setFilter(CUSTOM_METRIC_FILTER) + .build(); + ListMetricDescriptorsPagedResponse listMetricsResponse = + client.listMetricDescriptors(listMetricsRequest); + + for (MetricDescriptor existingMetric : listMetricsResponse.iterateAll()) { + existingMetrics.add(existingMetric.getDisplayName()); + } + + REQUIRED_METRICS.stream() + .filter(metric -> !existingMetrics.contains(metric.getDisplayName())) + .forEach(this::createMetric); + } + + private void createMetric(MetricDescriptor newMetric) { + CreateMetricDescriptorRequest request = + CreateMetricDescriptorRequest.newBuilder() + .setName(projectName) + .setMetricDescriptor(newMetric) + .build(); + + client.createMetricDescriptor(request); + } +} diff --git a/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/TimeSeriesSummary.java b/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/TimeSeriesSummary.java new file mode 100644 index 00000000000..04e2e23e543 --- /dev/null +++ b/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/TimeSeriesSummary.java @@ -0,0 +1,116 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine.bigquerylogging; + +import com.google.common.collect.Collections2; +import com.google.common.collect.Lists; +import com.google.monitoring.v3.Point; +import com.google.monitoring.v3.TimeSeries; +import com.google.protobuf.Timestamp; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +public abstract class TimeSeriesSummary { + private String name; + private Timestamp mostRecentRunTime; + T mostRecentValue; + List values; + + public static TimeSeriesSummary fromTimeSeries(TimeSeries timeSeries) { + switch (timeSeries.getValueType()) { + case STRING: + return new StringTimeSeriesSummary(timeSeries); + case INT64: + return new Int64TimeSeriesSummary(timeSeries); + default: + return null; + } + } + + private TimeSeriesSummary(TimeSeries timeSeries) { + name = timeSeries.getMetric().getType(); + } + + Point getMostRecentPoint(TimeSeries timeSeries) { + Point max = + Collections.max( + timeSeries.getPointsList(), + Comparator.comparingLong(p -> p.getInterval().getEndTime().getSeconds())); + mostRecentRunTime = max.getInterval().getEndTime(); + return max; + } + + public String getName() { + return name; + } + + public T getMostRecentValue() { + return mostRecentValue; + } + + public Timestamp getMostRecentRunTime() { + return mostRecentRunTime; + } + + public List getValues() { + return values; + } + + public abstract T getAverage(); + + public static class StringTimeSeriesSummary extends TimeSeriesSummary { + private StringTimeSeriesSummary(TimeSeries timeSeries) { + super(timeSeries); + Point max = getMostRecentPoint(timeSeries); + if (max == null) { + return; + } + mostRecentValue = max.getValue().getStringValue(); + values = + Lists.newArrayList( + Collections2.transform( + timeSeries.getPointsList(), point -> point.getValue().getStringValue())); + } + + @Override + public String getAverage() { + return values.stream().collect(Collectors.joining(",")); + } + } + + public static class Int64TimeSeriesSummary extends TimeSeriesSummary { + private Int64TimeSeriesSummary(TimeSeries timeSeries) { + super(timeSeries); + Point max = getMostRecentPoint(timeSeries); + if (max == null) { + return; + } + mostRecentValue = max.getValue().getInt64Value(); + values = + Lists.newArrayList( + Collections2.transform( + timeSeries.getPointsList(), point -> point.getValue().getInt64Value())); + } + + @Override + public Long getAverage() { + return values.stream().collect(Collectors.averagingLong(Long::longValue)).longValue(); + } + } +} diff --git a/appengine-java21/ee8/bigquery/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java21/ee8/bigquery/src/main/webapp/WEB-INF/appengine-web.xml new file mode 100644 index 00000000000..15fc6f754c4 --- /dev/null +++ b/appengine-java21/ee8/bigquery/src/main/webapp/WEB-INF/appengine-web.xml @@ -0,0 +1,10 @@ + + + java21 + true + + + + + dzlier-work + \ No newline at end of file diff --git a/appengine-java21/ee8/bigquery/src/main/webapp/index.jsp b/appengine-java21/ee8/bigquery/src/main/webapp/index.jsp new file mode 100644 index 00000000000..af045fbf1fc --- /dev/null +++ b/appengine-java21/ee8/bigquery/src/main/webapp/index.jsp @@ -0,0 +1,30 @@ +<%@ page import="com.example.appengine.bigquerylogging.BigQueryHome" %> + + + An example of using BigQuery and StackDriver Logging on AppEngine Standard + +

Run query

+
+ +
+

Most Recent Run Results

+ + + + + + <%= BigQueryHome.getMostRecentRun() %> +
URLView Count
+

Run Metric Values

+ + + + + + + + + <%= BigQueryHome.getMetricAverages() %> +
Metric TypeCountMost Recent End TimeMost Recent ValueAverage/Values
+ + diff --git a/appengine-java21/ee8/bigquery/src/test/java/com/example/appengine/bigquerylogging/BigQueryRunnerTest.java b/appengine-java21/ee8/bigquery/src/test/java/com/example/appengine/bigquerylogging/BigQueryRunnerTest.java new file mode 100644 index 00000000000..1332c3afa05 --- /dev/null +++ b/appengine-java21/ee8/bigquery/src/test/java/com/example/appengine/bigquerylogging/BigQueryRunnerTest.java @@ -0,0 +1,107 @@ +/* + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine.bigquerylogging; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.api.MetricDescriptor; +import com.google.api.gax.rpc.UnaryCallable; +import com.google.cloud.bigquery.BigQueryOptions; +import com.google.cloud.monitoring.v3.MetricServiceClient; +import com.google.cloud.monitoring.v3.MetricServiceClient.ListMetricDescriptorsPagedResponse; +import com.google.cloud.monitoring.v3.stub.MetricServiceStub; +import com.google.monitoring.v3.CreateMetricDescriptorRequest; +import com.google.monitoring.v3.CreateTimeSeriesRequest; +import com.google.monitoring.v3.ListMetricDescriptorsRequest; +import com.google.protobuf.Empty; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.Collections; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Tests for simple app sample. */ +@RunWith(JUnit4.class) +public class BigQueryRunnerTest { + private ByteArrayOutputStream bout; + private BigQueryRunner app; + + @Mock private MetricServiceStub metricsServiceStub; + + @Mock + private UnaryCallable + listCallable; + + @Mock private UnaryCallable createMetricCallable; + @Mock private UnaryCallable createTimeSeriesCallable; + @Mock private ListMetricDescriptorsPagedResponse listResponse; + + @Captor private ArgumentCaptor createTimeSeriesRequest; + + @Before + public void setUp() { + MockitoAnnotations.openMocks(this); + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + + MetricServiceClient metricsClient = MetricServiceClient.create(metricsServiceStub); + app = new BigQueryRunner(metricsClient, BigQueryOptions.getDefaultInstance().getService(), out); + + when(metricsServiceStub.listMetricDescriptorsPagedCallable()).thenReturn(listCallable); + when(listCallable.call(any(ListMetricDescriptorsRequest.class))).thenReturn(listResponse); + when(listResponse.iterateAll()).thenReturn(Collections.emptyList()); + + when(metricsServiceStub.createMetricDescriptorCallable()).thenReturn(createMetricCallable); + when(createMetricCallable.call(any(CreateMetricDescriptorRequest.class))).thenReturn(null); + + when(metricsServiceStub.createTimeSeriesCallable()).thenReturn(createTimeSeriesCallable); + when(createTimeSeriesCallable.call(any(CreateTimeSeriesRequest.class))) + .thenReturn(Empty.getDefaultInstance()); + } + + @Test + public void testRun() throws Exception { + app.runQuery(); + String got = bout.toString(); + assertThat(got).contains("Done writing metrics."); + verify(metricsServiceStub).listMetricDescriptorsPagedCallable(); + + verify(metricsServiceStub, times(2)).createMetricDescriptorCallable(); + + verify(metricsServiceStub).createTimeSeriesCallable(); + verify(createTimeSeriesCallable).call(createTimeSeriesRequest.capture()); + CreateTimeSeriesRequest actual = createTimeSeriesRequest.getValue(); + assertEquals(2, actual.getTimeSeriesCount()); + assertThat(actual.getTimeSeries(0).getMetric().getType()) + .isEqualTo("custom.googleapis.com/queryDuration"); + assertThat(actual.getTimeSeries(0).getPoints(0).getValue().getInt64Value()).isGreaterThan(0L); + assertThat(actual.getTimeSeries(1).getMetric().getType()) + .isEqualTo("custom.googleapis.com/rowsReturned"); + assertThat(actual.getTimeSeries(1).getPoints(0).getValue().getInt64Value()).isGreaterThan(0L); + } +} diff --git a/appengine-java21/ee8/bigtable/README.md b/appengine-java21/ee8/bigtable/README.md new file mode 100644 index 00000000000..c4df5547773 --- /dev/null +++ b/appengine-java21/ee8/bigtable/README.md @@ -0,0 +1,59 @@ +Bigtable-hello-j21 +================= + + +Open in Cloud Shell + +Moves the Bigtable Hello World application to Google App Engine Standard for Java 21. + + +* [Java 21](http://www.oracle.com/technetwork/java/javase/downloads/index.html) +* [Maven](https://maven.apache.org/download.cgi) (at least 3.3.9) +* [Gradle](https://gradle.org) +* [Google Cloud CLI](https://cloud.google.com/cli/) (aka gcloud) + +Initialize the Google Cloud CLI using: + + gcloud init + + gcloud auth application-default login + +Then you need to [Create a Cloud Bigtable Instance](https://cloud.google.com/bigtable/docs/creating-instance) + + +## Using Maven + +### Run Locally + + mvn -Dbigtable.projectID=PROJECTID -Dbigtable.instanceID=INSTANCEID appengine:run + +### Deploy to App Engine Standard for Java 21 + + mvn -Dbigtable.projectID=PROJECTID -Dbigtable.instanceID=INSTANCEID package appengine:deploy + +### Run Integration Tests + + mvn -Dbigtable.projectID=PROJECTID -Dbigtable.instanceID=INSTANCEID verify + +## Using Gradle + +### Run Locally + + gradle -Dbigtable.projectID=PROJECTID -Dbigtable.instanceID=INSTANCEID appengineRun + +### Integration Tests & Deploy to App Engine Standard for Java 21 + + gradle -Dbigtable.projectID=PROJECTID -Dbigtable.instanceID=INSTANCEID appengineDeploy + +As 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) +to (`src/main/test/...`). The following resources are quite useful: + +* [JUnit4](http://junit.org/junit4/) +* [Mockito](http://mockito.org/) +* [Truth](http://google.github.io/truth/) + +### When done + +Cloud Bigtable Instances should be [deleted](https://cloud.google.com/bigtable/docs/deleting-instance) +when they are no longer being used as they use significant resources. diff --git a/appengine-java21/ee8/bigtable/build.gradle b/appengine-java21/ee8/bigtable/build.gradle new file mode 100644 index 00000000000..999819d2758 --- /dev/null +++ b/appengine-java21/ee8/bigtable/build.gradle @@ -0,0 +1,113 @@ +// Copyright 2017 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// [START gae_java21_bigtable_gradle_file] +buildscript { // Configuration for building + repositories { + jcenter() // Bintray's repository - a fast Maven Central mirror & more + mavenCentral() + } + dependencies { + classpath 'com.google.cloud.tools:appengine-gradle-plugin:2.5.0' + classpath 'org.akhikhl.gretty:gretty:+' + } +} + +apply plugin: 'java' +apply plugin: 'war' +apply plugin: 'org.akhikhl.gretty' // To get webappcopy +apply plugin: 'com.google.cloud.tools.appengine' + +group = 'com.example.google.cloud.bigtable' +version = '0.1-SNAPSHOT' + +sourceCompatibility = 21 +targetCompatibility = 21 + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} + +repositories { + maven { url "https://oss.sonatype.org/content/repositories/snapshots" } + jcenter() + mavenCentral() +} + +dependencies { + compile group: 'com.google.cloud.bigtable', name: 'bigtable-hbase-1.2', version:'1.0.0-pre3' + compile group: 'org.apache.hbase', name: 'hbase-client', version:'2.5.6' + compile group: 'io.netty', name: 'netty-tcnative-boringssl-static', version:'2.0.62.Final' + compile group: 'jakarta.servlet.jsp.jstl', name: 'jakarta.servlet.jsp.jstl-api', version:'1.2.7' + + providedCompile group: 'jakarta.servlet', name: 'jakarta.servlet-api', version:'4.0.4' + + testCompile group: 'com.google.truth', name: 'truth', version:'1.4.4' + testCompile group: 'junit', name: 'junit', version:'4.13.2' + testCompile group: 'org.mockito', name: 'mockito-core', version:'4.11.0' +} + +import org.apache.tools.ant.filters.ReplaceTokens +gretty { + contextPath = '/' + servletContainer = 'jetty9' + + jvmArgs = [ '-DBIGTABLE_PROJECT=' + System.getProperty("bigtable.projectID"), + '-DBIGTABLE_INSTANCE=' + System.getProperty("bigtable.instanceID")] + + webappCopy { + // Enable filtering on all xml files in WEB-INF + filesMatching "**/WEB-INF/*.xml", { FileCopyDetails fileDetails -> + logger.lifecycle 'File filtered: {}', fileDetails.path + filter (ReplaceTokens, tokens: [ + 'bigtable.projectID' : System.getProperty("bigtable.projectID"), + 'bigtable.instanceID': System.getProperty("bigtable.instanceID") + ]) + } + } +} + +// Always run unit tests +appengineDeploy.dependsOn test + +// [START gae_java21_bigtable_gradle_model] +appengine { + run { + + } + deploy { // deploy configuration + stopPreviousVersion = true // default - stop the current version + promote = true // default - & make this the current version + } +} + +test { + useJUnit() + testLogging.showStandardStreams = true + + systemProperty 'BIGTABLE_PROJECT', System.getProperty("bigtable.projectID") + systemProperty 'BIGTABLE_INSTANCE',System.getProperty("bigtable.instanceID") + + beforeTest { descriptor -> + logger.lifecycle("test: " + descriptor + " Running") + } + + onOutput { descriptor, event -> + logger.lifecycle("test: " + descriptor + ": " + event.message ) + } + afterTest { descriptor, result -> + logger.lifecycle("test: " + descriptor + ": " + result ) + } +} +// [END gae_java21_bigtable_gradle_model] +// [END gae_java21_bigtable_gradle_file] diff --git a/appengine-java21/ee8/bigtable/gradle/wrapper/gradle-wrapper.properties b/appengine-java21/ee8/bigtable/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..6e82a0b51fb --- /dev/null +++ b/appengine-java21/ee8/bigtable/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Apr 03 21:11:48 PDT 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip diff --git a/appengine-java21/ee8/bigtable/gradlew b/appengine-java21/ee8/bigtable/gradlew new file mode 100755 index 00000000000..4453ccea33d --- /dev/null +++ b/appengine-java21/ee8/bigtable/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save ( ) { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/appengine-java21/ee8/bigtable/gradlew.bat b/appengine-java21/ee8/bigtable/gradlew.bat new file mode 100644 index 00000000000..e95643d6a2c --- /dev/null +++ b/appengine-java21/ee8/bigtable/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/appengine-java21/ee8/bigtable/pom.xml b/appengine-java21/ee8/bigtable/pom.xml new file mode 100644 index 00000000000..8a52e798a79 --- /dev/null +++ b/appengine-java21/ee8/bigtable/pom.xml @@ -0,0 +1,190 @@ + + + + + 4.0.0 + war + 0.1-SNAPSHOT + + com.example.appengine + bigtable-hello-j21 + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + 21 + 21 + + YOUR_PROJECT_ID + YOUR_INSTANCE_ID + false + + + + + com.google.cloud.bigtable + + bigtable-hbase-1.x-hadoop + 2.12.0 + + + + + jakarta.servlet + jakarta.servlet-api + 4.0.4 + jar + provided + + + jakarta.servlet.jsp.jstl + jakarta.servlet.jsp.jstl-api + 1.2.7 + + + + + + com.google.truth + truth + 1.4.4 + test + + + + junit + junit + 4.13.2 + test + + + org.mockito + mockito-core + 4.11.0 + test + + + + + + ${project.build.directory}/${project.build.finalName}/WEB-INF/classes + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + UTF-8 + + @ + + false + + + + + + org.apache.maven.plugins + maven-war-plugin + 3.4.0 + + true + + + + ${basedir}/src/main/webapp/WEB-INF + true + WEB-INF + + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.13 + + + + org.apache.maven.plugins + maven-failsafe-plugin + 3.2.2 + + + ${bigtable.projectID} + ${bigtable.instanceID} + + + + + + com.google.cloud.tools + appengine-maven-plugin + 2.8.0 + + GCLOUD_CONFIG + GCLOUD_CONFIG + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + + org.apache.maven.plugins + maven-clean-plugin + 3.3.2 + + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.4.1 + + + + [3.5.0,) + + + + + + + + + + snapshots-repo + https://oss.sonatype.org/content/repositories/snapshots + false + true + + + + diff --git a/appengine-java21/ee8/bigtable/settings.gradle b/appengine-java21/ee8/bigtable/settings.gradle new file mode 100644 index 00000000000..f77c416bd3a --- /dev/null +++ b/appengine-java21/ee8/bigtable/settings.gradle @@ -0,0 +1,14 @@ +// Copyright 2017 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +rootProject.name = 'bigtable-hello-j21' diff --git a/appengine-java21/ee8/bigtable/src/main/java/com/example/bigtable/BigtableHelloWorld.java b/appengine-java21/ee8/bigtable/src/main/java/com/example/bigtable/BigtableHelloWorld.java new file mode 100644 index 00000000000..3d6e07dae5a --- /dev/null +++ b/appengine-java21/ee8/bigtable/src/main/java/com/example/bigtable/BigtableHelloWorld.java @@ -0,0 +1,142 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.example.bigtable; + +// [START gae_java21_bigtable_helloworld] + +import java.io.IOException; +import org.apache.hadoop.hbase.HColumnDescriptor; +import org.apache.hadoop.hbase.HTableDescriptor; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.Admin; +import org.apache.hadoop.hbase.client.Connection; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.client.ResultScanner; +import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.client.Table; +import org.apache.hadoop.hbase.util.Bytes; + +/** + * A minimal application that connects to Cloud Bigtable using the native HBase API and performs + * some basic operations. + */ +public class BigtableHelloWorld { + + // Refer to table metadata names by byte array in the HBase API + private static final byte[] TABLE_NAME = Bytes.toBytes("Hello-Bigtable"); + private static final byte[] COLUMN_FAMILY_NAME = Bytes.toBytes("cf1"); + private static final byte[] COLUMN_NAME = Bytes.toBytes("greeting"); + + // Write some friendly greetings to Cloud Bigtable + private static final String[] GREETINGS = { + "Hello World!", "Hello Cloud Bigtable!", "Hello HBase!" + }; + + /** + * Create a table -- first time only. + * + * @param connection to Bigtable + * @return the status + */ + public static String create(Connection connection) { + try { + // The admin API lets us create, manage and delete tables + Admin admin = connection.getAdmin(); + + // Create a table with a single column family + HTableDescriptor descriptor = new HTableDescriptor(TableName.valueOf(TABLE_NAME)); + descriptor.addFamily(new HColumnDescriptor(COLUMN_FAMILY_NAME)); + + admin.createTable(descriptor); + } catch (IOException e) { + return "Table exists."; + } + return "Create table " + Bytes.toString(TABLE_NAME); + } + + /** Connects to Cloud Bigtable, runs some basic operations and prints the results. */ + public static String doHelloWorld() { + + StringBuilder result = new StringBuilder(); + + // Create the Bigtable connection, use try-with-resources to make sure it gets closed + Connection connection = BigtableHelper.getConnection(); + result.append(create(connection)); + result.append("

"); + try (Table table = connection.getTable(TableName.valueOf(TABLE_NAME))) { + + // Retrieve the table we just created so we can do some reads and writes + + // Write some rows to the table + result.append("Write some greetings to the table
"); + for (int i = 0; i < GREETINGS.length; i++) { + // Each row has a unique row key. + // + // Note: This example uses sequential numeric IDs for simplicity, but + // this can result in poor performance in a production application. + // Since rows are stored in sorted order by key, sequential keys can + // result in poor distribution of operations across nodes. + // + // For more information about how to design a Bigtable schema for the + // best performance, see the documentation: + // + // https://cloud.google.com/bigtable/docs/schema-design + String rowKey = "greeting" + i; + + // Put a single row into the table. We could also pass a list of Puts to write a batch. + Put put = new Put(Bytes.toBytes(rowKey)); + put.addColumn(COLUMN_FAMILY_NAME, COLUMN_NAME, Bytes.toBytes(GREETINGS[i])); + table.put(put); + } + + // Get the first greeting by row key + String rowKey = "greeting0"; + Result getResult = table.get(new Get(Bytes.toBytes(rowKey))); + String greeting = Bytes.toString(getResult.getValue(COLUMN_FAMILY_NAME, COLUMN_NAME)); + result.append("Get a single greeting by row key
"); + + result.append(" "); + result.append(rowKey); + result.append("= "); + result.append(greeting); + result.append("
"); + + // Now scan across all rows. + Scan scan = new Scan(); + + result.append("Scan for all greetings:"); + ResultScanner scanner = table.getScanner(scan); + for (Result row : scanner) { + byte[] valueBytes = row.getValue(COLUMN_FAMILY_NAME, COLUMN_NAME); + result.append(" "); + result.append(Bytes.toString(valueBytes)); + result.append("
"); + } + + } catch (IOException e) { + result.append("Exception while running HelloWorld: " + e.getMessage() + "
"); + result.append(e.toString()); + return result.toString(); + } + + return result.toString(); + } +} +// [END gae_java21_bigtable_helloworld] diff --git a/appengine-java21/ee8/bigtable/src/main/java/com/example/bigtable/BigtableHelper.java b/appengine-java21/ee8/bigtable/src/main/java/com/example/bigtable/BigtableHelper.java new file mode 100644 index 00000000000..dfcbd1e6352 --- /dev/null +++ b/appengine-java21/ee8/bigtable/src/main/java/com/example/bigtable/BigtableHelper.java @@ -0,0 +1,142 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.bigtable; + +import com.google.cloud.bigtable.hbase.BigtableConfiguration; +import java.io.IOException; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.annotation.WebListener; +import org.apache.hadoop.hbase.client.Connection; + +/** + * BigtableHelper, a ServletContextListener, is setup in web.xml to run before a jsp is run. Project + * / Instance settings can be passed as an Environment Variable, a System Property, or set in + * web.xml from a context-param + */ +@WebListener +public class BigtableHelper implements ServletContextListener { + + private static String PROJECT_ID; + private static String INSTANCE_ID; + + // The initial connection to Cloud Bigtable is an expensive operation -- We cache this Connection + // to speed things up. For this sample, keeping them here is a good idea, for + // your application, you may wish to keep this somewhere else. + private static Connection connection = null; // The authenticated connection + + private static ServletContext sc; + + /** Connect will establish the connection to Cloud Bigtable. */ + public static void connect() throws IOException { + + if (PROJECT_ID == null || INSTANCE_ID == null) { + if (sc != null) { + sc.log("environment variables BIGTABLE_PROJECT, and BIGTABLE_INSTANCE need to be defined."); + } + return; + } + + connection = BigtableConfiguration.connect(PROJECT_ID, INSTANCE_ID); + } + + /** + * Get the shared connection to Cloud Bigtable. + * + * @return the connection + */ + public static Connection getConnection() { + if (connection == null) { + try { + connect(); + } catch (IOException e) { + if (sc != null) { + sc.log("connect ", e); + } + } + } + if (connection == null) { + if (sc != null) { + sc.log("BigtableHelper-No Connection"); + } + } + return connection; + } + + @Override + public void contextInitialized(ServletContextEvent event) { + // This will be invoked as part of a warmup request, or the first user + // request if no warmup request was invoked. + + if (event != null) { + sc = event.getServletContext(); + if (PROJECT_ID == null) { + PROJECT_ID = sc.getInitParameter("BIGTABLE_PROJECT"); + } + if (INSTANCE_ID == null) { + INSTANCE_ID = sc.getInitParameter("BIGTABLE_INSTANCE"); + } + } + + if (PROJECT_ID != null && PROJECT_ID.startsWith("@")) { + PROJECT_ID = null; + } + if (INSTANCE_ID != null && INSTANCE_ID.startsWith("@")) { + INSTANCE_ID = null; + } + + if (PROJECT_ID == null) { + PROJECT_ID = System.getProperty("bigtable.projectID"); + } + if (INSTANCE_ID == null) { + INSTANCE_ID = System.getProperty("bigtable.instanceID"); + } + + try { + connect(); + } catch (IOException e) { + if (sc != null) { + sc.log("BigtableHelper - connect ", e); + } + } + if (connection == null) { + if (sc != null) { + sc.log("BigtableHelper-No Connection"); + } + } + if (sc != null) { + sc.log("ctx Initialized: " + PROJECT_ID + " " + INSTANCE_ID); + } + } + + @Override + public void contextDestroyed(ServletContextEvent event) { + // App Engine does not currently invoke this method. + if (connection == null) { + return; + } + try { + connection.close(); + } catch (IOException io) { + if (sc != null) { + sc.log("contextDestroyed ", io); + } + } + connection = null; + } +} diff --git a/appengine-java21/ee8/bigtable/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java21/ee8/bigtable/src/main/webapp/WEB-INF/appengine-web.xml new file mode 100644 index 00000000000..2209fe610cd --- /dev/null +++ b/appengine-java21/ee8/bigtable/src/main/webapp/WEB-INF/appengine-web.xml @@ -0,0 +1,24 @@ + + + + java21 + true + + + + + + + + diff --git a/appengine-java21/ee8/bigtable/src/main/webapp/WEB-INF/logging.properties b/appengine-java21/ee8/bigtable/src/main/webapp/WEB-INF/logging.properties new file mode 100644 index 00000000000..0c2ea51bc6d --- /dev/null +++ b/appengine-java21/ee8/bigtable/src/main/webapp/WEB-INF/logging.properties @@ -0,0 +1,13 @@ +# A default java.util.logging configuration. +# (All App Engine logging is through java.util.logging by default). +# +# To use this configuration, copy it into your application's WEB-INF +# folder and add the following to your appengine-web.xml: +# +# +# +# +# + +# Set the default logging level for all loggers to WARNING +.level = WARNING diff --git a/appengine-java21/ee8/bigtable/src/main/webapp/WEB-INF/web.xml b/appengine-java21/ee8/bigtable/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..2fecfb4115c --- /dev/null +++ b/appengine-java21/ee8/bigtable/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,35 @@ + + + + + + bigtable.jsp + + + BIGTABLE_PROJECT + @bigtable.projectID@ + + + BIGTABLE_INSTANCE + @bigtable.instanceID@ + + + diff --git a/appengine-java21/ee8/bigtable/src/main/webapp/bigtable.jsp b/appengine-java21/ee8/bigtable/src/main/webapp/bigtable.jsp new file mode 100644 index 00000000000..88a040051e6 --- /dev/null +++ b/appengine-java21/ee8/bigtable/src/main/webapp/bigtable.jsp @@ -0,0 +1,32 @@ + +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ page import="com.example.bigtable.BigtableHelloWorld" %> + +<%-- + ~ Copyright 2017 Google LLC + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); you + ~ may not use this file except in compliance with the License. You may + ~ obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + ~ implied. See the License for the specific language governing + ~ permissions and limitations under the License. + --%> + + + + + Hello App Engine Standard using Java 21 + + +

Hello App Engine -- Standard for Java 21!

+ +

This is <%= BigtableHelloWorld.doHelloWorld() %>. + + + diff --git a/appengine-java21/ee8/bigtable/src/main/webapp/favicon.ico b/appengine-java21/ee8/bigtable/src/main/webapp/favicon.ico new file mode 100644 index 00000000000..0062ab413e7 Binary files /dev/null and b/appengine-java21/ee8/bigtable/src/main/webapp/favicon.ico differ diff --git a/appengine-java21/ee8/bigtable/src/test/java/com/example/bigtable/BigtableHelloWorldTests.java b/appengine-java21/ee8/bigtable/src/test/java/com/example/bigtable/BigtableHelloWorldTests.java new file mode 100644 index 00000000000..7b13aa67e2f --- /dev/null +++ b/appengine-java21/ee8/bigtable/src/test/java/com/example/bigtable/BigtableHelloWorldTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.bigtable; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link BigtableHelloWorld}. */ +@RunWith(JUnit4.class) +public class BigtableHelloWorldTests { + + private BigtableHelper helper; + + @Before + public void setUp() throws Exception { + helper = new BigtableHelper(); + helper.contextInitialized(null); + } + + @Test + public void bigtable_test() { + String result = BigtableHelloWorld.doHelloWorld(); + assertThat(result).contains("Write some greetings to the table"); + assertThat(result).contains("Get a single greeting by row key"); + assertThat(result).contains("greeting0= Hello World!"); + assertThat(result).contains("Hello Cloud Bigtable!"); + assertThat(result).contains("Hello HBase!"); + } +} diff --git a/appengine-java21/ee8/datastore/README.md b/appengine-java21/ee8/datastore/README.md new file mode 100644 index 00000000000..cb258be8741 --- /dev/null +++ b/appengine-java21/ee8/datastore/README.md @@ -0,0 +1,32 @@ +# Google Cloud Datastore Sample + + +Open in Cloud Shell + +This sample demonstrates how to use [Google Cloud Datastore][java-datastore] +from [Google App Engine standard environment][ae-docs]. + +[java-datastore]: https://cloud.google.com/appengine/docs/java/datastore/ +[ae-docs]: https://cloud.google.com/appengine/docs/java/ + + +## Running locally + +This example uses the +[Cloud SDK Maven plugin](https://cloud.google.com/appengine/docs/legacy/standard/java/using-maven). +To run this sample locally: + + $ mvn appengine:run + +To see the results of the sample application, open +[localhost:8080](http://localhost:8080) in a web browser. + + +## Deploying + +In the following command, replace YOUR-PROJECT-ID with your +[Google Cloud Project ID](https://developers.google.com/console/help/new/#projectnumber) +and SOME-VERSION with a valid version number. + + $ mvn clean package appengine:deploy + $ mvn appengine:deployIndex diff --git a/appengine-java21/ee8/datastore/pom.xml b/appengine-java21/ee8/datastore/pom.xml new file mode 100644 index 00000000000..ae955be85df --- /dev/null +++ b/appengine-java21/ee8/datastore/pom.xml @@ -0,0 +1,181 @@ + + + 4.0.0 + war + 1.0-SNAPSHOT + com.example.appengine + appengine-datastore-j21 + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + 21 + 21 + + + + + + libraries-bom + com.google.cloud + import + pom + 26.28.0 + + + + + + + com.google.appengine + appengine-api-1.0-sdk + 2.0.39 + + + + jakarta.servlet + jakarta.servlet-api + 4.0.4 + jar + provided + + + + taglibs + standard + 1.1.2 + + + jakarta.servlet.jsp.jstl + jakarta.servlet.jsp.jstl-api + 1.2.7 + + + + com.google.auto.value + auto-value + 1.11.0 + provided + + + + com.google.auto.value + auto-value-annotations + + + + com.google.code.findbugs + jsr305 + 3.0.2 + + + + com.google.guava + guava + + + + + junit + junit + 4.13.2 + test + + + org.mockito + mockito-core + 4.11.0 + test + + + + com.google.appengine + appengine-testing + 2.0.39 + test + + + com.google.appengine + appengine-api-stubs + 2.0.39 + test + + + com.google.appengine + appengine-tools-sdk + 2.0.39 + test + + + com.google.truth + truth + 1.4.4 + test + + + + + + ${project.build.directory}/${project.build.finalName}/WEB-INF/classes + + + org.apache.maven.plugins + maven-war-plugin + 3.4.0 + + + org.jacoco + jacoco-maven-plugin + 0.8.13 + + + com.google.cloud.tools + appengine-maven-plugin + 2.5.0 + + GCLOUD_CONFIG + GCLOUD_CONFIG + true + true + + + + + maven-compiler-plugin + 3.11.0 + + + + com.google.auto.value + auto-value + 1.11.0 + + + + + + + diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/AbstractGuestbook.java b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/AbstractGuestbook.java new file mode 100644 index 00000000000..a7b46bc4727 --- /dev/null +++ b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/AbstractGuestbook.java @@ -0,0 +1,76 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.example.time.Clock; +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.users.User; +import com.google.appengine.api.users.UserService; +import com.google.appengine.api.users.UserServiceFactory; +import com.google.common.collect.ImmutableList; +import java.util.Date; +import java.util.List; + +/** + * A log of notes left by users. + * + *

This is meant to be subclassed to demonstrate different storage structures in Datastore. + */ +abstract class AbstractGuestbook { + + private final DatastoreService datastore; + private final UserService userService; + private final Clock clock; + + AbstractGuestbook(Clock clock) { + this.datastore = DatastoreServiceFactory.getDatastoreService(); + this.userService = UserServiceFactory.getUserService(); + this.clock = clock; + } + + /** + * Appends a new greeting to the guestbook and returns the {@link Entity} that was created. + **/ + public Greeting appendGreeting(String content) { + return Greeting.create( + createGreeting(datastore, userService.getCurrentUser(), Date.from(clock.now()), content)); + } + + /** + * Write a greeting to Datastore. + */ + protected abstract Entity createGreeting( + DatastoreService datastore, User user, Date date, String content); + + /** + * Return a list of the most recent greetings. + */ + public List listGreetings() { + ImmutableList.Builder greetings = ImmutableList.builder(); + for (Entity entity : listGreetingEntities(datastore)) { + greetings.add(Greeting.create(entity)); + } + return greetings.build(); + } + + /** + * Return a list of the most recent greetings. + */ + protected abstract List listGreetingEntities(DatastoreService datastore); +} diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/AbstractGuestbookServlet.java b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/AbstractGuestbookServlet.java new file mode 100644 index 00000000000..d9a0d518b92 --- /dev/null +++ b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/AbstractGuestbookServlet.java @@ -0,0 +1,58 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +abstract class AbstractGuestbookServlet extends HttpServlet { + + private final AbstractGuestbook guestbook; + + public AbstractGuestbookServlet(AbstractGuestbook guestbook) { + this.guestbook = guestbook; + } + + private void renderGuestbook(HttpServletRequest req, HttpServletResponse resp) + throws IOException, ServletException { + resp.setContentType("text/html"); + resp.setCharacterEncoding("UTF-8"); + req.setAttribute("greetings", guestbook.listGreetings()); + req.getRequestDispatcher("/guestbook.jsp").forward(req, resp); + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws IOException, ServletException { + renderGuestbook(req, resp); + } + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) + throws IOException, ServletException { + String content = req.getParameter("content"); + if (content == null || content.isEmpty()) { + resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "missing content"); + return; + } + guestbook.appendGreeting(content); + renderGuestbook(req, resp); + } +} diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/Greeting.java b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/Greeting.java new file mode 100644 index 00000000000..ff45e508205 --- /dev/null +++ b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/Greeting.java @@ -0,0 +1,42 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.users.User; +import com.google.auto.value.AutoValue; +import java.time.Instant; +import java.util.Date; +import javax.annotation.Nullable; + +@AutoValue +public abstract class Greeting { + + static Greeting create(Entity entity) { + User user = (User) entity.getProperty("user"); + Instant date = ((Date) entity.getProperty("date")).toInstant(); + String content = (String) entity.getProperty("content"); + return new AutoValue_Greeting(user, date, content); + } + + @Nullable + public abstract User getUser(); + + public abstract Instant getDate(); + + public abstract String getContent(); +} diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/Guestbook.java b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/Guestbook.java new file mode 100644 index 00000000000..ebacbf540ae --- /dev/null +++ b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/Guestbook.java @@ -0,0 +1,59 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.example.time.Clock; +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.users.User; +import java.util.Date; +import java.util.List; + +/** + * A log of notes left by users. + * + *

This demonstrates the use of Google Cloud Datastore using the App Engine APIs. See the documentation for more + * information. + */ +class Guestbook extends AbstractGuestbook { + + Guestbook(Clock clock) { + super(clock); + } + + @Override + protected Entity createGreeting( + DatastoreService datastore, User user, Date date, String content) { + // No parent key specified, so Greeting is a root entity. + Entity greeting = new Entity("Greeting"); + greeting.setProperty("user", user); + greeting.setProperty("date", date); + greeting.setProperty("content", content); + + datastore.put(greeting); + return greeting; + } + + @Override + protected List listGreetingEntities(DatastoreService datastore) { + Query query = new Query("Greeting").addSort("date", Query.SortDirection.DESCENDING); + return datastore.prepare(query).asList(FetchOptions.Builder.withLimit(10)); + } +} diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/GuestbookServlet.java b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/GuestbookServlet.java new file mode 100644 index 00000000000..019e5a4210c --- /dev/null +++ b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/GuestbookServlet.java @@ -0,0 +1,26 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.example.time.SystemClock; + +public class GuestbookServlet extends AbstractGuestbookServlet { + + public GuestbookServlet() { + super(new Guestbook(new SystemClock())); + } +} diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/GuestbookStrong.java b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/GuestbookStrong.java new file mode 100644 index 00000000000..a3c1bc08743 --- /dev/null +++ b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/GuestbookStrong.java @@ -0,0 +1,71 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.example.time.Clock; +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.KeyFactory; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.users.User; +import java.util.Date; +import java.util.List; + +/** + * A log of notes left by users. + * + *

This demonstrates the use of Google Cloud Datastore using the App Engine APIs. See the documentation for more + * information. + */ +class GuestbookStrong extends AbstractGuestbook { + + private final String guestbookName; + + GuestbookStrong(String guestbookName, Clock clock) { + super(clock); + this.guestbookName = guestbookName; + } + + @Override + protected Entity createGreeting( + DatastoreService datastore, User user, Date date, String content) { + // String guestbookName = "my guestbook"; -- Set elsewhere (injected to the constructor). + Key guestbookKey = KeyFactory.createKey("Guestbook", guestbookName); + + // Place greeting in the same entity group as guestbook. + Entity greeting = new Entity("Greeting", guestbookKey); + greeting.setProperty("user", user); + greeting.setProperty("date", date); + greeting.setProperty("content", content); + + datastore.put(greeting); + return greeting; + } + + @Override + protected List listGreetingEntities(DatastoreService datastore) { + Key guestbookKey = KeyFactory.createKey("Guestbook", guestbookName); + Query query = + new Query("Greeting", guestbookKey) + .setAncestor(guestbookKey) + .addSort("date", Query.SortDirection.DESCENDING); + return datastore.prepare(query).asList(FetchOptions.Builder.withLimit(10)); + } +} diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/GuestbookStrongServlet.java b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/GuestbookStrongServlet.java new file mode 100644 index 00000000000..2861c88d991 --- /dev/null +++ b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/GuestbookStrongServlet.java @@ -0,0 +1,28 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.example.time.SystemClock; + +public class GuestbookStrongServlet extends AbstractGuestbookServlet { + + public static final String GUESTBOOK_ID = "my guestbook"; + + public GuestbookStrongServlet() { + super(new GuestbookStrong(GUESTBOOK_ID, new SystemClock())); + } +} diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/ListPeopleServlet.java b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/ListPeopleServlet.java new file mode 100644 index 00000000000..8bb61cbbadc --- /dev/null +++ b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/ListPeopleServlet.java @@ -0,0 +1,91 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +// [START gae_java21_datastore_cursors] + +import com.google.appengine.api.datastore.Cursor; +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.PreparedQuery; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.SortDirection; +import com.google.appengine.api.datastore.QueryResultList; +import java.io.IOException; +import java.io.PrintWriter; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class ListPeopleServlet extends HttpServlet { + + static final int PAGE_SIZE = 15; + private final DatastoreService datastore; + + public ListPeopleServlet() { + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + FetchOptions fetchOptions = FetchOptions.Builder.withLimit(PAGE_SIZE); + + // If this servlet is passed a cursor parameter, let's use it. + String startCursor = req.getParameter("cursor"); + if (startCursor != null) { + fetchOptions.startCursor(Cursor.fromWebSafeString(startCursor)); + } + + Query q = new Query("Person").addSort("name", SortDirection.ASCENDING); + PreparedQuery pq = datastore.prepare(q); + + QueryResultList results; + try { + results = pq.asQueryResultList(fetchOptions); + } catch (IllegalArgumentException e) { + // IllegalArgumentException happens when an invalid cursor is used. + // A user could have manually entered a bad cursor in the URL or there + // may have been an internal implementation detail change in App Engine. + // Redirect to the page without the cursor parameter to show something + // rather than an error. + resp.sendRedirect("/people"); + return; + } + + resp.setContentType("text/html"); + resp.setCharacterEncoding("UTF-8"); + PrintWriter w = resp.getWriter(); + w.println(""); + w.println(""); + w.println("Cloud Datastore Cursor Sample"); + w.println("

    "); + for (Entity entity : results) { + w.println("
  • " + entity.getProperty("name") + "
  • "); + } + w.println("
"); + + String cursorString = results.getCursor().toWebSafeString(); + + // This servlet lives at '/people'. + w.println("Next page"); + } +} +// [END gae_java21_datastore_cursors] diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/ProjectionServlet.java b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/ProjectionServlet.java new file mode 100644 index 00000000000..d062f9d6d17 --- /dev/null +++ b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/ProjectionServlet.java @@ -0,0 +1,78 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.KeyFactory; +import com.google.appengine.api.datastore.PropertyProjection; +import com.google.appengine.api.datastore.Query; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Date; +import java.util.List; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Servlet to demonstrate use of Datastore projection queries. + * + *

See the + * documentation + * for using Datastore projection queries from the Google App Engine standard environment. + */ +@SuppressWarnings("serial") +public class ProjectionServlet extends HttpServlet { + + private static final String GUESTBOOK_ID = GuestbookStrongServlet.GUESTBOOK_ID; + private final DatastoreService datastore; + + public ProjectionServlet() { + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + resp.setContentType("text/plain"); + resp.setCharacterEncoding("UTF-8"); + PrintWriter out = resp.getWriter(); + out.printf("Latest entries from guestbook: \n"); + + Key guestbookKey = KeyFactory.createKey("Guestbook", GUESTBOOK_ID); + Query query = new Query("Greeting", guestbookKey); + addGuestbookProjections(query); + printGuestbookEntries(datastore, query, out); + } + + private void addGuestbookProjections(Query query) { + query.addProjection(new PropertyProjection("content", String.class)); + query.addProjection(new PropertyProjection("date", Date.class)); + } + + private void printGuestbookEntries(DatastoreService datastore, Query query, PrintWriter out) { + List guests = datastore.prepare(query).asList(FetchOptions.Builder.withLimit(5)); + for (Entity guest : guests) { + String content = (String) guest.getProperty("content"); + Date stamp = (Date) guest.getProperty("date"); + out.printf("Message %s posted on %s.\n", content, stamp.toString()); + } + } +} diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/StartupServlet.java b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/StartupServlet.java new file mode 100644 index 00000000000..9f4d2d06970 --- /dev/null +++ b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/StartupServlet.java @@ -0,0 +1,119 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.EntityNotFoundException; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.KeyFactory; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * A startup handler to populate the datastore with example entities. + */ +public class StartupServlet extends HttpServlet { + + static final String IS_POPULATED_ENTITY = "IsPopulated"; + static final String IS_POPULATED_KEY_NAME = "is-populated"; + + private static final String PERSON_ENTITY = "Person"; + private static final String NAME_PROPERTY = "name"; + private static final ImmutableList US_PRESIDENTS = + ImmutableList.builder() + .add("George Washington") + .add("John Adams") + .add("Thomas Jefferson") + .add("James Madison") + .add("James Monroe") + .add("John Quincy Adams") + .add("Andrew Jackson") + .add("Martin Van Buren") + .add("William Henry Harrison") + .add("John Tyler") + .add("James K. Polk") + .add("Zachary Taylor") + .add("Millard Fillmore") + .add("Franklin Pierce") + .add("James Buchanan") + .add("Abraham Lincoln") + .add("Andrew Johnson") + .add("Ulysses S. Grant") + .add("Rutherford B. Hayes") + .add("James A. Garfield") + .add("Chester A. Arthur") + .add("Grover Cleveland") + .add("Benjamin Harrison") + .add("Grover Cleveland") + .add("William McKinley") + .add("Theodore Roosevelt") + .add("William Howard Taft") + .add("Woodrow Wilson") + .add("Warren G. Harding") + .add("Calvin Coolidge") + .add("Herbert Hoover") + .add("Franklin D. Roosevelt") + .add("Harry S. Truman") + .add("Dwight D. Eisenhower") + .add("John F. Kennedy") + .add("Lyndon B. Johnson") + .add("Richard Nixon") + .add("Gerald Ford") + .add("Jimmy Carter") + .add("Ronald Reagan") + .add("George H. W. Bush") + .add("Bill Clinton") + .add("George W. Bush") + .add("Barack Obama") + .build(); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/plain"); + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + + Key isPopulatedKey = KeyFactory.createKey(IS_POPULATED_ENTITY, IS_POPULATED_KEY_NAME); + boolean isAlreadyPopulated; + try { + datastore.get(isPopulatedKey); + isAlreadyPopulated = true; + } catch (EntityNotFoundException expected) { + isAlreadyPopulated = false; + } + if (isAlreadyPopulated) { + resp.getWriter().println("ok"); + return; + } + + ImmutableList.Builder people = ImmutableList.builder(); + for (String name : US_PRESIDENTS) { + Entity person = new Entity(PERSON_ENTITY); + person.setProperty(NAME_PROPERTY, name); + people.add(person); + } + datastore.put(people.build()); + datastore.put(new Entity(isPopulatedKey)); + resp.getWriter().println("ok"); + } +} diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/StatsServlet.java b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/StatsServlet.java new file mode 100644 index 00000000000..74531a56e56 --- /dev/null +++ b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/StatsServlet.java @@ -0,0 +1,47 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.Query; +import java.io.IOException; +import java.io.PrintWriter; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class StatsServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + // [START gae_java21_datastore_stat_example] + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + Entity globalStat = datastore.prepare(new Query("__Stat_Total__")).asSingleEntity(); + Long totalBytes = (Long) globalStat.getProperty("bytes"); + Long totalEntities = (Long) globalStat.getProperty("count"); + // [END gae_java21_datastore_stat_example] + + resp.setContentType("text/plain"); + resp.setCharacterEncoding("UTF-8"); + PrintWriter w = resp.getWriter(); + w.printf("%d bytes\n%d entities\n", totalBytes, totalEntities); + } +} diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/time/Clock.java b/appengine-java21/ee8/datastore/src/main/java/com/example/time/Clock.java new file mode 100644 index 00000000000..a703577d7f9 --- /dev/null +++ b/appengine-java21/ee8/datastore/src/main/java/com/example/time/Clock.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.time; + +import java.time.Instant; + +/** + * Provides the current value of "now." To preserve testability, avoid all other libraries that + * access the system clock (whether {@linkplain System#currentTimeMillis directly} or {@linkplain + * java.time.Instant#now() indirectly}). + * + *

In production, use the {@link SystemClock} implementation to return the "real" system time. In + * tests, either use {@link com.example.time.testing.FakeClock}, or get an instance from a mocking + * framework such as Mockito. + */ +public interface Clock { + + /** + * Returns the current, absolute time according to this clock. + */ + Instant now(); +} diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/time/SystemClock.java b/appengine-java21/ee8/datastore/src/main/java/com/example/time/SystemClock.java new file mode 100644 index 00000000000..dcde41330f6 --- /dev/null +++ b/appengine-java21/ee8/datastore/src/main/java/com/example/time/SystemClock.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.time; + +import java.time.Instant; + +/** + * Clock implementation that returns the "real" system time. + * + *

This class exists so that we can use a fake implementation for unit testing classes that need + * the current time value. See {@link Clock} for general information about clocks. + */ +public class SystemClock implements Clock { + + /** + * Creates a new instance. All {@code SystemClock} instances function identically. + */ + public SystemClock() { + } + + @Override + public Instant now() { + return Instant.now(); + } +} diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/time/testing/FakeClock.java b/appengine-java21/ee8/datastore/src/main/java/com/example/time/testing/FakeClock.java new file mode 100644 index 00000000000..a08343e51b6 --- /dev/null +++ b/appengine-java21/ee8/datastore/src/main/java/com/example/time/testing/FakeClock.java @@ -0,0 +1,180 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.time.testing; + +import com.example.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.util.concurrent.atomic.AtomicLong; + +/** + * A Clock that returns a fixed Instant value as the current clock time. The fixed Instant is + * settable for testing. Test code should hold a reference to the FakeClock, while code under test + * should hold a Clock reference. + * + *

The clock time can be incremented/decremented manually, with {@link #incrementTime} and {@link + * #decrementTime} respectively. + * + *

The clock can also be configured so that the time is incremented whenever {@link #now()} is + * called: see {@link #setAutoIncrementStep}. + */ +public class FakeClock implements Clock { + + private static final Instant DEFAULT_TIME = Instant.ofEpochMilli(1000000000L); + private final long baseTimeMs; + private final AtomicLong fakeNowMs; + private volatile long autoIncrementStepMs; + + /** + * Creates a FakeClock instance initialized to an arbitrary constant. + */ + public FakeClock() { + this(DEFAULT_TIME); + } + + /** + * Creates a FakeClock instance initialized to the given time. + */ + public FakeClock(Instant now) { + baseTimeMs = now.toEpochMilli(); + fakeNowMs = new AtomicLong(baseTimeMs); + } + + /** + * Sets the value of the underlying instance for testing purposes. + * + * @return this + */ + public FakeClock setNow(Instant now) { + fakeNowMs.set(now.toEpochMilli()); + return this; + } + + @Override + public Instant now() { + return getAndAdd(autoIncrementStepMs); + } + + /** + * Returns the current time without applying an auto increment, if configured. The default + * behavior of {@link #now()} is the same as this method. + */ + public Instant peek() { + return Instant.ofEpochMilli(fakeNowMs.get()); + } + + /** + * Reset the given clock back to the base time with which the FakeClock was initially + * constructed. + * + * @return this + */ + public FakeClock resetTime() { + fakeNowMs.set(baseTimeMs); + return this; + } + + /** + * Increments the clock time by the given duration. + * + * @param duration the duration to increment the clock time by + * @return this + */ + public FakeClock incrementTime(Duration duration) { + incrementTime(duration.toMillis()); + return this; + } + + /** + * Increments the clock time by the given duration. + * + * @param durationMs the duration to increment the clock time by, in milliseconds + * @return this + */ + public FakeClock incrementTime(long durationMs) { + fakeNowMs.addAndGet(durationMs); + return this; + } + + /** + * Decrements the clock time by the given duration. + * + * @param duration the duration to decrement the clock time by + * @return this + */ + public FakeClock decrementTime(Duration duration) { + incrementTime(-duration.toMillis()); + return this; + } + + /** + * Decrements the clock time by the given duration. + * + * @param durationMs the duration to decrement the clock time by, in milliseconds + * @return this + */ + public FakeClock decrementTime(long durationMs) { + incrementTime(-durationMs); + return this; + } + + /** + * Sets the increment applied to the clock whenever it is queried. The increment is zero by + * default: the clock is left unchanged when queried. + * + * @param autoIncrementStep the new auto increment duration + * @return this + */ + public FakeClock setAutoIncrementStep(Duration autoIncrementStep) { + setAutoIncrementStep(autoIncrementStep.toMillis()); + return this; + } + + /** + * Sets the increment applied to the clock whenever it is queried. The increment is zero by + * default: the clock is left unchanged when queried. + * + * @param autoIncrementStepMs the new auto increment duration, in milliseconds + * @return this + */ + public FakeClock setAutoIncrementStep(long autoIncrementStepMs) { + this.autoIncrementStepMs = autoIncrementStepMs; + return this; + } + + /** + * Atomically adds the given value to the current time. + * + * @param durationMs the duration to add, in milliseconds + * @return the updated current time + * @see AtomicLong#addAndGet + */ + protected final Instant addAndGet(long durationMs) { + return Instant.ofEpochMilli(fakeNowMs.addAndGet(durationMs)); + } + + /** + * Atomically adds the given value to the current time. + * + * @param durationMs the duration to add, in milliseconds + * @return the previous time + * @see AtomicLong#getAndAdd + */ + protected final Instant getAndAdd(long durationMs) { + return Instant.ofEpochMilli(fakeNowMs.getAndAdd(durationMs)); + } +} diff --git a/appengine-java21/ee8/datastore/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java21/ee8/datastore/src/main/webapp/WEB-INF/appengine-web.xml new file mode 100644 index 00000000000..56e91137870 --- /dev/null +++ b/appengine-java21/ee8/datastore/src/main/webapp/WEB-INF/appengine-web.xml @@ -0,0 +1,22 @@ + + + + + + java21 + true + + + + diff --git a/appengine-java21/ee8/datastore/src/main/webapp/WEB-INF/datastore-indexes.xml b/appengine-java21/ee8/datastore/src/main/webapp/WEB-INF/datastore-indexes.xml new file mode 100644 index 00000000000..c99175eba3b --- /dev/null +++ b/appengine-java21/ee8/datastore/src/main/webapp/WEB-INF/datastore-indexes.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + diff --git a/appengine-java21/ee8/datastore/src/main/webapp/WEB-INF/web.xml b/appengine-java21/ee8/datastore/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..dddc47141c7 --- /dev/null +++ b/appengine-java21/ee8/datastore/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,98 @@ + + + + + guestbook-strong + com.example.appengine.GuestbookStrongServlet + + + guestbook-strong + / + + + guestbook + com.example.appengine.GuestbookServlet + + + guestbook + /guestbook + + + people + com.example.appengine.ListPeopleServlet + + + people + /people + + + projection + com.example.appengine.ProjectionServlet + + + projection + /projection + + + stats + com.example.appengine.StatsServlet + + + stats + /stats + + + + + startup + com.example.appengine.StartupServlet + + + startup + /_ah/start + + + + + profile + /* + + + CONFIDENTIAL + + + * + + + + + + profile + /stats + + + CONFIDENTIAL + + + admin + + + diff --git a/appengine-java21/ee8/datastore/src/main/webapp/guestbook.jsp b/appengine-java21/ee8/datastore/src/main/webapp/guestbook.jsp new file mode 100644 index 00000000000..5d2d5708f7e --- /dev/null +++ b/appengine-java21/ee8/datastore/src/main/webapp/guestbook.jsp @@ -0,0 +1,45 @@ + + +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> + + + + Guestbook + + +

Latest Greetings

+ +

+ ${greeting.content}
+ Posted: ${greeting.date} +

+
+ +

Add Greeting

+
+

+ + +

+

+ +

+
+ + + diff --git a/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/EntitiesTest.java b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/EntitiesTest.java new file mode 100644 index 00000000000..99cc7643eeb --- /dev/null +++ b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/EntitiesTest.java @@ -0,0 +1,373 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static org.junit.Assert.fail; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.EmbeddedEntity; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.EntityNotFoundException; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.KeyFactory; +import com.google.appengine.api.datastore.KeyRange; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests to demonstrate App Engine Datastore entities. */ +@RunWith(JUnit4.class) +public class EntitiesTest { + + // Set no eventual consistency, that way queries return all results. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void kindExample_writesEntity() throws Exception { + // [START gae_java21_datastore_kind_example] + Entity employee = new Entity("Employee", "asalieri"); + employee.setProperty("firstName", "Antonio"); + employee.setProperty("lastName", "Salieri"); + employee.setProperty("hireDate", new Date()); + employee.setProperty("attendedHrTraining", true); + + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + datastore.put(employee); + // [END gae_java21_datastore_kind_example] + + Entity got = datastore.get(employee.getKey()); + assertWithMessage("got.firstName") + .that((String) got.getProperty("firstName")) + .isEqualTo("Antonio"); + assertWithMessage("got.lastName") + .that((String) got.getProperty("lastName")) + .isEqualTo("Salieri"); + assertWithMessage("got.hireDate").that((Date) got.getProperty("hireDate")).isNotNull(); + assertWithMessage("got.attendedHrTraining") + .that((boolean) got.getProperty("attendedHrTraining")) + .isTrue(); + } + + @Test + public void identifiers_keyName_setsKeyName() throws Exception { + // [START gae_java21_datastore_identifiers_1] + Entity employee = new Entity("Employee", "asalieri"); + // [END gae_java21_datastore_identifiers_1] + datastore.put(employee); + + assertWithMessage("key name").that(employee.getKey().getName()).isEqualTo("asalieri"); + } + + @Test + public void identifiers_autoId_setsUnallocatedId() throws Exception { + KeyRange keys = datastore.allocateIds("Employee", 1); + long usedId = keys.getStart().getId(); + + // [START gae_java21_datastore_identifiers_2] + Entity employee = new Entity("Employee"); + // [END gae_java21_datastore_identifiers_2] + datastore.put(employee); + + assertWithMessage("key id").that(employee.getKey().getId()).isNotEqualTo(usedId); + } + + @Test + public void parent_withinEntityConstructor_setsParent() throws Exception { + // [START gae_java21_datastore_parent_1] + Entity employee = new Entity("Employee"); + datastore.put(employee); + + Entity address = new Entity("Address", employee.getKey()); + datastore.put(address); + // [END gae_java21_datastore_parent_1] + + assertWithMessage("address parent").that(address.getParent()).isEqualTo(employee.getKey()); + } + + @Test + public void parent_withKeyName_setsKeyName() throws Exception { + Entity employee = new Entity("Employee"); + datastore.put(employee); + + // [START gae_java21_datastore_parent_2] + Entity address = new Entity("Address", "addr1", employee.getKey()); + // [END gae_java21_datastore_parent_2] + datastore.put(address); + + assertWithMessage("address key name").that(address.getKey().getName()).isEqualTo("addr1"); + } + + @Test + public void datastoreServiceFactory_returnsDatastoreService() throws Exception { + // [START gae_java21_datastore_working_with_entities] + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + // [END gae_java21_datastore_working_with_entities] + assertWithMessage("datastore").that(datastore).isNotNull(); + } + + @Test + public void creatingAnEntity_withKeyName_writesEntity() throws Exception { + // [START gae_java21_datastore_creating_an_entity_1] + Entity employee = new Entity("Employee", "asalieri"); + // Set the entity properties. + // ... + datastore.put(employee); + // [END gae_java21_datastore_creating_an_entity_1] + + assertWithMessage("employee key name").that(employee.getKey().getName()).isEqualTo("asalieri"); + } + + private Key writeEmptyEmployee() { + // [START gae_java21_datastore_creating_an_entity_2] + Entity employee = new Entity("Employee"); + // Set the entity properties. + // ... + datastore.put(employee); + // [END gae_java21_datastore_creating_an_entity_2] + return employee.getKey(); + } + + @Test + public void creatingAnEntity_withoutKeyName_writesEntity() throws Exception { + Key employeeKey = writeEmptyEmployee(); + // [START gae_java21_datastore_retrieving_an_entity] + // Key employeeKey = ...; + Entity employee = datastore.get(employeeKey); + // [END gae_java21_datastore_retrieving_an_entity] + + assertWithMessage("retrieved key ID") + .that(employee.getKey().getId()) + .isEqualTo(employeeKey.getId()); + } + + @Test + public void deletingAnEntity_deletesAnEntity() throws Exception { + Entity employee = new Entity("Employee", "asalieri"); + datastore.put(employee); + + Key employeeKey = KeyFactory.createKey("Employee", "asalieri"); + // [START gae_java21_datastore_deleting_an_entity] + // Key employeeKey = ...; + datastore.delete(employeeKey); + // [END gae_java21_datastore_deleting_an_entity] + + try { + Entity got = datastore.get(employeeKey); + fail("Expected EntityNotFoundException"); + } catch (EntityNotFoundException expected) { + assertWithMessage("exception key name") + .that(expected.getKey().getName()) + .isEqualTo("asalieri"); + } + } + + @Test + public void repeatedProperties_storesList() throws Exception { + // [START gae_java21_datastore_repeated_properties] + Entity employee = new Entity("Employee"); + ArrayList favoriteFruit = new ArrayList<>(); + favoriteFruit.add("Pear"); + favoriteFruit.add("Apple"); + employee.setProperty("favoriteFruit", favoriteFruit); + datastore.put(employee); + + // Sometime later + employee = datastore.get(employee.getKey()); + @SuppressWarnings("unchecked") // Cast can't verify generic type. + ArrayList retrievedFruits = (ArrayList) employee.getProperty("favoriteFruit"); + // [END gae_java21_datastore_repeated_properties] + + assertThat(retrievedFruits).containsExactlyElementsIn(favoriteFruit).inOrder(); + } + + // CHECKSTYLE.OFF: VariableDeclarationUsageDistance + @SuppressWarnings("VariableDeclarationUsageDistance") + @Test + public void embeddedEntity_fromEmbedded_embedsProperties() throws Exception { + Entity employee = new Entity("Employee"); + // [START gae_java21_datastore_embedded_entities_1] + // Entity employee = ...; + EmbeddedEntity embeddedContactInfo = new EmbeddedEntity(); + + embeddedContactInfo.setProperty("homeAddress", "123 Fake St, Made, UP 45678"); + embeddedContactInfo.setProperty("phoneNumber", "555-555-5555"); + embeddedContactInfo.setProperty("emailAddress", "test@example.com"); + + employee.setProperty("contactInfo", embeddedContactInfo); + // [END gae_java21_datastore_embedded_entities_1] + datastore.put(employee); + + Entity gotEmployee = datastore.get(employee.getKey()); + EmbeddedEntity got = (EmbeddedEntity) gotEmployee.getProperty("contactInfo"); + assertWithMessage("got.homeAddress") + .that((String) got.getProperty("homeAddress")) + .isEqualTo("123 Fake St, Made, UP 45678"); + } + // CHECKSTYLE.ON: VariableDeclarationUsageDistance + + private Key putEmployeeWithContactInfo(Entity contactInfo) { + Entity employee = new Entity("Employee"); + // [START gae_java21_datastore_embedded_entities_2] + // Entity employee = ...; + // Entity contactInfo = ...; + EmbeddedEntity embeddedContactInfo = new EmbeddedEntity(); + + embeddedContactInfo.setKey(contactInfo.getKey()); // Optional, used so we can recover original. + embeddedContactInfo.setPropertiesFrom(contactInfo); + + employee.setProperty("contactInfo", embeddedContactInfo); + // [END gae_java21_datastore_embedded_entities_2] + datastore.put(employee); + return employee.getKey(); + } + + @Test + public void embeddedEntity_fromExisting_canRecover() throws Exception { + Entity initialContactInfo = new Entity("Contact"); + initialContactInfo.setProperty("homeAddress", "123 Fake St, Made, UP 45678"); + initialContactInfo.setProperty("phoneNumber", "555-555-5555"); + initialContactInfo.setProperty("emailAddress", "test@example.com"); + datastore.put(initialContactInfo); + Key employeeKey = putEmployeeWithContactInfo(initialContactInfo); + + // [START gae_java21_datastore_embedded_entities_3] + Entity employee = datastore.get(employeeKey); + EmbeddedEntity embeddedContactInfo = (EmbeddedEntity) employee.getProperty("contactInfo"); + + Key infoKey = embeddedContactInfo.getKey(); + Entity contactInfo = new Entity(infoKey); + contactInfo.setPropertiesFrom(embeddedContactInfo); + // [END gae_java21_datastore_embedded_entities_3] + datastore.put(contactInfo); + + Entity got = datastore.get(infoKey); + assertThat(got.getKey()).isEqualTo(initialContactInfo.getKey()); + assertWithMessage("got.homeAddress") + .that((String) got.getProperty("homeAddress")) + .isEqualTo("123 Fake St, Made, UP 45678"); + } + + @Test + public void batchOperations_putsEntities() { + // [START gae_java21_datastore_gae_batch_operations] + Entity employee1 = new Entity("Employee"); + Entity employee2 = new Entity("Employee"); + Entity employee3 = new Entity("Employee"); + // [START_EXCLUDE] + employee1.setProperty("firstName", "Bill"); + employee2.setProperty("firstName", "Jane"); + employee3.setProperty("firstName", "Alex"); + // [END_EXCLUDE] + + List employees = Arrays.asList(employee1, employee2, employee3); + datastore.put(employees); + // [END gae_java21_datastore_gae_batch_operations] + + Map got = + datastore.get(Arrays.asList(employee1.getKey(), employee2.getKey(), employee3.getKey())); + assertWithMessage("employee1.firstName") + .that((String) got.get(employee1.getKey()).getProperty("firstName")) + .isEqualTo("Bill"); + assertWithMessage("employee2.firstName") + .that((String) got.get(employee2.getKey()).getProperty("firstName")) + .isEqualTo("Jane"); + assertWithMessage("employee3.firstName") + .that((String) got.get(employee3.getKey()).getProperty("firstName")) + .isEqualTo("Alex"); + } + + @Test + public void createKey_makesKey() { + // [START gae_java21_datastore_generating_keys_1] + Key k1 = KeyFactory.createKey("Person", "GreatGrandpa"); + Key k2 = KeyFactory.createKey("Person", 74219); + // [END gae_java21_datastore_generating_keys_1] + + assertThat(k1).isNotNull(); + assertThat(k2).isNotNull(); + } + + @Test + public void keyFactoryBuilder_makeKeyWithParents() { + Key greatKey = KeyFactory.createKey("Person", "GreatGrandpa"); + Key grandKey = KeyFactory.createKey(greatKey, "Person", "Grandpa"); + Key dadKey = KeyFactory.createKey(grandKey, "Person", "Dad"); + Key meKey = KeyFactory.createKey(dadKey, "Person", "Me"); + + // [START gae_java21_datastore_generating_keys_2] + Key k = + new KeyFactory.Builder("Person", "GreatGrandpa") + .addChild("Person", "Grandpa") + .addChild("Person", "Dad") + .addChild("Person", "Me") + .getKey(); + // [END gae_java21_datastore_generating_keys_2] + + assertThat(k).isEqualTo(meKey); + } + + @Test + public void keyToString_getsPerson() throws Exception { + Entity p = new Entity("Person"); + p.setProperty("relationship", "Me"); + datastore.put(p); + Key k = p.getKey(); + + // [START gae_java21_datastore_generating_keys_3] + String personKeyStr = KeyFactory.keyToString(k); + + // Some time later (for example, after using personKeyStr in a link). + Key personKey = KeyFactory.stringToKey(personKeyStr); + Entity person = datastore.get(personKey); + // [END gae_java21_datastore_generating_keys_3] + + assertThat(personKey).isEqualTo(k); + assertWithMessage("person.relationship") + .that((String) person.getProperty("relationship")) + .isEqualTo("Me"); + } +} diff --git a/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/GuestbookStrongTest.java b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/GuestbookStrongTest.java new file mode 100644 index 00000000000..ca73e082d82 --- /dev/null +++ b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/GuestbookStrongTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import com.example.time.testing.FakeClock; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import com.google.appengine.tools.development.testing.LocalUserServiceTestConfig; +import java.time.Instant; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for {@link GuestbookStrong}. + */ +@RunWith(JUnit4.class) +public class GuestbookStrongTest { + + private static final Instant FAKE_NOW = Instant.ofEpochMilli(1234567890L); + private static final String GUESTBOOK_ID = "my guestbook"; + + // Set maximum eventual consistency. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(100), + // Make sure there is a user logged in. We enforce this in web.xml. + new LocalUserServiceTestConfig()) + .setEnvIsLoggedIn(true) + .setEnvEmail("test@example.com") + .setEnvAuthDomain("gmail.com"); + + private FakeClock clock; + private GuestbookStrong guestbookUnderTest; + + @Before + public void setUp() throws Exception { + helper.setUp(); + clock = new FakeClock(FAKE_NOW); + guestbookUnderTest = new GuestbookStrong(GUESTBOOK_ID, clock); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void appendGreeting_normalData_setsContentProperty() { + Greeting got = guestbookUnderTest.appendGreeting("Hello, Datastore!"); + + assertWithMessage("content property").that(got.getContent()).isEqualTo("Hello, Datastore!"); + } + + @Test + public void appendGreeting_normalData_setsDateProperty() { + Greeting got = guestbookUnderTest.appendGreeting("Hello, Datastore!"); + + assertWithMessage("date property").that(got.getDate()).isEqualTo(FAKE_NOW); + } + + @Test + public void listGreetings_maximumEventualConsistency_returnsAllGreetings() { + // Arrange + guestbookUnderTest.appendGreeting("Hello, Datastore!"); + guestbookUnderTest.appendGreeting("Hello, Eventual Consistency!"); + guestbookUnderTest.appendGreeting("Hello, World!"); + + // Act + List got = guestbookUnderTest.listGreetings(); + + // Assert + // Since we use an ancestor query, all greetings should be available. + assertThat(got).hasSize(3); + } +} diff --git a/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/GuestbookTest.java b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/GuestbookTest.java new file mode 100644 index 00000000000..f24f0740a34 --- /dev/null +++ b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/GuestbookTest.java @@ -0,0 +1,125 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import com.example.time.testing.FakeClock; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.dev.HighRepJobPolicy; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import com.google.appengine.tools.development.testing.LocalUserServiceTestConfig; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for {@link Guestbook}. + */ +@RunWith(JUnit4.class) +public class GuestbookTest { + + private static final class CustomHighRepJobPolicy implements HighRepJobPolicy { + + static int newJobCounter = 0; + static int existingJobCounter = 0; + + @Override + public boolean shouldApplyNewJob(Key entityGroup) { + // Every other new job fails to apply. + return newJobCounter++ % 2 == 0; + } + + @Override + public boolean shouldRollForwardExistingJob(Key entityGroup) { + // Existing jobs always apply after every Get and every Query. + return true; + } + } + + // Set custom, deterministic, eventual consistency. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + new LocalDatastoreServiceTestConfig() + .setAlternateHighRepJobPolicyClass(CustomHighRepJobPolicy.class), + // Make sure there is a user logged in. We enforce this in web.xml. + new LocalUserServiceTestConfig()) + .setEnvIsLoggedIn(true) + .setEnvEmail("test@example.com") + .setEnvAuthDomain("gmail.com"); + + private FakeClock clock; + private Guestbook guestbookUnderTest; + + @Before + public void setUp() throws Exception { + helper.setUp(); + clock = new FakeClock(); + guestbookUnderTest = new Guestbook(clock); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void appendGreeting_normalData_setsContentProperty() { + Greeting got = guestbookUnderTest.appendGreeting("Hello, Datastore!"); + + assertWithMessage("content property").that(got.getContent()).isEqualTo("Hello, Datastore!"); + } + + @Test + public void listGreetings_eventualConsistency_returnsPartialGreetings() { + // Arrange + guestbookUnderTest.appendGreeting("Hello, Datastore!"); + guestbookUnderTest.appendGreeting("Hello, Eventual Consistency!"); + guestbookUnderTest.appendGreeting("Hello, World!"); + guestbookUnderTest.appendGreeting("Güten Tag!"); + + // Act + List got = guestbookUnderTest.listGreetings(); + + // The first time we query we should half of the results due to the fact that we simulate + // eventual consistency by applying every other write. + assertThat(got).hasSize(2); + } + + @Test + public void listGreetings_groomedDatastore_returnsAllGreetings() { + // Arrange + guestbookUnderTest.appendGreeting("Hello, Datastore!"); + guestbookUnderTest.appendGreeting("Hello, Eventual Consistency!"); + guestbookUnderTest.appendGreeting("Hello, World!"); + + // Act + guestbookUnderTest.listGreetings(); + // Second global query sees both Entities because we "groom" (attempt to + // apply unapplied jobs) after every query. + List got = guestbookUnderTest.listGreetings(); + + assertThat(got).hasSize(3); + } +} diff --git a/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/IndexesTest.java b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/IndexesTest.java new file mode 100644 index 00000000000..cbb83d7a393 --- /dev/null +++ b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/IndexesTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertWithMessage; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.KeyFactory; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.Filter; +import com.google.appengine.api.datastore.Query.FilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests to demonstrate App Engine Datastore queries. + */ +@RunWith(JUnit4.class) +public class IndexesTest { + + // Set no eventual consistency, that way queries return all results. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void propertyFilterExample_returnsMatchingEntities() throws Exception { + // [START gae_java21_datastore_unindexed_properties_1] + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + + Key acmeKey = KeyFactory.createKey("Company", "Acme"); + + Entity tom = new Entity("Person", "Tom", acmeKey); + tom.setProperty("name", "Tom"); + tom.setProperty("age", 32); + datastore.put(tom); + + Entity lucy = new Entity("Person", "Lucy", acmeKey); + lucy.setProperty("name", "Lucy"); + lucy.setUnindexedProperty("age", 29); + datastore.put(lucy); + + Filter ageFilter = new FilterPredicate("age", FilterOperator.GREATER_THAN, 25); + + Query q = new Query("Person").setAncestor(acmeKey).setFilter(ageFilter); + + // Returns tom but not lucy, because her age is unindexed + List results = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults()); + // [END gae_java21_datastore_unindexed_properties_1] + + assertWithMessage("query results").that(results).containsExactly(tom); + } +} diff --git a/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/ListPeopleServletTest.java b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/ListPeopleServletTest.java new file mode 100644 index 00000000000..b2dc94cafed --- /dev/null +++ b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/ListPeopleServletTest.java @@ -0,0 +1,165 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.PreparedQuery; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.SortDirection; +import com.google.appengine.api.datastore.QueryResultList; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import com.google.common.collect.ImmutableList; +import java.io.PrintWriter; +import java.io.StringWriter; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link ListPeopleServlet}. + */ +@RunWith(JUnit4.class) +public class ListPeopleServletTest { + + private static final ImmutableList TEST_NAMES = + // Keep in alphabetical order, so this is the same as the query order. + ImmutableList.builder() + .add("Alpha") + .add("Bravo") + .add("Charlie") + .add("Delta") + .add("Echo") + .add("Foxtrot") + .add("Golf") + .add("Hotel") + .add("India") + .add("Juliett") + .add("Kilo") + .add("Lima") + .add("Mike") + .add("November") + .add("Oscar") + .add("Papa") + .add("Quebec") + .add("Romeo") + .add("Sierra") + .add("Tango") + .build(); + + // Set no eventual consistency, that way queries return all results. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + @Mock + private HttpServletRequest mockRequest; + @Mock + private HttpServletResponse mockResponse; + private StringWriter responseWriter; + private DatastoreService datastore; + + private ListPeopleServlet servletUnderTest; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + + // Add test data. + ImmutableList.Builder people = ImmutableList.builder(); + for (String name : TEST_NAMES) { + people.add(createPerson(name)); + } + datastore.put(people.build()); + + // Set up a fake HTTP response. + responseWriter = new StringWriter(); + when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter)); + + servletUnderTest = new ListPeopleServlet(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + private Entity createPerson(String name) { + Entity person = new Entity("Person"); + person.setProperty("name", name); + return person; + } + + @Test + public void doGet_noCursor_writesNames() throws Exception { + servletUnderTest.doGet(mockRequest, mockResponse); + + String response = responseWriter.toString(); + for (int i = 0; i < ListPeopleServlet.PAGE_SIZE; i++) { + assertWithMessage("ListPeopleServlet response").that(response).contains(TEST_NAMES.get(i)); + } + } + + private String getFirstCursor() { + Query q = new Query("Person").addSort("name", SortDirection.ASCENDING); + PreparedQuery pq = datastore.prepare(q); + FetchOptions fetchOptions = FetchOptions.Builder.withLimit(ListPeopleServlet.PAGE_SIZE); + QueryResultList results = pq.asQueryResultList(fetchOptions); + return results.getCursor().toWebSafeString(); + } + + @Test + public void doGet_withValidCursor_writesNames() throws Exception { + when(mockRequest.getParameter("cursor")).thenReturn(getFirstCursor()); + + servletUnderTest.doGet(mockRequest, mockResponse); + + String response = responseWriter.toString(); + int i = 0; + while (i + ListPeopleServlet.PAGE_SIZE < TEST_NAMES.size() && i < ListPeopleServlet.PAGE_SIZE) { + assertWithMessage("ListPeopleServlet response") + .that(response) + .contains(TEST_NAMES.get(i + ListPeopleServlet.PAGE_SIZE)); + i++; + } + } + + @Test + public void doGet_withInvalidCursor_writesRedirect() throws Exception { + when(mockRequest.getParameter("cursor")).thenReturn("ThisCursorIsTotallyInvalid"); + servletUnderTest.doGet(mockRequest, mockResponse); + verify(mockResponse).sendRedirect("/people"); + } +} diff --git a/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/MetadataEntityGroupTest.java b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/MetadataEntityGroupTest.java new file mode 100644 index 00000000000..18d8ac9eff3 --- /dev/null +++ b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/MetadataEntityGroupTest.java @@ -0,0 +1,164 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entities; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.EntityNotFoundException; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.PreparedQuery; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Transaction; +import com.google.appengine.api.memcache.MemcacheService; +import com.google.appengine.api.memcache.MemcacheServiceFactory; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalMemcacheServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.io.PrintWriter; +import java.io.Serializable; +import java.io.StringWriter; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests to demonstrate App Engine Datastore entity group metadata. + */ +@RunWith(JUnit4.class) +public class MetadataEntityGroupTest { + + // Set no eventual consistency, that way queries return all results. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + new LocalDatastoreServiceTestConfig().setDefaultHighRepJobPolicyUnappliedJobPercentage(0), + new LocalMemcacheServiceTestConfig()); + + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + // [START gae_java21_datastore_entity_group_1] + private static long getEntityGroupVersion(DatastoreService ds, Transaction tx, Key entityKey) { + try { + return Entities.getVersionProperty(ds.get(tx, Entities.createEntityGroupKey(entityKey))); + } catch (EntityNotFoundException e) { + // No entity group information, return a value strictly smaller than any + // possible version + return 0; + } + } + + private static void printEntityGroupVersions(DatastoreService ds, PrintWriter writer) { + Entity entity1 = new Entity("Simple"); + Key key1 = ds.put(entity1); + Key entityGroupKey = Entities.createEntityGroupKey(key1); + + // Print entity1's entity group version + writer.println("version " + getEntityGroupVersion(ds, null, key1)); + + // Write to a different entity group + Entity entity2 = new Entity("Simple"); + ds.put(entity2); + + // Will print the same version, as entity1's entity group has not changed + writer.println("version " + getEntityGroupVersion(ds, null, key1)); + + // Change entity1's entity group by adding a new child entity + Entity entity3 = new Entity("Simple", entity1.getKey()); + ds.put(entity3); + + // Will print a higher version, as entity1's entity group has changed + writer.println("version " + getEntityGroupVersion(ds, null, key1)); + } + // [END gae_java21_datastore_entity_group_1] + + @Test + public void printEntityGroupVersions_printsVersions() throws Exception { + StringWriter responseWriter = new StringWriter(); + printEntityGroupVersions(datastore, new PrintWriter(responseWriter)); + assertThat(responseWriter.toString()).contains("version"); + } + + // [START gae_java21_datastore_entity_group_2] + // A simple class for tracking consistent entity group counts. + private static class EntityGroupCount implements Serializable { + + long version; // Version of the entity group whose count we are tracking + int count; + + EntityGroupCount(long version, int count) { + this.version = version; + this.count = count; + } + + // Display count of entities in an entity group, with consistent caching + void showEntityGroupCount( + DatastoreService ds, MemcacheService cache, PrintWriter writer, Key entityGroupKey) { + EntityGroupCount egCount = (EntityGroupCount) cache.get(entityGroupKey); + // Reuses getEntityGroupVersion method from the previous example. + if (egCount != null && egCount.version == getEntityGroupVersion(ds, null, entityGroupKey)) { + // Cached value matched current entity group version, use that + writer.println(egCount.count + " entities (cached)"); + } else { + // Need to actually count entities. Using a transaction to get a consistent count + // and entity group version. + Transaction tx = ds.beginTransaction(); + PreparedQuery pq = ds.prepare(tx, new Query(entityGroupKey)); + int count = pq.countEntities(FetchOptions.Builder.withLimit(5000)); + cache.put( + entityGroupKey, + new EntityGroupCount(getEntityGroupVersion(ds, tx, entityGroupKey), count)); + tx.rollback(); + writer.println(count + " entities"); + } + } + } + // [END gae_java21_datastore_entity_group_2] + + @Test + public void entityGroupCount_printsCount() throws Exception { + StringWriter responseWriter = new StringWriter(); + MemcacheService cache = MemcacheServiceFactory.getMemcacheService(); + Entity entity1 = new Entity("Simple"); + Key key1 = datastore.put(entity1); + Key entityGroupKey = Entities.createEntityGroupKey(key1); + + EntityGroupCount groupCount = new EntityGroupCount(0, 0); + groupCount.showEntityGroupCount( + datastore, cache, new PrintWriter(responseWriter), entityGroupKey); + + assertThat(responseWriter.toString()).contains(" entities"); + } +} diff --git a/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/MetadataKindsTest.java b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/MetadataKindsTest.java new file mode 100644 index 00000000000..68394559c6d --- /dev/null +++ b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/MetadataKindsTest.java @@ -0,0 +1,122 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entities; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.CompositeFilterOperator; +import com.google.appengine.api.datastore.Query.Filter; +import com.google.appengine.api.datastore.Query.FilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests to demonstrate App Engine Datastore kinds metadata. + */ +@RunWith(JUnit4.class) +public class MetadataKindsTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google + // .com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private StringWriter responseWriter; + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + responseWriter = new StringWriter(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + // [START gae_java21_datastore_kind_query] + void printLowercaseKinds(DatastoreService ds, PrintWriter writer) { + + // Start with unrestricted kind query + Query q = new Query(Entities.KIND_METADATA_KIND); + + List subFils = new ArrayList<>(); + + // Limit to lowercase initial letters + subFils.add( + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + FilterOperator.GREATER_THAN_OR_EQUAL, + Entities.createKindKey("a"))); + + String endChar = Character.toString((char) ('z' + 1)); // Character after 'z' + + subFils.add( + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + FilterOperator.LESS_THAN, + Entities.createKindKey(endChar))); + + q.setFilter(CompositeFilterOperator.and(subFils)); + + // Print heading + writer.println("Lowercase kinds:"); + + // Print query results + for (Entity e : ds.prepare(q).asIterable()) { + writer.println(" " + e.getKey().getName()); + } + } + // [END gae_java21_datastore_kind_query] + + @Test + public void printLowercaseKinds_printsKinds() throws Exception { + datastore.put(new Entity("alpha")); + datastore.put(new Entity("beta")); + datastore.put(new Entity("NotIncluded")); + datastore.put(new Entity("zed")); + + printLowercaseKinds(datastore, new PrintWriter(responseWriter)); + + String response = responseWriter.toString(); + assertThat(response).contains("alpha"); + assertThat(response).contains("beta"); + assertThat(response).contains("zed"); + assertThat(response).doesNotContain("NotIncluded"); + } +} diff --git a/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/MetadataNamespacesTest.java b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/MetadataNamespacesTest.java new file mode 100644 index 00000000000..04924853263 --- /dev/null +++ b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/MetadataNamespacesTest.java @@ -0,0 +1,154 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.appengine.api.NamespaceManager; +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entities; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.CompositeFilterOperator; +import com.google.appengine.api.datastore.Query.Filter; +import com.google.appengine.api.datastore.Query.FilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests to demonstrate App Engine Datastore namespaces metadata. + */ +@RunWith(JUnit4.class) +public class MetadataNamespacesTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google + // .com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private StringWriter responseWriter; + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + responseWriter = new StringWriter(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + // [START gae_java21_datastore_queries_intro] + void printAllNamespaces(DatastoreService ds, PrintWriter writer) { + Query q = new Query(Entities.NAMESPACE_METADATA_KIND); + + for (Entity e : ds.prepare(q).asIterable()) { + // A nonzero numeric id denotes the default namespace; + // see Namespace Queries, below + if (e.getKey().getId() != 0) { + writer.println(""); + } else { + writer.println(e.getKey().getName()); + } + } + } + // [END gae_java21_datastore_queries_intro] + + @Test + public void printAllNamespaces_printsNamespaces() throws Exception { + datastore.put(new Entity("Simple")); + NamespaceManager.set("another-namespace"); + datastore.put(new Entity("Simple")); + + printAllNamespaces(datastore, new PrintWriter(responseWriter)); + + String response = responseWriter.toString(); + assertThat(response).contains(""); + assertThat(response).contains("another-namespace"); + } + + // [START gae_java21_datastore_namespace_query] + List getNamespaces(DatastoreService ds, String start, String end) { + + // Start with unrestricted namespace query + Query q = new Query(Entities.NAMESPACE_METADATA_KIND); + List subFilters = new ArrayList<>(); + // Limit to specified range, if any + if (start != null) { + subFilters.add( + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + FilterOperator.GREATER_THAN_OR_EQUAL, + Entities.createNamespaceKey(start))); + } + if (end != null) { + subFilters.add( + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + FilterOperator.LESS_THAN_OR_EQUAL, + Entities.createNamespaceKey(end))); + } + + q.setFilter(CompositeFilterOperator.and(subFilters)); + + // Initialize result list + List results = new ArrayList<>(); + + // Build list of query results + for (Entity e : ds.prepare(q).asIterable()) { + results.add(Entities.getNamespaceFromNamespaceKey(e.getKey())); + } + + // Return result list + return results; + } + // [END gae_java21_datastore_namespace_query] + + @Test + public void getNamespaces_returnsNamespaces() throws Exception { + NamespaceManager.set("alpha"); + datastore.put(new Entity("Simple")); + NamespaceManager.set("bravo"); + datastore.put(new Entity("Simple")); + NamespaceManager.set("charlie"); + datastore.put(new Entity("Simple")); + NamespaceManager.set("zed"); + datastore.put(new Entity("Simple")); + + List results = getNamespaces(datastore, "bravo", "echo"); + + assertThat(results).containsExactly("bravo", "charlie"); + } +} diff --git a/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/MetadataPropertiesTest.java b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/MetadataPropertiesTest.java new file mode 100644 index 00000000000..5386f42cfe5 --- /dev/null +++ b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/MetadataPropertiesTest.java @@ -0,0 +1,235 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entities; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.CompositeFilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; +import com.google.appengine.api.datastore.Query.SortDirection; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests to demonstrate App Engine Datastore properties metadata. + */ +@RunWith(JUnit4.class) +public class MetadataPropertiesTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google + // .com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private StringWriter responseWriter; + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + responseWriter = new StringWriter(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + // [START gae_java21_datastore_property_query_example] + void printProperties(DatastoreService ds, PrintWriter writer) { + + // Create unrestricted keys-only property query + Query q = new Query(Entities.PROPERTY_METADATA_KIND).setKeysOnly(); + + // Print query results + for (Entity e : ds.prepare(q).asIterable()) { + writer.println(e.getKey().getParent().getName() + ": " + e.getKey().getName()); + } + } + // [END gae_java21_datastore_property_query_example] + + @Test + public void printProperties_printsProperties() throws Exception { + Entity a = new Entity("Widget"); + a.setProperty("combobulators", 2); + a.setProperty("oscillatorState", "harmonzing"); + Entity b = new Entity("Ship"); + b.setProperty("sails", 2); + b.setProperty("captain", "Blackbeard"); + Entity c = new Entity("Ship"); + c.setProperty("captain", "Redbeard"); + c.setProperty("motor", "outboard"); + datastore.put(Arrays.asList(a, b, c)); + + printProperties(datastore, new PrintWriter(responseWriter)); + + String response = responseWriter.toString(); + assertThat(response).contains("Widget: combobulators"); + assertThat(response).contains("Widget: oscillatorState"); + assertThat(response).contains("Ship: sails"); + assertThat(response).contains("Ship: captain"); + assertThat(response).contains("Ship: motor"); + } + + // [START gae_java21_datastore_property_filtering_example] + void printPropertyRange(DatastoreService ds, PrintWriter writer) { + + // Start with unrestricted keys-only property query + Query q = new Query(Entities.PROPERTY_METADATA_KIND).setKeysOnly(); + + // Limit range + q.setFilter( + CompositeFilterOperator.and( + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + Query.FilterOperator.GREATER_THAN_OR_EQUAL, + Entities.createPropertyKey("Employee", "salary")), + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + Query.FilterOperator.LESS_THAN_OR_EQUAL, + Entities.createPropertyKey("Manager", "salary")))); + q.addSort(Entity.KEY_RESERVED_PROPERTY, SortDirection.ASCENDING); + + // Print query results + for (Entity e : ds.prepare(q).asIterable()) { + writer.println(e.getKey().getParent().getName() + ": " + e.getKey().getName()); + } + } + // [END gae_java21_datastore_property_filtering_example] + + @Test + public void printPropertyRange_printsProperties() throws Exception { + Entity account = new Entity("Account"); + account.setProperty("balance", "10.30"); + account.setProperty("company", "General Company"); + Entity employee = new Entity("Employee"); + employee.setProperty("name", "John Doe"); + employee.setProperty("ssn", "987-65-4321"); + Entity invoice = new Entity("Invoice"); + invoice.setProperty("date", new Date()); + invoice.setProperty("amount", "99.98"); + Entity manager = new Entity("Manager"); + manager.setProperty("name", "Jane Doe"); + manager.setProperty("title", "Technical Director"); + Entity product = new Entity("Product"); + product.setProperty("description", "Widget to re-ionize an oscillator"); + product.setProperty("price", "19.97"); + datastore.put(Arrays.asList(account, employee, invoice, manager, product)); + + printPropertyRange(datastore, new PrintWriter(responseWriter)); + + String response = responseWriter.toString(); + assertThat(response) + .isEqualTo("Employee: ssn\nInvoice: amount\nInvoice: date\nManager: name\n"); + } + + // [START gae_java21_datastore_property_ancestor_query_example] + List propertiesOfKind(DatastoreService ds, String kind) { + + // Start with unrestricted keys-only property query + Query q = new Query(Entities.PROPERTY_METADATA_KIND).setKeysOnly(); + + // Limit to specified kind + q.setAncestor(Entities.createKindKey(kind)); + + // Initialize result list + ArrayList results = new ArrayList<>(); + + //Build list of query results + for (Entity e : ds.prepare(q).asIterable()) { + results.add(e.getKey().getName()); + } + + // Return result list + return results; + } + // [END gae_java21_datastore_property_ancestor_query_example] + + @Test + public void propertiesOfKind_returnsProperties() throws Exception { + Entity a = new Entity("Alpha"); + a.setProperty("beta", 12); + a.setProperty("charlie", "misc."); + Entity b = new Entity("Alpha"); + b.setProperty("charlie", "assorted"); + b.setProperty("delta", new Date()); + Entity c = new Entity("Charlie"); + c.setProperty("charlie", "some"); + c.setProperty("echo", new Date()); + datastore.put(Arrays.asList(a, b, c)); + + List properties = propertiesOfKind(datastore, "Alpha"); + + assertThat(properties).containsExactly("beta", "charlie", "delta"); + } + + // [START gae_java21_datastore_property_representation_query_example] + Collection representationsOfProperty(DatastoreService ds, String kind, String property) { + + // Start with unrestricted non-keys-only property query + Query q = new Query(Entities.PROPERTY_METADATA_KIND); + + // Limit to specified kind and property + q.setFilter( + new FilterPredicate( + "__key__", Query.FilterOperator.EQUAL, Entities.createPropertyKey(kind, property))); + + // Get query result + Entity propInfo = ds.prepare(q).asSingleEntity(); + + // Return collection of property representations + return (Collection) propInfo.getProperty("property_representation"); + } + // [END gae_java21_datastore_property_representation_query_example] + + @Test + public void representationsOfProperty_returnsRepresentations() throws Exception { + Entity a = new Entity("Alpha"); + a.setProperty("beta", 12); + Entity b = new Entity("Alpha"); + b.setProperty("beta", true); + Entity c = new Entity("Alpha"); + c.setProperty("beta", new Date()); + datastore.put(Arrays.asList(a, b, c)); + + Collection results = representationsOfProperty(datastore, "Alpha", "beta"); + + assertThat(results).containsExactly("INT64", "BOOLEAN"); + } +} diff --git a/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/ProjectionServletTest.java b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/ProjectionServletTest.java new file mode 100644 index 00000000000..42e5574da0e --- /dev/null +++ b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/ProjectionServletTest.java @@ -0,0 +1,97 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.Mockito.when; + +import com.example.time.testing.FakeClock; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.io.PrintWriter; +import java.io.StringWriter; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link ProjectionServlet}. + */ +@RunWith(JUnit4.class) +public class ProjectionServletTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig()); + + @Mock + private HttpServletRequest mockRequest; + @Mock + private HttpServletResponse mockResponse; + private StringWriter responseWriter; + private ProjectionServlet servletUnderTest; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + helper.setUp(); + + // Set up a fake HTTP response. + responseWriter = new StringWriter(); + when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter)); + + servletUnderTest = new ProjectionServlet(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void doGet_emptyDatastore_writesNoGreetings() throws Exception { + servletUnderTest.doGet(mockRequest, mockResponse); + + assertWithMessage("ProjectionServlet response") + .that(responseWriter.toString()) + .doesNotContain("Message"); + } + + @Test + public void doGet_manyGreetings_writesLatestGreetings() throws Exception { + // Arrange + GuestbookStrong guestbook = + new GuestbookStrong(GuestbookStrongServlet.GUESTBOOK_ID, new FakeClock()); + guestbook.appendGreeting("Hello."); + guestbook.appendGreeting("Güten Tag!"); + guestbook.appendGreeting("Hi."); + guestbook.appendGreeting("Hola."); + + // Act + servletUnderTest.doGet(mockRequest, mockResponse); + String output = responseWriter.toString(); + + assertWithMessage("ProjectionServlet response").that(output).contains("Message Hello."); + assertWithMessage("ProjectionServlet response").that(output).contains("Message Güten Tag!"); + assertWithMessage("ProjectionServlet response").that(output).contains("Message Hola."); + } +} diff --git a/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/ProjectionTest.java b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/ProjectionTest.java new file mode 100644 index 00000000000..e1ad82e84f5 --- /dev/null +++ b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/ProjectionTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.PropertyProjection; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests to demonstrate App Engine Datastore projection queries. */ +@RunWith(JUnit4.class) +public class ProjectionTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google + // .com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private DatastoreService datastore; + + @Before + public void setUp() throws Exception { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void projectionQuery_grouping_filtersDuplicates() { + putTestData("some duplicate", 0L); + putTestData("some duplicate", 0L); + putTestData("too big", 1L); + + // [START gae_java21_datastore_grouping] + Query q = new Query("TestKind"); + q.addProjection(new PropertyProjection("A", String.class)); + q.addProjection(new PropertyProjection("B", Long.class)); + q.setDistinct(true); + q.setFilter(Query.FilterOperator.LESS_THAN.of("B", 1L)); + q.addSort("B", Query.SortDirection.DESCENDING); + q.addSort("A"); + // [END gae_java21_datastore_grouping] + + List entities = datastore.prepare(q).asList(FetchOptions.Builder.withLimit(5)); + assertThat(entities).hasSize(1); + Entity entity = entities.get(0); + assertWithMessage("entity.A") + .that((String) entity.getProperty("A")) + .isEqualTo("some duplicate"); + assertWithMessage("entity.B").that((long) entity.getProperty("B")).isEqualTo(0L); + } + + private void putTestData(String a, long b) { + Entity entity = new Entity("TestKind"); + entity.setProperty("A", a); + entity.setProperty("B", b); + datastore.put(entity); + } +} diff --git a/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/QueriesTest.java b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/QueriesTest.java new file mode 100644 index 00000000000..6cb2e03d13d --- /dev/null +++ b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/QueriesTest.java @@ -0,0 +1,844 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static org.junit.Assert.fail; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.PreparedQuery; +import com.google.appengine.api.datastore.PreparedQuery.TooManyResultsException; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.CompositeFilter; +import com.google.appengine.api.datastore.Query.CompositeFilterOperator; +import com.google.appengine.api.datastore.Query.Filter; +import com.google.appengine.api.datastore.Query.FilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; +import com.google.appengine.api.datastore.Query.SortDirection; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import com.google.common.collect.ImmutableList; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests to demonstrate App Engine Datastore queries. */ +@RunWith(JUnit4.class) +public class QueriesTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google + // .com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void propertyFilterExample_returnsMatchingEntities() throws Exception { + // Arrange + Entity p1 = new Entity("Person"); + p1.setProperty("height", 120); + Entity p2 = new Entity("Person"); + p2.setProperty("height", 180); + Entity p3 = new Entity("Person"); + p3.setProperty("height", 160); + datastore.put(ImmutableList.of(p1, p2, p3)); + + // Act + long minHeight = 160; + // [START gae_java21_datastore_datastore_property_filter]] + Filter propertyFilter = + new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight); + Query q = new Query("Person").setFilter(propertyFilter); + // [END gae_java21_datastore_datastore_property_filter]] + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(p2, p3); + } + + @Test + public void keyFilterExample_returnsMatchingEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + Entity b = new Entity("Person", "b"); + Entity c = new Entity("Person", "c"); + Entity aa = new Entity("Person", "aa", b.getKey()); + Entity bb = new Entity("Person", "bb", b.getKey()); + Entity aaa = new Entity("Person", "aaa", bb.getKey()); + Entity bbb = new Entity("Person", "bbb", bb.getKey()); + datastore.put(ImmutableList.of(a, b, c, aa, bb, aaa, bbb)); + + // Act + Key lastSeenKey = bb.getKey(); + // [START gae_java21_datastore_datastore_key_filter]] + Filter keyFilter = + new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey); + Query q = new Query("Person").setFilter(keyFilter); + // [END gae_java21_datastore_datastore_key_filter]] + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results") + .that(results) + .containsExactly( + aaa, // Ancestor path "b/bb/aaa" is greater than "b/bb". + bbb, // Ancestor path "b/bb/bbb" is greater than "b/bb". + c); // Key name identifier "c" is greater than b. + } + + @Test + public void keyFilterExample_kindless_returnsMatchingEntities() throws Exception { + // Arrange + Entity a = new Entity("Child", "a"); + Entity b = new Entity("Child", "b"); + Entity c = new Entity("Child", "c"); + Entity aa = new Entity("Child", "aa", b.getKey()); + Entity bb = new Entity("Child", "bb", b.getKey()); + Entity aaa = new Entity("Child", "aaa", bb.getKey()); + Entity bbb = new Entity("Child", "bbb", bb.getKey()); + Entity adult = new Entity("Adult", "a"); + Entity zooAnimal = new Entity("ZooAnimal", "a"); + datastore.put(ImmutableList.of(a, b, c, aa, bb, aaa, bbb, adult, zooAnimal)); + + // Act + Key lastSeenKey = bb.getKey(); + // [START gae_java21_datastore_kindless_query] + Filter keyFilter = + new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey); + Query q = new Query().setFilter(keyFilter); + // [END gae_java21_datastore_kindless_query] + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results") + .that(results) + .containsExactly( + aaa, // Ancestor path "b/bb/aaa" is greater than "b/bb". + bbb, // Ancestor path "b/bb/bbb" is greater than "b/bb". + zooAnimal, // Kind "ZooAnimal" is greater than "Child" + c); // Key name identifier "c" is greater than b. + } + + @Test + public void ancestorFilterExample_returnsMatchingEntities() throws Exception { + Entity a = new Entity("Person", "a"); + Entity b = new Entity("Person", "b"); + Entity aa = new Entity("Person", "aa", a.getKey()); + Entity ab = new Entity("Person", "ab", a.getKey()); + Entity bb = new Entity("Person", "bb", b.getKey()); + datastore.put(ImmutableList.of(a, b, aa, ab, bb)); + + Key ancestorKey = a.getKey(); + // [START gae_java21_datastore_ancestor_filter] + Query q = new Query("Person").setAncestor(ancestorKey); + // [END gae_java21_datastore_ancestor_filter] + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(a, aa, ab); + } + + @Test + public void ancestorQueryExample_returnsMatchingEntities() throws Exception { + // [START gae_java21_datastore_ancestor_query] + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + + Entity tom = new Entity("Person", "Tom"); + Key tomKey = tom.getKey(); + datastore.put(tom); + + Entity weddingPhoto = new Entity("Photo", tomKey); + weddingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/wedding_photo.jpg"); + + Entity babyPhoto = new Entity("Photo", tomKey); + babyPhoto.setProperty("imageURL", "http://domain.com/some/path/to/baby_photo.jpg"); + + Entity dancePhoto = new Entity("Photo", tomKey); + dancePhoto.setProperty("imageURL", "http://domain.com/some/path/to/dance_photo.jpg"); + + Entity campingPhoto = new Entity("Photo"); + campingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/camping_photo.jpg"); + + List photoList = Arrays.asList(weddingPhoto, babyPhoto, dancePhoto, campingPhoto); + datastore.put(photoList); + + Query photoQuery = new Query("Photo").setAncestor(tomKey); + + // This returns weddingPhoto, babyPhoto, and dancePhoto, + // but not campingPhoto, because tom is not an ancestor + List results = + datastore.prepare(photoQuery).asList(FetchOptions.Builder.withDefaults()); + // [END gae_java21_datastore_ancestor_query] + + assertWithMessage("query results") + .that(results) + .containsExactly(weddingPhoto, babyPhoto, dancePhoto); + } + + @Test + public void ancestorQueryExample_kindlessKeyFilter_returnsMatchingEntities() throws Exception { + // Arrange + Entity a = new Entity("Grandparent", "a"); + Entity b = new Entity("Grandparent", "b"); + Entity c = new Entity("Grandparent", "c"); + Entity aa = new Entity("Parent", "aa", a.getKey()); + Entity ba = new Entity("Parent", "ba", b.getKey()); + Entity bb = new Entity("Parent", "bb", b.getKey()); + Entity bc = new Entity("Parent", "bc", b.getKey()); + Entity cc = new Entity("Parent", "cc", c.getKey()); + Entity aaa = new Entity("Child", "aaa", aa.getKey()); + Entity bbb = new Entity("Child", "bbb", bb.getKey()); + datastore.put(ImmutableList.of(a, b, c, aa, ba, bb, bc, cc, aaa, bbb)); + + // Act + Key ancestorKey = b.getKey(); + Key lastSeenKey = bb.getKey(); + // [START gae_java21_datastore_kindless_ancestor_key_query] + Filter keyFilter = + new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey); + Query q = new Query().setAncestor(ancestorKey).setFilter(keyFilter); + // [END gae_java21_datastore_kindless_ancestor_key_query] + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(bc, bbb); + } + + @Test + public void ancestorQueryExample_kindlessKeyFilterFull_returnsMatchingEntities() + throws Exception { + // [START gae_java21_datastore_kindless_ancestor_query] + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + + Entity tom = new Entity("Person", "Tom"); + Key tomKey = tom.getKey(); + datastore.put(tom); + + Entity weddingPhoto = new Entity("Photo", tomKey); + weddingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/wedding_photo.jpg"); + + Entity weddingVideo = new Entity("Video", tomKey); + weddingVideo.setProperty("videoURL", "http://domain.com/some/path/to/wedding_video.avi"); + + List mediaList = Arrays.asList(weddingPhoto, weddingVideo); + datastore.put(mediaList); + + // By default, ancestor queries include the specified ancestor itself. + // The following filter excludes the ancestor from the query results. + Filter keyFilter = + new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, tomKey); + + Query mediaQuery = new Query().setAncestor(tomKey).setFilter(keyFilter); + + // Returns both weddingPhoto and weddingVideo, + // even though they are of different entity kinds + List results = + datastore.prepare(mediaQuery).asList(FetchOptions.Builder.withDefaults()); + // [END gae_java21_datastore_kindless_ancestor_query] + + assertWithMessage("query result keys") + .that(results) + .containsExactly(weddingPhoto, weddingVideo); + } + + @Test + public void keysOnlyExample_returnsMatchingEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + Entity b = new Entity("Building", "b"); + Entity c = new Entity("Person", "c"); + datastore.put(ImmutableList.of(a, b, c)); + + // [START gae_java21_datastore_keys_only] + Query q = new Query("Person").setKeysOnly(); + // [END gae_java21_datastore_keys_only] + + // Assert + List results = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(a, c); + } + + @Test + public void sortOrderExample_returnsSortedEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + a.setProperty("lastName", "Alpha"); + a.setProperty("height", 100); + Entity b = new Entity("Person", "b"); + b.setProperty("lastName", "Bravo"); + b.setProperty("height", 200); + Entity c = new Entity("Person", "c"); + c.setProperty("lastName", "Charlie"); + c.setProperty("height", 300); + datastore.put(ImmutableList.of(a, b, c)); + + // Act + // [START gae_java21_datastore_sort_order] + // Order alphabetically by last name: + Query q1 = new Query("Person").addSort("lastName", SortDirection.ASCENDING); + + // Order by height, tallest to shortest: + Query q2 = new Query("Person").addSort("height", SortDirection.DESCENDING); + // [END gae_java21_datastore_sort_order] + + // Assert + List lastNameResults = + datastore.prepare(q1.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("last name query results") + .that(lastNameResults) + .containsExactly(a, b, c) + .inOrder(); + List heightResults = + datastore.prepare(q2.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("height query results") + .that(heightResults) + .containsExactly(c, b, a) + .inOrder(); + } + + @Test + public void sortOrderExample_multipleSortOrders_returnsSortedEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + a.setProperty("lastName", "Alpha"); + a.setProperty("height", 100); + Entity b1 = new Entity("Person", "b1"); + b1.setProperty("lastName", "Bravo"); + b1.setProperty("height", 150); + Entity b2 = new Entity("Person", "b2"); + b2.setProperty("lastName", "Bravo"); + b2.setProperty("height", 200); + Entity c = new Entity("Person", "c"); + c.setProperty("lastName", "Charlie"); + c.setProperty("height", 300); + datastore.put(ImmutableList.of(a, b1, b2, c)); + + // Act + // [START gae_java21_datastore_multiple_sort_orders] + Query q = + new Query("Person") + .addSort("lastName", SortDirection.ASCENDING) + .addSort("height", SortDirection.DESCENDING); + // [END gae_java21_datastore_multiple_sort_orders] + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(a, b2, b1, c).inOrder(); + } + + @Test + public void queryInterface_multipleFilters_printsMatchedEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + a.setProperty("firstName", "Alph"); + a.setProperty("lastName", "Alpha"); + a.setProperty("height", 60); + Entity b = new Entity("Person", "b"); + b.setProperty("firstName", "Bee"); + b.setProperty("lastName", "Bravo"); + b.setProperty("height", 70); + Entity c = new Entity("Person", "c"); + c.setProperty("firstName", "Charles"); + c.setProperty("lastName", "Charlie"); + c.setProperty("height", 100); + datastore.put(ImmutableList.of(a, b, c)); + + StringWriter buf = new StringWriter(); + PrintWriter out = new PrintWriter(buf); + long minHeight = 60; + long maxHeight = 72; + + // Act + // [START gae_java21_datastore_interface_1] + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + + Filter heightMinFilter = + new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight); + + Filter heightMaxFilter = + new FilterPredicate("height", FilterOperator.LESS_THAN_OR_EQUAL, maxHeight); + + // Use CompositeFilter to combine multiple filters + CompositeFilter heightRangeFilter = + CompositeFilterOperator.and(heightMinFilter, heightMaxFilter); + + // Use class Query to assemble a query + Query q = new Query("Person").setFilter(heightRangeFilter); + + // Use PreparedQuery interface to retrieve results + PreparedQuery pq = datastore.prepare(q); + + for (Entity result : pq.asIterable()) { + String firstName = (String) result.getProperty("firstName"); + String lastName = (String) result.getProperty("lastName"); + Long height = (Long) result.getProperty("height"); + + out.println(firstName + " " + lastName + ", " + height + " inches tall"); + } + // [END gae_java21_datastore_interface_1] + + // Assert + assertThat(buf.toString()).contains("Alph Alpha, 60 inches tall"); + assertThat(buf.toString()).contains("Bee Bravo, 70 inches tall"); + assertThat(buf.toString()).doesNotContain("Charlie"); + } + + @Test + public void queryInterface_orFilter_printsMatchedEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + a.setProperty("height", 100); + Entity b = new Entity("Person", "b"); + b.setProperty("height", 150); + Entity c = new Entity("Person", "c"); + c.setProperty("height", 200); + datastore.put(ImmutableList.of(a, b, c)); + + StringWriter buf = new StringWriter(); + PrintWriter out = new PrintWriter(buf); + long minHeight = 125; + long maxHeight = 175; + + // Act + // [START gae_java21_datastore_interface_3] + Filter tooShortFilter = new FilterPredicate("height", FilterOperator.LESS_THAN, minHeight); + + Filter tooTallFilter = new FilterPredicate("height", FilterOperator.GREATER_THAN, maxHeight); + + Filter heightOutOfRangeFilter = CompositeFilterOperator.or(tooShortFilter, tooTallFilter); + + Query q = new Query("Person").setFilter(heightOutOfRangeFilter); + // [END gae_java21_datastore_interface_3] + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(a, c); + } + + @Test + public void queryRestrictions_compositeFilter_returnsMatchedEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + a.setProperty("birthYear", 1930); + Entity b = new Entity("Person", "b"); + b.setProperty("birthYear", 1960); + Entity c = new Entity("Person", "c"); + c.setProperty("birthYear", 1990); + datastore.put(ImmutableList.of(a, b, c)); + + // Act + long minBirthYear = 1940; + long maxBirthYear = 1980; + // [START gae_java21_datastore_inequality_filters_one_property_valid_1] + Filter birthYearMinFilter = + new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear); + + Filter birthYearMaxFilter = + new FilterPredicate("birthYear", FilterOperator.LESS_THAN_OR_EQUAL, maxBirthYear); + + Filter birthYearRangeFilter = + CompositeFilterOperator.and(birthYearMinFilter, birthYearMaxFilter); + + Query q = new Query("Person").setFilter(birthYearRangeFilter); + // [END gae_java21_datastore_inequality_filters_one_property_valid_1] + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(b); + } + + @Test + public void queryRestrictions_compositeFilter_isInvalid() throws Exception { + long minBirthYear = 1940; + long maxHeight = 200; + // [START gae_java21_datastore_inequality_filters_one_property_invalid] + Filter birthYearMinFilter = + new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear); + + Filter heightMaxFilter = + new FilterPredicate("height", FilterOperator.LESS_THAN_OR_EQUAL, maxHeight); + + Filter invalidFilter = CompositeFilterOperator.and(birthYearMinFilter, heightMaxFilter); + + Query q = new Query("Person").setFilter(invalidFilter); + // [END gae_java21_datastore_inequality_filters_one_property_invalid] + + // Note: The local devserver behavior is different than the production + // version of Cloud Datastore, so there aren't any assertions we can make + // in this test. The query appears to work with the local test runner, + // but will fail in production. + } + + @Test + public void queryRestrictions_compositeEqualFilter_returnsMatchedEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + a.setProperty("birthYear", 1930); + a.setProperty("city", "Somewhere"); + a.setProperty("lastName", "Someone"); + Entity b = new Entity("Person", "b"); + b.setProperty("birthYear", 1960); + b.setProperty("city", "Somewhere"); + b.setProperty("lastName", "Someone"); + Entity c = new Entity("Person", "c"); + c.setProperty("birthYear", 1990); + c.setProperty("city", "Somewhere"); + c.setProperty("lastName", "Someone"); + Entity d = new Entity("Person", "d"); + d.setProperty("birthYear", 1960); + d.setProperty("city", "Nowhere"); + d.setProperty("lastName", "Someone"); + Entity e = new Entity("Person", "e"); + e.setProperty("birthYear", 1960); + e.setProperty("city", "Somewhere"); + e.setProperty("lastName", "Noone"); + datastore.put(ImmutableList.of(a, b, c, d, e)); + long minBirthYear = 1940; + long maxBirthYear = 1980; + String targetCity = "Somewhere"; + String targetLastName = "Someone"; + + // [START gae_java21_datastore_inequality_filters_one_property_valid_2] + Filter lastNameFilter = new FilterPredicate("lastName", FilterOperator.EQUAL, targetLastName); + + Filter cityFilter = new FilterPredicate("city", FilterOperator.EQUAL, targetCity); + + Filter birthYearMinFilter = + new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear); + + Filter birthYearMaxFilter = + new FilterPredicate("birthYear", FilterOperator.LESS_THAN_OR_EQUAL, maxBirthYear); + + Filter validFilter = + CompositeFilterOperator.and( + lastNameFilter, cityFilter, birthYearMinFilter, birthYearMaxFilter); + + Query q = new Query("Person").setFilter(validFilter); + // [END gae_java21_datastore_inequality_filters_one_property_valid_2] + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(b); + } + + @Test + public void queryRestrictions_inequalitySortedFirst_returnsMatchedEntities() throws Exception { + // Arrange + Entity a = new Entity("Person", "a"); + a.setProperty("birthYear", 1930); + a.setProperty("lastName", "Someone"); + Entity b = new Entity("Person", "b"); + b.setProperty("birthYear", 1990); + b.setProperty("lastName", "Bravo"); + Entity c = new Entity("Person", "c"); + c.setProperty("birthYear", 1960); + c.setProperty("lastName", "Charlie"); + Entity d = new Entity("Person", "d"); + d.setProperty("birthYear", 1960); + d.setProperty("lastName", "Delta"); + datastore.put(ImmutableList.of(a, b, c, d)); + long minBirthYear = 1940; + + Filter birthYearMinFilter = + new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear); + + Query q = + new Query("Person") + .setFilter(birthYearMinFilter) + .addSort("birthYear", SortDirection.ASCENDING) + .addSort("lastName", SortDirection.ASCENDING); + + // Assert + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(c, d, b).inOrder(); + } + + @Test + public void queryRestrictions_missingSortOnInequality_isInvalid() throws Exception { + long minBirthYear = 1940; + // [START gae_java21_datastore_inequality_filters_sort_orders_invalid_1] + Filter birthYearMinFilter = + new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear); + + // Not valid. Missing sort on birthYear. + Query q = + new Query("Person") + .setFilter(birthYearMinFilter) + .addSort("lastName", SortDirection.ASCENDING); + // [END gae_java21_datastore_inequality_filters_sort_orders_invalid_1] + + // Note: The local devserver behavior is different than the production + // version of Cloud Datastore, so there aren't any assertions we can make + // in this test. The query appears to work with the local test runner, + // but will fail in production. + } + + @Test + public void queryRestrictions_sortWrongOrderOnInequality_isInvalid() throws Exception { + long minBirthYear = 1940; + // [START gae_java21_datastore_inequality_filters_sort_orders_invalid_2] + Filter birthYearMinFilter = + new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear); + + // Not valid. Sort on birthYear needs to be first. + Query q = + new Query("Person") + .setFilter(birthYearMinFilter) + .addSort("lastName", SortDirection.ASCENDING) + .addSort("birthYear", SortDirection.ASCENDING); + // [END gae_java21_datastore_inequality_filters_sort_orders_invalid_2] + + // Note: The local devserver behavior is different than the production + // version of Cloud Datastore, so there aren't any assertions we can make + // in this test. The query appears to work with the local test runner, + // but will fail in production. + } + + @Test + public void queryRestrictions_surprisingMultipleValuesAllMustMatch_returnsNoEntities() + throws Exception { + Entity a = new Entity("Widget", "a"); + List xs = Arrays.asList(1L, 2L); + a.setProperty("x", xs); + datastore.put(a); + + // [START gae_java21_datastore_surprising_behavior_1] + Query q = + new Query("Widget") + .setFilter( + CompositeFilterOperator.and( + new FilterPredicate("x", FilterOperator.GREATER_THAN, 1), + new FilterPredicate("x", FilterOperator.LESS_THAN, 2))); + // [END gae_java21_datastore_surprising_behavior_1] + + // Entity "a" will not match because no individual value matches all filters. + // See the documentation for more details: + // https://cloud.google.com/appengine/docs/java/datastore/query-restrictions + // #properties_with_multiple_values_can_behave_in_surprising_ways + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).isEmpty(); + } + + @Test + public void queryRestrictions_surprisingMultipleValuesEquals_returnsMatchedEntities() + throws Exception { + Entity a = new Entity("Widget", "a"); + a.setProperty("x", ImmutableList.of(1L, 2L)); + Entity b = new Entity("Widget", "b"); + b.setProperty("x", ImmutableList.of(1L, 3L)); + Entity c = new Entity("Widget", "c"); + c.setProperty("x", ImmutableList.of(-6L, 2L)); + Entity d = new Entity("Widget", "d"); + d.setProperty("x", ImmutableList.of(-6L, 4L)); + Entity e = new Entity("Widget", "e"); + e.setProperty("x", ImmutableList.of(1L, 2L, 3L)); + datastore.put(ImmutableList.of(a, b, c, d, e)); + + // [START gae_java21_datastore_surprising_behavior_2] + Query q = + new Query("Widget") + .setFilter( + CompositeFilterOperator.and( + new FilterPredicate("x", FilterOperator.EQUAL, 1), + new FilterPredicate("x", FilterOperator.EQUAL, 2))); + // [END gae_java21_datastore_surprising_behavior_2] + + // Only "a" and "e" have both 1 and 2 in the "x" array-valued property. + // See the documentation for more details: + // https://cloud.google.com/appengine/docs/java/datastore/query-restrictions + // #properties_with_multiple_values_can_behave_in_surprising_ways + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(a, e); + } + + @Test + public void queryRestrictions_surprisingMultipleValuesNotEquals_returnsMatchedEntities() + throws Exception { + Entity a = new Entity("Widget", "a"); + a.setProperty("x", ImmutableList.of(1L, 2L)); + Entity b = new Entity("Widget", "b"); + b.setProperty("x", ImmutableList.of(1L, 3L)); + Entity c = new Entity("Widget", "c"); + c.setProperty("x", ImmutableList.of(-6L, 2L)); + Entity d = new Entity("Widget", "d"); + d.setProperty("x", ImmutableList.of(-6L, 4L)); + Entity e = new Entity("Widget", "e"); + e.setProperty("x", ImmutableList.of(1L)); + datastore.put(ImmutableList.of(a, b, c, d, e)); + + // [START gae_java21_datastore_surprising_behavior_3] + Query q = new Query("Widget").setFilter(new FilterPredicate("x", FilterOperator.NOT_EQUAL, 1)); + // [END gae_java21_datastore_surprising_behavior_3] + + // The query matches any entity that has a some value other than 1. Only + // entity "e" is not matched. See the documentation for more details: + // https://cloud.google.com/appengine/docs/java/datastore/query-restrictions + // #properties_with_multiple_values_can_behave_in_surprising_ways + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(a, b, c, d); + } + + @Test + public void queryRestrictions_surprisingMultipleValuesTwoNotEquals_returnsMatchedEntities() + throws Exception { + Entity a = new Entity("Widget", "a"); + a.setProperty("x", ImmutableList.of(1L, 2L)); + Entity b = new Entity("Widget", "b"); + b.setProperty("x", ImmutableList.of(1L, 2L, 3L)); + datastore.put(ImmutableList.of(a, b)); + + // [START gae_java21_datastore_surprising_behavior_4] + Query q = + new Query("Widget") + .setFilter( + CompositeFilterOperator.and( + new FilterPredicate("x", FilterOperator.NOT_EQUAL, 1), + new FilterPredicate("x", FilterOperator.NOT_EQUAL, 2))); + // [END gae_java21_datastore_surprising_behavior_4] + + // The two NOT_EQUAL filters in the query become like the combination of queries: + // x < 1 OR (x > 1 AND x < 2) OR x > 2 + // + // Only "b" has some value which matches the "x > 2" portion of this query. + // + // See the documentation for more details: + // https://cloud.google.com/appengine/docs/java/datastore/query-restrictions + // #properties_with_multiple_values_can_behave_in_surprising_ways + List results = + datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).containsExactly(b); + } + + private Entity retrievePersonWithLastName(String targetLastName) { + // [START gae_java21_datastore_single_retrieval] + Query q = + new Query("Person") + .setFilter(new FilterPredicate("lastName", FilterOperator.EQUAL, targetLastName)); + + PreparedQuery pq = datastore.prepare(q); + Entity result = pq.asSingleEntity(); + // [END gae_java21_datastore_single_retrieval] + return result; + } + + @Test + public void singleRetrievalExample_singleEntity_returnsEntity() throws Exception { + Entity a = new Entity("Person", "a"); + a.setProperty("lastName", "Johnson"); + Entity b = new Entity("Person", "b"); + b.setProperty("lastName", "Smith"); + datastore.put(ImmutableList.of(a, b)); + + Entity result = retrievePersonWithLastName("Johnson"); + + assertWithMessage("result") + .that(result) + .isEqualTo(a); // Note: Entity.equals() only checks the Key. + } + + @Test + public void singleRetrievalExample_multitpleEntities_throwsException() throws Exception { + Entity a = new Entity("Person", "a"); + a.setProperty("lastName", "Johnson"); + Entity b = new Entity("Person", "b"); + b.setProperty("lastName", "Johnson"); + datastore.put(ImmutableList.of(a, b)); + + try { + Entity result = retrievePersonWithLastName("Johnson"); + fail("Expected TooManyResultsException"); + } catch (TooManyResultsException expected) { + // TooManyResultsException does not provide addition details. + } + } + + // [START gae_java21_datastore_query_limit] + private List getTallestPeople() { + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + + Query q = new Query("Person").addSort("height", SortDirection.DESCENDING); + + PreparedQuery pq = datastore.prepare(q); + return pq.asList(FetchOptions.Builder.withLimit(5)); + } + // [END gae_java21_datastore_query_limit] + + @Test + public void queryLimitExample_returnsLimitedEntities() throws Exception { + Entity a = new Entity("Person", "a"); + a.setProperty("height", 200); + Entity b = new Entity("Person", "b"); + b.setProperty("height", 199); + Entity c = new Entity("Person", "c"); + c.setProperty("height", 201); + Entity d = new Entity("Person", "d"); + d.setProperty("height", 198); + Entity e = new Entity("Person", "e"); + e.setProperty("height", 202); + Entity f = new Entity("Person", "f"); + f.setProperty("height", 197); + Entity g = new Entity("Person", "g"); + g.setProperty("height", 203); + Entity h = new Entity("Person", "h"); + h.setProperty("height", 196); + datastore.put(ImmutableList.of(a, b, c, d, e, f, g, h)); + + List results = getTallestPeople(); + + assertWithMessage("results").that(results).containsExactly(g, e, c, a, b).inOrder(); + } +} diff --git a/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/ReadPolicyTest.java b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/ReadPolicyTest.java new file mode 100644 index 00000000000..31c303d6cd2 --- /dev/null +++ b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/ReadPolicyTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertWithMessage; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceConfig; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.ReadPolicy; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for {@link ReadPolicy}. + */ +@RunWith(JUnit4.class) +public class ReadPolicyTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set 100% eventual consistency, so we can test with other job policies. + // https://cloud.google + // .com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(100)); + + @Before + public void setUp() { + helper.setUp(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void readPolicy_eventual_returnsNoResults() { + // [START gae_java21_datastore_data_consistency] + double deadline = 5.0; + + // Construct a read policy for eventual consistency + ReadPolicy policy = new ReadPolicy(ReadPolicy.Consistency.EVENTUAL); + + // Set the read policy + DatastoreServiceConfig eventuallyConsistentConfig = + DatastoreServiceConfig.Builder.withReadPolicy(policy); + + // Set the call deadline + DatastoreServiceConfig deadlineConfig = DatastoreServiceConfig.Builder.withDeadline(deadline); + + // Set both the read policy and the call deadline + DatastoreServiceConfig datastoreConfig = + DatastoreServiceConfig.Builder.withReadPolicy(policy).deadline(deadline); + + // Get Datastore service with the given configuration + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(datastoreConfig); + // [END gae_java21_datastore_data_consistency] + + Entity parent = new Entity("Person", "a"); + Entity child = new Entity("Person", "b", parent.getKey()); + datastore.put(ImmutableList.of(parent, child)); + + // Even though we are using an ancestor query, the policy is set to + // eventual, so we should get eventually-consistent results. Since the + // local data store test config is set to 100% unapplied jobs, there + // should be no results. + Query q = new Query("Person").setAncestor(parent.getKey()); + List results = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).isEmpty(); + } + + @Test + public void readPolicy_strong_returnsAllResults() { + double deadline = 5.0; + ReadPolicy policy = new ReadPolicy(ReadPolicy.Consistency.STRONG); + DatastoreServiceConfig datastoreConfig = + DatastoreServiceConfig.Builder.withReadPolicy(policy).deadline(deadline); + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(datastoreConfig); + + Entity parent = new Entity("Person", "a"); + Entity child = new Entity("Person", "b", parent.getKey()); + datastore.put(ImmutableList.of(parent, child)); + + Query q = new Query("Person").setAncestor(parent.getKey()); + List results = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults()); + assertWithMessage("query results").that(results).hasSize(2); + } +} diff --git a/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/StartupServletTest.java b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/StartupServletTest.java new file mode 100644 index 00000000000..2da82842484 --- /dev/null +++ b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/StartupServletTest.java @@ -0,0 +1,108 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.Mockito.when; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.Filter; +import com.google.appengine.api.datastore.Query.FilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.io.PrintWriter; +import java.io.StringWriter; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link StartupServlet}. + */ +@RunWith(JUnit4.class) +public class StartupServletTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google + // .com/appengine/docs/java/tools/localunittesting + // #Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + @Mock + private HttpServletRequest mockRequest; + @Mock + private HttpServletResponse mockResponse; + private StringWriter responseWriter; + private DatastoreService datastore; + + private StartupServlet servletUnderTest; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + + // Set up a fake HTTP response. + responseWriter = new StringWriter(); + when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter)); + + servletUnderTest = new StartupServlet(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void doGet_emptyDatastore_writesOkay() throws Exception { + servletUnderTest.doGet(mockRequest, mockResponse); + assertWithMessage("StartupServlet response").that(responseWriter.toString()).isEqualTo("ok\n"); + } + + @Test + public void doGet_emptyDatastore_writesPresidents() throws Exception { + servletUnderTest.doGet(mockRequest, mockResponse); + + Filter nameFilter = new FilterPredicate("name", FilterOperator.EQUAL, "George Washington"); + Query q = new Query("Person").setFilter(nameFilter); + Entity result = datastore.prepare(q).asSingleEntity(); + assertWithMessage("name").that(result.getProperty("name")).isEqualTo("George Washington"); + } + + @Test + public void doGet_alreadyPopulated_writesOkay() throws Exception { + datastore.put( + new Entity(StartupServlet.IS_POPULATED_ENTITY, StartupServlet.IS_POPULATED_KEY_NAME)); + servletUnderTest.doGet(mockRequest, mockResponse); + assertWithMessage("StartupServlet response").that(responseWriter.toString()).isEqualTo("ok\n"); + } +} diff --git a/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/TransactionsTest.java b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/TransactionsTest.java new file mode 100644 index 00000000000..8faf1144fff --- /dev/null +++ b/appengine-java21/ee8/datastore/src/test/java/com/example/appengine/TransactionsTest.java @@ -0,0 +1,309 @@ +/* + * Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static com.google.common.truth.Truth.assertWithMessage; +import static org.junit.Assert.fail; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.EntityNotFoundException; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.KeyFactory; +import com.google.appengine.api.datastore.PreparedQuery; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Transaction; +import com.google.appengine.api.datastore.TransactionOptions; +import com.google.appengine.api.taskqueue.Queue; +import com.google.appengine.api.taskqueue.QueueFactory; +import com.google.appengine.api.taskqueue.TaskOptions; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.util.ConcurrentModificationException; +import java.util.Date; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests to demonstrate App Engine Datastore transactions. + */ +@RunWith(JUnit4.class) +public class TransactionsTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Use High Rep job policy to allow cross group transactions in tests. + new LocalDatastoreServiceTestConfig().setApplyAllHighRepJobPolicy()); + + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @After + public void tearDown() { + // Clean up any dangling transactions. + Transaction txn = datastore.getCurrentTransaction(null); + if (txn != null && txn.isActive()) { + txn.rollback(); + } + helper.tearDown(); + } + + @Test + public void usingTransactions() throws Exception { + Entity joe = new Entity("Employee", "Joe"); + datastore.put(joe); + + // [START gae_java21_datastore_using_transactions] + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + Transaction txn = datastore.beginTransaction(); + try { + Key employeeKey = KeyFactory.createKey("Employee", "Joe"); + Entity employee = datastore.get(employeeKey); + employee.setProperty("vacationDays", 10); + + datastore.put(txn, employee); + + txn.commit(); + } finally { + if (txn.isActive()) { + txn.rollback(); + } + } + // [END gae_java21_datastore_using_transactions] + } + + @Test + public void entityGroups() throws Exception { + try { + // [START gae_java21_datastore_entity_groups] + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + Entity person = new Entity("Person", "tom"); + datastore.put(person); + + // Transactions on root entities + Transaction txn = datastore.beginTransaction(); + + Entity tom = datastore.get(person.getKey()); + tom.setProperty("age", 40); + datastore.put(txn, tom); + txn.commit(); + + // Transactions on child entities + txn = datastore.beginTransaction(); + tom = datastore.get(person.getKey()); + Entity photo = new Entity("Photo", tom.getKey()); + + // Create a Photo that is a child of the Person entity named "tom" + photo.setProperty("photoUrl", "http://domain.com/path/to/photo.jpg"); + datastore.put(txn, photo); + txn.commit(); + + // Transactions on entities in different entity groups + txn = datastore.beginTransaction(); + tom = datastore.get(person.getKey()); + Entity photoNotaChild = new Entity("Photo"); + photoNotaChild.setProperty("photoUrl", "http://domain.com/path/to/photo.jpg"); + datastore.put(txn, photoNotaChild); + + // Throws IllegalArgumentException because the Person entity + // and the Photo entity belong to different entity groups. + txn.commit(); + // [END gae_java21_datastore_entity_groups] + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + // We expect to get an exception that complains that we don't have a XG-transaction. + } + } + + @Test + @SuppressWarnings("checkstyle:AbbreviationAsWordInName") + public void creatingAnEntityInASpecificEntityGroup() throws Exception { + String boardName = "my-message-board"; + + // [START gae_java21_datastore_creating_an_entity_in_a_specific_entity_group] + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + + String messageTitle = "Some Title"; + String messageText = "Some message."; + Date postDate = new Date(); + + Key messageBoardKey = KeyFactory.createKey("MessageBoard", boardName); + + Entity message = new Entity("Message", messageBoardKey); + message.setProperty("message_title", messageTitle); + message.setProperty("message_text", messageText); + message.setProperty("post_date", postDate); + + Transaction txn = datastore.beginTransaction(); + datastore.put(txn, message); + + txn.commit(); + // [END gae_java21_datastore_creating_an_entity_in_a_specific_entity_group] + } + + @Test + public void crossGroupTransactions() throws Exception { + // [START gae_java21_datastore_cross_group_transactions] + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + TransactionOptions options = TransactionOptions.Builder.withXG(true); + Transaction txn = datastore.beginTransaction(options); + + Entity a = new Entity("A"); + a.setProperty("a", 22); + datastore.put(txn, a); + + Entity b = new Entity("B"); + b.setProperty("b", 11); + datastore.put(txn, b); + + txn.commit(); + // [END gae_java21_datastore_cross_group_transactions] + } + + @Test + public void usesForTransactions_relativeUpdates() throws Exception { + String boardName = "my-message-board"; + Entity b = new Entity("MessageBoard", boardName); + b.setProperty("count", 41); + datastore.put(b); + + // [START gae_java21_datastore_uses_for_transactions_1] + int retries = 3; + while (true) { + Transaction txn = datastore.beginTransaction(); + try { + Key boardKey = KeyFactory.createKey("MessageBoard", boardName); + Entity messageBoard = datastore.get(boardKey); + + long count = (Long) messageBoard.getProperty("count"); + ++count; + messageBoard.setProperty("count", count); + datastore.put(txn, messageBoard); + + txn.commit(); + break; + } catch (ConcurrentModificationException e) { + if (retries == 0) { + throw e; + } + // Allow retry to occur + --retries; + } finally { + if (txn.isActive()) { + txn.rollback(); + } + } + } + // [END gae_java21_datastore_uses_for_transactions_1] + + b = datastore.get(KeyFactory.createKey("MessageBoard", boardName)); + assertWithMessage("board.count").that((long) b.getProperty("count")).isEqualTo(42L); + } + + private Entity fetchOrCreate(String boardName) { + // [START gae_java21_datastore_uses_for_transactions_2] + Transaction txn = datastore.beginTransaction(); + Entity messageBoard; + Key boardKey; + try { + boardKey = KeyFactory.createKey("MessageBoard", boardName); + messageBoard = datastore.get(boardKey); + } catch (EntityNotFoundException e) { + messageBoard = new Entity("MessageBoard", boardName); + messageBoard.setProperty("count", 0L); + boardKey = datastore.put(txn, messageBoard); + } + txn.commit(); + // [END gae_java21_datastore_uses_for_transactions_2] + + return messageBoard; + } + + @Test + public void usesForTransactions_fetchOrCreate_fetchesExisting() throws Exception { + Entity b = new Entity("MessageBoard", "my-message-board"); + b.setProperty("count", 7); + datastore.put(b); + + Entity board = fetchOrCreate("my-message-board"); + + assertWithMessage("board.count").that((long) board.getProperty("count")).isEqualTo(7L); + } + + @Test + public void usesForTransactions_fetchOrCreate_createsNew() throws Exception { + Entity board = fetchOrCreate("my-message-board"); + assertWithMessage("board.count").that((long) board.getProperty("count")).isEqualTo(0L); + } + + @Test + public void usesForTransactions_readSnapshot() throws Exception { + String boardName = "my-message-board"; + Entity b = new Entity("MessageBoard", boardName); + b.setProperty("count", 13); + datastore.put(b); + + // [START gae_java21_datastore_uses_for_transactions_3] + DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); + + // Display information about a message board and its first 10 messages. + Key boardKey = KeyFactory.createKey("MessageBoard", boardName); + + Transaction txn = datastore.beginTransaction(); + + Entity messageBoard = datastore.get(boardKey); + long count = (Long) messageBoard.getProperty("count"); + + Query q = new Query("Message", boardKey); + + // This is an ancestor query. + PreparedQuery pq = datastore.prepare(txn, q); + List messages = pq.asList(FetchOptions.Builder.withLimit(10)); + + txn.commit(); + // [END gae_java21_datastore_uses_for_transactions_3] + + assertWithMessage("board.count").that(count).isEqualTo(13L); + } + + @Test + public void transactionalTaskEnqueuing() throws Exception { + // [START gae_java21_datastore_transactional_task_enqueuing] + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + Queue queue = QueueFactory.getDefaultQueue(); + Transaction txn = datastore.beginTransaction(); + // ... + + queue.add(txn, TaskOptions.Builder.withUrl("/path/to/handler")); + + // ... + + txn.commit(); + // [END gae_java21_datastore_transactional_task_enqueuing] + } +} diff --git a/appengine-java21/ee8/images/README.md b/appengine-java21/ee8/images/README.md new file mode 100644 index 00000000000..16f47030d5e --- /dev/null +++ b/appengine-java21/ee8/images/README.md @@ -0,0 +1,39 @@ +# Google App Engine Standard Environment Images Sample + + +Open in Cloud Shell + +This sample demonstrates how to use the Images Java API. + +See the [Google App Engine standard environment documentation][ae-docs] for more +detailed instructions. + +[ae-docs]: https://cloud.google.com/appengine/docs/java/ + +## Modify the app + +Using the [Google Cloud SDK](https://cloud.google.com/sdk/) create a bucket + + $ gcloud storage buckets create gs://YOUR-PROJECT-ID.appspot.com + +* Edit `src/main/java/com/example/appengine/images/ImageServlet.java` and set your `bucket` name. + +## Running locally + + This example uses the + [App Engine maven plugin](https://cloud.google.com/appengine/docs/java/tools/maven). + To run this sample locally: + + $ mvn appengine:run + + To see the results of the sample application, open + [localhost:8080](http://localhost:8080) in a web browser. + + +## Deploying + + In the following command, replace YOUR-PROJECT-ID with your + [Google Cloud Project ID](https://developers.google.com/console/help/new/#projectnumber) + and SOME-VERSION with a valid version number. + + $ mvn appengine:update -Dappengine.appId=YOUR-PROJECT-ID -Dappengine.version=SOME-VERSION diff --git a/appengine-java21/ee8/images/pom.xml b/appengine-java21/ee8/images/pom.xml new file mode 100644 index 00000000000..f980d6dcaf4 --- /dev/null +++ b/appengine-java21/ee8/images/pom.xml @@ -0,0 +1,84 @@ + + + 4.0.0 + war + 1.0-SNAPSHOT + com.example.appengine + appengine-images-j21 + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + 21 + 21 + + + + + com.google.appengine + appengine-api-1.0-sdk + 2.0.38 + + + + com.google.appengine.tools + appengine-gcs-client + 0.8.3 + + + + jakarta.servlet + jakarta.servlet-api + 6.1.0 + jar + provided + + + + + + ${project.build.directory}/${project.build.finalName}/WEB-INF/classes + + + com.google.cloud.tools + appengine-maven-plugin + 2.5.0 + + GCLOUD_CONFIG + GCLOUD_CONFIG + true + true + + + + + org.apache.maven.plugins + maven-war-plugin + 3.4.0 + + + + diff --git a/appengine-java21/ee8/images/src/main/java/com/example/appengine/images/ImagesServlet.java b/appengine-java21/ee8/images/src/main/java/com/example/appengine/images/ImagesServlet.java new file mode 100644 index 00000000000..9addc79fd08 --- /dev/null +++ b/appengine-java21/ee8/images/src/main/java/com/example/appengine/images/ImagesServlet.java @@ -0,0 +1,141 @@ +/* + * Copyright 2015 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine.images; + +import com.google.appengine.api.blobstore.BlobKey; +import com.google.appengine.api.blobstore.BlobstoreService; +import com.google.appengine.api.blobstore.BlobstoreServiceFactory; +import com.google.appengine.api.images.Image; +import com.google.appengine.api.images.ImagesService; +import com.google.appengine.api.images.ImagesServiceFactory; +import com.google.appengine.api.images.ServingUrlOptions; +import com.google.appengine.api.images.Transform; +import com.google.appengine.tools.cloudstorage.GcsFileOptions; +import com.google.appengine.tools.cloudstorage.GcsFilename; +import com.google.appengine.tools.cloudstorage.GcsService; +import com.google.appengine.tools.cloudstorage.GcsServiceFactory; +import com.google.appengine.tools.cloudstorage.RetryParams; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +// [START gae_java21_images_example] +@SuppressWarnings("serial") +// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required. +@WebServlet( + name = "images", + description = "Images: Write an image to a bucket and display it in various sizes", + urlPatterns = "/images") +public class ImagesServlet extends HttpServlet { + final String bucket = "YOUR-BUCKETNAME-HERE"; + + // [START gae_java21_images_gcs] + private final GcsService gcsService = + GcsServiceFactory.createGcsService( + new RetryParams.Builder() + .initialRetryDelayMillis(10) + .retryMaxAttempts(10) + .totalRetryPeriodMillis(15000) + .build()); + // [END gae_java21_images_gcs] + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + + // [START gae_java21_images_original_image] + // Read the image.jpg resource into a ByteBuffer. + FileInputStream fileInputStream = new FileInputStream(new File("WEB-INF/image.jpg")); + FileChannel fileChannel = fileInputStream.getChannel(); + ByteBuffer byteBuffer = ByteBuffer.allocate((int) fileChannel.size()); + fileChannel.read(byteBuffer); + + byte[] imageBytes = byteBuffer.array(); + + // Write the original image to Cloud Storage + gcsService.createOrReplace( + new GcsFilename(bucket, "image.jpeg"), + new GcsFileOptions.Builder().mimeType("image/jpeg").build(), + ByteBuffer.wrap(imageBytes)); + // [END gae_java21_images_original_image] + + // [START gae_java21_images_resize] + // Get an instance of the imagesService we can use to transform images. + ImagesService imagesService = ImagesServiceFactory.getImagesService(); + + // Make an image directly from a byte array, and transform it. + Image image = ImagesServiceFactory.makeImage(imageBytes); + Transform resize = ImagesServiceFactory.makeResize(100, 50); + Image resizedImage = imagesService.applyTransform(resize, image); + + // Write the transformed image back to a Cloud Storage object. + gcsService.createOrReplace( + new GcsFilename(bucket, "resizedImage.jpeg"), + new GcsFileOptions.Builder().mimeType("image/jpeg").build(), + ByteBuffer.wrap(resizedImage.getImageData())); + // [END gae_java21_images_resize] + + // [START gae_java21_images_rotate] + // Make an image from a Cloud Storage object, and transform it. + BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService(); + BlobKey blobKey = blobstoreService.createGsBlobKey("/gs/" + bucket + "/image.jpeg"); + Image blobImage = ImagesServiceFactory.makeImageFromBlob(blobKey); + Transform rotate = ImagesServiceFactory.makeRotate(90); + Image rotatedImage = imagesService.applyTransform(rotate, blobImage); + + // Write the transformed image back to a Cloud Storage object. + gcsService.createOrReplace( + new GcsFilename(bucket, "rotatedImage.jpeg"), + new GcsFileOptions.Builder().mimeType("image/jpeg").build(), + ByteBuffer.wrap(rotatedImage.getImageData())); + // [END gae_java21_images_rotate] + + // [START gae_java21_images_servingUrl] + // Create a fixed dedicated URL that points to the GCS hosted file + ServingUrlOptions options = + ServingUrlOptions.Builder.withGoogleStorageFileName("/gs/" + bucket + "/image.jpeg") + .imageSize(150) + .crop(true) + .secureUrl(true); + String url = imagesService.getServingUrl(options); + // [END gae_java21_images_servingUrl] + + // Output some simple HTML to display the images we wrote to Cloud Storage + // in the browser. + PrintWriter out = resp.getWriter(); + out.println("\n"); + out.println( + "AppEngine logo"); + out.println( + "AppEngine logo resized"); + out.println( + "AppEngine logo rotated"); + out.println("Hosted logo"); + out.println("\n"); + } +} +// [END gae_java21_images_example] diff --git a/appengine-java21/ee8/images/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java21/ee8/images/src/main/webapp/WEB-INF/appengine-web.xml new file mode 100644 index 00000000000..01bc089e2c1 --- /dev/null +++ b/appengine-java21/ee8/images/src/main/webapp/WEB-INF/appengine-web.xml @@ -0,0 +1,19 @@ + + + + + + java21 + true + diff --git a/appengine-java21/ee8/images/src/main/webapp/WEB-INF/image.jpg b/appengine-java21/ee8/images/src/main/webapp/WEB-INF/image.jpg new file mode 100644 index 00000000000..3a60da2619d Binary files /dev/null and b/appengine-java21/ee8/images/src/main/webapp/WEB-INF/image.jpg differ diff --git a/appengine-java21/ee8/images/src/main/webapp/WEB-INF/web.xml b/appengine-java21/ee8/images/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..edbcb9b2b2c --- /dev/null +++ b/appengine-java21/ee8/images/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,33 @@ + + + + java21 + + images + com.example.appengine.images.ImagesServlet + + + images + /* + + true + diff --git a/appengine-java21/ee8/users/README.md b/appengine-java21/ee8/users/README.md new file mode 100644 index 00000000000..e8a3c04aa60 --- /dev/null +++ b/appengine-java21/ee8/users/README.md @@ -0,0 +1,23 @@ +# Users Authentication sample for Google App Engine + + +Open in Cloud Shell + +This sample demonstrates how to use the [Users API][appid] on [Google App +Engine][ae-docs]. + +[appid]: https://cloud.google.com/appengine/docs/java/users/ +[ae-docs]: https://cloud.google.com/appengine/docs/java/ + +## Running locally +This example uses the +[Maven gcloud plugin](https://cloud.google.com/appengine/docs/legacy/standard/java/using-maven). +To run this sample locally: + + $ mvn appengine:run + +## Deploying +In the following command, replace YOUR-PROJECT-ID with your +[Google Cloud Project ID](https://developers.google.com/console/help/new/#projectnumber). + + $ mvn clean package appengine:deploy diff --git a/appengine-java21/ee8/users/pom.xml b/appengine-java21/ee8/users/pom.xml new file mode 100644 index 00000000000..3c65115463e --- /dev/null +++ b/appengine-java21/ee8/users/pom.xml @@ -0,0 +1,133 @@ + + + + 4.0.0 + war + 1.0-SNAPSHOT + com.example.appengine + appengine-users-j21 + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + 21 + 21 + + + + + + libraries-bom + com.google.cloud + import + pom + 26.28.0 + + + + + + + com.google.appengine + appengine-api-1.0-sdk + 2.0.39 + + + + jakarta.servlet + jakarta.servlet-api + 4.0.4 + jar + provided + + + + + junit + junit + 4.13.2 + test + + + org.mockito + mockito-core + 4.11.0 + test + + + com.google.appengine + appengine-testing + 2.0.39 + test + + + com.google.appengine + appengine-api-stubs + 2.0.39 + test + + + com.google.appengine + appengine-tools-sdk + 2.0.39 + test + + + com.google.truth + truth + 1.4.4 + test + + + + + + ${project.build.directory}/${project.build.finalName}/WEB-INF/classes + + + org.apache.maven.plugins + maven-war-plugin + 3.4.0 + + + org.jacoco + jacoco-maven-plugin + 0.8.13 + + + com.google.cloud.tools + appengine-maven-plugin + 2.5.0 + + GCLOUD_CONFIG + GCLOUD_CONFIG + true + true + + + + + diff --git a/appengine-java21/ee8/users/src/main/java/com/example/appengine/users/UsersServlet.java b/appengine-java21/ee8/users/src/main/java/com/example/appengine/users/UsersServlet.java new file mode 100644 index 00000000000..11a5aafd91b --- /dev/null +++ b/appengine-java21/ee8/users/src/main/java/com/example/appengine/users/UsersServlet.java @@ -0,0 +1,58 @@ +/* Copyright 2016 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// [START gae_java21_users_api] + +package com.example.appengine.users; + +import com.google.appengine.api.users.UserService; +import com.google.appengine.api.users.UserServiceFactory; +import java.io.IOException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required. +@WebServlet( + name = "UserAPI", + description = "UserAPI: Login / Logout with UserService", + urlPatterns = "/userapi" +) +public class UsersServlet extends HttpServlet { + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + UserService userService = UserServiceFactory.getUserService(); + + String thisUrl = req.getRequestURI(); + + resp.setContentType("text/html"); + if (req.getUserPrincipal() != null) { + resp.getWriter() + .println( + "

Hello, " + + req.getUserPrincipal().getName() + + "! You can sign out.

"); + } else { + resp.getWriter() + .println( + "

Please sign in.

"); + } + } +} +// [END gae_java21_users_api] diff --git a/appengine-java21/ee8/users/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java21/ee8/users/src/main/webapp/WEB-INF/appengine-web.xml new file mode 100644 index 00000000000..71f00b07474 --- /dev/null +++ b/appengine-java21/ee8/users/src/main/webapp/WEB-INF/appengine-web.xml @@ -0,0 +1,8 @@ + + + java21 + + + + true + diff --git a/appengine-java21/ee8/users/src/main/webapp/WEB-INF/web.xml b/appengine-java21/ee8/users/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..5fece3ce81b --- /dev/null +++ b/appengine-java21/ee8/users/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,26 @@ + + + + + userapi + + true + diff --git a/appengine-java21/ee8/users/src/test/java/com/example/appengine/users/UsersServletTest.java b/appengine-java21/ee8/users/src/test/java/com/example/appengine/users/UsersServletTest.java new file mode 100644 index 00000000000..e7195d3f2f2 --- /dev/null +++ b/appengine-java21/ee8/users/src/test/java/com/example/appengine/users/UsersServletTest.java @@ -0,0 +1,109 @@ +/* + * Copyright 2015 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine.users; + +import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.Mockito.when; + +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.io.PrintWriter; +import java.io.StringWriter; +import javax.management.remote.JMXPrincipal; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link UsersServlet}. + */ +@RunWith(JUnit4.class) +public class UsersServletTest { + + private static final String FAKE_URL = "fakey.fake.fak"; + private static final String FAKE_NAME = "Fake"; + // Set up a helper so that the ApiProxy returns a valid environment for local testing. + private final LocalServiceTestHelper helper = new LocalServiceTestHelper(); + + @Mock + private HttpServletRequest mockRequestNotLoggedIn; + @Mock + private HttpServletRequest mockRequestLoggedIn; + @Mock + private HttpServletResponse mockResponse; + private StringWriter responseWriter; + private UsersServlet servletUnderTest; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + helper.setUp(); + + // Set up some fake HTTP requests + // If the user isn't logged in, use this request + when(mockRequestNotLoggedIn.getRequestURI()).thenReturn(FAKE_URL); + when(mockRequestNotLoggedIn.getUserPrincipal()).thenReturn(null); + + // If the user is logged in, use this request + when(mockRequestLoggedIn.getRequestURI()).thenReturn(FAKE_URL); + // Most of the classes that implement Principal have been + // deprecated. JMXPrincipal seems like a safe choice. + when(mockRequestLoggedIn.getUserPrincipal()).thenReturn(new JMXPrincipal(FAKE_NAME)); + + // Set up a fake HTTP response. + responseWriter = new StringWriter(); + when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter)); + + servletUnderTest = new UsersServlet(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void doGet_userNotLoggedIn_writesResponse() throws Exception { + servletUnderTest.doGet(mockRequestNotLoggedIn, mockResponse); + + // If a user isn't logged in, we expect a prompt + // to login to be returned. + assertWithMessage("UsersServlet response") + .that(responseWriter.toString()) + .contains("

Please .

"); + } + + @Test + public void doGet_userLoggedIn_writesResponse() throws Exception { + servletUnderTest.doGet(mockRequestLoggedIn, mockResponse); + + // If a user is logged in, we expect a prompt + // to logout to be returned. + assertWithMessage("UsersServlet response") + .that(responseWriter.toString()) + .contains("

Hello, " + FAKE_NAME + "!"); + assertWithMessage("UsersServlet response").that(responseWriter.toString()).contains("sign out"); + } +} diff --git a/appengine-java25/ee11/analytics/README.md b/appengine-java25/ee11/analytics/README.md new file mode 100644 index 00000000000..8e4f59e0668 --- /dev/null +++ b/appengine-java25/ee11/analytics/README.md @@ -0,0 +1,24 @@ +# Google Analytics sample for Google App Engine + + +Open in Cloud Shell + +Integrating App Engine with Google Analytics using EE11. + +## Project setup, installation, and configuration + +- Register for [Google Analytics](http://www.google.com/analytics/), create +an application, and get a tracking Id. +- [Find your tracking Id](https://support.google.com/analytics/answer/1008080?hl=en) +and set it as an environment variable in [`appengine-web.xml`](src/main/webapp/WEB-INF/appengine-web.xml). + +## Running locally +This example uses the +[Maven Cloud CLI based plugin](https://cloud.google.com/appengine/docs/java/tools/using-maven). +To run this sample locally: + + $ mvn appengine:run + +## Deploying + + $ mvn clean package appengine:deploy diff --git a/appengine-java25/ee11/analytics/pom.xml b/appengine-java25/ee11/analytics/pom.xml new file mode 100644 index 00000000000..1f2e6f0670a --- /dev/null +++ b/appengine-java25/ee11/analytics/pom.xml @@ -0,0 +1,142 @@ + + + 4.0.0 + war + 1.0-SNAPSHOT + com.example.appengine + appengine-analytics-j25-ee11 + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + + 21 + 21 + 3.0.1 + + + + + + libraries-bom + com.google.cloud + import + pom + 26.28.0 + + + + + + + + com.google.appengine + appengine-api-1.0-sdk + ${appengine.sdk.version} + + + + jstl + jstl + 1.2 + + + + org.apache.httpcomponents + httpclient + 4.5.14 + + + + jakarta.servlet + jakarta.servlet-api + 6.1.0 + jar + provided + + + + + com.google.appengine + appengine-testing + ${appengine.sdk.version} + test + + + com.google.appengine + appengine-api-stubs + ${appengine.sdk.version} + test + + + + junit + junit + 4.13.2 + test + + + org.mockito + mockito-core + 4.11.0 + test + + + com.google.truth + truth + 1.1.5 + test + + + + + + ${project.build.directory}/${project.build.finalName}/WEB-INF/classes + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + + org.apache.maven.plugins + maven-war-plugin + 3.4.0 + + + com.google.cloud.tools + appengine-maven-plugin + 2.7.0 + + GCLOUD_CONFIG + analytics + true + true + + + + + diff --git a/appengine-java25/ee11/analytics/src/main/java/com/example/appengine/analytics/AnalyticsServlet.java b/appengine-java25/ee11/analytics/src/main/java/com/example/appengine/analytics/AnalyticsServlet.java new file mode 100644 index 00000000000..163c56a8877 --- /dev/null +++ b/appengine-java25/ee11/analytics/src/main/java/com/example/appengine/analytics/AnalyticsServlet.java @@ -0,0 +1,71 @@ +/* + * Copyright 2015 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine.analytics; + +// [START gae_java25_ee11_analytics_track] + +import com.google.appengine.api.urlfetch.URLFetchService; +import com.google.appengine.api.urlfetch.URLFetchServiceFactory; +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import org.apache.http.client.utils.URIBuilder; + +@SuppressWarnings("serial") +// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required. +@WebServlet( + name = "analytics", + description = "Analytics: Send Analytics Event to Google Analytics", + urlPatterns = "/analytics") +public class AnalyticsServlet extends HttpServlet { + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws IOException, ServletException { + String trackingId = System.getenv("GA_TRACKING_ID"); + URIBuilder builder = new URIBuilder(); + builder + .setScheme("http") + .setHost("www.google-analytics.com") + .setPath("/collect") + .addParameter("v", "1") // API Version. + .addParameter("tid", trackingId) // Tracking ID / Property ID. + // Anonymous Client Identifier. Ideally, this should be a UUID that + // is associated with particular user, device, or browser instance. + .addParameter("cid", "555") + .addParameter("t", "event") // Event hit type. + .addParameter("ec", "example") // Event category. + .addParameter("ea", "test action"); // Event action. + URI uri = null; + try { + uri = builder.build(); + } catch (URISyntaxException e) { + throw new ServletException("Problem building URI", e); + } + URLFetchService fetcher = URLFetchServiceFactory.getURLFetchService(); + URL url = uri.toURL(); + fetcher.fetch(url); + resp.getWriter().println("Event tracked."); + } +} +// [END gae_java25_ee11_analytics_track] diff --git a/appengine-java25/ee11/analytics/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java25/ee11/analytics/src/main/webapp/WEB-INF/appengine-web.xml new file mode 100644 index 00000000000..e8c0dcf01e1 --- /dev/null +++ b/appengine-java25/ee11/analytics/src/main/webapp/WEB-INF/appengine-web.xml @@ -0,0 +1,26 @@ + + + + + + java25 + true + appengine-analytics-j25-ee11 + + + + + + + diff --git a/appengine-java25/ee11/analytics/src/main/webapp/WEB-INF/web.xml b/appengine-java25/ee11/analytics/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..7b9437866da --- /dev/null +++ b/appengine-java25/ee11/analytics/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,22 @@ + + + + + + + analytics + + diff --git a/appengine-java25/ee8/analytics/README.md b/appengine-java25/ee8/analytics/README.md new file mode 100644 index 00000000000..4cc6d78023c --- /dev/null +++ b/appengine-java25/ee8/analytics/README.md @@ -0,0 +1,24 @@ +# Google Analytics sample for Google App Engine + + +Open in Cloud Shell + +Integrating App Engine with Google Analytics using EE8. + +## Project setup, installation, and configuration + +- Register for [Google Analytics](http://www.google.com/analytics/), create +an application, and get a tracking Id. +- [Find your tracking Id](https://support.google.com/analytics/answer/1008080?hl=en) +and set it as an environment variable in [`appengine-web.xml`](src/main/webapp/WEB-INF/appengine-web.xml). + +## Running locally +This example uses the +[Maven Cloud CLI based plugin](https://cloud.google.com/appengine/docs/java/tools/using-maven). +To run this sample locally: + + $ mvn appengine:run + +## Deploying + + $ mvn clean package appengine:deploy diff --git a/appengine-java25/ee8/analytics/pom.xml b/appengine-java25/ee8/analytics/pom.xml new file mode 100644 index 00000000000..6ef09a265fd --- /dev/null +++ b/appengine-java25/ee8/analytics/pom.xml @@ -0,0 +1,142 @@ + + + 4.0.0 + war + 1.0-SNAPSHOT + com.example.appengine + appengine-analytics-j25-ee8 + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + + 21 + 21 + 3.0.1 + + + + + + libraries-bom + com.google.cloud + import + pom + 26.28.0 + + + + + + + + com.google.appengine + appengine-api-1.0-sdk + ${appengine.sdk.version} + + + + jstl + jstl + 1.2 + + + + org.apache.httpcomponents + httpclient + 4.5.14 + + + + javax.servlet + javax.servlet-api + 4.0.1 + jar + provided + + + + + com.google.appengine + appengine-testing + ${appengine.sdk.version} + test + + + com.google.appengine + appengine-api-stubs + ${appengine.sdk.version} + test + + + + junit + junit + 4.13.2 + test + + + org.mockito + mockito-core + 4.11.0 + test + + + com.google.truth + truth + 1.1.5 + test + + + + + + ${project.build.directory}/${project.build.finalName}/WEB-INF/classes + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + + org.apache.maven.plugins + maven-war-plugin + 3.4.0 + + + com.google.cloud.tools + appengine-maven-plugin + 2.8.3 + + GCLOUD_CONFIG + analytics + true + true + + + + + diff --git a/appengine-java25/ee8/analytics/src/main/java/com/example/appengine/analytics/AnalyticsServlet.java b/appengine-java25/ee8/analytics/src/main/java/com/example/appengine/analytics/AnalyticsServlet.java new file mode 100644 index 00000000000..ee86db46055 --- /dev/null +++ b/appengine-java25/ee8/analytics/src/main/java/com/example/appengine/analytics/AnalyticsServlet.java @@ -0,0 +1,70 @@ +/* + * Copyright 2015 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine.analytics; + +// [START gae_java25_ee8_analytics_track] +import com.google.appengine.api.urlfetch.URLFetchService; +import com.google.appengine.api.urlfetch.URLFetchServiceFactory; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.http.client.utils.URIBuilder; + +@SuppressWarnings("serial") +// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required. +@WebServlet( + name = "analytics", + description = "Analytics: Send Analytics Event to Google Analytics", + urlPatterns = "/analytics") +public class AnalyticsServlet extends HttpServlet { + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws IOException, ServletException { + String trackingId = System.getenv("GA_TRACKING_ID"); + URIBuilder builder = new URIBuilder(); + builder + .setScheme("http") + .setHost("www.google-analytics.com") + .setPath("/collect") + .addParameter("v", "1") // API Version. + .addParameter("tid", trackingId) // Tracking ID / Property ID. + // Anonymous Client Identifier. Ideally, this should be a UUID that + // is associated with particular user, device, or browser instance. + .addParameter("cid", "555") + .addParameter("t", "event") // Event hit type. + .addParameter("ec", "example") // Event category. + .addParameter("ea", "test action"); // Event action. + URI uri = null; + try { + uri = builder.build(); + } catch (URISyntaxException e) { + throw new ServletException("Problem building URI", e); + } + URLFetchService fetcher = URLFetchServiceFactory.getURLFetchService(); + URL url = uri.toURL(); + fetcher.fetch(url); + resp.getWriter().println("Event tracked."); + } +} +// [END gae_java25_ee8_analytics_track] diff --git a/appengine-java25/ee8/analytics/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java25/ee8/analytics/src/main/webapp/WEB-INF/appengine-web.xml new file mode 100644 index 00000000000..3105a34dd14 --- /dev/null +++ b/appengine-java25/ee8/analytics/src/main/webapp/WEB-INF/appengine-web.xml @@ -0,0 +1,26 @@ + + + + + + java25 + true + appengine-analytics-j25-ee8 + + + + + + + diff --git a/appengine-java25/ee8/analytics/src/main/webapp/WEB-INF/web.xml b/appengine-java25/ee8/analytics/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..a15c139472e --- /dev/null +++ b/appengine-java25/ee8/analytics/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,23 @@ + + + + + + + analytics + + diff --git a/appengine-java25/helloworld/README.md b/appengine-java25/helloworld/README.md new file mode 100644 index 00000000000..b90c452ee8e --- /dev/null +++ b/appengine-java25/helloworld/README.md @@ -0,0 +1,81 @@ +HelloWorld for App Engine Standard (Java 25) +============================ + +This sample demonstrates how to deploy an application on Google App Engine. + +See the [Google App Engine standard environment documentation][ae-docs] for more +detailed instructions. + +[ae-docs]: https://cloud.google.com/appengine/docs/java/ + + +* [Java 25](https://www.oracle.com/java/technologies/downloads/) +* [Maven](https://maven.apache.org/download.cgi) (at least 3.5) +* [Google Cloud SDK](https://cloud.google.com/sdk/) (aka gcloud) + +## Setup + +• Download and initialize the [Cloud SDK](https://cloud.google.com/sdk/) + +``` +gcloud init +``` + +* Create an App Engine app within the current Google Cloud Project + +``` +gcloud app create +``` + +* In the `pom.xml`, update the [App Engine Maven Plugin](https://cloud.google.com/appengine/docs/standard/java/tools/maven-reference) +with your Google Cloud Project Id: + +``` + + com.google.cloud.tools + appengine-maven-plugin + 2.8.3 + + myProjectId + GCLOUD_CONFIG + + +``` +**Note:** `GCLOUD_CONFIG` is a special version for autogenerating an App Engine +version. Change this field to specify a specific version name. + +## Maven +### Running locally + + mvn package appengine:run + +To use visit: http://localhost:8080/ + +### Deploying + + mvn package appengine:deploy + +To use visit: https://YOUR-PROJECT-ID.appspot.com + +### Testing + + mvn verify + +As 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) +to (`src/main/test/...`). The following resources are quite useful: + +* [Junit4](http://junit.org/junit4/) +* [Mockito](http://mockito.org/) +* [Truth](http://google.github.io/truth/) + +## Gradle + +### Running locally + + ./gradlew appengineRun + +To use visit: http://localhost:8080/ + +### Deploying + + ./gradlew appengineDeploy diff --git a/appengine-java25/helloworld/build.gradle b/appengine-java25/helloworld/build.gradle new file mode 100644 index 00000000000..f9e6ed67ead --- /dev/null +++ b/appengine-java25/helloworld/build.gradle @@ -0,0 +1,68 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// [START gae_standard25_gradle] +apply plugin: 'java' +apply plugin: 'war' + +buildscript { + repositories { + // gretty plugin is in Maven Central + mavenCentral() + } + dependencies { + classpath 'com.google.cloud.tools:appengine-gradle-plugin:2.8.1' + classpath 'org.gretty:gretty:4.1.5' + } +} +apply plugin: 'org.gretty' +apply plugin: 'com.google.cloud.tools.appengine' + +repositories { + mavenCentral() +} + +appengine { + deploy { // deploy configuration + stopPreviousVersion = true // default - stop the current version + promote = true // default - & make this the current version + projectId = 'GCLOUD_CONFIG' + version = 'GCLOUD_CONFIG' + } +} + +sourceSets { + // In Gradle 8, the default location is app/src/java, which does not match + // Maven's directory structure. + main.java.srcDirs = ['src/main/java'] + main.resources.srcDirs = ['src/main/resources', 'src/main/webapp'] + test.java.srcDirs = ['src/test/java'] +} + +dependencies { + implementation 'com.google.appengine:appengine-api-1.0-sdk:3.0.1' + implementation 'jakarta.servlet:jakarta.servlet-api:6.1.0' + + // Test Dependencies + testImplementation 'com.google.appengine:appengine-testing:3.0.1' + testImplementation 'com.google.appengine:appengine-api-stubs:3.0.1' + testImplementation 'com.google.appengine:appengine-tools-sdk:3.0.1' + + testImplementation 'com.google.truth:truth:1.1.5' + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.mockito:mockito-core:4.11.0' +} +// [END gae_standard25_gradle] diff --git a/appengine-java25/helloworld/gradle/libs.versions.toml b/appengine-java25/helloworld/gradle/libs.versions.toml new file mode 100644 index 00000000000..e74f3857bde --- /dev/null +++ b/appengine-java25/helloworld/gradle/libs.versions.toml @@ -0,0 +1,10 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format + +[versions] +guava = "33.2.1-jre" +junit = "4.13.2" + +[libraries] +guava = { module = "com.google.guava:guava", version.ref = "guava" } +junit = { module = "junit:junit", version.ref = "junit" } diff --git a/appengine-java25/helloworld/gradle/wrapper/gradle-wrapper.properties b/appengine-java25/helloworld/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..48b43d35063 --- /dev/null +++ b/appengine-java25/helloworld/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-all.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/appengine-java25/helloworld/gradlew b/appengine-java25/helloworld/gradlew new file mode 100755 index 00000000000..f5feea6d6b1 --- /dev/null +++ b/appengine-java25/helloworld/gradlew @@ -0,0 +1,252 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/appengine-java25/helloworld/gradlew.bat b/appengine-java25/helloworld/gradlew.bat new file mode 100644 index 00000000000..9d21a21834d --- /dev/null +++ b/appengine-java25/helloworld/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/appengine-java25/helloworld/pom.xml b/appengine-java25/helloworld/pom.xml new file mode 100644 index 00000000000..c3b776def65 --- /dev/null +++ b/appengine-java25/helloworld/pom.xml @@ -0,0 +1,150 @@ + + + + + 4.0.0 + + war + com.example.appengine + helloworld-jdk25 + 1.0-SNAPSHOT + + + com.google.cloud.samples + shared-configuration + 1.2.2 + + + + + 21 + 21 + + + + + + + com.google.appengine + appengine-api-1.0-sdk + 3.0.1 + + + jakarta.servlet + jakarta.servlet-api + 6.1.0 + jar + provided + + + + + com.google.appengine + appengine-testing + 2.0.23 + test + + + com.google.appengine + appengine-api-stubs + 2.0.23 + test + + + com.google.appengine + appengine-tools-sdk + 2.0.23 + test + + + + com.google.truth + truth + 1.1.5 + test + + + junit + junit + 4.13.2 + test + + + org.mockito + mockito-core + 4.11.0 + test + + + + + + ${project.build.directory}/${project.build.finalName}/WEB-INF/classes + + + org.apache.maven.plugins + maven-failsafe-plugin + 3.3.1 + + + org.apache.maven.plugins + maven-surefire-plugin + 3.3.1 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + + org.jacoco + jacoco-maven-plugin + 0.8.14 + + + + prepare-agent + + + + report + prepare-package + + report + + + + + + org.apache.maven.plugins + maven-war-plugin + 3.4.0 + + + com.google.cloud.tools + appengine-maven-plugin + 2.8.3 + + + myProjectId + + GCLOUD_CONFIG + + + + + diff --git a/appengine-java25/helloworld/settings.gradle b/appengine-java25/helloworld/settings.gradle new file mode 100644 index 00000000000..9abb5e9318c --- /dev/null +++ b/appengine-java25/helloworld/settings.gradle @@ -0,0 +1,32 @@ +pluginManagement { + repositories { + gradlePluginPortal() + } +} +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.10/userguide/multi_project_builds.html in the Gradle documentation. + */ + +plugins { + // Apply the foojay-resolver plugin to allow automatic download of JDKs + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' +} + +rootProject.name = 'helloworld' \ No newline at end of file diff --git a/appengine-java25/helloworld/src/main/java/com/example/appengine/java25/HelloAppEngine.java b/appengine-java25/helloworld/src/main/java/com/example/appengine/java25/HelloAppEngine.java new file mode 100644 index 00000000000..1f7c1ba4656 --- /dev/null +++ b/appengine-java25/helloworld/src/main/java/com/example/appengine/java25/HelloAppEngine.java @@ -0,0 +1,41 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine.java25; + +import com.google.appengine.api.utils.SystemProperty; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Properties; + +// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required. +@WebServlet(name = "HelloAppEngine", value = "/hello") +public class HelloAppEngine extends HttpServlet { + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException { + Properties properties = System.getProperties(); + + response.setContentType("text/plain"); + response.getWriter().println("Hello App Engine - Standard using " + + SystemProperty.version.get() + " Java " + + properties.get("java.specification.version")); + } +} diff --git a/appengine-java25/helloworld/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java25/helloworld/src/main/webapp/WEB-INF/appengine-web.xml new file mode 100644 index 00000000000..5fb330b5354 --- /dev/null +++ b/appengine-java25/helloworld/src/main/webapp/WEB-INF/appengine-web.xml @@ -0,0 +1,5 @@ + + + java25 + true + \ No newline at end of file diff --git a/appengine-java25/helloworld/src/main/webapp/WEB-INF/web.xml b/appengine-java25/helloworld/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..702668c5ca3 --- /dev/null +++ b/appengine-java25/helloworld/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,13 @@ + + + helloworld + com.example.appengine.java25.HelloAppEngine + + + helloworld + /* + + \ No newline at end of file diff --git a/appengine-java25/helloworld/src/test/java/com/example/appengine/java25/HelloAppEngineTest.java b/appengine-java25/helloworld/src/test/java/com/example/appengine/java25/HelloAppEngineTest.java new file mode 100644 index 00000000000..70b351d99c0 --- /dev/null +++ b/appengine-java25/helloworld/src/test/java/com/example/appengine/java25/HelloAppEngineTest.java @@ -0,0 +1,80 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine.java25; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.PrintWriter; +import java.io.StringWriter; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link HelloAppEngine}. + */ +@RunWith(JUnit4.class) +public class HelloAppEngineTest { + + private static final String FAKE_URL = "fake.fk/hello"; + // Set up a helper so that the ApiProxy returns a valid environment for local testing. + private final LocalServiceTestHelper helper = new LocalServiceTestHelper(); + + @Mock + private HttpServletRequest mockRequest; + @Mock + private HttpServletResponse mockResponse; + private StringWriter responseWriter; + private HelloAppEngine servletUnderTest; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + helper.setUp(); + + // Set up some fake HTTP requests + when(mockRequest.getRequestURI()).thenReturn(FAKE_URL); + + // Set up a fake HTTP response. + responseWriter = new StringWriter(); + when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter)); + + servletUnderTest = new HelloAppEngine(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void doGetWritesResponse() throws Exception { + servletUnderTest.doGet(mockRequest, mockResponse); + + // We expect our hello world response. + assertThat(responseWriter.toString()) + .contains("Hello App Engine - Standard "); + } +} diff --git a/appengine-java8/analytics/src/main/java/com/example/appengine/analytics/AnalyticsServlet.java b/appengine-java8/analytics/src/main/java/com/example/appengine/analytics/AnalyticsServlet.java index 921099334ab..c6941f97d88 100644 --- a/appengine-java8/analytics/src/main/java/com/example/appengine/analytics/AnalyticsServlet.java +++ b/appengine-java8/analytics/src/main/java/com/example/appengine/analytics/AnalyticsServlet.java @@ -35,8 +35,7 @@ @WebServlet( name = "analytics", description = "Analytics: Send Analytics Event to Google Analytics", - urlPatterns = "/analytics" -) + urlPatterns = "/analytics") public class AnalyticsServlet extends HttpServlet { @Override diff --git a/appengine-java8/appidentity/src/main/java/com/example/appengine/appidentity/UrlShortener.java b/appengine-java8/appidentity/src/main/java/com/example/appengine/appidentity/UrlShortener.java index ce0a31accb5..e60b96a6422 100644 --- a/appengine-java8/appidentity/src/main/java/com/example/appengine/appidentity/UrlShortener.java +++ b/appengine-java8/appidentity/src/main/java/com/example/appengine/appidentity/UrlShortener.java @@ -31,7 +31,6 @@ @SuppressWarnings("serial") class UrlShortener { - // [START gae_java8_app_identity_google_apis] /** * Returns a shortened URL by calling the Google URL Shortener API. @@ -74,5 +73,4 @@ public String createShortUrl(String longUrl) throws Exception { } } } - // [END gae_java8_app_identity_google_apis] } diff --git a/appengine-java8/datastore/src/test/java/com/example/appengine/EntitiesTest.java b/appengine-java8/datastore/src/test/java/com/example/appengine/EntitiesTest.java index c510ac2620a..bc210689cbb 100644 --- a/appengine-java8/datastore/src/test/java/com/example/appengine/EntitiesTest.java +++ b/appengine-java8/datastore/src/test/java/com/example/appengine/EntitiesTest.java @@ -293,7 +293,6 @@ public void embeddedEntity_fromExisting_canRecover() throws Exception { @Test public void batchOperations_putsEntities() { - // [START gae_batch_operations] Entity employee1 = new Entity("Employee"); Entity employee2 = new Entity("Employee"); Entity employee3 = new Entity("Employee"); @@ -305,7 +304,6 @@ public void batchOperations_putsEntities() { List employees = Arrays.asList(employee1, employee2, employee3); datastore.put(employees); - // [END gae_batch_operations] Map got = datastore.get(Arrays.asList(employee1.getKey(), employee2.getKey(), employee3.getKey())); diff --git a/appengine-java8/datastore/src/test/java/com/example/appengine/QueriesTest.java b/appengine-java8/datastore/src/test/java/com/example/appengine/QueriesTest.java index 6c36df45fa8..63354323678 100644 --- a/appengine-java8/datastore/src/test/java/com/example/appengine/QueriesTest.java +++ b/appengine-java8/datastore/src/test/java/com/example/appengine/QueriesTest.java @@ -86,11 +86,9 @@ public void propertyFilterExample_returnsMatchingEntities() throws Exception { // Act long minHeight = 160; - // [START gae_java8_datastore_property_filter] Filter propertyFilter = new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight); Query q = new Query("Person").setFilter(propertyFilter); - // [END gae_java8_datastore_property_filter] // Assert List results = @@ -185,7 +183,6 @@ public void ancestorFilterExample_returnsMatchingEntities() throws Exception { @Test public void ancestorQueryExample_returnsMatchingEntities() throws Exception { - // [START gae_java8_datastore_ancestor_query] DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); Entity tom = new Entity("Person", "Tom"); @@ -213,7 +210,6 @@ public void ancestorQueryExample_returnsMatchingEntities() throws Exception { // but not campingPhoto, because tom is not an ancestor List results = datastore.prepare(photoQuery).asList(FetchOptions.Builder.withDefaults()); - // [END gae_java8_datastore_ancestor_query] assertWithMessage("query results") .that(results) @@ -477,7 +473,6 @@ public void queryRestrictions_compositeFilter_returnsMatchedEntities() throws Ex // Act long minBirthYear = 1940; long maxBirthYear = 1980; - // [START gae_java8_datastore_inequality_filters_one_property_valid_1] Filter birthYearMinFilter = new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear); @@ -488,7 +483,6 @@ public void queryRestrictions_compositeFilter_returnsMatchedEntities() throws Ex CompositeFilterOperator.and(birthYearMinFilter, birthYearMaxFilter); Query q = new Query("Person").setFilter(birthYearRangeFilter); - // [END gae_java8_datastore_inequality_filters_one_property_valid_1] // Assert List results = @@ -500,7 +494,6 @@ public void queryRestrictions_compositeFilter_returnsMatchedEntities() throws Ex public void queryRestrictions_compositeFilter_isInvalid() throws Exception { long minBirthYear = 1940; long maxHeight = 200; - // [START gae_java8_datastore_inequality_filters_one_property_invalid] Filter birthYearMinFilter = new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear); @@ -510,7 +503,6 @@ public void queryRestrictions_compositeFilter_isInvalid() throws Exception { Filter invalidFilter = CompositeFilterOperator.and(birthYearMinFilter, heightMaxFilter); Query q = new Query("Person").setFilter(invalidFilter); - // [END gae_java8_datastore_inequality_filters_one_property_invalid] // Note: The local devserver behavior is different than the production // version of Cloud Datastore, so there aren't any assertions we can make @@ -547,7 +539,6 @@ public void queryRestrictions_compositeEqualFilter_returnsMatchedEntities() thro String targetCity = "Somewhere"; String targetLastName = "Someone"; - // [START gae_java8_datastore_inequality_filters_one_property_valid_2] Filter lastNameFilter = new FilterPredicate("lastName", FilterOperator.EQUAL, targetLastName); Filter cityFilter = new FilterPredicate("city", FilterOperator.EQUAL, targetCity); @@ -563,7 +554,6 @@ public void queryRestrictions_compositeEqualFilter_returnsMatchedEntities() thro lastNameFilter, cityFilter, birthYearMinFilter, birthYearMaxFilter); Query q = new Query("Person").setFilter(validFilter); - // [END gae_java8_datastore_inequality_filters_one_property_valid_2] // Assert List results = @@ -607,7 +597,6 @@ public void queryRestrictions_inequalitySortedFirst_returnsMatchedEntities() thr @Test public void queryRestrictions_missingSortOnInequality_isInvalid() throws Exception { long minBirthYear = 1940; - // [START gae_java8_datastore_inequality_filters_sort_orders_invalid_1] Filter birthYearMinFilter = new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear); @@ -616,7 +605,6 @@ public void queryRestrictions_missingSortOnInequality_isInvalid() throws Excepti new Query("Person") .setFilter(birthYearMinFilter) .addSort("lastName", SortDirection.ASCENDING); - // [END gae_java8_datastore_inequality_filters_sort_orders_invalid_1] // Note: The local devserver behavior is different than the production // version of Cloud Datastore, so there aren't any assertions we can make @@ -627,7 +615,6 @@ public void queryRestrictions_missingSortOnInequality_isInvalid() throws Excepti @Test public void queryRestrictions_sortWrongOrderOnInequality_isInvalid() throws Exception { long minBirthYear = 1940; - // [START gae_java8_datastore_inequality_filters_sort_orders_invalid_2] Filter birthYearMinFilter = new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear); @@ -637,7 +624,6 @@ public void queryRestrictions_sortWrongOrderOnInequality_isInvalid() throws Exce .setFilter(birthYearMinFilter) .addSort("lastName", SortDirection.ASCENDING) .addSort("birthYear", SortDirection.ASCENDING); - // [END gae_java8_datastore_inequality_filters_sort_orders_invalid_2] // Note: The local devserver behavior is different than the production // version of Cloud Datastore, so there aren't any assertions we can make diff --git a/appengine-java8/endpoints-v2-backend/src/main/java/com/example/echo/Echo.java b/appengine-java8/endpoints-v2-backend/src/main/java/com/example/echo/Echo.java index 221c91facac..34d38230845 100644 --- a/appengine-java8/endpoints-v2-backend/src/main/java/com/example/echo/Echo.java +++ b/appengine-java8/endpoints-v2-backend/src/main/java/com/example/echo/Echo.java @@ -84,12 +84,10 @@ public Message echo(Message message, @Named("n") @Nullable Integer n) { *

Note that httpMethod is not specified. This will default to a reasonable HTTP method * depending on the API method name. In this case, the HTTP method will default to POST. */ - // [START echo_path] @ApiMethod(name = "echo_path_parameter", path = "echo/{n}") public Message echoPathParameter(Message message, @Named("n") int n) { return doEcho(message, n); } - // [END echo_path] /** * Echoes the received message back. If n is a non-negative integer, the message is copied that diff --git a/appengine-java8/endpoints-v2-migration/src/main/java/com/example/helloendpoints/Greetings.java b/appengine-java8/endpoints-v2-migration/src/main/java/com/example/helloendpoints/Greetings.java index f5becaf3bd9..5df237a35f9 100644 --- a/appengine-java8/endpoints-v2-migration/src/main/java/com/example/helloendpoints/Greetings.java +++ b/appengine-java8/endpoints-v2-migration/src/main/java/com/example/helloendpoints/Greetings.java @@ -27,8 +27,6 @@ // [END begin] -// [START api_def] - /** Defines v1 of a helloworld API, which provides simple "greeting" methods. */ @Api( name = "helloworld", @@ -45,8 +43,7 @@ public class Greetings { greetings.add(new HelloGreeting("hello world!")); greetings.add(new HelloGreeting("goodbye world!")); } - // [END api_def] - + // [START getgreetings] public HelloGreeting getGreeting(@Named("id") Integer id) throws NotFoundException { diff --git a/appengine-java8/endpoints-v2-skeleton/build.gradle b/appengine-java8/endpoints-v2-skeleton/build.gradle index 533b5574b07..cf18e3384b4 100644 --- a/appengine-java8/endpoints-v2-skeleton/build.gradle +++ b/appengine-java8/endpoints-v2-skeleton/build.gradle @@ -36,7 +36,7 @@ apply plugin: 'com.google.cloud.tools.endpoints-framework-server' apply plugin: 'com.google.cloud.tools.appengine' // [END plugin_applys] -// [START dependencies] +// [START appengine_endpoints_gradle_dependencies] dependencies { compile 'com.google.endpoints:endpoints-framework:2.2.2' compile 'com.google.appengine:appengine-api-1.0-sdk:2.0.23' @@ -44,7 +44,7 @@ dependencies { compile 'javax.inject:javax.inject:1' compileOnly 'javax.servlet:javax.servlet-api:3.1.0' } -// [END dependencies] +// [END appengine_endpoints_gradle_dependencies] // [START endpoints_server_configuration] // You must replace YOUR_PROJECT_ID with your Google Cloud Project Id diff --git a/appengine-java8/firebase-tictactoe/src/main/webapp/static/main.js b/appengine-java8/firebase-tictactoe/src/main/webapp/static/main.js index 6ae24b0247d..20d43818160 100644 --- a/appengine-java8/firebase-tictactoe/src/main/webapp/static/main.js +++ b/appengine-java8/firebase-tictactoe/src/main/webapp/static/main.js @@ -140,14 +140,12 @@ function initGame(gameKey, me, token, channelId, initialMessage) { }); // [END auth_login] - // [START add_listener] // setup a database reference at path /channels/channelId channel = firebase.database().ref('channels/' + channelId); // add a listener to the path that fires any time the value of the data changes channel.on('value', function(data) { onMessage(data.val()); }); - // [END add_listener] onOpened(); // let the server know that the channel is open } diff --git a/appengine-java8/gaeinfo/pom.xml b/appengine-java8/gaeinfo/pom.xml index dce3f035380..e7762ab5adb 100644 --- a/appengine-java8/gaeinfo/pom.xml +++ b/appengine-java8/gaeinfo/pom.xml @@ -33,12 +33,11 @@ Copyright 2017 Google Inc. 1.2.0 - - + + 1.8 1.8 - diff --git a/appengine-java8/images/README.md b/appengine-java8/images/README.md index 63189a64927..0ae0c3ff45e 100644 --- a/appengine-java8/images/README.md +++ b/appengine-java8/images/README.md @@ -14,7 +14,7 @@ detailed instructions. Using the [Google Cloud SDK](https://cloud.google.com/sdk/) create a bucket - $ gsutil mb YOUR-PROJECT-ID.appspot.com + $ gcloud storage buckets create gs://YOUR-PROJECT-ID.appspot.com * Edit `src/main/java/com/example/appengine/images/ImageServlet.java` and set your `bucket` name. diff --git a/appengine-java8/images/src/main/webapp/WEB-INF/web.xml b/appengine-java8/images/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..e31839a606f --- /dev/null +++ b/appengine-java8/images/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,27 @@ + + + + + gaeinfo + + diff --git a/appengine-java8/mailgun/pom.xml b/appengine-java8/mailgun/pom.xml index dcd43daf301..cd9d25dc969 100644 --- a/appengine-java8/mailgun/pom.xml +++ b/appengine-java8/mailgun/pom.xml @@ -44,7 +44,6 @@ jar provided - com.sun.jersey jersey-core @@ -60,7 +59,6 @@ jersey-multipart 1.19.4 - diff --git a/appengine-java8/mailgun/src/main/webapp/index.html b/appengine-java8/mailgun/src/main/webapp/index.html index 9ec44eaad7c..157eaecd020 100644 --- a/appengine-java8/mailgun/src/main/webapp/index.html +++ b/appengine-java8/mailgun/src/main/webapp/index.html @@ -16,12 +16,10 @@ Mailgun on Google App Engine Managed VMs -

- diff --git a/appengine-java8/mailjet/pom.xml b/appengine-java8/mailjet/pom.xml index 581b4e03b52..a92bf0aa690 100644 --- a/appengine-java8/mailjet/pom.xml +++ b/appengine-java8/mailjet/pom.xml @@ -50,7 +50,6 @@ provided - com.sun.jersey jersey-core @@ -66,7 +65,6 @@ jersey-multipart 1.19.4 - diff --git a/appengine-java8/mailjet/src/main/java/com/example/appengine/mailjet/MailjetServlet.java b/appengine-java8/mailjet/src/main/java/com/example/appengine/mailjet/MailjetServlet.java index 9dcc3b6c1f3..b98526a3aa0 100644 --- a/appengine-java8/mailjet/src/main/java/com/example/appengine/mailjet/MailjetServlet.java +++ b/appengine-java8/mailjet/src/main/java/com/example/appengine/mailjet/MailjetServlet.java @@ -14,8 +14,6 @@ * limitations under the License. */ -// [START mailjet_imports] - package com.example.appengine.mailjet; import com.mailjet.client.ClientOptions; @@ -32,9 +30,6 @@ import org.json.JSONArray; import org.json.JSONObject; -// [END mailjet_imports] - -// [START app] @SuppressWarnings("serial") public class MailjetServlet extends HttpServlet { @@ -76,4 +71,3 @@ public void doPost(HttpServletRequest req, HttpServletResponse resp) } } } -// [END app] diff --git a/appengine-java8/mailjet/src/main/webapp/index.html b/appengine-java8/mailjet/src/main/webapp/index.html index 10f5dcc59dd..7745012ff70 100644 --- a/appengine-java8/mailjet/src/main/webapp/index.html +++ b/appengine-java8/mailjet/src/main/webapp/index.html @@ -16,12 +16,10 @@ Mailgun on Google App Engine Managed VMs -
- diff --git a/appengine-java8/memcache/pom.xml b/appengine-java8/memcache/pom.xml index 82990796371..3a6c5087d3c 100644 --- a/appengine-java8/memcache/pom.xml +++ b/appengine-java8/memcache/pom.xml @@ -45,7 +45,6 @@ Copyright 2015 Google Inc. provided - com.google.appengine appengine-api-1.0-sdk @@ -56,7 +55,6 @@ Copyright 2015 Google Inc. xmemcached 2.4.8 - diff --git a/appengine-java8/metadata/pom.xml b/appengine-java8/metadata/pom.xml index e9a4296fe4f..024acc78d7d 100644 --- a/appengine-java8/metadata/pom.xml +++ b/appengine-java8/metadata/pom.xml @@ -33,12 +33,10 @@ Copyright 2017 Google Inc. 1.2.0 - 1.8 1.8 - diff --git a/appengine-java8/pubsub/pom.xml b/appengine-java8/pubsub/pom.xml index 5a0c470e12f..5abb8988256 100644 --- a/appengine-java8/pubsub/pom.xml +++ b/appengine-java8/pubsub/pom.xml @@ -38,7 +38,6 @@ false - @@ -62,7 +61,6 @@ com.google.cloud google-cloud-datastore - javax.servlet javax.servlet-api @@ -75,9 +73,7 @@ jatl 0.2.3 - - ${project.build.directory}/${project.build.finalName}/WEB-INF/classes diff --git a/appengine-java8/remote-client/pom.xml b/appengine-java8/remote-client/pom.xml index 512ff0b3cff..b5dc95e2300 100644 --- a/appengine-java8/remote-client/pom.xml +++ b/appengine-java8/remote-client/pom.xml @@ -36,7 +36,6 @@ - com.google.appengine appengine-remote-api @@ -47,7 +46,6 @@ appengine-api-1.0-sdk 2.0.23 - diff --git a/appengine-java8/search/src/main/java/com/example/appengine/search/DeleteServlet.java b/appengine-java8/search/src/main/java/com/example/appengine/search/DeleteServlet.java index 6166f7fd93f..1244d9c8efd 100644 --- a/appengine-java8/search/src/main/java/com/example/appengine/search/DeleteServlet.java +++ b/appengine-java8/search/src/main/java/com/example/appengine/search/DeleteServlet.java @@ -17,7 +17,6 @@ package com.example.appengine.search; // @formatter:off -// [START delete_import] import com.google.appengine.api.search.Document; import com.google.appengine.api.search.Field; import com.google.appengine.api.search.GetRequest; @@ -36,7 +35,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -// [END delete_import] // CHECKSTYLE:OFF // @formatter:on // CHECKSTYLE:ON diff --git a/appengine-java8/search/src/main/java/com/example/appengine/search/DocumentServlet.java b/appengine-java8/search/src/main/java/com/example/appengine/search/DocumentServlet.java index c5e63b15f89..bc5dea48792 100644 --- a/appengine-java8/search/src/main/java/com/example/appengine/search/DocumentServlet.java +++ b/appengine-java8/search/src/main/java/com/example/appengine/search/DocumentServlet.java @@ -16,8 +16,6 @@ package com.example.appengine.search; -// [START document_import] - import com.google.appengine.api.search.Document; import com.google.appengine.api.search.Field; import com.google.appengine.api.users.User; @@ -30,8 +28,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -// [END document_import] - /** * A servlet for creating Search API Document. */ diff --git a/appengine-java8/translate-pubsub/pom.xml b/appengine-java8/translate-pubsub/pom.xml index 9a121dc68ad..c1b4804b434 100644 --- a/appengine-java8/translate-pubsub/pom.xml +++ b/appengine-java8/translate-pubsub/pom.xml @@ -37,7 +37,6 @@ 1.8 - @@ -65,7 +64,6 @@ com.google.cloud google-cloud-translate - javax.servlet javax.servlet-api @@ -73,9 +71,7 @@ jar provided - - ${project.build.directory}/${project.build.finalName}/WEB-INF/classes diff --git a/auth/.gitignore b/auth/.gitignore new file mode 100644 index 00000000000..305842cb8a4 --- /dev/null +++ b/auth/.gitignore @@ -0,0 +1,3 @@ +# Ignore GCP and IdP secret files +src/main/java/com/google/cloud/auth/samples/customcredentials/aws/custom-credentials-aws-secrets.json +src/main/java/com/google/cloud/auth/samples/customcredentials/okta/custom-credentials-okta-secrets.json diff --git a/auth/pom.xml b/auth/pom.xml index cd51f47198d..c943b0a1ca4 100644 --- a/auth/pom.xml +++ b/auth/pom.xml @@ -30,7 +30,7 @@ limitations under the License. com.google.cloud.samples shared-configuration - 1.2.0 + 1.2.2 @@ -52,6 +52,13 @@ limitations under the License. pom import + + software.amazon.awssdk + bom + 2.25.41 + pom + import + @@ -82,6 +89,14 @@ limitations under the License. com.google.cloud google-cloud-language + + software.amazon.awssdk + auth + + + software.amazon.awssdk + regions + junit diff --git a/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/CustomCredentialSupplierAwsWorkload.java b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/CustomCredentialSupplierAwsWorkload.java new file mode 100644 index 00000000000..82f4836da58 --- /dev/null +++ b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/CustomCredentialSupplierAwsWorkload.java @@ -0,0 +1,237 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.auth.samples.customcredentials.aws; + +// [START auth_custom_credential_supplier_aws] +import com.google.auth.oauth2.AwsCredentials; +import com.google.auth.oauth2.AwsSecurityCredentials; +import com.google.auth.oauth2.AwsSecurityCredentialsSupplier; +import com.google.auth.oauth2.ExternalAccountSupplierContext; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.cloud.storage.Bucket; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import java.io.IOException; +import java.io.Reader; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Map; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; +import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain; + +// [END auth_custom_credential_supplier_aws] + +/** + * This sample demonstrates how to use a custom AWS security credentials supplier to authenticate to + * Google Cloud Storage using AWS Workload Identity Federation. + */ +public class CustomCredentialSupplierAwsWorkload { + + public static void main(String[] args) throws IOException { + + // Reads the custom-credentials-aws-secrets.json if running locally. + loadConfigFromFile(); + + // The audience for the workload identity federation. + // Format: //iam.googleapis.com/projects//locations/global/ + // workloadIdentityPools//providers/ + String gcpWorkloadAudience = getConfiguration("GCP_WORKLOAD_AUDIENCE"); + + // The bucket to fetch data from. + String gcsBucketName = getConfiguration("GCS_BUCKET_NAME"); + + // (Optional) The service account impersonation URL. + String saImpersonationUrl = getConfiguration("GCP_SERVICE_ACCOUNT_IMPERSONATION_URL"); + + if (gcpWorkloadAudience == null || gcsBucketName == null) { + System.err.println( + "Required configuration missing. Please provide it in a " + + "custom-credentials-aws-secrets.json file or as environment variables: " + + "GCP_WORKLOAD_AUDIENCE, GCS_BUCKET_NAME"); + return; + } + + try { + System.out.println("Retrieving metadata for bucket: " + gcsBucketName + "..."); + Bucket bucket = + authenticateWithAwsCredentials(gcpWorkloadAudience, saImpersonationUrl, gcsBucketName); + + System.out.println(" --- SUCCESS! ---"); + System.out.println("Bucket details:"); + System.out.printf(" Name: %s%n", bucket.getName()); + System.out.printf(" Location: %s%n", bucket.getLocation()); + System.out.printf(" Storage Class: %s%n", bucket.getStorageClass()); + System.out.printf(" Metageneration: %s%n", bucket.getMetageneration()); + } catch (Exception e) { + System.err.println("Authentication or Request failed: " + e.getMessage()); + } + } + + /** + * Helper method to retrieve configuration. It checks Environment variables first, then System + * properties (populated by loadConfigFromFile). + */ + static String getConfiguration(String key) { + String value = System.getenv(key); + if (value == null) { + value = System.getProperty(key); + } + return value; + } + + /** + * If a local secrets file is present, load it into the System Properties. This is a + * "just-in-time" configuration for local development. These variables are only set for the + * current process. + */ + static void loadConfigFromFile() { + // By default, this expects the file to be in the project root. + String secretsFilePath = "custom-credentials-aws-secrets.json"; + if (!Files.exists(Paths.get(secretsFilePath))) { + return; + } + + try (Reader reader = Files.newBufferedReader(Paths.get(secretsFilePath))) { + // Use Gson to parse the JSON file into a Map + Gson gson = new Gson(); + Type type = new TypeToken>() {}.getType(); + Map secrets = gson.fromJson(reader, type); + + if (secrets == null) { + return; + } + + // AWS SDK for Java looks for System Properties with specific names (camelCase) + // if environment variables are missing. + if (secrets.containsKey("aws_access_key_id")) { + System.setProperty("aws.accessKeyId", secrets.get("aws_access_key_id")); + } + if (secrets.containsKey("aws_secret_access_key")) { + System.setProperty("aws.secretAccessKey", secrets.get("aws_secret_access_key")); + } + if (secrets.containsKey("aws_region")) { + System.setProperty("aws.region", secrets.get("aws_region")); + } + + // Set custom GCP variables as System Properties so getConfiguration() can find them. + if (secrets.containsKey("gcp_workload_audience")) { + System.setProperty("GCP_WORKLOAD_AUDIENCE", secrets.get("gcp_workload_audience")); + } + if (secrets.containsKey("gcs_bucket_name")) { + System.setProperty("GCS_BUCKET_NAME", secrets.get("gcs_bucket_name")); + } + if (secrets.containsKey("gcp_service_account_impersonation_url")) { + System.setProperty( + "GCP_SERVICE_ACCOUNT_IMPERSONATION_URL", + secrets.get("gcp_service_account_impersonation_url")); + } + + } catch (IOException e) { + System.err.println("Error reading secrets file: " + e.getMessage()); + } + } + + /** + * Authenticates using a custom AWS credential supplier and retrieves bucket metadata. + * + * @param gcpWorkloadAudience The WIF provider audience. + * @param saImpersonationUrl Optional service account impersonation URL. + * @param gcsBucketName The GCS bucket name. + * @return The Bucket object containing metadata. + * @throws IOException If authentication fails. + */ + // [START auth_custom_credential_supplier_aws] + public static Bucket authenticateWithAwsCredentials( + String gcpWorkloadAudience, String saImpersonationUrl, String gcsBucketName) + throws IOException { + + CustomAwsSupplier customSupplier = new CustomAwsSupplier(); + + AwsCredentials.Builder credentialsBuilder = + AwsCredentials.newBuilder() + .setAudience(gcpWorkloadAudience) + // This token type indicates that the subject token is an AWS Signature Version 4 signed + // request. This is required for AWS Workload Identity Federation. + .setSubjectTokenType("urn:ietf:params:aws:token-type:aws4_request") + .setAwsSecurityCredentialsSupplier(customSupplier); + + if (saImpersonationUrl != null) { + credentialsBuilder.setServiceAccountImpersonationUrl(saImpersonationUrl); + } + + GoogleCredentials credentials = credentialsBuilder.build(); + + Storage storage = StorageOptions.newBuilder().setCredentials(credentials).build().getService(); + + return storage.get(gcsBucketName); + } + + /** + * Custom AWS Security Credentials Supplier. + * + *

This implementation resolves AWS credentials and regions using the default provider chains + * from the AWS SDK (v2). This supports environment variables, ~/.aws/credentials, and EC2/EKS + * metadata. + */ + private static class CustomAwsSupplier implements AwsSecurityCredentialsSupplier { + private final AwsCredentialsProvider awsCredentialsProvider; + private String region; + + public CustomAwsSupplier() { + // The AWS SDK handles caching internally. + this.awsCredentialsProvider = DefaultCredentialsProvider.create(); + } + + @Override + public String getRegion(ExternalAccountSupplierContext context) { + if (this.region == null) { + Region awsRegion = new DefaultAwsRegionProviderChain().getRegion(); + if (awsRegion == null) { + throw new IllegalStateException( + "Unable to resolve AWS region. Ensure AWS_REGION is set or configured."); + } + this.region = awsRegion.id(); + } + return this.region; + } + + @Override + public AwsSecurityCredentials getCredentials(ExternalAccountSupplierContext context) { + software.amazon.awssdk.auth.credentials.AwsCredentials credentials = + this.awsCredentialsProvider.resolveCredentials(); + + if (credentials == null) { + throw new IllegalStateException("Unable to resolve AWS credentials."); + } + + String sessionToken = null; + if (credentials instanceof AwsSessionCredentials) { + sessionToken = ((AwsSessionCredentials) credentials).sessionToken(); + } + + return new AwsSecurityCredentials( + credentials.accessKeyId(), credentials.secretAccessKey(), sessionToken); + } + } + // [END auth_custom_credential_supplier_aws] +} diff --git a/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/Dockerfile b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/Dockerfile new file mode 100644 index 00000000000..29aaaba49b0 --- /dev/null +++ b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/Dockerfile @@ -0,0 +1,21 @@ +FROM maven:3.9-eclipse-temurin-17 AS builder + +WORKDIR /app + +COPY pom.xml . +COPY src ./src + +RUN mvn clean package dependency:copy-dependencies \ + -DoutputDirectory=target/libs \ + -DskipTests + +FROM eclipse-temurin:17.0.18_8-jre + +RUN useradd -m appuser +USER appuser +WORKDIR /app + +COPY --from=builder --chown=appuser:appuser /app/target/auth-1.0.jar app.jar +COPY --from=builder --chown=appuser:appuser /app/target/libs lib/ + +CMD ["java", "-cp", "app.jar:lib/*", "com.google.cloud.auth.samples.customcredentials.aws.CustomCredentialSupplierAwsWorkload"] diff --git a/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/README.md b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/README.md new file mode 100644 index 00000000000..6dd7d5428c8 --- /dev/null +++ b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/README.md @@ -0,0 +1,124 @@ +# Running the Custom AWS Credential Supplier Sample (Java) + +This sample demonstrates how to use a custom AWS security credential supplier to authenticate with Google Cloud using AWS as an external identity provider. It uses the **AWS SDK for Java (v2)** to fetch credentials from sources like Amazon Elastic Kubernetes Service (EKS) with IAM Roles for Service Accounts (IRSA), Elastic Container Service (ECS), or Fargate. + +## Prerequisites + +* An AWS account. +* A Google Cloud project with the IAM API enabled. +* A GCS bucket. +* **Java 11** or later installed. +* **Maven** installed. + +If you want to use AWS security credentials that cannot be retrieved using methods supported natively by the Google Auth library, a custom `AwsSecurityCredentialsSupplier` implementation may be specified. The supplier must return valid, unexpired AWS security credentials when called by the Google Cloud Auth library. + +## Running Locally + +For local development, you can provide credentials and configuration in a JSON file. + +### Build the Project + +Ensure you have Java and Maven installed, then build the project to download dependencies and create an executable JAR: + +```bash +mvn clean package +``` + +### Configure Credentials for Local Development + +1. Copy the example secrets file to a new file named `custom-credentials-aws-secrets.json` in the project root: + ```bash + cp custom-credentials-aws-secrets.json.example custom-credentials-aws-secrets.json + ``` +2. Open `custom-credentials-aws-secrets.json` and fill in the required values for your AWS and Google Cloud configuration. Do not check your `custom-credentials-aws-secrets.json` file into version control. + +**Note:** This file is only used for local development and is not needed when running in a containerized environment like EKS with IRSA. + +### Run the Application + +Execute the JAR file generated in the `target` directory: + +```bash +java -jar target/custom-credential-aws-1.0-SNAPSHOT.jar +``` + +*Note: Adjust the JAR filename version if you modified it in your `pom.xml`.* + +When run locally, the application will detect the `custom-credentials-aws-secrets.json` file and use it to configure the necessary system properties for the AWS SDK. + +## Running in a Containerized Environment (EKS) + +This section provides a brief overview of how to run the sample in an Amazon EKS cluster. + +### EKS Cluster Setup + +First, you need an EKS cluster. You can create one using `eksctl` or the AWS Management Console. For detailed instructions, refer to the [Amazon EKS documentation](https://docs.aws.amazon.com/eks/latest/userguide/create-cluster.html). + +### Configure IAM Roles for Service Accounts (IRSA) + +IRSA enables you to associate an IAM role with a Kubernetes service account. This provides a secure way for your pods to access AWS services without hardcoding long-lived credentials. + +Run the following command to create the IAM role and bind it to a Kubernetes Service Account: + +```bash +eksctl create iamserviceaccount \ + --name your-k8s-service-account \ + --namespace default \ + --cluster your-cluster-name \ + --region your-aws-region \ + --role-name your-role-name \ + --attach-policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \ + --approve +``` + +> **Note**: The `--attach-policy-arn` flag is used here to demonstrate attaching permissions. Update this with the specific AWS policy ARN your application requires. + +For a deep dive into how this works without using `eksctl`, refer to the [IAM Roles for Service Accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) documentation. + +### Configure Google Cloud to Trust the AWS Role + +To allow your AWS role to authenticate as a Google Cloud service account, you need to configure Workload Identity Federation. This process involves these key steps: + +1. **Create a Workload Identity Pool and an AWS Provider:** The pool holds the configuration, and the provider is set up to trust your AWS account. + +2. **Create or select a Google Cloud Service Account:** This service account will be impersonated by your AWS role. + +3. **Bind the AWS Role to the Google Cloud Service Account:** Create an IAM policy binding that gives your AWS role the `Workload Identity User` (`roles/iam.workloadIdentityUser`) role on the Google Cloud service account. + +For more detailed information, see the documentation on [Configuring Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation-with-other-clouds). + +### Containerize and Package the Application + +Create a `Dockerfile` for the Java application and push the image to a container registry (for example Amazon ECR) that your EKS cluster can access. + +**Note:** The provided [`Dockerfile`](Dockerfile) is an example that may need modification for your specific needs. + +Build and push the image: +```bash +docker build -t your-container-image:latest . +docker push your-container-image:latest +``` + +### Deploy to EKS + +Create a Kubernetes deployment manifest to deploy your application to the EKS cluster. See the [`pod.yaml`](pod.yaml) file for an example. + +**Note:** The provided [`pod.yaml`](pod.yaml) is an example and may need to be modified for your specific needs. + +Deploy the pod: + +```bash +kubectl apply -f pod.yaml +``` + +### Clean Up + +To clean up the resources, delete the EKS cluster and any other AWS and Google Cloud resources you created. + +```bash +eksctl delete cluster --name your-cluster-name +``` + +## Testing + +This sample is not continuously tested. It is provided for instructional purposes and may require modifications to work in your environment. diff --git a/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/custom-credentials-aws-secrets.json.example b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/custom-credentials-aws-secrets.json.example new file mode 100644 index 00000000000..300dc70c138 --- /dev/null +++ b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/custom-credentials-aws-secrets.json.example @@ -0,0 +1,8 @@ +{ + "aws_access_key_id": "YOUR_AWS_ACCESS_KEY_ID", + "aws_secret_access_key": "YOUR_AWS_SECRET_ACCESS_KEY", + "aws_region": "YOUR_AWS_REGION", + "gcp_workload_audience": "YOUR_GCP_WORKLOAD_AUDIENCE", + "gcs_bucket_name": "YOUR_GCS_BUCKET_NAME", + "gcp_service_account_impersonation_url": "YOUR_GCP_SERVICE_ACCOUNT_IMPERSONATION_URL" +} diff --git a/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/pod.yaml b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/pod.yaml new file mode 100644 index 00000000000..7cc35c293ca --- /dev/null +++ b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/pod.yaml @@ -0,0 +1,44 @@ +# Copyright 2025 Google LLC +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: Pod +metadata: + name: custom-credential-pod-java +spec: + # The Kubernetes Service Account that is annotated with the corresponding + # AWS IAM role ARN. See the README for instructions on setting up IAM + # Roles for Service Accounts (IRSA). + serviceAccountName: your-k8s-service-account + containers: + - name: gcp-auth-sample-java + # The container image pushed to the container registry + # For example, Amazon Elastic Container Registry + image: your-container-image:latest + env: + # REQUIRED: The AWS region. The AWS SDK for Java requires this + # to be set explicitly in containers. + - name: AWS_REGION + value: "your-aws-region" + + # REQUIRED: The full identifier of the Workload Identity Pool provider + - name: GCP_WORKLOAD_AUDIENCE + value: "your-gcp-workload-audience" + + # OPTIONAL: Enable Google Cloud service account impersonation + # - name: GCP_SERVICE_ACCOUNT_IMPERSONATION_URL + # value: "your-gcp-service-account-impersonation-url" + + # REQUIRED: The bucket to list + - name: GCS_BUCKET_NAME + value: "your-gcs-bucket-name" diff --git a/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/okta/CustomCredentialSupplierOktaWorkload.java b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/okta/CustomCredentialSupplierOktaWorkload.java new file mode 100644 index 00000000000..e628a90ba8f --- /dev/null +++ b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/okta/CustomCredentialSupplierOktaWorkload.java @@ -0,0 +1,306 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.auth.samples.customcredentials.okta; + +// [START auth_custom_credential_supplier_okta] +import com.google.api.client.json.GenericJson; +import com.google.api.client.json.gson.GsonFactory; +import com.google.auth.oauth2.ExternalAccountSupplierContext; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.auth.oauth2.IdentityPoolCredentials; +import com.google.auth.oauth2.IdentityPoolSubjectTokenSupplier; +import com.google.cloud.storage.Bucket; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.lang.reflect.Type; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.Instant; +import java.util.Base64; +import java.util.Map; + +// [END auth_custom_credential_supplier_okta] + +/** + * This sample demonstrates how to use a custom subject token supplier to authenticate to Google + * Cloud Storage, using Okta as the identity provider. + */ +public class CustomCredentialSupplierOktaWorkload { + + public static void main(String[] args) throws IOException { + + // Reads the custom-credentials-okta-secrets.json if running locally. + loadConfigFromFile(); + + // The audience for the workload identity federation. + // Format: //iam.googleapis.com/projects//locations/global/ + // workloadIdentityPools//providers/ + String gcpWorkloadAudience = getConfiguration("GCP_WORKLOAD_AUDIENCE"); + + // The bucket to fetch data from. + String gcsBucketName = getConfiguration("GCS_BUCKET_NAME"); + + // (Optional) The service account impersonation URL. + String saImpersonationUrl = getConfiguration("GCP_SERVICE_ACCOUNT_IMPERSONATION_URL"); + + // Okta Configuration + String oktaDomain = getConfiguration("OKTA_DOMAIN"); + String oktaClientId = getConfiguration("OKTA_CLIENT_ID"); + String oktaClientSecret = getConfiguration("OKTA_CLIENT_SECRET"); + + if (gcpWorkloadAudience == null + || gcsBucketName == null + || oktaDomain == null + || oktaClientId == null + || oktaClientSecret == null) { + System.err.println( + "Error: Missing required configuration. " + + "Please provide it in a custom-credentials-okta-secrets.json file or as " + + "environment variables: GCP_WORKLOAD_AUDIENCE, GCS_BUCKET_NAME, " + + "OKTA_DOMAIN, OKTA_CLIENT_ID, OKTA_CLIENT_SECRET"); + return; + } + + try { + System.out.println("Getting metadata for bucket: " + gcsBucketName + "..."); + Bucket bucket = + authenticateWithOktaCredentials( + gcpWorkloadAudience, + saImpersonationUrl, + gcsBucketName, + oktaDomain, + oktaClientId, + oktaClientSecret); + + System.out.println(" --- SUCCESS! ---"); + System.out.printf("Bucket Name: %s%n", bucket.getName()); + System.out.printf("Bucket Location: %s%n", bucket.getLocation()); + } catch (Exception e) { + System.err.println("Authentication or Request failed: " + e.getMessage()); + } + } + + /** + * Helper method to retrieve configuration. It checks Environment variables first, then System + * properties (populated by loadConfigFromFile). + */ + static String getConfiguration(String key) { + String value = System.getenv(key); + if (value == null) { + value = System.getProperty(key); + } + return value; + } + + /** + * If a local secrets file is present, load it into the System Properties. This is a + * "just-in-time" configuration for local development. These variables are only set for the + * current process. + */ + static void loadConfigFromFile() { + // By default, this expects the file to be in the project root. + String secretsFilePath = "custom-credentials-okta-secrets.json"; + if (!Files.exists(Paths.get(secretsFilePath))) { + return; + } + + try (Reader reader = Files.newBufferedReader(Paths.get(secretsFilePath))) { + Gson gson = new Gson(); + Type type = new TypeToken>() {}.getType(); + Map secrets = gson.fromJson(reader, type); + + if (secrets == null) { + return; + } + + // Map JSON keys (snake_case) to System Properties (UPPER_UNDERSCORE) + if (secrets.containsKey("gcp_workload_audience")) { + System.setProperty("GCP_WORKLOAD_AUDIENCE", secrets.get("gcp_workload_audience")); + } + if (secrets.containsKey("gcs_bucket_name")) { + System.setProperty("GCS_BUCKET_NAME", secrets.get("gcs_bucket_name")); + } + if (secrets.containsKey("gcp_service_account_impersonation_url")) { + System.setProperty( + "GCP_SERVICE_ACCOUNT_IMPERSONATION_URL", + secrets.get("gcp_service_account_impersonation_url")); + } + if (secrets.containsKey("okta_domain")) { + System.setProperty("OKTA_DOMAIN", secrets.get("okta_domain")); + } + if (secrets.containsKey("okta_client_id")) { + System.setProperty("OKTA_CLIENT_ID", secrets.get("okta_client_id")); + } + if (secrets.containsKey("okta_client_secret")) { + System.setProperty("OKTA_CLIENT_SECRET", secrets.get("okta_client_secret")); + } + + } catch (IOException e) { + System.err.println("Error reading secrets file: " + e.getMessage()); + } catch (JsonSyntaxException e) { + System.err.println("Error: File is not valid JSON."); + } + } + + /** + * Authenticates using a custom Okta credential supplier and retrieves bucket metadata. + * + * @param gcpWorkloadAudience The WIF provider audience. + * @param saImpersonationUrl Optional service account impersonation URL. + * @param gcsBucketName The GCS bucket name. + * @param oktaDomain The Okta organization domain. + * @param oktaClientId The Okta application Client ID. + * @param oktaClientSecret The Okta application Client Secret. + * @return The Bucket object containing metadata. + * @throws IOException If authentication or the API request fails. + */ + // [START auth_custom_credential_supplier_okta] + public static Bucket authenticateWithOktaCredentials( + String gcpWorkloadAudience, + String saImpersonationUrl, + String gcsBucketName, + String oktaDomain, + String oktaClientId, + String oktaClientSecret) + throws IOException { + + OktaClientCredentialsSupplier oktaSupplier = + new OktaClientCredentialsSupplier(oktaDomain, oktaClientId, oktaClientSecret); + + IdentityPoolCredentials.Builder credentialsBuilder = + IdentityPoolCredentials.newBuilder() + .setAudience(gcpWorkloadAudience) + // This token type indicates that the subject token is a JSON Web Token (JWT). + // This is required for Workload Identity Federation with an OIDC provider like Okta. + .setSubjectTokenType("urn:ietf:params:oauth:token-type:jwt") + .setTokenUrl("https://sts.googleapis.com/v1/token") + .setSubjectTokenSupplier(oktaSupplier); + + if (saImpersonationUrl != null) { + credentialsBuilder.setServiceAccountImpersonationUrl(saImpersonationUrl); + } + + GoogleCredentials credentials = credentialsBuilder.build(); + + Storage storage = StorageOptions.newBuilder().setCredentials(credentials).build().getService(); + + return storage.get(gcsBucketName); + } + + /** + * A custom SubjectTokenSupplier that authenticates with Okta using the Client Credentials grant + * flow. + */ + private static class OktaClientCredentialsSupplier implements IdentityPoolSubjectTokenSupplier { + + private static final long TOKEN_REFRESH_BUFFER_SECONDS = 60; + + private final String oktaTokenUrl; + private final String clientId; + private final String clientSecret; + private String accessToken; + private Instant expiryTime; + + public OktaClientCredentialsSupplier(String domain, String clientId, String clientSecret) { + // Ensure domain doesn't have a trailing slash for cleaner URL construction + String cleanedDomain = + domain.endsWith("/") ? domain.substring(0, domain.length() - 1) : domain; + this.oktaTokenUrl = cleanedDomain + "/oauth2/default/v1/token"; + this.clientId = clientId; + this.clientSecret = clientSecret; + } + + /** + * Main method called by the auth library. It will fetch a new token if one is not already + * cached. + */ + @Override + public String getSubjectToken(ExternalAccountSupplierContext context) throws IOException { + // Check if the current token is still valid (with a 60-second buffer). + boolean isTokenValid = + this.accessToken != null + && this.expiryTime != null + && Instant.now().isBefore(this.expiryTime.minusSeconds(TOKEN_REFRESH_BUFFER_SECONDS)); + + if (isTokenValid) { + return this.accessToken; + } + + fetchOktaAccessToken(); + return this.accessToken; + } + + /** + * Performs the Client Credentials grant flow by making a POST request to Okta's token endpoint. + */ + private void fetchOktaAccessToken() throws IOException { + URL url = new URL(this.oktaTokenUrl); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + conn.setRequestProperty("Accept", "application/json"); + + // The client_id and client_secret are sent in a Basic Auth header. + String auth = this.clientId + ":" + this.clientSecret; + String encodedAuth = + Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8)); + conn.setRequestProperty("Authorization", "Basic " + encodedAuth); + + conn.setDoOutput(true); + try (java.io.OutputStream out = conn.getOutputStream()) { + // Scopes define the permissions the access token will have. + // Update "gcp.test.read" to match your Okta configuration. + String params = "grant_type=client_credentials&scope=gcp.test.read"; + out.write(params.getBytes(StandardCharsets.UTF_8)); + out.flush(); + } + + int responseCode = conn.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_OK) { + try (BufferedReader in = + new BufferedReader( + new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) { + + GenericJson jsonObject = + GsonFactory.getDefaultInstance().createJsonParser(in).parse(GenericJson.class); + + if (jsonObject.containsKey("access_token") && jsonObject.containsKey("expires_in")) { + this.accessToken = (String) jsonObject.get("access_token"); + Number expiresInNumber = (Number) jsonObject.get("expires_in"); + this.expiryTime = Instant.now().plusSeconds(expiresInNumber.longValue()); + } else { + throw new IOException("Access token or expires_in not found in Okta response."); + } + } + } else { + throw new IOException("Failed to authenticate with Okta. Response code: " + responseCode); + } + } + } + // [END auth_custom_credential_supplier_okta] +} diff --git a/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/okta/README.md b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/okta/README.md new file mode 100644 index 00000000000..f2992fc16ed --- /dev/null +++ b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/okta/README.md @@ -0,0 +1,83 @@ +# Running the Custom Okta Credential Supplier Sample (Java) + +This sample demonstrates how to use a custom subject token supplier to authenticate with Google Cloud using Okta as an external identity provider. It uses the Client Credentials flow for machine-to-machine (M2M) authentication. + +## Prerequisites + +* An Okta developer account. +* A Google Cloud project with the IAM API enabled. +* A Google Cloud Storage bucket. Ensure that the authenticated user has access to this bucket. +* Java 11 or later installed. +* Maven installed. + +## Okta Configuration + +Before running the sample, you need to configure an Okta application for Machine-to-Machine (M2M) communication. + +### Create an M2M Application in Okta + +1. Log in to your Okta developer console. +2. Navigate to **Applications** > **Applications** and click **Create App Integration**. +3. Select **API Services** as the sign-on method and click **Next**. +4. Give your application a name and click **Save**. + +### Obtain Okta Credentials + +Once the application is created, you will find the following information in the **General** tab: + +* **Okta Domain**: Your Okta developer domain (e.g., `https://dev-123456.okta.com`). +* **Client ID**: The client ID for your application. +* **Client Secret**: The client secret for your application. + +You will need these values to configure the sample. + +## Google Cloud Configuration + +You need to configure a Workload Identity Pool in Google Cloud to trust the Okta application. + +### Set up Workload Identity Federation + +1. In the Google Cloud Console, navigate to **IAM & Admin** > **Workload Identity Federation**. +2. Click **Create Pool** to create a new Workload Identity Pool. +3. Add a new **OIDC provider** to the pool. +4. Configure the provider with your Okta domain as the issuer URL. +5. Map the Okta `sub` (subject) assertion to a GCP principal. + +For detailed instructions, refer to the [Workload Identity Federation documentation](https://cloud.google.com/iam/docs/workload-identity-federation). + +## Running the Sample + +To run the sample on your local system, you need to build the project and provide configuration via a secrets file. + +### 1. Build the Project + +This command compiles your code and downloads all dependencies. +```bash +mvn clean package +``` + +### Configure Credentials for Local Development + +1. Copy the example secrets file to a new file named `custom-credentials-okta-secrets.json` in the project root: + ```bash + cp custom-credentials-okta-secrets.json.example custom-credentials-okta-secrets.json + ``` +2. Open `custom-credentials-okta-secrets.json` and fill in the required values for your AWS and Google Cloud configuration. Do not check your `custom-credentials-okta-secrets.json` file into version control. + +### 3. Run the Application + +First, generate the classpath file: +```bash +mvn dependency:build-classpath -Dmdep.outputFile=cp.txt +``` + +Now, run the application, providing the generated classpath: +```bash +java -cp "target/auth-1.0.jar:$(cat cp.txt)" com.google.cloud.auth.samples.customcredentials.okta.CustomCredentialSupplierOktaWorkload +``` + +The script will detect the `custom-credentials-okta-secrets.json` file, authenticate with Okta to get an OIDC token, exchange it for a Google Cloud federated token, and retrieve metadata for your GCS bucket. + +## Testing + +This sample is not continuously tested. It is provided for instructional purposes and may require modifications to work in your environment. diff --git a/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/okta/custom-credentials-okta-secrets.json.example b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/okta/custom-credentials-okta-secrets.json.example new file mode 100644 index 00000000000..5564fab6333 --- /dev/null +++ b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/okta/custom-credentials-okta-secrets.json.example @@ -0,0 +1,8 @@ +{ + "okta_domain": "https://your-okta-domain.okta.com", + "okta_client_id": "your-okta-client-id", + "okta_client_secret": "your-okta-client-secret", + "gcp_workload_audience": "//iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider", + "gcs_bucket_name": "your-gcs-bucket-name", + "gcp_service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/my-service-account@my-project.iam.gserviceaccount.com:generateAccessToken" +} \ No newline at end of file diff --git a/auth/src/test/java/com/google/cloud/auth/samples/customcredentials/aws/CustomCredentialSupplierAwsWorkloadTest.java b/auth/src/test/java/com/google/cloud/auth/samples/customcredentials/aws/CustomCredentialSupplierAwsWorkloadTest.java new file mode 100644 index 00000000000..7dd63657302 --- /dev/null +++ b/auth/src/test/java/com/google/cloud/auth/samples/customcredentials/aws/CustomCredentialSupplierAwsWorkloadTest.java @@ -0,0 +1,72 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.auth.samples.customcredentials.aws; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assume.assumeTrue; + +import com.google.cloud.storage.Bucket; +import org.junit.BeforeClass; +import org.junit.Test; + +public class CustomCredentialSupplierAwsWorkloadTest { + + private static final String AUDIENCE_KEY = "GCP_WORKLOAD_AUDIENCE"; + private static final String BUCKET_KEY = "GCS_BUCKET_NAME"; + private static final String IMPERSONATION_KEY = "GCP_SERVICE_ACCOUNT_IMPERSONATION_URL"; + + @BeforeClass + public static void setupConfiguration() { + // This will load secrets.json into System Properties if the file exists. + CustomCredentialSupplierAwsWorkload.loadConfigFromFile(); + + String audience = CustomCredentialSupplierAwsWorkload.getConfiguration(AUDIENCE_KEY); + String bucket = CustomCredentialSupplierAwsWorkload.getConfiguration(BUCKET_KEY); + + // Check for AWS specific requirements (needed by the SDK) + String awsKey = System.getProperty("aws.accessKeyId"); // Set by loadConfigFromFile + if (awsKey == null) { + awsKey = System.getenv("AWS_ACCESS_KEY_ID"); + } + + // Skip the test if configuration is missing (mirrors pytest.skip). + assumeTrue("Skipping test: " + AUDIENCE_KEY + " is missing.", audience != null); + assumeTrue("Skipping test: " + BUCKET_KEY + " is missing.", bucket != null); + assumeTrue("Skipping test: AWS Credentials not found.", awsKey != null); + } + + @Test + public void testAuthenticateWithAwsCredentials_system() throws Exception { + // Retrieve values using the helper + String audience = CustomCredentialSupplierAwsWorkload.getConfiguration(AUDIENCE_KEY); + String bucketName = CustomCredentialSupplierAwsWorkload.getConfiguration(BUCKET_KEY); + String impersonationUrl = + CustomCredentialSupplierAwsWorkload.getConfiguration(IMPERSONATION_KEY); + + // Act: Run the authentication sample + Bucket bucket = + CustomCredentialSupplierAwsWorkload.authenticateWithAwsCredentials( + audience, impersonationUrl, bucketName); + + // Verify we got a valid bucket object back from the API + assertThat(bucket).isNotNull(); + assertThat(bucket.getName()).isEqualTo(bucketName); + + // Verify we can actually access metadata (proving auth worked) + assertThat(bucket.getLocation()).isNotNull(); + } +} diff --git a/auth/src/test/java/com/google/cloud/auth/samples/customcredentials/okta/CustomCredentialSupplierOktaWorkloadTest.java b/auth/src/test/java/com/google/cloud/auth/samples/customcredentials/okta/CustomCredentialSupplierOktaWorkloadTest.java new file mode 100644 index 00000000000..83aefdf3793 --- /dev/null +++ b/auth/src/test/java/com/google/cloud/auth/samples/customcredentials/okta/CustomCredentialSupplierOktaWorkloadTest.java @@ -0,0 +1,86 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.auth.samples.customcredentials.okta; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assume.assumeTrue; + +import com.google.cloud.storage.Bucket; +import org.junit.BeforeClass; +import org.junit.Test; + +public class CustomCredentialSupplierOktaWorkloadTest { + + private static final String AUDIENCE_KEY = "GCP_WORKLOAD_AUDIENCE"; + private static final String BUCKET_KEY = "GCS_BUCKET_NAME"; + private static final String IMPERSONATION_KEY = "GCP_SERVICE_ACCOUNT_IMPERSONATION_URL"; + + private static final String OKTA_DOMAIN_KEY = "OKTA_DOMAIN"; + private static final String OKTA_CLIENT_ID_KEY = "OKTA_CLIENT_ID"; + private static final String OKTA_CLIENT_SECRET_KEY = "OKTA_CLIENT_SECRET"; + + @BeforeClass + public static void setupConfiguration() { + // 1. Call the loader from the main class. + // This will load custom-credentials-okta-secrets.json into System Properties if the file + // exists. + CustomCredentialSupplierOktaWorkload.loadConfigFromFile(); + + // 2. Validate requirements using the main class's getConfiguration helper. + // This checks both Environment Variables and the System Properties we just loaded. + requireConfig(AUDIENCE_KEY); + requireConfig(BUCKET_KEY); + requireConfig(OKTA_DOMAIN_KEY); + requireConfig(OKTA_CLIENT_ID_KEY); + requireConfig(OKTA_CLIENT_SECRET_KEY); + } + + private static void requireConfig(String key) { + String value = CustomCredentialSupplierOktaWorkload.getConfiguration(key); + assumeTrue("Skipping test: " + key + " is missing.", value != null && !value.isEmpty()); + } + + /** + * System Test: Verifies the full end-to-end authentication flow. This runs against the real + * Google Cloud and Okta APIs. + */ + @Test + public void testAuthenticateWithOktaCredentials_system() throws Exception { + // Retrieve values using the helper from the main class + String audience = CustomCredentialSupplierOktaWorkload.getConfiguration(AUDIENCE_KEY); + String bucketName = CustomCredentialSupplierOktaWorkload.getConfiguration(BUCKET_KEY); + String impersonationUrl = + CustomCredentialSupplierOktaWorkload.getConfiguration(IMPERSONATION_KEY); + + String oktaDomain = CustomCredentialSupplierOktaWorkload.getConfiguration(OKTA_DOMAIN_KEY); + String oktaClientId = CustomCredentialSupplierOktaWorkload.getConfiguration(OKTA_CLIENT_ID_KEY); + String oktaSecret = + CustomCredentialSupplierOktaWorkload.getConfiguration(OKTA_CLIENT_SECRET_KEY); + + // Act: Run the authentication sample + Bucket bucket = + CustomCredentialSupplierOktaWorkload.authenticateWithOktaCredentials( + audience, impersonationUrl, bucketName, oktaDomain, oktaClientId, oktaSecret); + + // Assert: Verify we got a valid bucket object back from the API + assertThat(bucket).isNotNull(); + assertThat(bucket.getName()).isEqualTo(bucketName); + + // Verify we can actually access metadata (proving auth worked) + assertThat(bucket.getLocation()).isNotNull(); + } +} diff --git a/automl/pom.xml b/automl/pom.xml deleted file mode 100644 index b8138df5b7c..00000000000 --- a/automl/pom.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - 4.0.0 - com.example.automl - automl-snippets - jar - Google Cloud Auto ML Snippets - https://github.com/GoogleCloudPlatform/java-docs-samples/tree/main/automl - - - - com.google.cloud.samples - shared-configuration - 1.2.0 - - - - 1.8 - 1.8 - UTF-8 - - - - - - - - com.google.cloud - libraries-bom - 26.32.0 - pom - import - - - - - - - com.google.cloud - google-cloud-automl - - - com.google.cloud - google-cloud-bigquery - - - com.google.cloud - google-cloud-storage - - - - net.sourceforge.argparse4j - argparse4j - 0.9.0 - - - junit - junit - 4.13.2 - test - - - com.google.truth - truth - 1.4.0 - test - - - com.google.cloud - google-cloud-core - test - tests - - - - - diff --git a/automl/resources/dandelion.jpg b/automl/resources/dandelion.jpg deleted file mode 100644 index 326e4c1bf53..00000000000 Binary files a/automl/resources/dandelion.jpg and /dev/null differ diff --git a/automl/resources/input.txt b/automl/resources/input.txt deleted file mode 100644 index 5aecd6590fc..00000000000 --- a/automl/resources/input.txt +++ /dev/null @@ -1 +0,0 @@ -Tell me how this ends \ No newline at end of file diff --git a/automl/resources/salad.jpg b/automl/resources/salad.jpg deleted file mode 100644 index a7f960b5030..00000000000 Binary files a/automl/resources/salad.jpg and /dev/null differ diff --git a/automl/resources/test.png b/automl/resources/test.png deleted file mode 100644 index 653342a46e5..00000000000 Binary files a/automl/resources/test.png and /dev/null differ diff --git a/automl/src/main/java/beta/automl/BatchPredict.java b/automl/src/main/java/beta/automl/BatchPredict.java deleted file mode 100644 index 8eab116c971..00000000000 --- a/automl/src/main/java/beta/automl/BatchPredict.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -// [START automl_tables_batch_predict] -// [START automl_batch_predict_beta] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1beta1.BatchPredictInputConfig; -import com.google.cloud.automl.v1beta1.BatchPredictOutputConfig; -import com.google.cloud.automl.v1beta1.BatchPredictRequest; -import com.google.cloud.automl.v1beta1.BatchPredictResult; -import com.google.cloud.automl.v1beta1.GcsDestination; -import com.google.cloud.automl.v1beta1.GcsSource; -import com.google.cloud.automl.v1beta1.ModelName; -import com.google.cloud.automl.v1beta1.OperationMetadata; -import com.google.cloud.automl.v1beta1.PredictionServiceClient; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -abstract class BatchPredict { - - static void batchPredict() throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - String inputUri = "gs://YOUR_BUCKET_ID/path_to_your_input_csv_or_jsonl"; - String outputUri = "gs://YOUR_BUCKET_ID/path_to_save_results/"; - batchPredict(projectId, modelId, inputUri, outputUri); - } - - static void batchPredict(String projectId, String modelId, String inputUri, String outputUri) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (PredictionServiceClient client = PredictionServiceClient.create()) { - // Get the full path of the model. - ModelName name = ModelName.of(projectId, "us-central1", modelId); - - // Configure the source of the file from a GCS bucket - GcsSource gcsSource = GcsSource.newBuilder().addInputUris(inputUri).build(); - BatchPredictInputConfig inputConfig = - BatchPredictInputConfig.newBuilder().setGcsSource(gcsSource).build(); - - // Configure where to store the output in a GCS bucket - GcsDestination gcsDestination = - GcsDestination.newBuilder().setOutputUriPrefix(outputUri).build(); - BatchPredictOutputConfig outputConfig = - BatchPredictOutputConfig.newBuilder().setGcsDestination(gcsDestination).build(); - - // Build the request that will be sent to the API - BatchPredictRequest request = - BatchPredictRequest.newBuilder() - .setName(name.toString()) - .setInputConfig(inputConfig) - .setOutputConfig(outputConfig) - .build(); - - // Start an asynchronous request - OperationFuture future = - client.batchPredictAsync(request); - - System.out.println("Waiting for operation to complete..."); - future.get(); - System.out.println("Batch Prediction results saved to specified Cloud Storage bucket."); - } - } -} -// [END automl_batch_predict_beta] -// [END automl_tables_batch_predict] diff --git a/automl/src/main/java/beta/automl/CancelOperation.java b/automl/src/main/java/beta/automl/CancelOperation.java deleted file mode 100644 index 1e0e4fd2a71..00000000000 --- a/automl/src/main/java/beta/automl/CancelOperation.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -// [START automl_cancel_operation_beta] - -import com.google.cloud.automl.v1beta1.AutoMlClient; -import io.grpc.StatusRuntimeException; -import java.io.IOException; - -class CancelOperation { - - static void cancelOperation() throws IOException, InterruptedException, StatusRuntimeException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String location = "us-central1"; - String operationId = "YOUR_OPERATION_ID"; - String operationFullId = - String.format("projects/%s/locations/%s/operations/%s", projectId, location, operationId); - cancelOperation(operationFullId); - } - - static void cancelOperation(String operationFullId) - throws IOException, InterruptedException, StatusRuntimeException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - client.getOperationsClient().cancelOperation(operationFullId); - System.out.println("Operation cancelled"); - } - } -} -// [END automl_cancel_operation_beta] diff --git a/automl/src/main/java/beta/automl/DeleteDataset.java b/automl/src/main/java/beta/automl/DeleteDataset.java deleted file mode 100644 index 9ef47c5c2fa..00000000000 --- a/automl/src/main/java/beta/automl/DeleteDataset.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -// [START automl_delete_dataset_beta] -// [START automl_tables_delete_dataset] -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.cloud.automl.v1beta1.DatasetName; -import com.google.protobuf.Empty; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class DeleteDataset { - - static void deleteDataset() throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String datasetId = "YOUR_DATASET_ID"; - deleteDataset(projectId, datasetId); - } - - // Delete a dataset - static void deleteDataset(String projectId, String datasetId) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // Get the full path of the dataset. - DatasetName datasetFullId = DatasetName.of(projectId, "us-central1", datasetId); - Empty response = client.deleteDatasetAsync(datasetFullId).get(); - System.out.format("Dataset deleted. %s%n", response); - } - } -} -// [END automl_tables_delete_dataset] -// [END automl_delete_dataset_beta] diff --git a/automl/src/main/java/beta/automl/DeleteModel.java b/automl/src/main/java/beta/automl/DeleteModel.java deleted file mode 100644 index 77cc99bb2c9..00000000000 --- a/automl/src/main/java/beta/automl/DeleteModel.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -// [START automl_tables_delete_model] -// [START automl_delete_model_beta] -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.cloud.automl.v1beta1.ModelName; -import com.google.protobuf.Empty; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class DeleteModel { - - public static void main(String[] args) - throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - deleteModel(projectId, modelId); - } - - // Delete a model - static void deleteModel(String projectId, String modelId) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // Get the full path of the model. - ModelName modelFullId = ModelName.of(projectId, "us-central1", modelId); - - // Delete a model. - Empty response = client.deleteModelAsync(modelFullId).get(); - - System.out.println("Model deletion started..."); - System.out.println(String.format("Model deleted. %s", response)); - } - } -} -// [END automl_delete_model_beta] -// [END automl_tables_delete_model] diff --git a/automl/src/main/java/beta/automl/DeployModel.java b/automl/src/main/java/beta/automl/DeployModel.java deleted file mode 100644 index 55a3105dd1c..00000000000 --- a/automl/src/main/java/beta/automl/DeployModel.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -// [START automl_tables_deploy_model] -// [START automl_deploy_model_beta] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.cloud.automl.v1beta1.DeployModelRequest; -import com.google.cloud.automl.v1beta1.ModelName; -import com.google.cloud.automl.v1beta1.OperationMetadata; -import com.google.protobuf.Empty; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class DeployModel { - - public static void main(String[] args) - throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - deployModel(projectId, modelId); - } - - // Deploy a model for prediction - static void deployModel(String projectId, String modelId) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // Get the full path of the model. - ModelName modelFullId = ModelName.of(projectId, "us-central1", modelId); - DeployModelRequest request = - DeployModelRequest.newBuilder().setName(modelFullId.toString()).build(); - OperationFuture future = client.deployModelAsync(request); - - future.get(); - System.out.println("Model deployment finished"); - } - } -} -// [END automl_deploy_model_beta] -// [END automl_tables_deploy_model] diff --git a/automl/src/main/java/beta/automl/GetModel.java b/automl/src/main/java/beta/automl/GetModel.java deleted file mode 100644 index 710b78053eb..00000000000 --- a/automl/src/main/java/beta/automl/GetModel.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -// [START automl_get_model_beta] -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.cloud.automl.v1beta1.Model; -import com.google.cloud.automl.v1beta1.ModelName; -import io.grpc.StatusRuntimeException; -import java.io.IOException; - -class GetModel { - - static void getModel() throws IOException, StatusRuntimeException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - getModel(projectId, modelId); - } - - // Get a model - static void getModel(String projectId, String modelId) - throws IOException, StatusRuntimeException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // Get the full path of the model. - ModelName modelFullId = ModelName.of(projectId, "us-central1", modelId); - Model model = client.getModel(modelFullId); - - // Display the model information. - System.out.format("Model name: %s%n", model.getName()); - // To get the model id, you have to parse it out of the `name` field. As models Ids are - // required for other methods. - // Name Format: `projects/{project_id}/locations/{location_id}/models/{model_id}` - String[] names = model.getName().split("/"); - String retrievedModelId = names[names.length - 1]; - System.out.format("Model id: %s%n", retrievedModelId); - System.out.format("Model display name: %s%n", model.getDisplayName()); - System.out.println("Model create time:"); - System.out.format("\tseconds: %s%n", model.getCreateTime().getSeconds()); - System.out.format("\tnanos: %s%n", model.getCreateTime().getNanos()); - System.out.format("Model deployment state: %s%n", model.getDeploymentState()); - } - } -} -// [END automl_get_model_beta] diff --git a/automl/src/main/java/beta/automl/GetModelEvaluation.java b/automl/src/main/java/beta/automl/GetModelEvaluation.java deleted file mode 100644 index 74f35f5a74e..00000000000 --- a/automl/src/main/java/beta/automl/GetModelEvaluation.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -// [START automl_video_classification_get_model_evaluation_beta] -// [START automl_video_object_tracking_get_model_evaluation_beta] -// [START automl_tables_get_model_evaluation] -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.cloud.automl.v1beta1.ModelEvaluation; -import com.google.cloud.automl.v1beta1.ModelEvaluationName; -import java.io.IOException; - -class GetModelEvaluation { - - static void getModelEvaluation() throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - String modelEvaluationId = "YOUR_MODEL_EVALUATION_ID"; - getModelEvaluation(projectId, modelId, modelEvaluationId); - } - - // Get a model evaluation - static void getModelEvaluation(String projectId, String modelId, String modelEvaluationId) - throws IOException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // Get the full path of the model evaluation. - ModelEvaluationName modelEvaluationFullId = - ModelEvaluationName.of(projectId, "us-central1", modelId, modelEvaluationId); - - // Get complete detail of the model evaluation. - ModelEvaluation modelEvaluation = client.getModelEvaluation(modelEvaluationFullId); - - System.out.format("Model Evaluation Name: %s%n", modelEvaluation.getName()); - System.out.format("Model Annotation Spec Id: %s", modelEvaluation.getAnnotationSpecId()); - System.out.println("Create Time:"); - System.out.format("\tseconds: %s%n", modelEvaluation.getCreateTime().getSeconds()); - System.out.format("\tnanos: %s", modelEvaluation.getCreateTime().getNanos() / 1e9); - System.out.format( - "Evalution Example Count: %d%n", modelEvaluation.getEvaluatedExampleCount()); - - // [END automl_video_object_tracking_get_model_evaluation_beta] - System.out.format( - "Classification Model Evaluation Metrics: %s%n", - modelEvaluation.getClassificationEvaluationMetrics()); - // [END automl_video_classification_get_model_evaluation_beta] - - // [START automl_video_object_tracking_get_model_evaluation_beta] - System.out.format( - "Video Object Tracking Evaluation Metrics: %s%n", - modelEvaluation.getVideoObjectTrackingEvaluationMetrics()); - // [START automl_video_classification_get_model_evaluation_beta] - } - } -} -// [END automl_tables_get_model_evaluation] -// [END automl_video_object_tracking_get_model_evaluation_beta] -// [END automl_video_classification_get_model_evaluation_beta] diff --git a/automl/src/main/java/beta/automl/GetOperationStatus.java b/automl/src/main/java/beta/automl/GetOperationStatus.java deleted file mode 100644 index 77dc703407e..00000000000 --- a/automl/src/main/java/beta/automl/GetOperationStatus.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -// [START automl_get_operation_status_beta] -// [START automl_tables_get_operation_status] -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.longrunning.Operation; -import java.io.IOException; - -class GetOperationStatus { - - static void getOperationStatus() throws IOException { - // TODO(developer): Replace these variables before running the sample. - String operationFullId = "projects/[projectId]/locations/us-central1/operations/[operationId]"; - getOperationStatus(operationFullId); - } - - // Get the status of an operation - static void getOperationStatus(String operationFullId) throws IOException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // Get the latest state of a long-running operation. - Operation operation = client.getOperationsClient().getOperation(operationFullId); - - // Display operation details. - System.out.println("Operation details:"); - System.out.format("\tName: %s%n", operation.getName()); - System.out.format("\tMetadata Type Url: %s%n", operation.getMetadata().getTypeUrl()); - System.out.format("\tDone: %s%n", operation.getDone()); - if (operation.hasResponse()) { - System.out.format("\tResponse Type Url: %s%n", operation.getResponse().getTypeUrl()); - } - if (operation.hasError()) { - System.out.println("\tResponse:"); - System.out.format("\t\tError code: %s%n", operation.getError().getCode()); - System.out.format("\t\tError message: %s%n", operation.getError().getMessage()); - } - } - } -} -// [END automl_tables_get_operation_status] -// [END automl_get_operation_status_beta] diff --git a/automl/src/main/java/beta/automl/ImportDataset.java b/automl/src/main/java/beta/automl/ImportDataset.java deleted file mode 100644 index 72f19b19233..00000000000 --- a/automl/src/main/java/beta/automl/ImportDataset.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -// [START automl_import_dataset_beta] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.api.gax.retrying.RetrySettings; -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.cloud.automl.v1beta1.AutoMlSettings; -import com.google.cloud.automl.v1beta1.DatasetName; -import com.google.cloud.automl.v1beta1.GcsSource; -import com.google.cloud.automl.v1beta1.InputConfig; -import com.google.cloud.automl.v1beta1.OperationMetadata; -import com.google.protobuf.Empty; -import java.io.IOException; -import java.util.Arrays; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import org.threeten.bp.Duration; - -class ImportDataset { - - public static void main(String[] args) - throws IOException, ExecutionException, InterruptedException, TimeoutException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String datasetId = "YOUR_DATASET_ID"; - String path = "gs://BUCKET_ID/path_to_training_data.csv"; - importDataset(projectId, datasetId, path); - } - - // Import a dataset - static void importDataset(String projectId, String datasetId, String path) - throws IOException, ExecutionException, InterruptedException, TimeoutException { - Duration totalTimeout = Duration.ofMinutes(45); - RetrySettings retrySettings = RetrySettings.newBuilder().setTotalTimeout(totalTimeout).build(); - AutoMlSettings.Builder builder = AutoMlSettings.newBuilder(); - builder.importDataSettings().setRetrySettings(retrySettings).build(); - AutoMlSettings settings = builder.build(); - - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create(settings)) { - // Get the complete path of the dataset. - DatasetName datasetFullId = DatasetName.of(projectId, "us-central1", datasetId); - - // Get multiple Google Cloud Storage URIs to import data from - GcsSource gcsSource = - GcsSource.newBuilder().addAllInputUris(Arrays.asList(path.split(","))).build(); - - // Import data from the input URI - InputConfig inputConfig = InputConfig.newBuilder().setGcsSource(gcsSource).build(); - System.out.println("Processing import..."); - - // Start the import job - OperationFuture operation = - client.importDataAsync(datasetFullId, inputConfig); - - System.out.format("Operation name: %s%n", operation.getName()); - - // If you want to wait for the operation to finish, adjust the timeout appropriately. The - // operation will still run if you choose not to wait for it to complete. You can check the - // status of your operation using the operation's name. - Empty response = operation.get(45, TimeUnit.MINUTES); - System.out.format("Dataset imported. %s%n", response); - } catch (TimeoutException e) { - System.out.println("The operation's polling period was not long enough."); - System.out.println("You can use the Operation's name to get the current status."); - System.out.println("The import job is still running and will complete as expected."); - throw e; - } - } -} -// [END automl_import_dataset_beta] diff --git a/automl/src/main/java/beta/automl/ListDatasets.java b/automl/src/main/java/beta/automl/ListDatasets.java deleted file mode 100644 index 7e624aa6fef..00000000000 --- a/automl/src/main/java/beta/automl/ListDatasets.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -// [START automl_video_classification_list_datasets_beta] -// [START automl_video_object_tracking_list_datasets_beta] -// [START automl_tables_list_datasets] -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.cloud.automl.v1beta1.Dataset; -import com.google.cloud.automl.v1beta1.ListDatasetsRequest; -import com.google.cloud.automl.v1beta1.LocationName; -import java.io.IOException; - -class ListDatasets { - - static void listDatasets() throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - listDatasets(projectId); - } - - // List the datasets - static void listDatasets(String projectId) throws IOException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "us-central1"); - ListDatasetsRequest request = - ListDatasetsRequest.newBuilder().setParent(projectLocation.toString()).build(); - - // List all the datasets available in the region by applying filter. - System.out.println("List of datasets:"); - for (Dataset dataset : client.listDatasets(request).iterateAll()) { - // Display the dataset information - System.out.format("%nDataset name: %s%n", dataset.getName()); - // To get the dataset id, you have to parse it out of the `name` field. As dataset Ids are - // required for other methods. - // Name Form: `projects/{project_id}/locations/{location_id}/datasets/{dataset_id}` - String[] names = dataset.getName().split("/"); - String retrievedDatasetId = names[names.length - 1]; - System.out.format("Dataset id: %s%n", retrievedDatasetId); - System.out.format("Dataset display name: %s%n", dataset.getDisplayName()); - System.out.println("Dataset create time:"); - System.out.format("\tseconds: %s%n", dataset.getCreateTime().getSeconds()); - System.out.format("\tnanos: %s%n", dataset.getCreateTime().getNanos()); - - // [END automl_video_object_tracking_list_datasets_beta] - // [END automl_tables_list_datasets] - System.out.format( - "Video classification dataset metadata: %s%n", - dataset.getVideoClassificationDatasetMetadata()); - // [END automl_video_classification_list_datasets_beta] - - // [START automl_video_object_tracking_list_datasets_beta] - System.out.format( - "Video object tracking dataset metadata: %s%n", - dataset.getVideoObjectTrackingDatasetMetadata()); - // [END automl_video_object_tracking_list_datasets_beta] - - // [START automl_tables_list_datasets] - System.out.format("Tables dataset metadata: %s%n", dataset.getTablesDatasetMetadata()); - - // [START automl_video_classification_list_datasets_beta] - // [START automl_video_object_tracking_list_datasets_beta] - } - } - } -} -// [END automl_video_classification_list_datasets_beta] -// [END automl_video_object_tracking_list_datasets_beta] -// [END automl_tables_list_datasets] diff --git a/automl/src/main/java/beta/automl/ListModelEvaluations.java b/automl/src/main/java/beta/automl/ListModelEvaluations.java deleted file mode 100644 index 2fad4ea8e76..00000000000 --- a/automl/src/main/java/beta/automl/ListModelEvaluations.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -// [START automl_tables_list_model_evaluations] -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.cloud.automl.v1beta1.ListModelEvaluationsRequest; -import com.google.cloud.automl.v1beta1.ModelEvaluation; -import com.google.cloud.automl.v1beta1.ModelName; -import java.io.IOException; - -class ListModelEvaluations { - - public static void main(String[] args) throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - listModelEvaluations(projectId, modelId); - } - - // List model evaluations - static void listModelEvaluations(String projectId, String modelId) throws IOException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // Get the full path of the model. - ModelName modelFullId = ModelName.of(projectId, "us-central1", modelId); - ListModelEvaluationsRequest modelEvaluationsrequest = - ListModelEvaluationsRequest.newBuilder().setParent(modelFullId.toString()).build(); - - // List all the model evaluations in the model by applying filter. - System.out.println("List of model evaluations:"); - for (ModelEvaluation modelEvaluation : - client.listModelEvaluations(modelEvaluationsrequest).iterateAll()) { - - System.out.format("Model Evaluation Name: %s%n", modelEvaluation.getName()); - System.out.format("Model Annotation Spec Id: %s", modelEvaluation.getAnnotationSpecId()); - System.out.println("Create Time:"); - System.out.format("\tseconds: %s%n", modelEvaluation.getCreateTime().getSeconds()); - System.out.format("\tnanos: %s", modelEvaluation.getCreateTime().getNanos() / 1e9); - System.out.format( - "Evalution Example Count: %d%n", modelEvaluation.getEvaluatedExampleCount()); - - System.out.format( - "Tables Model Evaluation Metrics: %s%n", - modelEvaluation.getClassificationEvaluationMetrics()); - } - } - } -} -// [END automl_tables_list_model_evaluations] diff --git a/automl/src/main/java/beta/automl/ListModels.java b/automl/src/main/java/beta/automl/ListModels.java deleted file mode 100644 index 87d1b0a7f10..00000000000 --- a/automl/src/main/java/beta/automl/ListModels.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -// [START automl_tables_list_models] -// [START automl_list_models_beta] -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.cloud.automl.v1beta1.ListModelsRequest; -import com.google.cloud.automl.v1beta1.LocationName; -import com.google.cloud.automl.v1beta1.Model; -import java.io.IOException; - -class ListModels { - - static void listModels() throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - listModels(projectId); - } - - // List the models available in the specified location - static void listModels(String projectId) throws IOException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "us-central1"); - - // Create list models request. - ListModelsRequest listModelsRequest = - ListModelsRequest.newBuilder() - .setParent(projectLocation.toString()) - .setFilter("") - .build(); - - // List all the models available in the region by applying filter. - System.out.println("List of models:"); - for (Model model : client.listModels(listModelsRequest).iterateAll()) { - // Display the model information. - System.out.format("Model name: %s%n", model.getName()); - // To get the model id, you have to parse it out of the `name` field. As models Ids are - // required for other methods. - // Name Format: `projects/{project_id}/locations/{location_id}/models/{model_id}` - String[] names = model.getName().split("/"); - String retrievedModelId = names[names.length - 1]; - System.out.format("Model id: %s%n", retrievedModelId); - System.out.format("Model display name: %s%n", model.getDisplayName()); - System.out.println("Model create time:"); - System.out.format("\tseconds: %s%n", model.getCreateTime().getSeconds()); - System.out.format("\tnanos: %s%n", model.getCreateTime().getNanos()); - System.out.format("Model deployment state: %s%n", model.getDeploymentState()); - } - } - } -} -// [END automl_list_models_beta] -// [END automl_tables_list_models] diff --git a/automl/src/main/java/beta/automl/SetEndpoint.java b/automl/src/main/java/beta/automl/SetEndpoint.java deleted file mode 100644 index 4718b220a23..00000000000 --- a/automl/src/main/java/beta/automl/SetEndpoint.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.cloud.automl.v1beta1.AutoMlSettings; -import com.google.cloud.automl.v1beta1.Dataset; -import com.google.cloud.automl.v1beta1.ListDatasetsRequest; -import com.google.cloud.automl.v1beta1.LocationName; -import java.io.IOException; - -class SetEndpoint { - - // Change your endpoint - static void setEndpoint(String projectId) throws IOException { - // [START automl_set_endpoint] - AutoMlSettings settings = - AutoMlSettings.newBuilder().setEndpoint("eu-automl.googleapis.com:443").build(); - - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - AutoMlClient client = AutoMlClient.create(settings); - - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "eu"); - // [END automl_set_endpoint] - - ListDatasetsRequest request = - ListDatasetsRequest.newBuilder() - .setParent(projectLocation.toString()) - .setFilter("translation_dataset_metadata:*") - .build(); - // List all the datasets available - System.out.println("List of datasets:"); - for (Dataset dataset : client.listDatasets(request).iterateAll()) { - System.out.println(dataset); - } - client.close(); - } -} diff --git a/automl/src/main/java/beta/automl/TablesBatchPredictBigQuery.java b/automl/src/main/java/beta/automl/TablesBatchPredictBigQuery.java deleted file mode 100644 index b0bc37f262e..00000000000 --- a/automl/src/main/java/beta/automl/TablesBatchPredictBigQuery.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -// [START automl_tables_batch_predict_bq] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1beta1.BatchPredictInputConfig; -import com.google.cloud.automl.v1beta1.BatchPredictOutputConfig; -import com.google.cloud.automl.v1beta1.BatchPredictRequest; -import com.google.cloud.automl.v1beta1.BatchPredictResult; -import com.google.cloud.automl.v1beta1.BigQueryDestination; -import com.google.cloud.automl.v1beta1.BigQuerySource; -import com.google.cloud.automl.v1beta1.ModelName; -import com.google.cloud.automl.v1beta1.OperationMetadata; -import com.google.cloud.automl.v1beta1.PredictionServiceClient; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -abstract class TablesBatchPredictBigQuery { - - static void batchPredict() throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - String inputUri = "bq://YOUR_PROJECT_ID.bqDatasetID.bqTableId"; - String outputUri = "bq://YOUR_PROJECT_ID"; - batchPredict(projectId, modelId, inputUri, outputUri); - } - - static void batchPredict(String projectId, String modelId, String inputUri, String outputUri) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (PredictionServiceClient client = PredictionServiceClient.create()) { - // Get the full path of the model. - ModelName name = ModelName.of(projectId, "us-central1", modelId); - - // Configure the source of the file from BigQuery - BigQuerySource bigQuerySource = BigQuerySource.newBuilder().setInputUri(inputUri).build(); - BatchPredictInputConfig inputConfig = - BatchPredictInputConfig.newBuilder().setBigquerySource(bigQuerySource).build(); - - // Configure where to store the output in BigQuery - BigQueryDestination bigQueryDestination = - BigQueryDestination.newBuilder().setOutputUri(outputUri).build(); - BatchPredictOutputConfig outputConfig = - BatchPredictOutputConfig.newBuilder().setBigqueryDestination(bigQueryDestination).build(); - - // Build the request that will be sent to the API - BatchPredictRequest request = - BatchPredictRequest.newBuilder() - .setName(name.toString()) - .setInputConfig(inputConfig) - .setOutputConfig(outputConfig) - .build(); - - // Start an asynchronous request - OperationFuture future = - client.batchPredictAsync(request); - - System.out.println("Waiting for operation to complete..."); - future.get(); - System.out.println("Batch Prediction results saved to BigQuery."); - } - } -} -// [END automl_tables_batch_predict_bq] diff --git a/automl/src/main/java/beta/automl/TablesCreateDataset.java b/automl/src/main/java/beta/automl/TablesCreateDataset.java deleted file mode 100644 index b5390f33705..00000000000 --- a/automl/src/main/java/beta/automl/TablesCreateDataset.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -// [START automl_tables_create_dataset] -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.cloud.automl.v1beta1.Dataset; -import com.google.cloud.automl.v1beta1.LocationName; -import com.google.cloud.automl.v1beta1.TablesDatasetMetadata; -import java.io.IOException; - -class TablesCreateDataset { - - public static void main(String[] args) throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String displayName = "YOUR_DATASET_NAME"; - createDataset(projectId, displayName); - } - - // Create a dataset - static void createDataset(String projectId, String displayName) throws IOException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "us-central1"); - TablesDatasetMetadata metadata = TablesDatasetMetadata.newBuilder().build(); - Dataset dataset = - Dataset.newBuilder() - .setDisplayName(displayName) - .setTablesDatasetMetadata(metadata) - .build(); - - Dataset createdDataset = client.createDataset(projectLocation, dataset); - - // Display the dataset information. - System.out.format("Dataset name: %s%n", createdDataset.getName()); - // To get the dataset id, you have to parse it out of the `name` field. As dataset Ids are - // required for other methods. - // Name Form: `projects/{project_id}/locations/{location_id}/datasets/{dataset_id}` - String[] names = createdDataset.getName().split("/"); - String datasetId = names[names.length - 1]; - System.out.format("Dataset id: %s%n", datasetId); - } - } -} -// [END automl_tables_create_dataset] diff --git a/automl/src/main/java/beta/automl/TablesCreateModel.java b/automl/src/main/java/beta/automl/TablesCreateModel.java deleted file mode 100644 index 9c95119e17a..00000000000 --- a/automl/src/main/java/beta/automl/TablesCreateModel.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -// [START automl_tables_create_model] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.cloud.automl.v1beta1.ColumnSpec; -import com.google.cloud.automl.v1beta1.ColumnSpecName; -import com.google.cloud.automl.v1beta1.LocationName; -import com.google.cloud.automl.v1beta1.Model; -import com.google.cloud.automl.v1beta1.OperationMetadata; -import com.google.cloud.automl.v1beta1.TablesModelMetadata; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class TablesCreateModel { - - public static void main(String[] args) - throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String datasetId = "YOUR_DATASET_ID"; - String tableSpecId = "YOUR_TABLE_SPEC_ID"; - String columnSpecId = "YOUR_COLUMN_SPEC_ID"; - String displayName = "YOUR_DATASET_NAME"; - createModel(projectId, datasetId, tableSpecId, columnSpecId, displayName); - } - - // Create a model - static void createModel( - String projectId, - String datasetId, - String tableSpecId, - String columnSpecId, - String displayName) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "us-central1"); - - // Get the complete path of the column. - ColumnSpecName columnSpecName = - ColumnSpecName.of(projectId, "us-central1", datasetId, tableSpecId, columnSpecId); - - // Build the get column spec. - ColumnSpec targetColumnSpec = - ColumnSpec.newBuilder().setName(columnSpecName.toString()).build(); - - // Set model metadata. - TablesModelMetadata metadata = - TablesModelMetadata.newBuilder() - .setTargetColumnSpec(targetColumnSpec) - .setTrainBudgetMilliNodeHours(24000) - .build(); - - Model model = - Model.newBuilder() - .setDisplayName(displayName) - .setDatasetId(datasetId) - .setTablesModelMetadata(metadata) - .build(); - - // Create a model with the model metadata in the region. - OperationFuture future = - client.createModelAsync(projectLocation, model); - // OperationFuture.get() will block until the model is created, which may take several hours. - // You can use OperationFuture.getInitialFuture to get a future representing the initial - // response to the request, which contains information while the operation is in progress. - System.out.format("Training operation name: %s%n", future.getInitialFuture().get().getName()); - System.out.println("Training started..."); - } - } -} -// [END automl_tables_create_model] diff --git a/automl/src/main/java/beta/automl/TablesGetModel.java b/automl/src/main/java/beta/automl/TablesGetModel.java deleted file mode 100644 index 443491252b5..00000000000 --- a/automl/src/main/java/beta/automl/TablesGetModel.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -// [START automl_tables_get_model] - -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.cloud.automl.v1beta1.Model; -import com.google.cloud.automl.v1beta1.ModelName; -import com.google.cloud.automl.v1beta1.TablesModelColumnInfo; -import io.grpc.StatusRuntimeException; -import java.io.IOException; -import java.text.DateFormat; -import java.text.SimpleDateFormat; - -public class TablesGetModel { - - public static void main(String[] args) throws IOException, StatusRuntimeException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String region = "YOUR_REGION"; - String modelId = "YOUR_MODEL_ID"; - getModel(projectId, region, modelId); - } - - // Demonstrates using the AutoML client to get model details. - public static void getModel(String projectId, String computeRegion, String modelId) - throws IOException, StatusRuntimeException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - - // Get the full path of the model. - ModelName modelFullId = ModelName.of(projectId, computeRegion, modelId); - - // Get complete detail of the model. - Model model = client.getModel(modelFullId); - - // Display the model information. - System.out.format("Model name: %s%n", model.getName()); - System.out.format( - "Model Id: %s\n", model.getName().split("/")[model.getName().split("/").length - 1]); - System.out.format("Model display name: %s%n", model.getDisplayName()); - System.out.format("Dataset Id: %s%n", model.getDatasetId()); - System.out.println("Tables Model Metadata: "); - System.out.format( - "\tTraining budget: %s%n", model.getTablesModelMetadata().getTrainBudgetMilliNodeHours()); - System.out.format( - "\tTraining cost: %s%n", model.getTablesModelMetadata().getTrainBudgetMilliNodeHours()); - - DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); - String createTime = - dateFormat.format(new java.util.Date(model.getCreateTime().getSeconds() * 1000)); - System.out.format("Model create time: %s%n", createTime); - - System.out.format("Model deployment state: %s%n", model.getDeploymentState()); - - // Get features of top importance - for (TablesModelColumnInfo info : - model.getTablesModelMetadata().getTablesModelColumnInfoList()) { - System.out.format( - "Column: %s - Importance: %.2f%n", - info.getColumnDisplayName(), info.getFeatureImportance()); - } - } - } -} -// [END automl_tables_get_model] diff --git a/automl/src/main/java/beta/automl/TablesImportDataset.java b/automl/src/main/java/beta/automl/TablesImportDataset.java deleted file mode 100644 index ce0e5fc16f0..00000000000 --- a/automl/src/main/java/beta/automl/TablesImportDataset.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -// [START automl_tables_import_data] -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.cloud.automl.v1beta1.BigQuerySource; -import com.google.cloud.automl.v1beta1.DatasetName; -import com.google.cloud.automl.v1beta1.GcsSource; -import com.google.cloud.automl.v1beta1.InputConfig; -import com.google.protobuf.Empty; -import java.io.IOException; -import java.util.Arrays; -import java.util.concurrent.ExecutionException; - -class TablesImportDataset { - - public static void main(String[] args) - throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String datasetId = "YOUR_DATASET_ID"; - String path = "gs://BUCKET_ID/path/to//data.csv or bq://project_id.dataset_id.table_id"; - importDataset(projectId, datasetId, path); - } - - // Import a dataset via BigQuery or Google Cloud Storage - static void importDataset(String projectId, String datasetId, String path) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // Get the complete path of the dataset. - DatasetName datasetFullId = DatasetName.of(projectId, "us-central1", datasetId); - - InputConfig.Builder inputConfigBuilder = InputConfig.newBuilder(); - - // Determine which source type was used for the input path (BigQuery or GCS) - if (path.startsWith("bq")) { - // Get training data file to be imported from a BigQuery source. - BigQuerySource.Builder bigQuerySource = BigQuerySource.newBuilder(); - bigQuerySource.setInputUri(path); - inputConfigBuilder.setBigquerySource(bigQuerySource); - } else { - // Get multiple Google Cloud Storage URIs to import data from - GcsSource gcsSource = - GcsSource.newBuilder().addAllInputUris(Arrays.asList(path.split(","))).build(); - inputConfigBuilder.setGcsSource(gcsSource); - } - - // Import data from the input URI - System.out.println("Processing import..."); - - Empty response = client.importDataAsync(datasetFullId, inputConfigBuilder.build()).get(); - System.out.format("Dataset imported. %s%n", response); - } - } -} -// [END automl_tables_import_data] diff --git a/automl/src/main/java/beta/automl/TablesPredict.java b/automl/src/main/java/beta/automl/TablesPredict.java deleted file mode 100644 index fd2257c899e..00000000000 --- a/automl/src/main/java/beta/automl/TablesPredict.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -// [START automl_tables_predict] -import com.google.cloud.automl.v1beta1.AnnotationPayload; -import com.google.cloud.automl.v1beta1.ExamplePayload; -import com.google.cloud.automl.v1beta1.ModelName; -import com.google.cloud.automl.v1beta1.PredictRequest; -import com.google.cloud.automl.v1beta1.PredictResponse; -import com.google.cloud.automl.v1beta1.PredictionServiceClient; -import com.google.cloud.automl.v1beta1.Row; -import com.google.cloud.automl.v1beta1.TablesAnnotation; -import com.google.protobuf.Value; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -class TablesPredict { - - public static void main(String[] args) throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - // Values should match the input expected by your model. - List values = new ArrayList<>(); - // values.add(Value.newBuilder().setBoolValue(true).build()); - // values.add(Value.newBuilder().setNumberValue(10).build()); - // values.add(Value.newBuilder().setStringValue("YOUR_STRING").build()); - predict(projectId, modelId, values); - } - - static void predict(String projectId, String modelId, List values) throws IOException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (PredictionServiceClient client = PredictionServiceClient.create()) { - // Get the full path of the model. - ModelName name = ModelName.of(projectId, "us-central1", modelId); - Row row = Row.newBuilder().addAllValues(values).build(); - ExamplePayload payload = ExamplePayload.newBuilder().setRow(row).build(); - - // Feature importance gives you visibility into how the features in a specific prediction - // request informed the resulting prediction. For more info, see: - // https://cloud.google.com/automl-tables/docs/features#local - PredictRequest request = - PredictRequest.newBuilder() - .setName(name.toString()) - .setPayload(payload) - .putParams("feature_importance", "true") - .build(); - - PredictResponse response = client.predict(request); - - System.out.println("Prediction results:"); - for (AnnotationPayload annotationPayload : response.getPayloadList()) { - TablesAnnotation tablesAnnotation = annotationPayload.getTables(); - System.out.format( - "Classification label: %s%n", tablesAnnotation.getValue().getStringValue()); - System.out.format("Classification score: %.3f%n", tablesAnnotation.getScore()); - // Get features of top importance - tablesAnnotation - .getTablesModelColumnInfoList() - .forEach( - info -> - System.out.format( - "\tColumn: %s - Importance: %.2f%n", - info.getColumnDisplayName(), info.getFeatureImportance())); - } - } - } -} -// [END automl_tables_predict] diff --git a/automl/src/main/java/beta/automl/UndeployModel.java b/automl/src/main/java/beta/automl/UndeployModel.java deleted file mode 100644 index 7325f519dba..00000000000 --- a/automl/src/main/java/beta/automl/UndeployModel.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -// [START automl_tables_undeploy_model] -// [START automl_undeploy_model_beta] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.cloud.automl.v1beta1.ModelName; -import com.google.cloud.automl.v1beta1.OperationMetadata; -import com.google.cloud.automl.v1beta1.UndeployModelRequest; -import com.google.protobuf.Empty; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class UndeployModel { - - static void undeployModel() throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - undeployModel(projectId, modelId); - } - - // Undeploy a model from prediction - static void undeployModel(String projectId, String modelId) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // Get the full path of the model. - ModelName modelFullId = ModelName.of(projectId, "us-central1", modelId); - UndeployModelRequest request = - UndeployModelRequest.newBuilder().setName(modelFullId.toString()).build(); - OperationFuture future = client.undeployModelAsync(request); - - future.get(); - System.out.println("Model undeployment finished"); - } - } -} -// [END automl_undeploy_model_beta] -// [END automl_tables_undeploy_model] diff --git a/automl/src/main/java/beta/automl/VideoClassificationCreateDataset.java b/automl/src/main/java/beta/automl/VideoClassificationCreateDataset.java deleted file mode 100644 index 1fe70c9f2b2..00000000000 --- a/automl/src/main/java/beta/automl/VideoClassificationCreateDataset.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -// [START automl_video_classification_create_dataset_beta] -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.cloud.automl.v1beta1.Dataset; -import com.google.cloud.automl.v1beta1.LocationName; -import com.google.cloud.automl.v1beta1.VideoClassificationDatasetMetadata; -import java.io.IOException; - -class VideoClassificationCreateDataset { - - public static void main(String[] args) throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String displayName = "YOUR_DATASET_NAME"; - createDataset(projectId, displayName); - } - - // Create a dataset - static void createDataset(String projectId, String displayName) throws IOException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "us-central1"); - VideoClassificationDatasetMetadata metadata = - VideoClassificationDatasetMetadata.newBuilder().build(); - Dataset dataset = - Dataset.newBuilder() - .setDisplayName(displayName) - .setVideoClassificationDatasetMetadata(metadata) - .build(); - - Dataset createdDataset = client.createDataset(projectLocation, dataset); - - // Display the dataset information. - System.out.format("Dataset name: %s%n", createdDataset.getName()); - // To get the dataset id, you have to parse it out of the `name` field. As dataset Ids are - // required for other methods. - // Name Form: `projects/{project_id}/locations/{location_id}/datasets/{dataset_id}` - String[] names = createdDataset.getName().split("/"); - String datasetId = names[names.length - 1]; - System.out.format("Dataset id: %s%n", datasetId); - } - } -} -// [END automl_video_classification_create_dataset_beta] diff --git a/automl/src/main/java/beta/automl/VideoClassificationCreateModel.java b/automl/src/main/java/beta/automl/VideoClassificationCreateModel.java deleted file mode 100644 index 94f3407d442..00000000000 --- a/automl/src/main/java/beta/automl/VideoClassificationCreateModel.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -// [START automl_video_classification_create_model_beta] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.cloud.automl.v1beta1.LocationName; -import com.google.cloud.automl.v1beta1.Model; -import com.google.cloud.automl.v1beta1.OperationMetadata; -import com.google.cloud.automl.v1beta1.VideoClassificationModelMetadata; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class VideoClassificationCreateModel { - - public static void main(String[] args) - throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String datasetId = "YOUR_DATASET_ID"; - String displayName = "YOUR_DATASET_NAME"; - createModel(projectId, datasetId, displayName); - } - - // Create a model - static void createModel(String projectId, String datasetId, String displayName) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "us-central1"); - // Set model metadata. - VideoClassificationModelMetadata metadata = - VideoClassificationModelMetadata.newBuilder().build(); - Model model = - Model.newBuilder() - .setDisplayName(displayName) - .setDatasetId(datasetId) - .setVideoClassificationModelMetadata(metadata) - .build(); - - // Create a model with the model metadata in the region. - OperationFuture future = - client.createModelAsync(projectLocation, model); - // OperationFuture.get() will block until the model is created, which may take several hours. - // You can use OperationFuture.getInitialFuture to get a future representing the initial - // response to the request, which contains information while the operation is in progress. - System.out.format("Training operation name: %s%n", future.getInitialFuture().get().getName()); - System.out.println("Training started..."); - } - } -} -// [END automl_video_classification_create_model_beta] diff --git a/automl/src/main/java/beta/automl/VideoObjectTrackingCreateDataset.java b/automl/src/main/java/beta/automl/VideoObjectTrackingCreateDataset.java deleted file mode 100644 index 61b2a1ada3a..00000000000 --- a/automl/src/main/java/beta/automl/VideoObjectTrackingCreateDataset.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -// [START automl_video_object_tracking_create_dataset_beta] -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.cloud.automl.v1beta1.Dataset; -import com.google.cloud.automl.v1beta1.LocationName; -import com.google.cloud.automl.v1beta1.VideoObjectTrackingDatasetMetadata; -import java.io.IOException; - -class VideoObjectTrackingCreateDataset { - - public static void main(String[] args) throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String displayName = "YOUR_DATASET_NAME"; - createDataset(projectId, displayName); - } - - // Create a dataset - static void createDataset(String projectId, String displayName) throws IOException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "us-central1"); - VideoObjectTrackingDatasetMetadata metadata = - VideoObjectTrackingDatasetMetadata.newBuilder().build(); - Dataset dataset = - Dataset.newBuilder() - .setDisplayName(displayName) - .setVideoObjectTrackingDatasetMetadata(metadata) - .build(); - - Dataset createdDataset = client.createDataset(projectLocation, dataset); - - // Display the dataset information. - System.out.format("Dataset name: %s%n", createdDataset.getName()); - // To get the dataset id, you have to parse it out of the `name` field. As dataset Ids are - // required for other methods. - // Name Form: `projects/{project_id}/locations/{location_id}/datasets/{dataset_id}` - String[] names = createdDataset.getName().split("/"); - String datasetId = names[names.length - 1]; - System.out.format("Dataset id: %s%n", datasetId); - } - } -} -// [END automl_video_object_tracking_create_dataset_beta] diff --git a/automl/src/main/java/beta/automl/VideoObjectTrackingCreateModel.java b/automl/src/main/java/beta/automl/VideoObjectTrackingCreateModel.java deleted file mode 100644 index 29df7866613..00000000000 --- a/automl/src/main/java/beta/automl/VideoObjectTrackingCreateModel.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -// [START automl_video_object_tracking_create_model_beta] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.cloud.automl.v1beta1.LocationName; -import com.google.cloud.automl.v1beta1.Model; -import com.google.cloud.automl.v1beta1.OperationMetadata; -import com.google.cloud.automl.v1beta1.VideoObjectTrackingModelMetadata; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class VideoObjectTrackingCreateModel { - - public static void main(String[] args) - throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String datasetId = "YOUR_DATASET_ID"; - String displayName = "YOUR_DATASET_NAME"; - createModel(projectId, datasetId, displayName); - } - - // Create a model - static void createModel(String projectId, String datasetId, String displayName) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "us-central1"); - // Set model metadata. - VideoObjectTrackingModelMetadata metadata = - VideoObjectTrackingModelMetadata.newBuilder().build(); - Model model = - Model.newBuilder() - .setDisplayName(displayName) - .setDatasetId(datasetId) - .setVideoObjectTrackingModelMetadata(metadata) - .build(); - - // Create a model with the model metadata in the region. - OperationFuture future = - client.createModelAsync(projectLocation, model); - // OperationFuture.get() will block until the model is created, which may take several hours. - // You can use OperationFuture.getInitialFuture to get a future representing the initial - // response to the request, which contains information while the operation is in progress. - System.out.format("Training operation name: %s%n", future.getInitialFuture().get().getName()); - System.out.println("Training started..."); - } - } -} -// [END automl_video_object_tracking_create_model_beta] diff --git a/automl/src/main/java/com/example/automl/BatchPredict.java b/automl/src/main/java/com/example/automl/BatchPredict.java deleted file mode 100644 index 03a0055155c..00000000000 --- a/automl/src/main/java/com/example/automl/BatchPredict.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_batch_predict] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1.BatchPredictInputConfig; -import com.google.cloud.automl.v1.BatchPredictOutputConfig; -import com.google.cloud.automl.v1.BatchPredictRequest; -import com.google.cloud.automl.v1.BatchPredictResult; -import com.google.cloud.automl.v1.GcsDestination; -import com.google.cloud.automl.v1.GcsSource; -import com.google.cloud.automl.v1.ModelName; -import com.google.cloud.automl.v1.OperationMetadata; -import com.google.cloud.automl.v1.PredictionServiceClient; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -abstract class BatchPredict { - - static void batchPredict() throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - String inputUri = "gs://YOUR_BUCKET_ID/path_to_your_input_csv_or_jsonl"; - String outputUri = "gs://YOUR_BUCKET_ID/path_to_save_results/"; - batchPredict(projectId, modelId, inputUri, outputUri); - } - - static void batchPredict(String projectId, String modelId, String inputUri, String outputUri) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (PredictionServiceClient client = PredictionServiceClient.create()) { - // Get the full path of the model. - ModelName name = ModelName.of(projectId, "us-central1", modelId); - GcsSource gcsSource = GcsSource.newBuilder().addInputUris(inputUri).build(); - BatchPredictInputConfig inputConfig = - BatchPredictInputConfig.newBuilder().setGcsSource(gcsSource).build(); - GcsDestination gcsDestination = - GcsDestination.newBuilder().setOutputUriPrefix(outputUri).build(); - BatchPredictOutputConfig outputConfig = - BatchPredictOutputConfig.newBuilder().setGcsDestination(gcsDestination).build(); - BatchPredictRequest request = - BatchPredictRequest.newBuilder() - .setName(name.toString()) - .setInputConfig(inputConfig) - .setOutputConfig(outputConfig) - .build(); - - OperationFuture future = - client.batchPredictAsync(request); - - System.out.println("Waiting for operation to complete..."); - future.get(); - System.out.println("Batch Prediction results saved to specified Cloud Storage bucket."); - } - } -} -// [END automl_batch_predict] diff --git a/automl/src/main/java/com/example/automl/DeleteDataset.java b/automl/src/main/java/com/example/automl/DeleteDataset.java deleted file mode 100644 index fcb6e2e913c..00000000000 --- a/automl/src/main/java/com/example/automl/DeleteDataset.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_delete_dataset] -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.DatasetName; -import com.google.protobuf.Empty; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class DeleteDataset { - - static void deleteDataset() throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String datasetId = "YOUR_DATASET_ID"; - deleteDataset(projectId, datasetId); - } - - // Delete a dataset - static void deleteDataset(String projectId, String datasetId) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // Get the full path of the dataset. - DatasetName datasetFullId = DatasetName.of(projectId, "us-central1", datasetId); - Empty response = client.deleteDatasetAsync(datasetFullId).get(); - System.out.format("Dataset deleted. %s\n", response); - } - } -} -// [END automl_delete_dataset] diff --git a/automl/src/main/java/com/example/automl/DeleteModel.java b/automl/src/main/java/com/example/automl/DeleteModel.java deleted file mode 100644 index 66969ff75d4..00000000000 --- a/automl/src/main/java/com/example/automl/DeleteModel.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_delete_model] -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.ModelName; -import com.google.protobuf.Empty; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class DeleteModel { - - public static void main(String[] args) - throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - deleteModel(projectId, modelId); - } - - // Delete a model - static void deleteModel(String projectId, String modelId) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // Get the full path of the model. - ModelName modelFullId = ModelName.of(projectId, "us-central1", modelId); - - // Delete a model. - Empty response = client.deleteModelAsync(modelFullId).get(); - - System.out.println("Model deletion started..."); - System.out.println(String.format("Model deleted. %s", response)); - } - } -} -// [END automl_delete_model] diff --git a/automl/src/main/java/com/example/automl/DeployModel.java b/automl/src/main/java/com/example/automl/DeployModel.java deleted file mode 100644 index ecc338cf37e..00000000000 --- a/automl/src/main/java/com/example/automl/DeployModel.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_deploy_model] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.DeployModelRequest; -import com.google.cloud.automl.v1.ModelName; -import com.google.cloud.automl.v1.OperationMetadata; -import com.google.protobuf.Empty; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class DeployModel { - - public static void main(String[] args) - throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - deployModel(projectId, modelId); - } - - // Deploy a model for prediction - static void deployModel(String projectId, String modelId) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // Get the full path of the model. - ModelName modelFullId = ModelName.of(projectId, "us-central1", modelId); - DeployModelRequest request = - DeployModelRequest.newBuilder().setName(modelFullId.toString()).build(); - OperationFuture future = client.deployModelAsync(request); - - future.get(); - System.out.println("Model deployment finished"); - } - } -} -// [END automl_deploy_model] diff --git a/automl/src/main/java/com/example/automl/ExportDataset.java b/automl/src/main/java/com/example/automl/ExportDataset.java deleted file mode 100644 index 812869b9a56..00000000000 --- a/automl/src/main/java/com/example/automl/ExportDataset.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_export_dataset] -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.DatasetName; -import com.google.cloud.automl.v1.GcsDestination; -import com.google.cloud.automl.v1.OutputConfig; -import com.google.protobuf.Empty; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class ExportDataset { - - static void exportDataset() throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String datasetId = "YOUR_DATASET_ID"; - String gcsUri = "gs://BUCKET_ID/path_to_export/"; - exportDataset(projectId, datasetId, gcsUri); - } - - // Export a dataset to a GCS bucket - static void exportDataset(String projectId, String datasetId, String gcsUri) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // Get the complete path of the dataset. - DatasetName datasetFullId = DatasetName.of(projectId, "us-central1", datasetId); - GcsDestination gcsDestination = - GcsDestination.newBuilder().setOutputUriPrefix(gcsUri).build(); - - // Export the dataset to the output URI. - OutputConfig outputConfig = - OutputConfig.newBuilder().setGcsDestination(gcsDestination).build(); - - System.out.println("Processing export..."); - Empty response = client.exportDataAsync(datasetFullId, outputConfig).get(); - System.out.format("Dataset exported. %s\n", response); - } - } -} -// [END automl_export_dataset] diff --git a/automl/src/main/java/com/example/automl/GetDataset.java b/automl/src/main/java/com/example/automl/GetDataset.java deleted file mode 100644 index e1028d7052d..00000000000 --- a/automl/src/main/java/com/example/automl/GetDataset.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_language_entity_extraction_get_dataset] -// [START automl_language_sentiment_analysis_get_dataset] -// [START automl_language_text_classification_get_dataset] -// [START automl_translate_get_dataset] -// [START automl_vision_classification_get_dataset] -// [START automl_vision_object_detection_get_dataset] -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.Dataset; -import com.google.cloud.automl.v1.DatasetName; -import java.io.IOException; - -class GetDataset { - - static void getDataset() throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String datasetId = "YOUR_DATASET_ID"; - getDataset(projectId, datasetId); - } - - // Get a dataset - static void getDataset(String projectId, String datasetId) throws IOException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // Get the complete path of the dataset. - DatasetName datasetFullId = DatasetName.of(projectId, "us-central1", datasetId); - Dataset dataset = client.getDataset(datasetFullId); - - // Display the dataset information - System.out.format("Dataset name: %s\n", dataset.getName()); - // To get the dataset id, you have to parse it out of the `name` field. As dataset Ids are - // required for other methods. - // Name Form: `projects/{project_id}/locations/{location_id}/datasets/{dataset_id}` - String[] names = dataset.getName().split("/"); - String retrievedDatasetId = names[names.length - 1]; - System.out.format("Dataset id: %s\n", retrievedDatasetId); - System.out.format("Dataset display name: %s\n", dataset.getDisplayName()); - System.out.println("Dataset create time:"); - System.out.format("\tseconds: %s\n", dataset.getCreateTime().getSeconds()); - System.out.format("\tnanos: %s\n", dataset.getCreateTime().getNanos()); - // [END automl_language_sentiment_analysis_get_dataset] - // [END automl_language_text_classification_get_dataset] - // [END automl_translate_get_dataset] - // [END automl_vision_classification_get_dataset] - // [END automl_vision_object_detection_get_dataset] - System.out.format( - "Text extraction dataset metadata: %s\n", dataset.getTextExtractionDatasetMetadata()); - // [END automl_language_entity_extraction_get_dataset] - - // [START automl_language_sentiment_analysis_get_dataset] - System.out.format( - "Text sentiment dataset metadata: %s\n", dataset.getTextSentimentDatasetMetadata()); - // [END automl_language_sentiment_analysis_get_dataset] - - // [START automl_language_text_classification_get_dataset] - System.out.format( - "Text classification dataset metadata: %s\n", - dataset.getTextClassificationDatasetMetadata()); - // [END automl_language_text_classification_get_dataset] - - // [START automl_translate_get_dataset] - System.out.println("Translation dataset metadata:"); - System.out.format( - "\tSource language code: %s\n", - dataset.getTranslationDatasetMetadata().getSourceLanguageCode()); - System.out.format( - "\tTarget language code: %s\n", - dataset.getTranslationDatasetMetadata().getTargetLanguageCode()); - // [END automl_translate_get_dataset] - - // [START automl_vision_classification_get_dataset] - System.out.format( - "Image classification dataset metadata: %s\n", - dataset.getImageClassificationDatasetMetadata()); - // [END automl_vision_classification_get_dataset] - - // [START automl_vision_object_detection_get_dataset] - System.out.format( - "Image object detection dataset metadata: %s\n", - dataset.getImageObjectDetectionDatasetMetadata()); - // [START automl_language_entity_extraction_get_dataset] - // [START automl_language_sentiment_analysis_get_dataset] - // [START automl_language_text_classification_get_dataset] - // [START automl_translate_get_dataset] - // [START automl_vision_classification_get_dataset] - } - } -} -// [END automl_language_entity_extraction_get_dataset] -// [END automl_language_sentiment_analysis_get_dataset] -// [END automl_language_text_classification_get_dataset] -// [END automl_translate_get_dataset] -// [END automl_vision_classification_get_dataset] -// [END automl_vision_object_detection_get_dataset] diff --git a/automl/src/main/java/com/example/automl/GetModel.java b/automl/src/main/java/com/example/automl/GetModel.java deleted file mode 100644 index 70b97905c2e..00000000000 --- a/automl/src/main/java/com/example/automl/GetModel.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_get_model] -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.Model; -import com.google.cloud.automl.v1.ModelName; -import java.io.IOException; - -class GetModel { - - static void getModel() throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - getModel(projectId, modelId); - } - - // Get a model - static void getModel(String projectId, String modelId) throws IOException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // Get the full path of the model. - ModelName modelFullId = ModelName.of(projectId, "us-central1", modelId); - Model model = client.getModel(modelFullId); - - // Display the model information. - System.out.format("Model name: %s\n", model.getName()); - // To get the model id, you have to parse it out of the `name` field. As models Ids are - // required for other methods. - // Name Format: `projects/{project_id}/locations/{location_id}/models/{model_id}` - String[] names = model.getName().split("/"); - String retrievedModelId = names[names.length - 1]; - System.out.format("Model id: %s\n", retrievedModelId); - System.out.format("Model display name: %s\n", model.getDisplayName()); - System.out.println("Model create time:"); - System.out.format("\tseconds: %s\n", model.getCreateTime().getSeconds()); - System.out.format("\tnanos: %s\n", model.getCreateTime().getNanos()); - System.out.format("Model deployment state: %s\n", model.getDeploymentState()); - } - } -} -// [END automl_get_model] diff --git a/automl/src/main/java/com/example/automl/GetModelEvaluation.java b/automl/src/main/java/com/example/automl/GetModelEvaluation.java deleted file mode 100644 index 694888d53fa..00000000000 --- a/automl/src/main/java/com/example/automl/GetModelEvaluation.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_language_entity_extraction_get_model_evaluation] -// [START automl_language_sentiment_analysis_get_model_evaluation] -// [START automl_language_text_classification_get_model_evaluation] -// [START automl_translate_get_model_evaluation] -// [START automl_vision_classification_get_model_evaluation] -// [START automl_vision_object_detection_get_model_evaluation] - -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.ModelEvaluation; -import com.google.cloud.automl.v1.ModelEvaluationName; -import java.io.IOException; - -class GetModelEvaluation { - - static void getModelEvaluation() throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - String modelEvaluationId = "YOUR_MODEL_EVALUATION_ID"; - getModelEvaluation(projectId, modelId, modelEvaluationId); - } - - // Get a model evaluation - static void getModelEvaluation(String projectId, String modelId, String modelEvaluationId) - throws IOException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // Get the full path of the model evaluation. - ModelEvaluationName modelEvaluationFullId = - ModelEvaluationName.of(projectId, "us-central1", modelId, modelEvaluationId); - - // Get complete detail of the model evaluation. - ModelEvaluation modelEvaluation = client.getModelEvaluation(modelEvaluationFullId); - - System.out.format("Model Evaluation Name: %s\n", modelEvaluation.getName()); - System.out.format("Model Annotation Spec Id: %s", modelEvaluation.getAnnotationSpecId()); - System.out.println("Create Time:"); - System.out.format("\tseconds: %s\n", modelEvaluation.getCreateTime().getSeconds()); - System.out.format("\tnanos: %s", modelEvaluation.getCreateTime().getNanos() / 1e9); - System.out.format( - "Evalution Example Count: %d\n", modelEvaluation.getEvaluatedExampleCount()); - // [END automl_language_sentiment_analysis_get_model_evaluation] - // [END automl_language_text_classification_get_model_evaluation] - // [END automl_translate_get_model_evaluation] - // [END automl_vision_classification_get_model_evaluation] - // [END automl_vision_object_detection_get_model_evaluation] - System.out.format( - "Entity Extraction Model Evaluation Metrics: %s\n", - modelEvaluation.getTextExtractionEvaluationMetrics()); - // [END automl_language_entity_extraction_get_model_evaluation] - - // [START automl_language_sentiment_analysis_get_model_evaluation] - System.out.format( - "Sentiment Analysis Model Evaluation Metrics: %s\n", - modelEvaluation.getTextSentimentEvaluationMetrics()); - // [END automl_language_sentiment_analysis_get_model_evaluation] - - // [START automl_language_text_classification_get_model_evaluation] - // [START automl_vision_classification_get_model_evaluation] - System.out.format( - "Classification Model Evaluation Metrics: %s\n", - modelEvaluation.getClassificationEvaluationMetrics()); - // [END automl_language_text_classification_get_model_evaluation] - // [END automl_vision_classification_get_model_evaluation] - - // [START automl_translate_get_model_evaluation] - System.out.format( - "Translate Model Evaluation Metrics: %s\n", - modelEvaluation.getTranslationEvaluationMetrics()); - // [END automl_translate_get_model_evaluation] - - // [START automl_vision_object_detection_get_model_evaluation] - System.out.format( - "Object Detection Model Evaluation Metrics: %s\n", - modelEvaluation.getImageObjectDetectionEvaluationMetrics()); - // [START automl_language_entity_extraction_get_model_evaluation] - // [START automl_language_sentiment_analysis_get_model_evaluation] - // [START automl_language_text_classification_get_model_evaluation] - // [START automl_translate_get_model_evaluation] - // [START automl_vision_classification_get_model_evaluation] - } - } -} -// [END automl_language_entity_extraction_get_model_evaluation] -// [END automl_language_sentiment_analysis_get_model_evaluation] -// [END automl_language_text_classification_get_model_evaluation] -// [END automl_translate_get_model_evaluation] -// [END automl_vision_classification_get_model_evaluation] -// [END automl_vision_object_detection_get_model_evaluation] diff --git a/automl/src/main/java/com/example/automl/GetOperationStatus.java b/automl/src/main/java/com/example/automl/GetOperationStatus.java deleted file mode 100644 index 07bfe02933d..00000000000 --- a/automl/src/main/java/com/example/automl/GetOperationStatus.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_get_operation_status] -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.longrunning.Operation; -import java.io.IOException; - -class GetOperationStatus { - - static void getOperationStatus() throws IOException { - // TODO(developer): Replace these variables before running the sample. - String operationFullId = "projects/[projectId]/locations/us-central1/operations/[operationId]"; - getOperationStatus(operationFullId); - } - - // Get the status of an operation - static void getOperationStatus(String operationFullId) throws IOException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // Get the latest state of a long-running operation. - Operation operation = client.getOperationsClient().getOperation(operationFullId); - - // Display operation details. - System.out.println("Operation details:"); - System.out.format("\tName: %s\n", operation.getName()); - System.out.format("\tMetadata Type Url: %s\n", operation.getMetadata().getTypeUrl()); - System.out.format("\tDone: %s\n", operation.getDone()); - if (operation.hasResponse()) { - System.out.format("\tResponse Type Url: %s\n", operation.getResponse().getTypeUrl()); - } - if (operation.hasError()) { - System.out.println("\tResponse:"); - System.out.format("\t\tError code: %s\n", operation.getError().getCode()); - System.out.format("\t\tError message: %s\n", operation.getError().getMessage()); - } - } - } -} -// [END automl_get_operation_status] diff --git a/automl/src/main/java/com/example/automl/ImportDataset.java b/automl/src/main/java/com/example/automl/ImportDataset.java deleted file mode 100644 index 3ead88326b3..00000000000 --- a/automl/src/main/java/com/example/automl/ImportDataset.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_import_dataset] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.DatasetName; -import com.google.cloud.automl.v1.GcsSource; -import com.google.cloud.automl.v1.InputConfig; -import com.google.cloud.automl.v1.OperationMetadata; -import com.google.protobuf.Empty; -import java.io.IOException; -import java.util.Arrays; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -class ImportDataset { - - public static void main(String[] args) - throws IOException, ExecutionException, InterruptedException, TimeoutException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String datasetId = "YOUR_DATASET_ID"; - String path = "gs://BUCKET_ID/path_to_training_data.csv"; - importDataset(projectId, datasetId, path); - } - - // Import a dataset - static void importDataset(String projectId, String datasetId, String path) - throws IOException, ExecutionException, InterruptedException, TimeoutException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // Get the complete path of the dataset. - DatasetName datasetFullId = DatasetName.of(projectId, "us-central1", datasetId); - - // Get multiple Google Cloud Storage URIs to import data from - GcsSource gcsSource = - GcsSource.newBuilder().addAllInputUris(Arrays.asList(path.split(","))).build(); - - // Import data from the input URI - InputConfig inputConfig = InputConfig.newBuilder().setGcsSource(gcsSource).build(); - System.out.println("Processing import..."); - - // Start the import job - OperationFuture operation = - client.importDataAsync(datasetFullId, inputConfig); - - System.out.format("Operation name: %s%n", operation.getName()); - - // If you want to wait for the operation to finish, adjust the timeout appropriately. The - // operation will still run if you choose not to wait for it to complete. You can check the - // status of your operation using the operation's name. - Empty response = operation.get(45, TimeUnit.MINUTES); - System.out.format("Dataset imported. %s%n", response); - } catch (TimeoutException e) { - System.out.println("The operation's polling period was not long enough."); - System.out.println("You can use the Operation's name to get the current status."); - System.out.println("The import job is still running and will complete as expected."); - throw e; - } - } -} -// [END automl_import_dataset] diff --git a/automl/src/main/java/com/example/automl/LanguageEntityExtractionCreateDataset.java b/automl/src/main/java/com/example/automl/LanguageEntityExtractionCreateDataset.java deleted file mode 100644 index 7adf18dc623..00000000000 --- a/automl/src/main/java/com/example/automl/LanguageEntityExtractionCreateDataset.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_language_entity_extraction_create_dataset] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.Dataset; -import com.google.cloud.automl.v1.LocationName; -import com.google.cloud.automl.v1.OperationMetadata; -import com.google.cloud.automl.v1.TextExtractionDatasetMetadata; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class LanguageEntityExtractionCreateDataset { - - static void createDataset() throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String displayName = "YOUR_DATASET_NAME"; - createDataset(projectId, displayName); - } - - // Create a dataset - static void createDataset(String projectId, String displayName) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "us-central1"); - - TextExtractionDatasetMetadata metadata = TextExtractionDatasetMetadata.newBuilder().build(); - Dataset dataset = - Dataset.newBuilder() - .setDisplayName(displayName) - .setTextExtractionDatasetMetadata(metadata) - .build(); - OperationFuture future = - client.createDatasetAsync(projectLocation, dataset); - - Dataset createdDataset = future.get(); - - // Display the dataset information. - System.out.format("Dataset name: %s\n", createdDataset.getName()); - // To get the dataset id, you have to parse it out of the `name` field. As dataset Ids are - // required for other methods. - // Name Form: `projects/{project_id}/locations/{location_id}/datasets/{dataset_id}` - String[] names = createdDataset.getName().split("/"); - String datasetId = names[names.length - 1]; - System.out.format("Dataset id: %s\n", datasetId); - } - } -} -// [END automl_language_entity_extraction_create_dataset] diff --git a/automl/src/main/java/com/example/automl/LanguageEntityExtractionCreateModel.java b/automl/src/main/java/com/example/automl/LanguageEntityExtractionCreateModel.java deleted file mode 100644 index f2da97894df..00000000000 --- a/automl/src/main/java/com/example/automl/LanguageEntityExtractionCreateModel.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_language_entity_extraction_create_model] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.LocationName; -import com.google.cloud.automl.v1.Model; -import com.google.cloud.automl.v1.OperationMetadata; -import com.google.cloud.automl.v1.TextExtractionModelMetadata; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class LanguageEntityExtractionCreateModel { - - static void createModel() throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String datasetId = "YOUR_DATASET_ID"; - String displayName = "YOUR_DATASET_NAME"; - createModel(projectId, datasetId, displayName); - } - - // Create a model - static void createModel(String projectId, String datasetId, String displayName) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "us-central1"); - // Set model metadata. - TextExtractionModelMetadata metadata = TextExtractionModelMetadata.newBuilder().build(); - Model model = - Model.newBuilder() - .setDisplayName(displayName) - .setDatasetId(datasetId) - .setTextExtractionModelMetadata(metadata) - .build(); - - // Create a model with the model metadata in the region. - OperationFuture future = - client.createModelAsync(projectLocation, model); - // OperationFuture.get() will block until the model is created, which may take several hours. - // You can use OperationFuture.getInitialFuture to get a future representing the initial - // response to the request, which contains information while the operation is in progress. - System.out.format("Training operation name: %s\n", future.getInitialFuture().get().getName()); - System.out.println("Training started..."); - } - } -} -// [END automl_language_entity_extraction_create_model] diff --git a/automl/src/main/java/com/example/automl/LanguageEntityExtractionPredict.java b/automl/src/main/java/com/example/automl/LanguageEntityExtractionPredict.java deleted file mode 100644 index 065990613b3..00000000000 --- a/automl/src/main/java/com/example/automl/LanguageEntityExtractionPredict.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_language_entity_extraction_predict] -import com.google.cloud.automl.v1.AnnotationPayload; -import com.google.cloud.automl.v1.ExamplePayload; -import com.google.cloud.automl.v1.ModelName; -import com.google.cloud.automl.v1.PredictRequest; -import com.google.cloud.automl.v1.PredictResponse; -import com.google.cloud.automl.v1.PredictionServiceClient; -import com.google.cloud.automl.v1.TextSegment; -import com.google.cloud.automl.v1.TextSnippet; -import java.io.IOException; - -class LanguageEntityExtractionPredict { - - static void predict() throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - String content = "text to predict"; - predict(projectId, modelId, content); - } - - static void predict(String projectId, String modelId, String content) throws IOException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (PredictionServiceClient client = PredictionServiceClient.create()) { - // Get the full path of the model. - ModelName name = ModelName.of(projectId, "us-central1", modelId); - - // For available mime types, see: - // https://cloud.google.com/automl/docs/reference/rest/v1/projects.locations.models/predict#textsnippet - TextSnippet textSnippet = - TextSnippet.newBuilder() - .setContent(content) - .setMimeType("text/plain") // Types: text/plain, text/html - .build(); - ExamplePayload payload = ExamplePayload.newBuilder().setTextSnippet(textSnippet).build(); - PredictRequest predictRequest = - PredictRequest.newBuilder().setName(name.toString()).setPayload(payload).build(); - - PredictResponse response = client.predict(predictRequest); - - for (AnnotationPayload annotationPayload : response.getPayloadList()) { - System.out.format("Text Extract Entity Type: %s\n", annotationPayload.getDisplayName()); - System.out.format("Text score: %.2f\n", annotationPayload.getTextExtraction().getScore()); - TextSegment textSegment = annotationPayload.getTextExtraction().getTextSegment(); - System.out.format("Text Extract Entity Content: %s\n", textSegment.getContent()); - System.out.format("Text Start Offset: %s\n", textSegment.getStartOffset()); - System.out.format("Text End Offset: %s\n\n", textSegment.getEndOffset()); - } - } - } -} -// [END automl_language_entity_extraction_predict] diff --git a/automl/src/main/java/com/example/automl/LanguageSentimentAnalysisCreateDataset.java b/automl/src/main/java/com/example/automl/LanguageSentimentAnalysisCreateDataset.java deleted file mode 100644 index 6ab0be85851..00000000000 --- a/automl/src/main/java/com/example/automl/LanguageSentimentAnalysisCreateDataset.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_language_sentiment_analysis_create_dataset] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.Dataset; -import com.google.cloud.automl.v1.LocationName; -import com.google.cloud.automl.v1.OperationMetadata; -import com.google.cloud.automl.v1.TextSentimentDatasetMetadata; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class LanguageSentimentAnalysisCreateDataset { - - static void createDataset() throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String displayName = "YOUR_DATASET_NAME"; - createDataset(projectId, displayName); - } - - // Create a dataset - static void createDataset(String projectId, String displayName) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "us-central1"); - // Specify the text classification type for the dataset. - TextSentimentDatasetMetadata metadata = - TextSentimentDatasetMetadata.newBuilder() - .setSentimentMax(4) // Possible max sentiment score: 1-10 - .build(); - Dataset dataset = - Dataset.newBuilder() - .setDisplayName(displayName) - .setTextSentimentDatasetMetadata(metadata) - .build(); - OperationFuture future = - client.createDatasetAsync(projectLocation, dataset); - - Dataset createdDataset = future.get(); - - // Display the dataset information. - System.out.format("Dataset name: %s\n", createdDataset.getName()); - // To get the dataset id, you have to parse it out of the `name` field. As dataset Ids are - // required for other methods. - // Name Form: `projects/{project_id}/locations/{location_id}/datasets/{dataset_id}` - String[] names = createdDataset.getName().split("/"); - String datasetId = names[names.length - 1]; - System.out.format("Dataset id: %s\n", datasetId); - } - } -} -// [END automl_language_sentiment_analysis_create_dataset] diff --git a/automl/src/main/java/com/example/automl/LanguageSentimentAnalysisCreateModel.java b/automl/src/main/java/com/example/automl/LanguageSentimentAnalysisCreateModel.java deleted file mode 100644 index 04af77592e5..00000000000 --- a/automl/src/main/java/com/example/automl/LanguageSentimentAnalysisCreateModel.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_language_sentiment_analysis_create_model] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.LocationName; -import com.google.cloud.automl.v1.Model; -import com.google.cloud.automl.v1.OperationMetadata; -import com.google.cloud.automl.v1.TextSentimentModelMetadata; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class LanguageSentimentAnalysisCreateModel { - - static void createModel() throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String datasetId = "YOUR_DATASET_ID"; - String displayName = "YOUR_DATASET_NAME"; - createModel(projectId, datasetId, displayName); - } - - // Create a model - static void createModel(String projectId, String datasetId, String displayName) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "us-central1"); - // Set model metadata. - System.out.println(datasetId); - TextSentimentModelMetadata metadata = TextSentimentModelMetadata.newBuilder().build(); - Model model = - Model.newBuilder() - .setDisplayName(displayName) - .setDatasetId(datasetId) - .setTextSentimentModelMetadata(metadata) - .build(); - - // Create a model with the model metadata in the region. - OperationFuture future = - client.createModelAsync(projectLocation, model); - // OperationFuture.get() will block until the model is created, which may take several hours. - // You can use OperationFuture.getInitialFuture to get a future representing the initial - // response to the request, which contains information while the operation is in progress. - System.out.format("Training operation name: %s\n", future.getInitialFuture().get().getName()); - System.out.println("Training started..."); - } - } -} -// [END automl_language_sentiment_analysis_create_model] diff --git a/automl/src/main/java/com/example/automl/LanguageSentimentAnalysisPredict.java b/automl/src/main/java/com/example/automl/LanguageSentimentAnalysisPredict.java deleted file mode 100644 index 65945c26023..00000000000 --- a/automl/src/main/java/com/example/automl/LanguageSentimentAnalysisPredict.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_language_sentiment_analysis_predict] -import com.google.cloud.automl.v1.AnnotationPayload; -import com.google.cloud.automl.v1.ExamplePayload; -import com.google.cloud.automl.v1.ModelName; -import com.google.cloud.automl.v1.PredictRequest; -import com.google.cloud.automl.v1.PredictResponse; -import com.google.cloud.automl.v1.PredictionServiceClient; -import com.google.cloud.automl.v1.TextSnippet; -import java.io.IOException; - -class LanguageSentimentAnalysisPredict { - - static void predict() throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - String content = "text to predict"; - predict(projectId, modelId, content); - } - - static void predict(String projectId, String modelId, String content) throws IOException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (PredictionServiceClient client = PredictionServiceClient.create()) { - // Get the full path of the model. - ModelName name = ModelName.of(projectId, "us-central1", modelId); - - // For available mime types, see: - // https://cloud.google.com/automl/docs/reference/rest/v1/projects.locations.models/predict#textsnippet - TextSnippet textSnippet = - TextSnippet.newBuilder() - .setContent(content) - .setMimeType("text/plain") // Types: text/plain, text/html - .build(); - ExamplePayload payload = ExamplePayload.newBuilder().setTextSnippet(textSnippet).build(); - PredictRequest predictRequest = - PredictRequest.newBuilder().setName(name.toString()).setPayload(payload).build(); - - PredictResponse response = client.predict(predictRequest); - - for (AnnotationPayload annotationPayload : response.getPayloadList()) { - System.out.format("Predicted class name: %s\n", annotationPayload.getDisplayName()); - System.out.format( - "Predicted sentiment score: %d\n", annotationPayload.getTextSentiment().getSentiment()); - } - } - } -} -// [END automl_language_sentiment_analysis_predict] diff --git a/automl/src/main/java/com/example/automl/LanguageTextClassificationCreateDataset.java b/automl/src/main/java/com/example/automl/LanguageTextClassificationCreateDataset.java deleted file mode 100644 index 9332c06a246..00000000000 --- a/automl/src/main/java/com/example/automl/LanguageTextClassificationCreateDataset.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_language_text_classification_create_dataset] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.ClassificationType; -import com.google.cloud.automl.v1.Dataset; -import com.google.cloud.automl.v1.LocationName; -import com.google.cloud.automl.v1.OperationMetadata; -import com.google.cloud.automl.v1.TextClassificationDatasetMetadata; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class LanguageTextClassificationCreateDataset { - - public static void main(String[] args) - throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String displayName = "YOUR_DATASET_NAME"; - createDataset(projectId, displayName); - } - - // Create a dataset - static void createDataset(String projectId, String displayName) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "us-central1"); - - // Specify the classification type - // Types: - // MultiLabel: Multiple labels are allowed for one example. - // MultiClass: At most one label is allowed per example. - ClassificationType classificationType = ClassificationType.MULTILABEL; - - // Specify the text classification type for the dataset. - TextClassificationDatasetMetadata metadata = - TextClassificationDatasetMetadata.newBuilder() - .setClassificationType(classificationType) - .build(); - Dataset dataset = - Dataset.newBuilder() - .setDisplayName(displayName) - .setTextClassificationDatasetMetadata(metadata) - .build(); - OperationFuture future = - client.createDatasetAsync(projectLocation, dataset); - - Dataset createdDataset = future.get(); - - // Display the dataset information. - System.out.format("Dataset name: %s\n", createdDataset.getName()); - // To get the dataset id, you have to parse it out of the `name` field. As dataset Ids are - // required for other methods. - // Name Form: `projects/{project_id}/locations/{location_id}/datasets/{dataset_id}` - String[] names = createdDataset.getName().split("/"); - String datasetId = names[names.length - 1]; - System.out.format("Dataset id: %s\n", datasetId); - } - } -} -// [END automl_language_text_classification_create_dataset] diff --git a/automl/src/main/java/com/example/automl/LanguageTextClassificationCreateModel.java b/automl/src/main/java/com/example/automl/LanguageTextClassificationCreateModel.java deleted file mode 100644 index bf26db03101..00000000000 --- a/automl/src/main/java/com/example/automl/LanguageTextClassificationCreateModel.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_language_text_classification_create_model] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.LocationName; -import com.google.cloud.automl.v1.Model; -import com.google.cloud.automl.v1.OperationMetadata; -import com.google.cloud.automl.v1.TextClassificationModelMetadata; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class LanguageTextClassificationCreateModel { - - public static void main(String[] args) - throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String datasetId = "YOUR_DATASET_ID"; - String displayName = "YOUR_DATASET_NAME"; - createModel(projectId, datasetId, displayName); - } - - // Create a model - static void createModel(String projectId, String datasetId, String displayName) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "us-central1"); - // Set model metadata. - TextClassificationModelMetadata metadata = - TextClassificationModelMetadata.newBuilder().build(); - Model model = - Model.newBuilder() - .setDisplayName(displayName) - .setDatasetId(datasetId) - .setTextClassificationModelMetadata(metadata) - .build(); - - // Create a model with the model metadata in the region. - OperationFuture future = - client.createModelAsync(projectLocation, model); - // OperationFuture.get() will block until the model is created, which may take several hours. - // You can use OperationFuture.getInitialFuture to get a future representing the initial - // response to the request, which contains information while the operation is in progress. - System.out.format("Training operation name: %s\n", future.getInitialFuture().get().getName()); - System.out.println("Training started..."); - } - } -} -// [END automl_language_text_classification_create_model] diff --git a/automl/src/main/java/com/example/automl/LanguageTextClassificationPredict.java b/automl/src/main/java/com/example/automl/LanguageTextClassificationPredict.java deleted file mode 100644 index e2da94aa121..00000000000 --- a/automl/src/main/java/com/example/automl/LanguageTextClassificationPredict.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_language_text_classification_predict] -import com.google.cloud.automl.v1.AnnotationPayload; -import com.google.cloud.automl.v1.ExamplePayload; -import com.google.cloud.automl.v1.ModelName; -import com.google.cloud.automl.v1.PredictRequest; -import com.google.cloud.automl.v1.PredictResponse; -import com.google.cloud.automl.v1.PredictionServiceClient; -import com.google.cloud.automl.v1.TextSnippet; -import java.io.IOException; - -class LanguageTextClassificationPredict { - - public static void main(String[] args) throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - String content = "text to predict"; - predict(projectId, modelId, content); - } - - static void predict(String projectId, String modelId, String content) throws IOException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (PredictionServiceClient client = PredictionServiceClient.create()) { - // Get the full path of the model. - ModelName name = ModelName.of(projectId, "us-central1", modelId); - - // For available mime types, see: - // https://cloud.google.com/automl/docs/reference/rest/v1/projects.locations.models/predict#textsnippet - TextSnippet textSnippet = - TextSnippet.newBuilder() - .setContent(content) - .setMimeType("text/plain") // Types: text/plain, text/html - .build(); - ExamplePayload payload = ExamplePayload.newBuilder().setTextSnippet(textSnippet).build(); - PredictRequest predictRequest = - PredictRequest.newBuilder().setName(name.toString()).setPayload(payload).build(); - - PredictResponse response = client.predict(predictRequest); - - for (AnnotationPayload annotationPayload : response.getPayloadList()) { - System.out.format("Predicted class name: %s\n", annotationPayload.getDisplayName()); - System.out.format( - "Predicted sentiment score: %.2f\n\n", - annotationPayload.getClassification().getScore()); - } - } - } -} -// [END automl_language_text_classification_predict] diff --git a/automl/src/main/java/com/example/automl/ListDatasets.java b/automl/src/main/java/com/example/automl/ListDatasets.java deleted file mode 100644 index b766a673ff4..00000000000 --- a/automl/src/main/java/com/example/automl/ListDatasets.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_language_entity_extraction_list_datasets] -// [START automl_language_sentiment_analysis_list_datasets] -// [START automl_language_text_classification_list_datasets] -// [START automl_translate_list_datasets] -// [START automl_vision_classification_list_datasets] -// [START automl_vision_object_detection_list_datasets] -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.Dataset; -import com.google.cloud.automl.v1.ListDatasetsRequest; -import com.google.cloud.automl.v1.LocationName; -import java.io.IOException; - -class ListDatasets { - - static void listDatasets() throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - listDatasets(projectId); - } - - // List the datasets - static void listDatasets(String projectId) throws IOException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "us-central1"); - ListDatasetsRequest request = - ListDatasetsRequest.newBuilder().setParent(projectLocation.toString()).build(); - - // List all the datasets available in the region by applying filter. - System.out.println("List of datasets:"); - for (Dataset dataset : client.listDatasets(request).iterateAll()) { - // Display the dataset information - System.out.format("\nDataset name: %s\n", dataset.getName()); - // To get the dataset id, you have to parse it out of the `name` field. As dataset Ids are - // required for other methods. - // Name Form: `projects/{project_id}/locations/{location_id}/datasets/{dataset_id}` - String[] names = dataset.getName().split("/"); - String retrievedDatasetId = names[names.length - 1]; - System.out.format("Dataset id: %s\n", retrievedDatasetId); - System.out.format("Dataset display name: %s\n", dataset.getDisplayName()); - System.out.println("Dataset create time:"); - System.out.format("\tseconds: %s\n", dataset.getCreateTime().getSeconds()); - System.out.format("\tnanos: %s\n", dataset.getCreateTime().getNanos()); - // [END automl_language_sentiment_analysis_list_datasets] - // [END automl_language_text_classification_list_datasets] - // [END automl_translate_list_datasets] - // [END automl_vision_classification_list_datasets] - // [END automl_vision_object_detection_list_datasets] - System.out.format( - "Text extraction dataset metadata: %s\n", dataset.getTextExtractionDatasetMetadata()); - // [END automl_language_entity_extraction_list_datasets] - - // [START automl_language_sentiment_analysis_list_datasets] - System.out.format( - "Text sentiment dataset metadata: %s\n", dataset.getTextSentimentDatasetMetadata()); - // [END automl_language_sentiment_analysis_list_datasets] - - // [START automl_language_text_classification_list_datasets] - System.out.format( - "Text classification dataset metadata: %s\n", - dataset.getTextClassificationDatasetMetadata()); - // [END automl_language_text_classification_list_datasets] - - // [START automl_translate_list_datasets] - System.out.println("Translation dataset metadata:"); - System.out.format( - "\tSource language code: %s\n", - dataset.getTranslationDatasetMetadata().getSourceLanguageCode()); - System.out.format( - "\tTarget language code: %s\n", - dataset.getTranslationDatasetMetadata().getTargetLanguageCode()); - // [END automl_translate_list_datasets] - - // [START automl_vision_classification_list_datasets] - System.out.format( - "Image classification dataset metadata: %s\n", - dataset.getImageClassificationDatasetMetadata()); - // [END automl_vision_classification_list_datasets] - - // [START automl_vision_object_detection_list_datasets] - System.out.format( - "Image object detection dataset metadata: %s\n", - dataset.getImageObjectDetectionDatasetMetadata()); - // [START automl_language_entity_extraction_list_datasets] - // [START automl_language_sentiment_analysis_list_datasets] - // [START automl_language_text_classification_list_datasets] - // [START automl_translate_list_datasets] - // [START automl_vision_classification_list_datasets] - } - } - } -} -// [END automl_language_entity_extraction_list_datasets] -// [END automl_language_sentiment_analysis_list_datasets] -// [END automl_language_text_classification_list_datasets] -// [END automl_translate_list_datasets] -// [END automl_vision_classification_list_datasets] -// [END automl_vision_object_detection_list_datasets] diff --git a/automl/src/main/java/com/example/automl/ListModelEvaluations.java b/automl/src/main/java/com/example/automl/ListModelEvaluations.java deleted file mode 100644 index 5b25ec08fe7..00000000000 --- a/automl/src/main/java/com/example/automl/ListModelEvaluations.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_language_entity_extraction_list_model_evaluations] -// [START automl_language_sentiment_analysis_list_model_evaluations] -// [START automl_language_text_classification_list_model_evaluations] -// [START automl_translate_list_model_evaluations] -// [START automl_vision_classification_list_model_evaluations] -// [START automl_vision_object_detection_list_model_evaluations] - -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.ListModelEvaluationsRequest; -import com.google.cloud.automl.v1.ModelEvaluation; -import com.google.cloud.automl.v1.ModelName; -import java.io.IOException; - -class ListModelEvaluations { - - public static void main(String[] args) throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - listModelEvaluations(projectId, modelId); - } - - // List model evaluations - static void listModelEvaluations(String projectId, String modelId) throws IOException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // Get the full path of the model. - ModelName modelFullId = ModelName.of(projectId, "us-central1", modelId); - ListModelEvaluationsRequest modelEvaluationsrequest = - ListModelEvaluationsRequest.newBuilder().setParent(modelFullId.toString()).build(); - - // List all the model evaluations in the model by applying filter. - System.out.println("List of model evaluations:"); - for (ModelEvaluation modelEvaluation : - client.listModelEvaluations(modelEvaluationsrequest).iterateAll()) { - - System.out.format("Model Evaluation Name: %s\n", modelEvaluation.getName()); - System.out.format("Model Annotation Spec Id: %s", modelEvaluation.getAnnotationSpecId()); - System.out.println("Create Time:"); - System.out.format("\tseconds: %s\n", modelEvaluation.getCreateTime().getSeconds()); - System.out.format("\tnanos: %s", modelEvaluation.getCreateTime().getNanos() / 1e9); - System.out.format( - "Evalution Example Count: %d\n", modelEvaluation.getEvaluatedExampleCount()); - // [END automl_language_sentiment_analysis_list_model_evaluations] - // [END automl_language_text_classification_list_model_evaluations] - // [END automl_translate_list_model_evaluations] - // [END automl_vision_classification_list_model_evaluations] - // [END automl_vision_object_detection_list_model_evaluations] - System.out.format( - "Entity Extraction Model Evaluation Metrics: %s\n", - modelEvaluation.getTextExtractionEvaluationMetrics()); - // [END automl_language_entity_extraction_list_model_evaluations] - - // [START automl_language_sentiment_analysis_list_model_evaluations] - System.out.format( - "Sentiment Analysis Model Evaluation Metrics: %s\n", - modelEvaluation.getTextSentimentEvaluationMetrics()); - // [END automl_language_sentiment_analysis_list_model_evaluations] - - // [START automl_language_text_classification_list_model_evaluations] - // [START automl_vision_classification_list_model_evaluations] - System.out.format( - "Classification Model Evaluation Metrics: %s\n", - modelEvaluation.getClassificationEvaluationMetrics()); - // [END automl_language_text_classification_list_model_evaluations] - // [END automl_vision_classification_list_model_evaluations] - - // [START automl_translate_list_model_evaluations] - System.out.format( - "Translate Model Evaluation Metrics: %s\n", - modelEvaluation.getTranslationEvaluationMetrics()); - // [END automl_translate_list_model_evaluations] - - // [START automl_vision_object_detection_list_model_evaluations] - System.out.format( - "Object Detection Model Evaluation Metrics: %s\n", - modelEvaluation.getImageObjectDetectionEvaluationMetrics()); - // [START automl_language_entity_extraction_list_model_evaluations] - // [START automl_language_sentiment_analysis_list_model_evaluations] - // [START automl_language_text_classification_list_model_evaluations] - // [START automl_translate_list_model_evaluations] - // [START automl_vision_classification_list_model_evaluations] - } - } - } -} -// [END automl_language_entity_extraction_list_model_evaluations] -// [END automl_language_sentiment_analysis_list_model_evaluations] -// [END automl_language_text_classification_list_model_evaluations] -// [END automl_translate_list_model_evaluations] -// [END automl_vision_classification_list_model_evaluations] -// [END automl_vision_object_detection_list_model_evaluations] diff --git a/automl/src/main/java/com/example/automl/ListModels.java b/automl/src/main/java/com/example/automl/ListModels.java deleted file mode 100644 index c24f09b28ae..00000000000 --- a/automl/src/main/java/com/example/automl/ListModels.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_list_models] -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.ListModelsRequest; -import com.google.cloud.automl.v1.LocationName; -import com.google.cloud.automl.v1.Model; -import java.io.IOException; - -class ListModels { - - static void listModels() throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - listModels(projectId); - } - - // List the models available in the specified location - static void listModels(String projectId) throws IOException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "us-central1"); - - // Create list models request. - ListModelsRequest listModelsRequest = - ListModelsRequest.newBuilder() - .setParent(projectLocation.toString()) - .setFilter("") - .build(); - - // List all the models available in the region by applying filter. - System.out.println("List of models:"); - for (Model model : client.listModels(listModelsRequest).iterateAll()) { - // Display the model information. - System.out.format("Model name: %s\n", model.getName()); - // To get the model id, you have to parse it out of the `name` field. As models Ids are - // required for other methods. - // Name Format: `projects/{project_id}/locations/{location_id}/models/{model_id}` - String[] names = model.getName().split("/"); - String retrievedModelId = names[names.length - 1]; - System.out.format("Model id: %s\n", retrievedModelId); - System.out.format("Model display name: %s\n", model.getDisplayName()); - System.out.println("Model create time:"); - System.out.format("\tseconds: %s\n", model.getCreateTime().getSeconds()); - System.out.format("\tnanos: %s\n", model.getCreateTime().getNanos()); - System.out.format("Model deployment state: %s\n", model.getDeploymentState()); - } - } - } -} -// [END automl_list_models] diff --git a/automl/src/main/java/com/example/automl/ListOperationStatus.java b/automl/src/main/java/com/example/automl/ListOperationStatus.java deleted file mode 100644 index a980d41bad5..00000000000 --- a/automl/src/main/java/com/example/automl/ListOperationStatus.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_list_operation_status] -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.LocationName; -import com.google.longrunning.ListOperationsRequest; -import com.google.longrunning.Operation; -import java.io.IOException; - -class ListOperationStatus { - - static void listOperationStatus() throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - listOperationStatus(projectId); - } - - // Get the status of an operation - static void listOperationStatus(String projectId) throws IOException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "us-central1"); - - // Create list operations request. - ListOperationsRequest listrequest = - ListOperationsRequest.newBuilder().setName(projectLocation.toString()).build(); - - // List all the operations names available in the region by applying filter. - for (Operation operation : - client.getOperationsClient().listOperations(listrequest).iterateAll()) { - System.out.println("Operation details:"); - System.out.format("\tName: %s\n", operation.getName()); - System.out.format("\tMetadata Type Url: %s\n", operation.getMetadata().getTypeUrl()); - System.out.format("\tDone: %s\n", operation.getDone()); - if (operation.hasResponse()) { - System.out.format("\tResponse Type Url: %s\n", operation.getResponse().getTypeUrl()); - } - if (operation.hasError()) { - System.out.println("\tResponse:"); - System.out.format("\t\tError code: %s\n", operation.getError().getCode()); - System.out.format("\t\tError message: %s\n\n", operation.getError().getMessage()); - } - } - } - } -} -// [END automl_list_operation_status] diff --git a/automl/src/main/java/com/example/automl/TranslateCreateDataset.java b/automl/src/main/java/com/example/automl/TranslateCreateDataset.java deleted file mode 100644 index 77769bdbec4..00000000000 --- a/automl/src/main/java/com/example/automl/TranslateCreateDataset.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_translate_create_dataset] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.Dataset; -import com.google.cloud.automl.v1.LocationName; -import com.google.cloud.automl.v1.OperationMetadata; -import com.google.cloud.automl.v1.TranslationDatasetMetadata; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class TranslateCreateDataset { - - public static void main(String[] args) - throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String displayName = "YOUR_DATASET_NAME"; - createDataset(projectId, displayName); - } - - // Create a dataset - static void createDataset(String projectId, String displayName) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "us-central1"); - - // Specify the source and target language. - TranslationDatasetMetadata translationDatasetMetadata = - TranslationDatasetMetadata.newBuilder() - .setSourceLanguageCode("en") - .setTargetLanguageCode("ja") - .build(); - Dataset dataset = - Dataset.newBuilder() - .setDisplayName(displayName) - .setTranslationDatasetMetadata(translationDatasetMetadata) - .build(); - OperationFuture future = - client.createDatasetAsync(projectLocation, dataset); - - Dataset createdDataset = future.get(); - - // Display the dataset information. - System.out.format("Dataset name: %s\n", createdDataset.getName()); - // To get the dataset id, you have to parse it out of the `name` field. As dataset Ids are - // required for other methods. - // Name Form: `projects/{project_id}/locations/{location_id}/datasets/{dataset_id}` - String[] names = createdDataset.getName().split("/"); - String datasetId = names[names.length - 1]; - System.out.format("Dataset id: %s\n", datasetId); - } - } -} -// [END automl_translate_create_dataset] diff --git a/automl/src/main/java/com/example/automl/TranslateCreateModel.java b/automl/src/main/java/com/example/automl/TranslateCreateModel.java deleted file mode 100644 index cddd597c6bb..00000000000 --- a/automl/src/main/java/com/example/automl/TranslateCreateModel.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_translate_create_model] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.LocationName; -import com.google.cloud.automl.v1.Model; -import com.google.cloud.automl.v1.OperationMetadata; -import com.google.cloud.automl.v1.TranslationModelMetadata; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class TranslateCreateModel { - - public static void main(String[] args) - throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String datasetId = "YOUR_DATASET_ID"; - String displayName = "YOUR_DATASET_NAME"; - createModel(projectId, datasetId, displayName); - } - - // Create a model - static void createModel(String projectId, String datasetId, String displayName) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "us-central1"); - TranslationModelMetadata translationModelMetadata = - TranslationModelMetadata.newBuilder().build(); - Model model = - Model.newBuilder() - .setDisplayName(displayName) - .setDatasetId(datasetId) - .setTranslationModelMetadata(translationModelMetadata) - .build(); - - // Create a model with the model metadata in the region. - OperationFuture future = - client.createModelAsync(projectLocation, model); - // OperationFuture.get() will block until the model is created, which may take several hours. - // You can use OperationFuture.getInitialFuture to get a future representing the initial - // response to the request, which contains information while the operation is in progress. - System.out.format("Training operation name: %s\n", future.getInitialFuture().get().getName()); - System.out.println("Training started..."); - } - } -} -// [END automl_translate_create_model] diff --git a/automl/src/main/java/com/example/automl/TranslatePredict.java b/automl/src/main/java/com/example/automl/TranslatePredict.java deleted file mode 100644 index 2385b831c44..00000000000 --- a/automl/src/main/java/com/example/automl/TranslatePredict.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_translate_predict] -import com.google.cloud.automl.v1.ExamplePayload; -import com.google.cloud.automl.v1.ModelName; -import com.google.cloud.automl.v1.PredictRequest; -import com.google.cloud.automl.v1.PredictResponse; -import com.google.cloud.automl.v1.PredictionServiceClient; -import com.google.cloud.automl.v1.TextSnippet; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; - -class TranslatePredict { - - public static void main(String[] args) throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - String filePath = "path_to_local_file.txt"; - predict(projectId, modelId, filePath); - } - - static void predict(String projectId, String modelId, String filePath) throws IOException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (PredictionServiceClient client = PredictionServiceClient.create()) { - // Get the full path of the model. - ModelName name = ModelName.of(projectId, "us-central1", modelId); - - String content = new String(Files.readAllBytes(Paths.get(filePath))); - - TextSnippet textSnippet = TextSnippet.newBuilder().setContent(content).build(); - ExamplePayload payload = ExamplePayload.newBuilder().setTextSnippet(textSnippet).build(); - PredictRequest predictRequest = - PredictRequest.newBuilder().setName(name.toString()).setPayload(payload).build(); - - PredictResponse response = client.predict(predictRequest); - TextSnippet translatedContent = - response.getPayload(0).getTranslation().getTranslatedContent(); - System.out.format("Translated Content: %s\n", translatedContent.getContent()); - } - } -} -// [END automl_translate_predict] diff --git a/automl/src/main/java/com/example/automl/UndeployModel.java b/automl/src/main/java/com/example/automl/UndeployModel.java deleted file mode 100644 index e7ef532e94d..00000000000 --- a/automl/src/main/java/com/example/automl/UndeployModel.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_undeploy_model] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.ModelName; -import com.google.cloud.automl.v1.OperationMetadata; -import com.google.cloud.automl.v1.UndeployModelRequest; -import com.google.protobuf.Empty; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class UndeployModel { - - static void undeployModel() throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - undeployModel(projectId, modelId); - } - - // Undeploy a model from prediction - static void undeployModel(String projectId, String modelId) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // Get the full path of the model. - ModelName modelFullId = ModelName.of(projectId, "us-central1", modelId); - UndeployModelRequest request = - UndeployModelRequest.newBuilder().setName(modelFullId.toString()).build(); - OperationFuture future = client.undeployModelAsync(request); - - future.get(); - System.out.println("Model undeployment finished"); - } - } -} -// [END automl_undeploy_model] diff --git a/automl/src/main/java/com/example/automl/VisionClassificationCreateDataset.java b/automl/src/main/java/com/example/automl/VisionClassificationCreateDataset.java deleted file mode 100644 index f3e2d951d01..00000000000 --- a/automl/src/main/java/com/example/automl/VisionClassificationCreateDataset.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_vision_classification_create_dataset] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.ClassificationType; -import com.google.cloud.automl.v1.Dataset; -import com.google.cloud.automl.v1.ImageClassificationDatasetMetadata; -import com.google.cloud.automl.v1.LocationName; -import com.google.cloud.automl.v1.OperationMetadata; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class VisionClassificationCreateDataset { - - public static void main(String[] args) - throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String displayName = "YOUR_DATASET_NAME"; - createDataset(projectId, displayName); - } - - // Create a dataset - static void createDataset(String projectId, String displayName) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "us-central1"); - - // Specify the classification type - // Types: - // MultiLabel: Multiple labels are allowed for one example. - // MultiClass: At most one label is allowed per example. - ClassificationType classificationType = ClassificationType.MULTILABEL; - ImageClassificationDatasetMetadata metadata = - ImageClassificationDatasetMetadata.newBuilder() - .setClassificationType(classificationType) - .build(); - Dataset dataset = - Dataset.newBuilder() - .setDisplayName(displayName) - .setImageClassificationDatasetMetadata(metadata) - .build(); - OperationFuture future = - client.createDatasetAsync(projectLocation, dataset); - - Dataset createdDataset = future.get(); - - // Display the dataset information. - System.out.format("Dataset name: %s\n", createdDataset.getName()); - // To get the dataset id, you have to parse it out of the `name` field. As dataset Ids are - // required for other methods. - // Name Form: `projects/{project_id}/locations/{location_id}/datasets/{dataset_id}` - String[] names = createdDataset.getName().split("/"); - String datasetId = names[names.length - 1]; - System.out.format("Dataset id: %s\n", datasetId); - } - } -} -// [END automl_vision_classification_create_dataset] diff --git a/automl/src/main/java/com/example/automl/VisionClassificationCreateModel.java b/automl/src/main/java/com/example/automl/VisionClassificationCreateModel.java deleted file mode 100644 index ea4da40b1d6..00000000000 --- a/automl/src/main/java/com/example/automl/VisionClassificationCreateModel.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_vision_classification_create_model] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.ImageClassificationModelMetadata; -import com.google.cloud.automl.v1.LocationName; -import com.google.cloud.automl.v1.Model; -import com.google.cloud.automl.v1.OperationMetadata; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class VisionClassificationCreateModel { - - public static void main(String[] args) - throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String datasetId = "YOUR_DATASET_ID"; - String displayName = "YOUR_DATASET_NAME"; - createModel(projectId, datasetId, displayName); - } - - // Create a model - static void createModel(String projectId, String datasetId, String displayName) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "us-central1"); - // Set model metadata. - ImageClassificationModelMetadata metadata = - ImageClassificationModelMetadata.newBuilder().setTrainBudgetMilliNodeHours(24000).build(); - Model model = - Model.newBuilder() - .setDisplayName(displayName) - .setDatasetId(datasetId) - .setImageClassificationModelMetadata(metadata) - .build(); - - // Create a model with the model metadata in the region. - OperationFuture future = - client.createModelAsync(projectLocation, model); - // OperationFuture.get() will block until the model is created, which may take several hours. - // You can use OperationFuture.getInitialFuture to get a future representing the initial - // response to the request, which contains information while the operation is in progress. - System.out.format("Training operation name: %s\n", future.getInitialFuture().get().getName()); - System.out.println("Training started..."); - } - } -} -// [END automl_vision_classification_create_model] diff --git a/automl/src/main/java/com/example/automl/VisionClassificationDeployModelNodeCount.java b/automl/src/main/java/com/example/automl/VisionClassificationDeployModelNodeCount.java deleted file mode 100644 index c2d27f3a132..00000000000 --- a/automl/src/main/java/com/example/automl/VisionClassificationDeployModelNodeCount.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_vision_classification_deploy_model_node_count] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.DeployModelRequest; -import com.google.cloud.automl.v1.ImageClassificationModelDeploymentMetadata; -import com.google.cloud.automl.v1.ModelName; -import com.google.cloud.automl.v1.OperationMetadata; -import com.google.protobuf.Empty; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class VisionClassificationDeployModelNodeCount { - - static void visionClassificationDeployModelNodeCount() - throws InterruptedException, ExecutionException, IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - visionClassificationDeployModelNodeCount(projectId, modelId); - } - - // Deploy a model for prediction with a specified node count (can be used to redeploy a model) - static void visionClassificationDeployModelNodeCount(String projectId, String modelId) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // Get the full path of the model. - ModelName modelFullId = ModelName.of(projectId, "us-central1", modelId); - ImageClassificationModelDeploymentMetadata metadata = - ImageClassificationModelDeploymentMetadata.newBuilder().setNodeCount(2).build(); - DeployModelRequest request = - DeployModelRequest.newBuilder() - .setName(modelFullId.toString()) - .setImageClassificationModelDeploymentMetadata(metadata) - .build(); - OperationFuture future = client.deployModelAsync(request); - - future.get(); - System.out.println("Model deployment finished"); - } - } -} -// [END automl_vision_classification_deploy_model_node_count] diff --git a/automl/src/main/java/com/example/automl/VisionClassificationPredict.java b/automl/src/main/java/com/example/automl/VisionClassificationPredict.java deleted file mode 100644 index 6f110cf9eff..00000000000 --- a/automl/src/main/java/com/example/automl/VisionClassificationPredict.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_vision_classification_predict] -import com.google.cloud.automl.v1.AnnotationPayload; -import com.google.cloud.automl.v1.ExamplePayload; -import com.google.cloud.automl.v1.Image; -import com.google.cloud.automl.v1.ModelName; -import com.google.cloud.automl.v1.PredictRequest; -import com.google.cloud.automl.v1.PredictResponse; -import com.google.cloud.automl.v1.PredictionServiceClient; -import com.google.protobuf.ByteString; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; - -class VisionClassificationPredict { - - public static void main(String[] args) throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - String filePath = "path_to_local_file.jpg"; - predict(projectId, modelId, filePath); - } - - static void predict(String projectId, String modelId, String filePath) throws IOException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (PredictionServiceClient client = PredictionServiceClient.create()) { - // Get the full path of the model. - ModelName name = ModelName.of(projectId, "us-central1", modelId); - ByteString content = ByteString.copyFrom(Files.readAllBytes(Paths.get(filePath))); - Image image = Image.newBuilder().setImageBytes(content).build(); - ExamplePayload payload = ExamplePayload.newBuilder().setImage(image).build(); - PredictRequest predictRequest = - PredictRequest.newBuilder() - .setName(name.toString()) - .setPayload(payload) - .putParams( - "score_threshold", "0.8") // [0.0-1.0] Only produce results higher than this value - .build(); - - PredictResponse response = client.predict(predictRequest); - - for (AnnotationPayload annotationPayload : response.getPayloadList()) { - System.out.format("Predicted class name: %s\n", annotationPayload.getDisplayName()); - System.out.format( - "Predicted class score: %.2f\n", annotationPayload.getClassification().getScore()); - } - } - } -} -// [END automl_vision_classification_predict] diff --git a/automl/src/main/java/com/example/automl/VisionObjectDetectionCreateDataset.java b/automl/src/main/java/com/example/automl/VisionObjectDetectionCreateDataset.java deleted file mode 100644 index 874099d537d..00000000000 --- a/automl/src/main/java/com/example/automl/VisionObjectDetectionCreateDataset.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_vision_object_detection_create_dataset] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.Dataset; -import com.google.cloud.automl.v1.ImageObjectDetectionDatasetMetadata; -import com.google.cloud.automl.v1.LocationName; -import com.google.cloud.automl.v1.OperationMetadata; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class VisionObjectDetectionCreateDataset { - - static void createDataset() throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String displayName = "YOUR_DATASET_NAME"; - createDataset(projectId, displayName); - } - - // Create a dataset - static void createDataset(String projectId, String displayName) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "us-central1"); - - ImageObjectDetectionDatasetMetadata metadata = - ImageObjectDetectionDatasetMetadata.newBuilder().build(); - Dataset dataset = - Dataset.newBuilder() - .setDisplayName(displayName) - .setImageObjectDetectionDatasetMetadata(metadata) - .build(); - OperationFuture future = - client.createDatasetAsync(projectLocation, dataset); - - Dataset createdDataset = future.get(); - - // Display the dataset information. - System.out.format("Dataset name: %s\n", createdDataset.getName()); - // To get the dataset id, you have to parse it out of the `name` field. As dataset Ids are - // required for other methods. - // Name Form: `projects/{project_id}/locations/{location_id}/datasets/{dataset_id}` - String[] names = createdDataset.getName().split("/"); - String datasetId = names[names.length - 1]; - System.out.format("Dataset id: %s\n", datasetId); - } - } -} -// [END automl_vision_object_detection_create_dataset] diff --git a/automl/src/main/java/com/example/automl/VisionObjectDetectionCreateModel.java b/automl/src/main/java/com/example/automl/VisionObjectDetectionCreateModel.java deleted file mode 100644 index 2e0ccbc43d9..00000000000 --- a/automl/src/main/java/com/example/automl/VisionObjectDetectionCreateModel.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_vision_object_detection_create_model] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.ImageObjectDetectionModelMetadata; -import com.google.cloud.automl.v1.LocationName; -import com.google.cloud.automl.v1.Model; -import com.google.cloud.automl.v1.OperationMetadata; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class VisionObjectDetectionCreateModel { - - static void createModel() throws IOException, ExecutionException, InterruptedException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String datasetId = "YOUR_DATASET_ID"; - String displayName = "YOUR_DATASET_NAME"; - createModel(projectId, datasetId, displayName); - } - - // Create a model - static void createModel(String projectId, String datasetId, String displayName) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // A resource that represents Google Cloud Platform location. - LocationName projectLocation = LocationName.of(projectId, "us-central1"); - // Set model metadata. - ImageObjectDetectionModelMetadata metadata = - ImageObjectDetectionModelMetadata.newBuilder().build(); - Model model = - Model.newBuilder() - .setDisplayName(displayName) - .setDatasetId(datasetId) - .setImageObjectDetectionModelMetadata(metadata) - .build(); - - // Create a model with the model metadata in the region. - OperationFuture future = - client.createModelAsync(projectLocation, model); - // OperationFuture.get() will block until the model is created, which may take several hours. - // You can use OperationFuture.getInitialFuture to get a future representing the initial - // response to the request, which contains information while the operation is in progress. - System.out.format("Training operation name: %s\n", future.getInitialFuture().get().getName()); - System.out.println("Training started..."); - } - } -} -// [END automl_vision_object_detection_create_model] diff --git a/automl/src/main/java/com/example/automl/VisionObjectDetectionDeployModelNodeCount.java b/automl/src/main/java/com/example/automl/VisionObjectDetectionDeployModelNodeCount.java deleted file mode 100644 index 42621d3e87f..00000000000 --- a/automl/src/main/java/com/example/automl/VisionObjectDetectionDeployModelNodeCount.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_vision_object_detection_deploy_model_node_count] -import com.google.api.gax.longrunning.OperationFuture; -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.DeployModelRequest; -import com.google.cloud.automl.v1.ImageObjectDetectionModelDeploymentMetadata; -import com.google.cloud.automl.v1.ModelName; -import com.google.cloud.automl.v1.OperationMetadata; -import com.google.protobuf.Empty; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -class VisionObjectDetectionDeployModelNodeCount { - - static void visionObjectDetectionDeployModelNodeCount() - throws InterruptedException, ExecutionException, IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - visionObjectDetectionDeployModelNodeCount(projectId, modelId); - } - - // Deploy a model for prediction with a specified node count (can be used to redeploy a model) - static void visionObjectDetectionDeployModelNodeCount(String projectId, String modelId) - throws IOException, ExecutionException, InterruptedException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (AutoMlClient client = AutoMlClient.create()) { - // Get the full path of the model. - ModelName modelFullId = ModelName.of(projectId, "us-central1", modelId); - ImageObjectDetectionModelDeploymentMetadata metadata = - ImageObjectDetectionModelDeploymentMetadata.newBuilder().setNodeCount(2).build(); - DeployModelRequest request = - DeployModelRequest.newBuilder() - .setName(modelFullId.toString()) - .setImageObjectDetectionModelDeploymentMetadata(metadata) - .build(); - OperationFuture future = client.deployModelAsync(request); - - future.get(); - System.out.println("Model deployment finished"); - } - } -} -// [END automl_vision_object_detection_deploy_model_node_count] diff --git a/automl/src/main/java/com/example/automl/VisionObjectDetectionPredict.java b/automl/src/main/java/com/example/automl/VisionObjectDetectionPredict.java deleted file mode 100644 index 8d7dec6b357..00000000000 --- a/automl/src/main/java/com/example/automl/VisionObjectDetectionPredict.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -// [START automl_vision_object_detection_predict] -import com.google.cloud.automl.v1.AnnotationPayload; -import com.google.cloud.automl.v1.BoundingPoly; -import com.google.cloud.automl.v1.ExamplePayload; -import com.google.cloud.automl.v1.Image; -import com.google.cloud.automl.v1.ModelName; -import com.google.cloud.automl.v1.NormalizedVertex; -import com.google.cloud.automl.v1.PredictRequest; -import com.google.cloud.automl.v1.PredictResponse; -import com.google.cloud.automl.v1.PredictionServiceClient; -import com.google.protobuf.ByteString; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; - -class VisionObjectDetectionPredict { - - static void predict() throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "YOUR_PROJECT_ID"; - String modelId = "YOUR_MODEL_ID"; - String filePath = "path_to_local_file.jpg"; - predict(projectId, modelId, filePath); - } - - static void predict(String projectId, String modelId, String filePath) throws IOException { - // Initialize client that will be used to send requests. This client only needs to be created - // once, and can be reused for multiple requests. After completing all of your requests, call - // the "close" method on the client to safely clean up any remaining background resources. - try (PredictionServiceClient client = PredictionServiceClient.create()) { - // Get the full path of the model. - ModelName name = ModelName.of(projectId, "us-central1", modelId); - ByteString content = ByteString.copyFrom(Files.readAllBytes(Paths.get(filePath))); - Image image = Image.newBuilder().setImageBytes(content).build(); - ExamplePayload payload = ExamplePayload.newBuilder().setImage(image).build(); - PredictRequest predictRequest = - PredictRequest.newBuilder() - .setName(name.toString()) - .setPayload(payload) - .putParams( - "score_threshold", "0.5") // [0.0-1.0] Only produce results higher than this value - .build(); - - PredictResponse response = client.predict(predictRequest); - for (AnnotationPayload annotationPayload : response.getPayloadList()) { - System.out.format("Predicted class name: %s\n", annotationPayload.getDisplayName()); - System.out.format( - "Predicted class score: %.2f\n", - annotationPayload.getImageObjectDetection().getScore()); - BoundingPoly boundingPoly = annotationPayload.getImageObjectDetection().getBoundingBox(); - System.out.println("Normalized Vertices:"); - for (NormalizedVertex vertex : boundingPoly.getNormalizedVerticesList()) { - System.out.format("\tX: %.2f, Y: %.2f\n", vertex.getX(), vertex.getY()); - } - } - } - } -} -// [END automl_vision_object_detection_predict] diff --git a/automl/src/test/java/beta/automl/BatchPredictTest.java b/automl/src/test/java/beta/automl/BatchPredictTest.java deleted file mode 100644 index db8b6377c45..00000000000 --- a/automl/src/test/java/beta/automl/BatchPredictTest.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class BatchPredictTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String BUCKET_ID = PROJECT_ID + "-lcm"; - private static final String MODEL_ID = "VCN0000000000000000000"; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testBatchPredict() { - // As batch prediction can take a long time. Try to batch predict on a model and confirm that - // the model was not found, but other elements of the request were valid. - try { - String inputUri = String.format("gs://%s/entity-extraction/input.csv", BUCKET_ID); - String outputUri = String.format("gs://%s/TEST_BATCH_PREDICT/", BUCKET_ID); - BatchPredict.batchPredict(PROJECT_ID, MODEL_ID, inputUri, outputUri); - String got = bout.toString(); - assertThat(got).contains("does not exist"); - } catch (IOException | ExecutionException | InterruptedException e) { - assertThat(e.getMessage()).contains("does not exist"); - } - } -} diff --git a/automl/src/test/java/beta/automl/CancelOperationTest.java b/automl/src/test/java/beta/automl/CancelOperationTest.java deleted file mode 100644 index 30ae1177463..00000000000 --- a/automl/src/test/java/beta/automl/CancelOperationTest.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.api.gax.rpc.NotFoundException; -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import io.grpc.StatusRuntimeException; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -public class CancelOperationTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testCancelOperation() throws IOException { - String operationFullPathId = - String.format( - "projects/%s/locations/%s/operations/%s", PROJECT_ID, "us-central1", "TCN0000000000"); - // Any cancelled operation on models or datasets will be hidden once the operations are flagged - // as failed operations - // which makes them hard to delete in the teardown. - try { - CancelOperation.cancelOperation(operationFullPathId); - String got = bout.toString(); - assertThat(got).contains("not found"); - } catch (NotFoundException | StatusRuntimeException | InterruptedException e) { - assertThat(e.getMessage()).contains("not found"); - } - } -} diff --git a/automl/src/test/java/beta/automl/DeleteDatasetTest.java b/automl/src/test/java/beta/automl/DeleteDatasetTest.java deleted file mode 100644 index 794a795e8aa..00000000000 --- a/automl/src/test/java/beta/automl/DeleteDatasetTest.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.cloud.automl.v1beta1.Dataset; -import com.google.cloud.automl.v1beta1.LocationName; -import com.google.cloud.automl.v1beta1.TextExtractionDatasetMetadata; -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class DeleteDatasetTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - private String datasetId; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() throws IOException { - // Create a fake dataset to be deleted - // Create a random dataset name with a length of 32 characters (max allowed by AutoML) - // To prevent name collisions when running tests in multiple java versions at once. - // AutoML doesn't allow "-", but accepts "_" - String datasetName = - String.format("test_%s", UUID.randomUUID().toString().replace("-", "_").substring(0, 26)); - try (AutoMlClient client = AutoMlClient.create()) { - LocationName projectLocation = LocationName.of(PROJECT_ID, "us-central1"); - TextExtractionDatasetMetadata metadata = TextExtractionDatasetMetadata.newBuilder().build(); - Dataset dataset = - Dataset.newBuilder() - .setDisplayName(datasetName) - .setTextExtractionDatasetMetadata(metadata) - .build(); - Dataset createdDataset = client.createDataset(projectLocation, dataset); - String[] names = createdDataset.getName().split("/"); - datasetId = names[names.length - 1]; - } - - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testDeleteDataset() throws IOException, ExecutionException, InterruptedException { - DeleteDataset.deleteDataset(PROJECT_ID, datasetId); - String got = bout.toString(); - assertThat(got).contains("Dataset deleted."); - } -} diff --git a/automl/src/test/java/beta/automl/DeleteModelTest.java b/automl/src/test/java/beta/automl/DeleteModelTest.java deleted file mode 100644 index b69cababe76..00000000000 --- a/automl/src/test/java/beta/automl/DeleteModelTest.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -public class DeleteModelTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testDeleteModel() { - // As model creation can take many hours, instead try to delete a - // nonexistent model and confirm that the model was not found, but other - // elements of the request were valid. - try { - DeleteModel.deleteModel(PROJECT_ID, "TRL0000000000000000000"); - String got = bout.toString(); - assertThat(got).contains("The model does not exist"); - } catch (IOException | ExecutionException | InterruptedException e) { - assertThat(e.getMessage()).contains("The model does not exist"); - } - } -} diff --git a/automl/src/test/java/beta/automl/DeployModelTest.java b/automl/src/test/java/beta/automl/DeployModelTest.java deleted file mode 100644 index 2e3ce08d98e..00000000000 --- a/automl/src/test/java/beta/automl/DeployModelTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -public class DeployModelTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String MODEL_ID = "TEN0000000000000000000"; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testDeployModel() { - // As model deployment can take a long time, instead try to deploy a - // nonexistent model and confirm that the model was not found, but other - // elements of the request were valid. - try { - DeployModel.deployModel(PROJECT_ID, MODEL_ID); - String got = bout.toString(); - assertThat(got).contains("The model does not exist"); - } catch (IOException | ExecutionException | InterruptedException e) { - assertThat(e.getMessage()).contains("The model does not exist"); - } - } -} diff --git a/automl/src/test/java/beta/automl/GetModelEvaluationTest.java b/automl/src/test/java/beta/automl/GetModelEvaluationTest.java deleted file mode 100644 index 8c789f883ba..00000000000 --- a/automl/src/test/java/beta/automl/GetModelEvaluationTest.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.ListModelEvaluationsRequest; -import com.google.cloud.automl.v1.ModelEvaluation; -import com.google.cloud.automl.v1.ModelName; -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -public class GetModelEvaluationTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String MODEL_ID = System.getenv("ENTITY_EXTRACTION_MODEL_ID"); - private String modelEvaluationId; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - requireEnvVar("ENTITY_EXTRACTION_MODEL_ID"); - } - - @Before - public void setUp() throws IOException { - // Get a model evaluation ID from the List request first to be used in the Get call - try (AutoMlClient client = AutoMlClient.create()) { - ModelName modelFullId = ModelName.of(PROJECT_ID, "us-central1", MODEL_ID); - ListModelEvaluationsRequest modelEvaluationsrequest = - ListModelEvaluationsRequest.newBuilder().setParent(modelFullId.toString()).build(); - ModelEvaluation modelEvaluation = - client - .listModelEvaluations(modelEvaluationsrequest) - .getPage() - .getValues() - .iterator() - .next(); - modelEvaluationId = modelEvaluation.getName().split("/modelEvaluations/")[1]; - } - - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testGetModelEvaluation() throws IOException { - GetModelEvaluation.getModelEvaluation(PROJECT_ID, MODEL_ID, modelEvaluationId); - String got = bout.toString(); - assertThat(got).contains("Model Evaluation Name:"); - } -} diff --git a/automl/src/test/java/beta/automl/GetModelTest.java b/automl/src/test/java/beta/automl/GetModelTest.java deleted file mode 100644 index d8e2b5c1b2c..00000000000 --- a/automl/src/test/java/beta/automl/GetModelTest.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -public class GetModelTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String MODEL_ID = System.getenv("ENTITY_EXTRACTION_MODEL_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - requireEnvVar("ENTITY_EXTRACTION_MODEL_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testGetModel() throws IOException { - GetModel.getModel(PROJECT_ID, MODEL_ID); - String got = bout.toString(); - assertThat(got).contains("Model id: " + MODEL_ID); - } -} diff --git a/automl/src/test/java/beta/automl/GetOperationStatusTest.java b/automl/src/test/java/beta/automl/GetOperationStatusTest.java deleted file mode 100644 index 2a9b5c228de..00000000000 --- a/automl/src/test/java/beta/automl/GetOperationStatusTest.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.cloud.automl.v1beta1.LocationName; -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import com.google.longrunning.ListOperationsRequest; -import com.google.longrunning.Operation; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -public class GetOperationStatusTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private String operationId; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() throws IOException { - // Use list operations to get a single operation id for the get call. - try (AutoMlClient client = AutoMlClient.create()) { - LocationName projectLocation = LocationName.of(PROJECT_ID, "us-central1"); - ListOperationsRequest request = - ListOperationsRequest.newBuilder().setName(projectLocation.toString()).build(); - Operation operation = - client.getOperationsClient().listOperations(request).iterateAll().iterator().next(); - operationId = operation.getName(); - } - - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testGetOperationStatus() throws IOException { - GetOperationStatus.getOperationStatus(operationId); - String got = bout.toString(); - assertThat(got).contains("Operation details:"); - } -} diff --git a/automl/src/test/java/beta/automl/ImportDatasetTest.java b/automl/src/test/java/beta/automl/ImportDatasetTest.java deleted file mode 100644 index 45a95385d5c..00000000000 --- a/automl/src/test/java/beta/automl/ImportDatasetTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.api.gax.rpc.NotFoundException; -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeoutException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class ImportDatasetTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String BUCKET_ID = PROJECT_ID + "-lcm"; - private static final String BUCKET = "gs://" + BUCKET_ID; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testImportDataset() throws TimeoutException { - try { - ImportDataset.importDataset( - PROJECT_ID, "TCN0000000000", BUCKET + "/entity-extraction/dataset.csv"); - String got = bout.toString(); - assertThat(got).contains("doesn't exist"); - } catch (NotFoundException | IOException | ExecutionException | InterruptedException e) { - assertThat(e.getMessage()).contains("doesn't exist"); - } - } -} diff --git a/automl/src/test/java/beta/automl/ListDatasetsTest.java b/automl/src/test/java/beta/automl/ListDatasetsTest.java deleted file mode 100644 index 3a9c45e1d17..00000000000 --- a/automl/src/test/java/beta/automl/ListDatasetsTest.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class ListDatasetsTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testListDataset() throws IOException { - ListDatasets.listDatasets(PROJECT_ID); - String got = bout.toString(); - assertThat(got).contains("Dataset id:"); - } -} diff --git a/automl/src/test/java/beta/automl/ListModelEvaluationsTest.java b/automl/src/test/java/beta/automl/ListModelEvaluationsTest.java deleted file mode 100644 index d6deda9de7e..00000000000 --- a/automl/src/test/java/beta/automl/ListModelEvaluationsTest.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -public class ListModelEvaluationsTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String MODEL_ID = System.getenv("ENTITY_EXTRACTION_MODEL_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - requireEnvVar("ENTITY_EXTRACTION_MODEL_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testListModelEvaluations() throws IOException { - ListModelEvaluations.listModelEvaluations(PROJECT_ID, MODEL_ID); - String got = bout.toString(); - assertThat(got).contains("Model Evaluation Name:"); - } -} diff --git a/automl/src/test/java/beta/automl/ListModelsTest.java b/automl/src/test/java/beta/automl/ListModelsTest.java deleted file mode 100644 index 67deb9da3ee..00000000000 --- a/automl/src/test/java/beta/automl/ListModelsTest.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -public class ListModelsTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - // Skipping this test until backend is cleaned up. - // https://github.com/googleapis/java-automl/issues/291 - @Test - @Ignore - public void testListModels() throws IOException { - ListModels.listModels(PROJECT_ID); - String got = bout.toString(); - assertThat(got).contains("Model id:"); - } -} diff --git a/automl/src/test/java/beta/automl/SetEndpointIT.java b/automl/src/test/java/beta/automl/SetEndpointIT.java deleted file mode 100644 index 51a08314c23..00000000000 --- a/automl/src/test/java/beta/automl/SetEndpointIT.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** Tests for Automl Set Endpoint */ -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class SetEndpointIT { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testSetEndpoint() throws IOException { - // Act - SetEndpoint.setEndpoint(PROJECT_ID); - - // Assert - String got = bout.toString(); - assertThat(got).contains("display_name: \"do_not_delete_eu\""); - } -} diff --git a/automl/src/test/java/beta/automl/TablesBatchPredictBigQueryTest.java b/automl/src/test/java/beta/automl/TablesBatchPredictBigQueryTest.java deleted file mode 100644 index 184a889dc67..00000000000 --- a/automl/src/test/java/beta/automl/TablesBatchPredictBigQueryTest.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class TablesBatchPredictBigQueryTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - - private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); - private static final String MODEL_ID = "TBL0000000000000000000"; - private static final String INPUT_URI = - String.format( - "bq://%s.automl_do_not_delete_predict_test.automl_predict_test_table", PROJECT_ID); - private static final String OUTPUT_URI = "bq://" + PROJECT_ID; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("GOOGLE_CLOUD_PROJECT"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testTablesBigQueryBatchPredict() { - // As batch prediction can take a long time. Try to batch predict on a model and confirm that - // the model was not found, but other elements of the request were valid. - try { - TablesBatchPredictBigQuery.batchPredict(PROJECT_ID, MODEL_ID, INPUT_URI, OUTPUT_URI); - String got = bout.toString(); - assertThat(got).contains("does not exist"); - } catch (IOException | ExecutionException | InterruptedException e) { - assertThat(e.getMessage()).contains("does not exist"); - } - } -} diff --git a/automl/src/test/java/beta/automl/TablesCreateDatasetTest.java b/automl/src/test/java/beta/automl/TablesCreateDatasetTest.java deleted file mode 100644 index d8bc81ef59a..00000000000 --- a/automl/src/test/java/beta/automl/TablesCreateDatasetTest.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class TablesCreateDatasetTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - private String datasetId; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() throws InterruptedException, ExecutionException, IOException { - // Delete the created dataset - DeleteDataset.deleteDataset(PROJECT_ID, datasetId); - System.setOut(originalPrintStream); - } - - @Test - public void testTablesCreateDataset() - throws IOException, ExecutionException, InterruptedException { - // Create a random dataset name with a length of 32 characters (max allowed by AutoML) - // To prevent name collisions when running tests in multiple java versions at once. - // AutoML doesn't allow "-", but accepts "_" - String datasetName = - String.format("test_%s", UUID.randomUUID().toString().replace("-", "_").substring(0, 26)); - TablesCreateDataset.createDataset(PROJECT_ID, datasetName); - - String got = bout.toString(); - assertThat(got).contains("Dataset id:"); - datasetId = got.split("Dataset id: ")[1].split("\n")[0]; - } -} diff --git a/automl/src/test/java/beta/automl/TablesCreateModelTest.java b/automl/src/test/java/beta/automl/TablesCreateModelTest.java deleted file mode 100644 index d13ec984bdc..00000000000 --- a/automl/src/test/java/beta/automl/TablesCreateModelTest.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class TablesCreateModelTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - - private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); - private static final String DATASET_ID = "TBL00000000000000000000"; - private static final String TABLE_SPEC_ID = "3172574831249981440"; - private static final String COLUMN_SPEC_ID = "3224682886313541632"; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testTablesCreateModel() throws IOException, ExecutionException, InterruptedException { - try { - // Create a random dataset name with a length of 32 characters (max allowed by AutoML) - // To prevent name collisions when running tests in multiple java versions at once. - // AutoML doesn't allow "-", but accepts "_" - String modelName = - String.format("test_%s", UUID.randomUUID().toString().replace("-", "_").substring(0, 26)); - TablesCreateModel.createModel( - PROJECT_ID, DATASET_ID, TABLE_SPEC_ID, COLUMN_SPEC_ID, modelName); - String got = bout.toString(); - assertThat(got).contains("Dataset does not exist"); - } catch (IOException | ExecutionException | InterruptedException e) { - assertThat(e.getMessage()).contains("Dataset does not exist"); - } - } -} diff --git a/automl/src/test/java/beta/automl/TablesGetModelTest.java b/automl/src/test/java/beta/automl/TablesGetModelTest.java deleted file mode 100644 index 80a52c5b237..00000000000 --- a/automl/src/test/java/beta/automl/TablesGetModelTest.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -public class TablesGetModelTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String MODEL_ID = "TBL7473655411900416000"; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testGetModel() throws IOException { - GetModel.getModel(PROJECT_ID, MODEL_ID); - String got = bout.toString(); - assertThat(got).contains("Model id: " + MODEL_ID); - } -} diff --git a/automl/src/test/java/beta/automl/TablesImportDatasetTest.java b/automl/src/test/java/beta/automl/TablesImportDatasetTest.java deleted file mode 100644 index fc36afd9272..00000000000 --- a/automl/src/test/java/beta/automl/TablesImportDatasetTest.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class TablesImportDatasetTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testTablesImportDataset() { - try { - TablesImportDataset.importDataset( - PROJECT_ID, "TEN0000000000000000000", "gs://cloud-ml-tables-data/bank-marketing.csv"); - String got = bout.toString(); - assertThat(got).contains("The Dataset doesn't exist or is inaccessible for use with AutoMl."); - } catch (IOException | ExecutionException | InterruptedException e) { - assertThat(e.getMessage()) - .contains("The Dataset doesn't exist or is inaccessible for use with AutoMl."); - } - } -} diff --git a/automl/src/test/java/beta/automl/TablesPredictTest.java b/automl/src/test/java/beta/automl/TablesPredictTest.java deleted file mode 100644 index b1410a3930a..00000000000 --- a/automl/src/test/java/beta/automl/TablesPredictTest.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.DeployModelRequest; -import com.google.cloud.automl.v1.Model; -import com.google.cloud.automl.v1.ModelName; -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import com.google.protobuf.Value; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class TablesPredictTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - - private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); - private static final String MODEL_ID = "TBL7972827093840953344"; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() throws IOException, ExecutionException, InterruptedException { - // Verify that the model is deployed for prediction - try (AutoMlClient client = AutoMlClient.create()) { - ModelName modelFullId = ModelName.of(PROJECT_ID, "us-central1", MODEL_ID); - Model model = client.getModel(modelFullId); - if (model.getDeploymentState() == Model.DeploymentState.UNDEPLOYED) { - // Deploy the model if not deployed - DeployModelRequest request = - DeployModelRequest.newBuilder().setName(modelFullId.toString()).build(); - client.deployModelAsync(request).get(); - } - } - - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testTablesPredict() throws IOException { - List values = new ArrayList<>(); - values.add(Value.newBuilder().setNumberValue(39).build()); // Age - values.add(Value.newBuilder().setStringValue("technician").build()); // Job - values.add(Value.newBuilder().setStringValue("married").build()); // MaritalStatus - values.add(Value.newBuilder().setStringValue("secondary").build()); // Education - values.add(Value.newBuilder().setStringValue("no").build()); // Default - values.add(Value.newBuilder().setNumberValue(52).build()); // Balance - values.add(Value.newBuilder().setStringValue("no").build()); // Housing - values.add(Value.newBuilder().setStringValue("no").build()); // Loan - values.add(Value.newBuilder().setStringValue("cellular").build()); // Contact - values.add(Value.newBuilder().setNumberValue(12).build()); // Day - values.add(Value.newBuilder().setStringValue("aug").build()); // Month - values.add(Value.newBuilder().setNumberValue(96).build()); // Duration - values.add(Value.newBuilder().setNumberValue(2).build()); // Campaign - values.add(Value.newBuilder().setNumberValue(-1).build()); // PDays - values.add(Value.newBuilder().setNumberValue(0).build()); // Previous - values.add(Value.newBuilder().setStringValue("unknown").build()); // POutcome - - TablesPredict.predict(PROJECT_ID, MODEL_ID, values); - - String got = bout.toString(); - assertThat(got).contains("Prediction results:"); - } -} diff --git a/automl/src/test/java/beta/automl/UndeployModelTest.java b/automl/src/test/java/beta/automl/UndeployModelTest.java deleted file mode 100644 index 9740fa86cab..00000000000 --- a/automl/src/test/java/beta/automl/UndeployModelTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -public class UndeployModelTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String MODEL_ID = "TEN0000000000000000000"; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testUndeployModel() { - // As model deployment can take a long time, instead try to deploy a - // nonexistent model and confirm that the model was not found, but other - // elements of the request were valid. - try { - UndeployModel.undeployModel(PROJECT_ID, MODEL_ID); - String got = bout.toString(); - assertThat(got).contains("The model does not exist"); - } catch (IOException | ExecutionException | InterruptedException e) { - assertThat(e.getMessage()).contains("The model does not exist"); - } - } -} diff --git a/automl/src/test/java/beta/automl/VideoClassificationCreateDatasetTest.java b/automl/src/test/java/beta/automl/VideoClassificationCreateDatasetTest.java deleted file mode 100644 index 174fe37e6c7..00000000000 --- a/automl/src/test/java/beta/automl/VideoClassificationCreateDatasetTest.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.cloud.automl.v1beta1.DatasetName; -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class VideoClassificationCreateDatasetTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - private String datasetId; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() throws InterruptedException, ExecutionException, IOException { - // Delete the created dataset - try (AutoMlClient client = AutoMlClient.create()) { - // Get the full path of the dataset. - DatasetName datasetFullId = DatasetName.of(PROJECT_ID, "us-central1", datasetId); - client.deleteDatasetAsync(datasetFullId).get(); - } - System.setOut(originalPrintStream); - } - - @Test - public void testCreateDataset() throws IOException, ExecutionException, InterruptedException { - // Create a random dataset name with a length of 32 characters (max allowed by AutoML) - // To prevent name collisions when running tests in multiple java versions at once. - // AutoML doesn't allow "-", but accepts "_" - String datasetName = - String.format("test_%s", UUID.randomUUID().toString().replace("-", "_").substring(0, 26)); - VideoClassificationCreateDataset.createDataset(PROJECT_ID, datasetName); - - String got = bout.toString(); - assertThat(got).contains("Dataset id:"); - datasetId = got.split("Dataset id: ")[1].split("\n")[0]; - } -} diff --git a/automl/src/test/java/beta/automl/VideoClassificationCreateModelTest.java b/automl/src/test/java/beta/automl/VideoClassificationCreateModelTest.java deleted file mode 100644 index 4dc9df168f0..00000000000 --- a/automl/src/test/java/beta/automl/VideoClassificationCreateModelTest.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class VideoClassificationCreateModelTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - - private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); - private static final String DATASET_ID = "VCN00000000000000"; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("GOOGLE_CLOUD_PROJECT"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testVisionClassificationCreateModel() - throws IOException, ExecutionException, InterruptedException { - try { - // Create a random dataset name with a length of 32 characters (max allowed by AutoML) - // To prevent name collisions when running tests in multiple java versions at once. - // AutoML doesn't allow "-", but accepts "_" - String modelName = - String.format("test_%s", UUID.randomUUID().toString().replace("-", "_").substring(0, 26)); - VideoClassificationCreateModel.createModel(PROJECT_ID, DATASET_ID, modelName); - String got = bout.toString(); - assertThat(got).contains("Dataset does not exist"); - } catch (IOException | ExecutionException | InterruptedException e) { - assertThat(e.getMessage()).contains("Dataset does not exist"); - } - } -} diff --git a/automl/src/test/java/beta/automl/VideoObjectTrackingCreateDatasetTest.java b/automl/src/test/java/beta/automl/VideoObjectTrackingCreateDatasetTest.java deleted file mode 100644 index 63c8a66bdb1..00000000000 --- a/automl/src/test/java/beta/automl/VideoObjectTrackingCreateDatasetTest.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.automl.v1beta1.AutoMlClient; -import com.google.cloud.automl.v1beta1.DatasetName; -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class VideoObjectTrackingCreateDatasetTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - private String datasetId; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() throws InterruptedException, ExecutionException, IOException { - // Delete the created dataset - try (AutoMlClient client = AutoMlClient.create()) { - // Get the full path of the dataset. - DatasetName datasetFullId = DatasetName.of(PROJECT_ID, "us-central1", datasetId); - client.deleteDatasetAsync(datasetFullId).get(); - } - System.setOut(originalPrintStream); - } - - @Test - public void testCreateDataset() throws IOException, ExecutionException, InterruptedException { - // Create a random dataset name with a length of 32 characters (max allowed by AutoML) - // To prevent name collisions when running tests in multiple java versions at once. - // AutoML doesn't allow "-", but accepts "_" - String datasetName = - String.format("test_%s", UUID.randomUUID().toString().replace("-", "_").substring(0, 26)); - VideoObjectTrackingCreateDataset.createDataset(PROJECT_ID, datasetName); - - String got = bout.toString(); - assertThat(got).contains("Dataset id:"); - datasetId = got.split("Dataset id: ")[1].split("\n")[0]; - } -} diff --git a/automl/src/test/java/beta/automl/VideoObjectTrackingCreateModelTest.java b/automl/src/test/java/beta/automl/VideoObjectTrackingCreateModelTest.java deleted file mode 100644 index fca9e38ff5a..00000000000 --- a/automl/src/test/java/beta/automl/VideoObjectTrackingCreateModelTest.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package beta.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class VideoObjectTrackingCreateModelTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - - private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); - private static final String DATASET_ID = "VOT0000000000000000"; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static String requireEnvVar(String varName) { - String value = System.getenv(varName); - assertNotNull( - "Environment variable " + varName + " is required to perform these tests.", - System.getenv(varName)); - return value; - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("GOOGLE_CLOUD_PROJECT"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testVisionClassificationCreateModel() - throws IOException, ExecutionException, InterruptedException { - - try { - // Create a random dataset name with a length of 32 characters (max allowed by AutoML) - // To prevent name collisions when running tests in multiple java versions at once. - // AutoML doesn't allow "-", but accepts "_" - String modelName = - String.format("test_%s", UUID.randomUUID().toString().replace("-", "_").substring(0, 26)); - VideoObjectTrackingCreateModel.createModel(PROJECT_ID, DATASET_ID, modelName); - - String got = bout.toString(); - assertThat(got).contains("Dataset does not exist"); - } catch (IOException | ExecutionException | InterruptedException e) { - assertThat(e.getMessage()).contains("Dataset does not exist"); - } - } -} diff --git a/automl/src/test/java/com/example/automl/BatchPredictTest.java b/automl/src/test/java/com/example/automl/BatchPredictTest.java deleted file mode 100644 index 98c0846bc96..00000000000 --- a/automl/src/test/java/com/example/automl/BatchPredictTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class BatchPredictTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String BUCKET_ID = PROJECT_ID + "-lcm"; - private static final String MODEL_ID = "TEN0000000000000000000"; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - requireEnvVar("ENTITY_EXTRACTION_MODEL_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testBatchPredict() { - // As batch prediction can take a long time. Try to batch predict on a model and confirm that - // the model was not found, but other elements of the request were valid. - try { - String inputUri = String.format("gs://%s/entity-extraction/input.jsonl", BUCKET_ID); - String outputUri = String.format("gs://%s/TEST_BATCH_PREDICT/", BUCKET_ID); - BatchPredict.batchPredict(PROJECT_ID, MODEL_ID, inputUri, outputUri); - String got = bout.toString(); - assertThat(got).contains("does not exist"); - } catch (IOException | ExecutionException | InterruptedException e) { - assertThat(e.getMessage()).contains("does not exist"); - } - } -} diff --git a/automl/src/test/java/com/example/automl/DeleteDatasetTest.java b/automl/src/test/java/com/example/automl/DeleteDatasetTest.java deleted file mode 100644 index def0431c7bc..00000000000 --- a/automl/src/test/java/com/example/automl/DeleteDatasetTest.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class DeleteDatasetTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - private String datasetId; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() throws InterruptedException, ExecutionException, IOException { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - - // Create a fake dataset to be deleted - // Create a random dataset name with a length of 32 characters (max allowed by AutoML) - // To prevent name collisions when running tests in multiple java versions at once. - // AutoML doesn't allow "-", but accepts "_" - String datasetName = - String.format("test_%s", UUID.randomUUID().toString().replace("-", "_").substring(0, 26)); - LanguageEntityExtractionCreateDataset.createDataset(PROJECT_ID, datasetName); - String got = bout.toString(); - datasetId = got.split("Dataset id: ")[1].split("\n")[0]; - - bout = new ByteArrayOutputStream(); - out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testDeleteDataset() throws IOException, ExecutionException, InterruptedException { - DeleteDataset.deleteDataset(PROJECT_ID, datasetId); - String got = bout.toString(); - assertThat(got).contains("Dataset deleted."); - } -} diff --git a/automl/src/test/java/com/example/automl/DeleteModelTest.java b/automl/src/test/java/com/example/automl/DeleteModelTest.java deleted file mode 100644 index 40bf14b55fe..00000000000 --- a/automl/src/test/java/com/example/automl/DeleteModelTest.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -public class DeleteModelTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testDeleteModel() { - // As model creation can take many hours, instead try to delete a - // nonexistent model and confirm that the model was not found, but other - // elements of the request were valid. - try { - DeleteModel.deleteModel(PROJECT_ID, "TRL0000000000000000000"); - String got = bout.toString(); - assertThat(got).contains("The model does not exist"); - } catch (IOException | ExecutionException | InterruptedException e) { - assertThat(e.getMessage()).contains("The model does not exist"); - } - } -} diff --git a/automl/src/test/java/com/example/automl/DeployModelTest.java b/automl/src/test/java/com/example/automl/DeployModelTest.java deleted file mode 100644 index 28208289aaf..00000000000 --- a/automl/src/test/java/com/example/automl/DeployModelTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -public class DeployModelTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String MODEL_ID = "TEN0000000000000000000"; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testDeployModel() { - // As model deployment can take a long time, instead try to deploy a - // nonexistent model and confirm that the model was not found, but other - // elements of the request were valid. - try { - DeployModel.deployModel(PROJECT_ID, MODEL_ID); - String got = bout.toString(); - assertThat(got).contains("The model does not exist"); - } catch (IOException | ExecutionException | InterruptedException e) { - assertThat(e.getMessage()).contains("The model does not exist"); - } - } -} diff --git a/automl/src/test/java/com/example/automl/ExportDatasetTest.java b/automl/src/test/java/com/example/automl/ExportDatasetTest.java deleted file mode 100644 index fe823e40812..00000000000 --- a/automl/src/test/java/com/example/automl/ExportDatasetTest.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class ExportDatasetTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String DATASET_ID = "TEN0000000000000000000"; - private static final String BUCKET_ID = PROJECT_ID + "-lcm"; - private static final String BUCKET = "gs://" + BUCKET_ID; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - requireEnvVar("ENTITY_EXTRACTION_DATASET_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testExportDataset() throws IOException, ExecutionException, InterruptedException { - // As exporting a dataset can take a long time and only one operation can be run on a dataset - // at once. Try to export a nonexistent dataset and confirm that the dataset was not found, but - // other elements of the request were valid. - try { - ExportDataset.exportDataset(PROJECT_ID, DATASET_ID, BUCKET + "/TEST_EXPORT_OUTPUT/"); - String got = bout.toString(); - assertThat(got).contains("The Dataset doesn't exist or is inaccessible for use with AutoMl."); - } catch (IOException | ExecutionException | InterruptedException e) { - assertThat(e.getMessage()) - .contains("The Dataset doesn't exist or is inaccessible for use with AutoMl."); - } - } -} diff --git a/automl/src/test/java/com/example/automl/GetDatasetTest.java b/automl/src/test/java/com/example/automl/GetDatasetTest.java deleted file mode 100644 index 9daf0c15a68..00000000000 --- a/automl/src/test/java/com/example/automl/GetDatasetTest.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class GetDatasetTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String DATASET_ID = System.getenv("ENTITY_EXTRACTION_DATASET_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - requireEnvVar("ENTITY_EXTRACTION_DATASET_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testGetDataset() throws IOException { - GetDataset.getDataset(PROJECT_ID, DATASET_ID); - String got = bout.toString(); - assertThat(got).contains("Dataset id:"); - } -} diff --git a/automl/src/test/java/com/example/automl/GetModelEvaluationTest.java b/automl/src/test/java/com/example/automl/GetModelEvaluationTest.java deleted file mode 100644 index f29a8b1e66b..00000000000 --- a/automl/src/test/java/com/example/automl/GetModelEvaluationTest.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -public class GetModelEvaluationTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String MODEL_ID = System.getenv("ENTITY_EXTRACTION_MODEL_ID"); - private String modelEvaluationId; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - requireEnvVar("ENTITY_EXTRACTION_MODEL_ID"); - } - - @Before - public void setUp() throws IOException { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - - // Get a model evaluation ID from the List request first to be used in the Get call - ListModelEvaluations.listModelEvaluations(PROJECT_ID, MODEL_ID); - String got = bout.toString(); - modelEvaluationId = got.split(MODEL_ID + "/modelEvaluations/")[1].split("\n")[0]; - assertThat(got).contains("Model Evaluation Name:"); - - bout = new ByteArrayOutputStream(); - out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testGetModelEvaluation() throws IOException { - GetModelEvaluation.getModelEvaluation(PROJECT_ID, MODEL_ID, modelEvaluationId); - String got = bout.toString(); - assertThat(got).contains("Model Evaluation Name:"); - } -} diff --git a/automl/src/test/java/com/example/automl/GetModelTest.java b/automl/src/test/java/com/example/automl/GetModelTest.java deleted file mode 100644 index be2404ad5b1..00000000000 --- a/automl/src/test/java/com/example/automl/GetModelTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -public class GetModelTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String MODEL_ID = System.getenv("ENTITY_EXTRACTION_MODEL_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - requireEnvVar("ENTITY_EXTRACTION_MODEL_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testGetModel() throws IOException { - GetModel.getModel(PROJECT_ID, MODEL_ID); - String got = bout.toString(); - assertThat(got).contains("Model id: " + MODEL_ID); - } -} diff --git a/automl/src/test/java/com/example/automl/GetOperationStatusTest.java b/automl/src/test/java/com/example/automl/GetOperationStatusTest.java deleted file mode 100644 index 7ddb70b1287..00000000000 --- a/automl/src/test/java/com/example/automl/GetOperationStatusTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -public class GetOperationStatusTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private String operationId; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() throws IOException { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - - ListOperationStatus.listOperationStatus(PROJECT_ID); - String got = bout.toString(); - operationId = got.split("\n")[1].split(":")[1].trim(); - assertThat(got).contains("Operation details:"); - - bout = new ByteArrayOutputStream(); - out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testGetOperationStatus() throws IOException { - GetOperationStatus.getOperationStatus(operationId); - String got = bout.toString(); - assertThat(got).contains("Operation details:"); - } -} diff --git a/automl/src/test/java/com/example/automl/ImportDatasetTest.java b/automl/src/test/java/com/example/automl/ImportDatasetTest.java deleted file mode 100644 index 6b07e7a5ffe..00000000000 --- a/automl/src/test/java/com/example/automl/ImportDatasetTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.api.gax.rpc.NotFoundException; -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import io.grpc.StatusRuntimeException; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeoutException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class ImportDatasetTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String DATASET_ID = "TEN0000000000000000000"; - private static final String BUCKET_ID = PROJECT_ID + "-lcm"; - private static final String BUCKET = "gs://" + BUCKET_ID; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - System.setOut(originalPrintStream); - } - - @Test - public void testImportDataset() throws InterruptedException, TimeoutException, IOException { - - try { - ImportDataset.importDataset( - PROJECT_ID, DATASET_ID, BUCKET + "/entity-extraction/dataset.csv"); - String got = bout.toString(); - assertThat(got).contains("The Dataset doesn't exist "); - } catch (NotFoundException | ExecutionException | StatusRuntimeException ex) { - assertThat(ex.getMessage()).contains("The Dataset doesn't exist"); - } - } -} diff --git a/automl/src/test/java/com/example/automl/LanguageEntityExtractionCreateDatasetTest.java b/automl/src/test/java/com/example/automl/LanguageEntityExtractionCreateDatasetTest.java deleted file mode 100644 index 65f1ac25fad..00000000000 --- a/automl/src/test/java/com/example/automl/LanguageEntityExtractionCreateDatasetTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class LanguageEntityExtractionCreateDatasetTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - private String datasetId; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() throws InterruptedException, ExecutionException, IOException { - // Delete the created dataset - DeleteDataset.deleteDataset(PROJECT_ID, datasetId); - System.setOut(originalPrintStream); - } - - @Test - public void testCreateDataset() throws IOException, ExecutionException, InterruptedException { - // Create a random dataset name with a length of 32 characters (max allowed by AutoML) - // To prevent name collisions when running tests in multiple java versions at once. - // AutoML doesn't allow "-", but accepts "_" - String datasetName = - String.format("test_%s", UUID.randomUUID().toString().replace("-", "_").substring(0, 26)); - LanguageEntityExtractionCreateDataset.createDataset(PROJECT_ID, datasetName); - - String got = bout.toString(); - assertThat(got).contains("Dataset id:"); - datasetId = got.split("Dataset id: ")[1].split("\n")[0]; - } -} diff --git a/automl/src/test/java/com/example/automl/LanguageEntityExtractionCreateModelTest.java b/automl/src/test/java/com/example/automl/LanguageEntityExtractionCreateModelTest.java deleted file mode 100644 index c1896347972..00000000000 --- a/automl/src/test/java/com/example/automl/LanguageEntityExtractionCreateModelTest.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class LanguageEntityExtractionCreateModelTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String DATASET_ID = "TEN0000000000000000000"; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testLanguageEntityExtractionCreateModel() { - // As entity extraction does not let you cancel model creation, instead try to create a model - // from a nonexistent dataset, but other elements of the request were valid. - try { - // Create a random dataset name with a length of 32 characters (max allowed by AutoML) - // To prevent name collisions when running tests in multiple java versions at once. - // AutoML doesn't allow "-", but accepts "_" - String modelName = - String.format("test_%s", UUID.randomUUID().toString().replace("-", "_").substring(0, 26)); - LanguageEntityExtractionCreateModel.createModel(PROJECT_ID, DATASET_ID, modelName); - String got = bout.toString(); - assertThat(got).contains("Dataset does not exist"); - } catch (IOException | ExecutionException | InterruptedException e) { - assertThat(e.getMessage()).contains("Dataset does not exist"); - } - } -} diff --git a/automl/src/test/java/com/example/automl/LanguageEntityExtractionPredictTest.java b/automl/src/test/java/com/example/automl/LanguageEntityExtractionPredictTest.java deleted file mode 100644 index b990a450a39..00000000000 --- a/automl/src/test/java/com/example/automl/LanguageEntityExtractionPredictTest.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.DeployModelRequest; -import com.google.cloud.automl.v1.Model; -import com.google.cloud.automl.v1.ModelName; -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class LanguageEntityExtractionPredictTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String MODEL_ID = System.getenv("ENTITY_EXTRACTION_MODEL_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("GOOGLE_CLOUD_PROJECT"); - requireEnvVar("AUTOML_PROJECT_ID"); - requireEnvVar("ENTITY_EXTRACTION_MODEL_ID"); - } - - @Before - public void setUp() throws IOException, ExecutionException, InterruptedException { - // Verify that the model is deployed for prediction - try (AutoMlClient client = AutoMlClient.create()) { - ModelName modelFullId = ModelName.of(PROJECT_ID, "us-central1", MODEL_ID); - Model model = client.getModel(modelFullId); - if (model.getDeploymentState() == Model.DeploymentState.UNDEPLOYED) { - // Deploy the model if not deployed - DeployModelRequest request = - DeployModelRequest.newBuilder().setName(modelFullId.toString()).build(); - client.deployModelAsync(request).get(); - } - } - - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testPredict() throws IOException { - String text = "Constitutional mutations in the WT1 gene in patients with Denys-Drash syndrome."; - LanguageEntityExtractionPredict.predict(PROJECT_ID, MODEL_ID, text); - String got = bout.toString(); - assertThat(got).contains("Text Extract Entity Type:"); - } -} diff --git a/automl/src/test/java/com/example/automl/LanguageSentimentAnalysisCreateDatasetTest.java b/automl/src/test/java/com/example/automl/LanguageSentimentAnalysisCreateDatasetTest.java deleted file mode 100644 index 4be69247705..00000000000 --- a/automl/src/test/java/com/example/automl/LanguageSentimentAnalysisCreateDatasetTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class LanguageSentimentAnalysisCreateDatasetTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - private String datasetId; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() throws InterruptedException, ExecutionException, IOException { - // Delete the created dataset - DeleteDataset.deleteDataset(PROJECT_ID, datasetId); - System.setOut(originalPrintStream); - } - - @Test - public void testCreateDataset() throws IOException, ExecutionException, InterruptedException { - // Create a random dataset name with a length of 32 characters (max allowed by AutoML) - // To prevent name collisions when running tests in multiple java versions at once. - // AutoML doesn't allow "-", but accepts "_" - String datasetName = - String.format("test_%s", UUID.randomUUID().toString().replace("-", "_").substring(0, 26)); - LanguageSentimentAnalysisCreateDataset.createDataset(PROJECT_ID, datasetName); - - String got = bout.toString(); - assertThat(got).contains("Dataset id:"); - datasetId = got.split("Dataset id: ")[1].split("\n")[0]; - } -} diff --git a/automl/src/test/java/com/example/automl/LanguageSentimentAnalysisCreateModelTest.java b/automl/src/test/java/com/example/automl/LanguageSentimentAnalysisCreateModelTest.java deleted file mode 100644 index 8ab18ba1367..00000000000 --- a/automl/src/test/java/com/example/automl/LanguageSentimentAnalysisCreateModelTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class LanguageSentimentAnalysisCreateModelTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String DATASET_ID = "TST00000000000000000"; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - System.setOut(originalPrintStream); - } - - @Test - public void testLanguageSentimentAnalysisCreateModel() { - // Create a model from a nonexistent dataset. - try { - // Create a random dataset name with a length of 32 characters (max allowed by AutoML) - // To prevent name collisions when running tests in multiple java versions at once. - // AutoML doesn't allow "-", but accepts "_" - String modelName = - String.format("test_%s", UUID.randomUUID().toString().replace("-", "_").substring(0, 26)); - LanguageSentimentAnalysisCreateModel.createModel(PROJECT_ID, DATASET_ID, modelName); - String got = bout.toString(); - assertThat(got).contains("Dataset does not exist"); - } catch (IOException | ExecutionException | InterruptedException e) { - assertThat(e.getMessage()).contains("Dataset does not exist"); - } - } -} diff --git a/automl/src/test/java/com/example/automl/LanguageSentimentAnalysisPredictTest.java b/automl/src/test/java/com/example/automl/LanguageSentimentAnalysisPredictTest.java deleted file mode 100644 index 2a2b5f2fc3f..00000000000 --- a/automl/src/test/java/com/example/automl/LanguageSentimentAnalysisPredictTest.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.DeployModelRequest; -import com.google.cloud.automl.v1.Model; -import com.google.cloud.automl.v1.ModelName; -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class LanguageSentimentAnalysisPredictTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String MODEL_ID = System.getenv("SENTIMENT_ANALYSIS_MODEL_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - requireEnvVar("SENTIMENT_ANALYSIS_MODEL_ID"); - } - - @Before - public void setUp() throws IOException, ExecutionException, InterruptedException { - // Verify that the model is deployed for prediction - try (AutoMlClient client = AutoMlClient.create()) { - ModelName modelFullId = ModelName.of(PROJECT_ID, "us-central1", MODEL_ID); - Model model = client.getModel(modelFullId); - if (model.getDeploymentState() == Model.DeploymentState.UNDEPLOYED) { - // Deploy the model if not deployed - DeployModelRequest request = - DeployModelRequest.newBuilder().setName(modelFullId.toString()).build(); - client.deployModelAsync(request).get(); - } - } - - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testPredict() throws IOException { - String text = "Hopefully this Claritin kicks in soon"; - LanguageSentimentAnalysisPredict.predict(PROJECT_ID, MODEL_ID, text); - String got = bout.toString(); - assertThat(got).contains("Predicted sentiment score:"); - } -} diff --git a/automl/src/test/java/com/example/automl/LanguageTextClassificationCreateDatasetTest.java b/automl/src/test/java/com/example/automl/LanguageTextClassificationCreateDatasetTest.java deleted file mode 100644 index 1cc9161ca07..00000000000 --- a/automl/src/test/java/com/example/automl/LanguageTextClassificationCreateDatasetTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class LanguageTextClassificationCreateDatasetTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - private String datasetId; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() throws InterruptedException, ExecutionException, IOException { - // Delete the created dataset - DeleteDataset.deleteDataset(PROJECT_ID, datasetId); - System.setOut(originalPrintStream); - } - - @Test - public void testCreateDataset() throws IOException, ExecutionException, InterruptedException { - // Create a random dataset name with a length of 32 characters (max allowed by AutoML) - // To prevent name collisions when running tests in multiple java versions at once. - // AutoML doesn't allow "-", but accepts "_" - String datasetName = - String.format("test_%s", UUID.randomUUID().toString().replace("-", "_").substring(0, 26)); - LanguageTextClassificationCreateDataset.createDataset(PROJECT_ID, datasetName); - - String got = bout.toString(); - assertThat(got).contains("Dataset id:"); - datasetId = got.split("Dataset id: ")[1].split("\n")[0]; - } -} diff --git a/automl/src/test/java/com/example/automl/LanguageTextClassificationCreateModelTest.java b/automl/src/test/java/com/example/automl/LanguageTextClassificationCreateModelTest.java deleted file mode 100644 index 6aa35a46d16..00000000000 --- a/automl/src/test/java/com/example/automl/LanguageTextClassificationCreateModelTest.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class LanguageTextClassificationCreateModelTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String DATASET_ID = "TCN00000000000000000000"; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - System.setOut(originalPrintStream); - } - - @Test - public void testLanguageTextClassificationCreateModel() { - // Create a model from a nonexistent dataset. - try { - // Create a random dataset name with a length of 32 characters (max allowed by AutoML) - // To prevent name collisions when running tests in multiple java versions at once. - // AutoML doesn't allow "-", but accepts "_" - String modelName = - String.format("test_%s", UUID.randomUUID().toString().replace("-", "_").substring(0, 26)); - LanguageTextClassificationCreateModel.createModel(PROJECT_ID, DATASET_ID, modelName); - String got = bout.toString(); - assertThat(got).contains("Dataset does not exist"); - } catch (IOException | ExecutionException | InterruptedException e) { - assertThat(e.getMessage()).contains("Dataset does not exist"); - } - } -} diff --git a/automl/src/test/java/com/example/automl/LanguageTextClassificationPredictTest.java b/automl/src/test/java/com/example/automl/LanguageTextClassificationPredictTest.java deleted file mode 100644 index 1ae0b49cfe3..00000000000 --- a/automl/src/test/java/com/example/automl/LanguageTextClassificationPredictTest.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.DeployModelRequest; -import com.google.cloud.automl.v1.Model; -import com.google.cloud.automl.v1.ModelName; -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class LanguageTextClassificationPredictTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String MODEL_ID = System.getenv("TEXT_CLASSIFICATION_MODEL_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - requireEnvVar("TEXT_CLASSIFICATION_MODEL_ID"); - } - - @Before - public void setUp() throws IOException, ExecutionException, InterruptedException { - // Verify that the model is deployed for prediction - try (AutoMlClient client = AutoMlClient.create()) { - ModelName modelFullId = ModelName.of(PROJECT_ID, "us-central1", MODEL_ID); - Model model = client.getModel(modelFullId); - if (model.getDeploymentState() == Model.DeploymentState.UNDEPLOYED) { - // Deploy the model if not deployed - DeployModelRequest request = - DeployModelRequest.newBuilder().setName(modelFullId.toString()).build(); - client.deployModelAsync(request).get(); - } - } - - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testPredict() throws IOException { - String text = "Fruit and nut flavour"; - LanguageTextClassificationPredict.predict(PROJECT_ID, MODEL_ID, text); - String got = bout.toString(); - assertThat(got).contains("Predicted class name:"); - } -} diff --git a/automl/src/test/java/com/example/automl/ListDatasetsTest.java b/automl/src/test/java/com/example/automl/ListDatasetsTest.java deleted file mode 100644 index 1708ad1515f..00000000000 --- a/automl/src/test/java/com/example/automl/ListDatasetsTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class ListDatasetsTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testListDataset() throws IOException { - ListDatasets.listDatasets(PROJECT_ID); - String got = bout.toString(); - assertThat(got).contains("Dataset id:"); - } -} diff --git a/automl/src/test/java/com/example/automl/ListModelEvaluationsTest.java b/automl/src/test/java/com/example/automl/ListModelEvaluationsTest.java deleted file mode 100644 index e507d399177..00000000000 --- a/automl/src/test/java/com/example/automl/ListModelEvaluationsTest.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -public class ListModelEvaluationsTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String MODEL_ID = System.getenv("ENTITY_EXTRACTION_MODEL_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - requireEnvVar("ENTITY_EXTRACTION_MODEL_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testListModelEvaluations() throws IOException { - ListModelEvaluations.listModelEvaluations(PROJECT_ID, MODEL_ID); - String got = bout.toString(); - assertThat(got).contains("Model Evaluation Name:"); - } -} diff --git a/automl/src/test/java/com/example/automl/ListModelsTest.java b/automl/src/test/java/com/example/automl/ListModelsTest.java deleted file mode 100644 index 8c251806b9a..00000000000 --- a/automl/src/test/java/com/example/automl/ListModelsTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -public class ListModelsTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testListModels() throws IOException { - ListModels.listModels(PROJECT_ID); - String got = bout.toString(); - assertThat(got).contains("Model id:"); - } -} diff --git a/automl/src/test/java/com/example/automl/ListOperationStatusTest.java b/automl/src/test/java/com/example/automl/ListOperationStatusTest.java deleted file mode 100644 index 588b1f86212..00000000000 --- a/automl/src/test/java/com/example/automl/ListOperationStatusTest.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.api.client.util.ExponentialBackOff; -import com.google.api.gax.rpc.ResourceExhaustedException; -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.LocationName; -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import com.google.longrunning.ListOperationsRequest; -import com.google.longrunning.Operation; -import com.google.longrunning.OperationsClient; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -public class ListOperationStatusTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() throws IOException, InterruptedException { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - - // if the LRO status count more than 300, delete half of operations. - try (AutoMlClient client = AutoMlClient.create()) { - OperationsClient operationsClient = client.getOperationsClient(); - LocationName projectLocation = LocationName.of(PROJECT_ID, "us-central1"); - ListOperationsRequest listRequest = - ListOperationsRequest.newBuilder().setName(projectLocation.toString()).build(); - List operationFullPathsToBeDeleted = new ArrayList<>(); - for (Operation operation : operationsClient.listOperations(listRequest).iterateAll()) { - // collect unused operation into the list. - // Filter: deleting already done operations. - if (operation.getDone() && !operation.hasError()) { - operationFullPathsToBeDeleted.add(operation.getName()); - } - } - - if (operationFullPathsToBeDeleted.size() > 300) { - System.out.println("Cleaning up..."); - - for (String operationFullPath : - operationFullPathsToBeDeleted.subList(0, operationFullPathsToBeDeleted.size() / 2)) { - // retry_interval * (random value in range [1 - rand_factor, 1 + rand_factor]) - ExponentialBackOff exponentialBackOff = - new ExponentialBackOff.Builder() - .setInitialIntervalMillis(60000) - .setMaxElapsedTimeMillis(300000) - .setRandomizationFactor(0.5) - .setMultiplier(1.1) - .setMaxIntervalMillis(80000) - .build(); - - // delete unused operations. - try { - operationsClient.deleteOperation(operationFullPath); - } catch (ResourceExhaustedException ex) { - // exponential back off and retry. - long backOffInMillis = exponentialBackOff.nextBackOffMillis(); - System.out.printf( - "Backing off for %d milliseconds " + "due to Resource exhaustion.\n", - backOffInMillis); - if (backOffInMillis < 0) { - break; - } - System.out.println("Backing off" + backOffInMillis); - TimeUnit.MILLISECONDS.sleep(backOffInMillis); - } catch (Exception ex) { - throw ex; - } - } - } else { - // Clear the list since we wont anything with the list. - operationFullPathsToBeDeleted.clear(); - } - } - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testOperationStatus() throws IOException { - ListOperationStatus.listOperationStatus(PROJECT_ID); - String got = bout.toString(); - assertThat(got).contains("Operation details:"); - } -} diff --git a/automl/src/test/java/com/example/automl/TranslateCreateDatasetTest.java b/automl/src/test/java/com/example/automl/TranslateCreateDatasetTest.java deleted file mode 100644 index fe64e5bc541..00000000000 --- a/automl/src/test/java/com/example/automl/TranslateCreateDatasetTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class TranslateCreateDatasetTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - private String got; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() throws InterruptedException, ExecutionException, IOException { - String datasetId = got.split("Dataset id: ")[1].split("\n")[0]; - - // Delete the created dataset - DeleteDataset.deleteDataset(PROJECT_ID, datasetId); - System.setOut(originalPrintStream); - } - - @Test - public void testCreateDataset() throws IOException, ExecutionException, InterruptedException { - // Create a random dataset name with a length of 32 characters (max allowed by AutoML) - // To prevent name collisions when running tests in multiple java versions at once. - // AutoML doesn't allow "-", but accepts "_" - String datasetName = - String.format("test_%s", UUID.randomUUID().toString().replace("-", "_").substring(0, 26)); - TranslateCreateDataset.createDataset(PROJECT_ID, datasetName); - - got = bout.toString(); - assertThat(got).contains("Dataset id:"); - } -} diff --git a/automl/src/test/java/com/example/automl/TranslateCreateModelTest.java b/automl/src/test/java/com/example/automl/TranslateCreateModelTest.java deleted file mode 100644 index 1f197bd704f..00000000000 --- a/automl/src/test/java/com/example/automl/TranslateCreateModelTest.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -// Tests for Automl translation models. -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -public class TranslateCreateModelTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String DATASET_ID = "TRL00000000000000000"; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - System.setOut(originalPrintStream); - } - - @Test - public void testTranslateCreateModel() { - // Create a model from a nonexistent dataset. - try { - // Create a random dataset name with a length of 32 characters (max allowed by AutoML) - // To prevent name collisions when running tests in multiple java versions at once. - // AutoML doesn't allow "-", but accepts "_" - String modelName = - String.format("test_%s", UUID.randomUUID().toString().replace("-", "_").substring(0, 26)); - TranslateCreateModel.createModel(PROJECT_ID, DATASET_ID, modelName); - String got = bout.toString(); - // After setting DATASET_ID, change line below to - // assertThat(got).contains("Training started..."); - assertThat(got).contains("Dataset does not exist"); - } catch (IOException | ExecutionException | InterruptedException e) { - // After setting DATASET_ID, change line below to - // assertThat(e.getMessage()).contains("Training started..."); - assertThat(e.getMessage()).contains("Dataset does not exist"); - } - } -} diff --git a/automl/src/test/java/com/example/automl/TranslatePredictTest.java b/automl/src/test/java/com/example/automl/TranslatePredictTest.java deleted file mode 100644 index 08b584b9916..00000000000 --- a/automl/src/test/java/com/example/automl/TranslatePredictTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -// Tests for translation "Predict" sample. -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class TranslatePredictTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String modelId = System.getenv("TRANSLATION_MODEL_ID"); - private static final String filePath = "./resources/input.txt"; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - requireEnvVar("TRANSLATION_MODEL_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testPredict() throws IOException { - // Act - TranslatePredict.predict(PROJECT_ID, modelId, filePath); - - // Assert - String got = bout.toString(); - assertThat(got).contains("Translated Content"); - } -} diff --git a/automl/src/test/java/com/example/automl/UndeployModelTest.java b/automl/src/test/java/com/example/automl/UndeployModelTest.java deleted file mode 100644 index f0aa8ad2b70..00000000000 --- a/automl/src/test/java/com/example/automl/UndeployModelTest.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -public class UndeployModelTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String MODEL_ID = "TEN0000000000000000000"; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testUndeployModel() { - // As model deployment can take a long time, instead try to deploy a - // nonexistent model and confirm that the model was not found, but other - // elements of the request were valid. - try { - UndeployModel.undeployModel(PROJECT_ID, MODEL_ID); - String got = bout.toString(); - assertThat(got).contains("The model does not exist"); - } catch (IOException | ExecutionException | InterruptedException e) { - assertThat(e.getMessage()).contains("The model does not exist"); - } - } -} diff --git a/automl/src/test/java/com/example/automl/VisionClassificationCreateDatasetTest.java b/automl/src/test/java/com/example/automl/VisionClassificationCreateDatasetTest.java deleted file mode 100644 index 67a66ae5af0..00000000000 --- a/automl/src/test/java/com/example/automl/VisionClassificationCreateDatasetTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class VisionClassificationCreateDatasetTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - private String datasetId; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() throws InterruptedException, ExecutionException, IOException { - // Delete the created dataset - DeleteDataset.deleteDataset(PROJECT_ID, datasetId); - System.setOut(originalPrintStream); - } - - @Test - public void testCreateDataset() throws IOException, ExecutionException, InterruptedException { - // Create a random dataset name with a length of 32 characters (max allowed by AutoML) - // To prevent name collisions when running tests in multiple java versions at once. - // AutoML doesn't allow "-", but accepts "_" - String datasetName = - String.format("test_%s", UUID.randomUUID().toString().replace("-", "_").substring(0, 26)); - VisionClassificationCreateDataset.createDataset(PROJECT_ID, datasetName); - - String got = bout.toString(); - assertThat(got).contains("Dataset id:"); - datasetId = got.split("Dataset id: ")[1].split("\n")[0]; - } -} diff --git a/automl/src/test/java/com/example/automl/VisionClassificationCreateModelTest.java b/automl/src/test/java/com/example/automl/VisionClassificationCreateModelTest.java deleted file mode 100644 index b23e7a062ab..00000000000 --- a/automl/src/test/java/com/example/automl/VisionClassificationCreateModelTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class VisionClassificationCreateModelTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String DATASET_ID = "ICN000000000000000000"; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - System.setOut(originalPrintStream); - } - - @Test - public void testVisionClassificationCreateModel() { - // Create a model from a nonexistent dataset. - try { - // Create a random dataset name with a length of 32 characters (max allowed by AutoML) - // To prevent name collisions when running tests in multiple java versions at once. - // AutoML doesn't allow "-", but accepts "_" - String modelName = - String.format("test_%s", UUID.randomUUID().toString().replace("-", "_").substring(0, 26)); - VisionClassificationCreateModel.createModel(PROJECT_ID, DATASET_ID, modelName); - String got = bout.toString(); - assertThat(got).contains("Dataset does not exist"); - } catch (IOException | ExecutionException | InterruptedException e) { - assertThat(e.getMessage()).contains("Dataset does not exist"); - } - } -} diff --git a/automl/src/test/java/com/example/automl/VisionClassificationDeployModelNodeCountTest.java b/automl/src/test/java/com/example/automl/VisionClassificationDeployModelNodeCountTest.java deleted file mode 100644 index 68523487b15..00000000000 --- a/automl/src/test/java/com/example/automl/VisionClassificationDeployModelNodeCountTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -public class VisionClassificationDeployModelNodeCountTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String MODEL_ID = "ICN0000000000000000000"; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testDeployModelWithNodeCount() { - // As model deployment can take a long time, instead try to deploy a - // nonexistent model and confirm that the model was not found, but other - // elements of the request were valid. - try { - VisionClassificationDeployModelNodeCount.visionClassificationDeployModelNodeCount( - PROJECT_ID, MODEL_ID); - String got = bout.toString(); - assertThat(got).contains("The model does not exist"); - } catch (IOException | ExecutionException | InterruptedException e) { - assertThat(e.getMessage()).contains("The model does not exist"); - } - } -} diff --git a/automl/src/test/java/com/example/automl/VisionClassificationPredictTest.java b/automl/src/test/java/com/example/automl/VisionClassificationPredictTest.java deleted file mode 100644 index 32642df53a6..00000000000 --- a/automl/src/test/java/com/example/automl/VisionClassificationPredictTest.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.DeployModelRequest; -import com.google.cloud.automl.v1.Model; -import com.google.cloud.automl.v1.ModelName; -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class VisionClassificationPredictTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String MODEL_ID = System.getenv("VISION_CLASSIFICATION_MODEL_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - requireEnvVar("VISION_CLASSIFICATION_MODEL_ID"); - } - - @Before - public void setUp() throws IOException, ExecutionException, InterruptedException { - // Verify that the model is deployed for prediction - try (AutoMlClient client = AutoMlClient.create()) { - ModelName modelFullId = ModelName.of(PROJECT_ID, "us-central1", MODEL_ID); - Model model = client.getModel(modelFullId); - if (model.getDeploymentState() == Model.DeploymentState.UNDEPLOYED) { - // Deploy the model if not deployed - DeployModelRequest request = - DeployModelRequest.newBuilder().setName(modelFullId.toString()).build(); - client.deployModelAsync(request).get(); - } - } - - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testPredict() throws IOException { - String filePath = "resources/test.png"; - VisionClassificationPredict.predict(PROJECT_ID, MODEL_ID, filePath); - String got = bout.toString(); - assertThat(got).contains("Predicted class name:"); - } -} diff --git a/automl/src/test/java/com/example/automl/VisionObjectDetectionCreateDatasetTest.java b/automl/src/test/java/com/example/automl/VisionObjectDetectionCreateDatasetTest.java deleted file mode 100644 index 5070c1e04b2..00000000000 --- a/automl/src/test/java/com/example/automl/VisionObjectDetectionCreateDatasetTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class VisionObjectDetectionCreateDatasetTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - private String datasetId; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() throws InterruptedException, ExecutionException, IOException { - // Delete the created dataset - DeleteDataset.deleteDataset(PROJECT_ID, datasetId); - System.setOut(originalPrintStream); - } - - @Test - public void testCreateDataset() throws IOException, ExecutionException, InterruptedException { - // Create a random dataset name with a length of 32 characters (max allowed by AutoML) - // To prevent name collisions when running tests in multiple java versions at once. - // AutoML doesn't allow "-", but accepts "_" - String datasetName = - String.format("test_%s", UUID.randomUUID().toString().replace("-", "_").substring(0, 26)); - VisionObjectDetectionCreateDataset.createDataset(PROJECT_ID, datasetName); - - String got = bout.toString(); - assertThat(got).contains("Dataset id:"); - datasetId = got.split("Dataset id: ")[1].split("\n")[0]; - } -} diff --git a/automl/src/test/java/com/example/automl/VisionObjectDetectionCreateModelTest.java b/automl/src/test/java/com/example/automl/VisionObjectDetectionCreateModelTest.java deleted file mode 100644 index f18f8a4ab35..00000000000 --- a/automl/src/test/java/com/example/automl/VisionObjectDetectionCreateModelTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class VisionObjectDetectionCreateModelTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String DATASET_ID = "IOD0000000000000000"; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - System.setOut(originalPrintStream); - } - - @Test - public void testVisionObjectDetectionCreateModel() { - // Create a model from a nonexistent dataset. - try { - // Create a random dataset name with a length of 32 characters (max allowed by AutoML) - // To prevent name collisions when running tests in multiple java versions at once. - // AutoML doesn't allow "-", but accepts "_" - String modelName = - String.format("test_%s", UUID.randomUUID().toString().replace("-", "_").substring(0, 26)); - VisionObjectDetectionCreateModel.createModel(PROJECT_ID, DATASET_ID, modelName); - String got = bout.toString(); - assertThat(got).contains("Dataset does not exist"); - } catch (IOException | ExecutionException | InterruptedException e) { - assertThat(e.getMessage()).contains("Dataset does not exist"); - } - } -} diff --git a/automl/src/test/java/com/example/automl/VisionObjectDetectionDeployModelNodeCountTest.java b/automl/src/test/java/com/example/automl/VisionObjectDetectionDeployModelNodeCountTest.java deleted file mode 100644 index 604f3a2d924..00000000000 --- a/automl/src/test/java/com/example/automl/VisionObjectDetectionDeployModelNodeCountTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -public class VisionObjectDetectionDeployModelNodeCountTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String MODEL_ID = "0000000000000000000000"; - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - } - - @Before - public void setUp() { - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testDeployModelWithNodeCount() { - // As model deployment can take a long time, instead try to deploy a - // nonexistent model and confirm that the model was not found, but other - // elements of the request were valid. - try { - VisionObjectDetectionDeployModelNodeCount.visionObjectDetectionDeployModelNodeCount( - PROJECT_ID, MODEL_ID); - String got = bout.toString(); - assertThat(got).contains("The model does not exist"); - } catch (IOException | ExecutionException | InterruptedException e) { - assertThat(e.getMessage()).contains("The model does not exist"); - } - } -} diff --git a/automl/src/test/java/com/example/automl/VisionObjectDetectionPredictTest.java b/automl/src/test/java/com/example/automl/VisionObjectDetectionPredictTest.java deleted file mode 100644 index 6b705cc5048..00000000000 --- a/automl/src/test/java/com/example/automl/VisionObjectDetectionPredictTest.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.automl; - -import static com.google.common.truth.Truth.assertThat; -import static junit.framework.TestCase.assertNotNull; - -import com.google.cloud.automl.v1.AutoMlClient; -import com.google.cloud.automl.v1.DeployModelRequest; -import com.google.cloud.automl.v1.Model; -import com.google.cloud.automl.v1.ModelName; -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Ignore("This test is ignored because the legacy version of AutoML API is deprecated") -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class VisionObjectDetectionPredictTest { - @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private static final String PROJECT_ID = System.getenv("AUTOML_PROJECT_ID"); - private static final String MODEL_ID = System.getenv("OBJECT_DETECTION_MODEL_ID"); - private ByteArrayOutputStream bout; - private PrintStream originalPrintStream; - - private static void requireEnvVar(String varName) { - assertNotNull( - System.getenv(varName), - String.format("Environment variable '%s' is required to perform these tests.", varName)); - - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("AUTOML_PROJECT_ID"); - requireEnvVar("OBJECT_DETECTION_MODEL_ID"); - } - - @Before - public void setUp() throws IOException, ExecutionException, InterruptedException { - // Verify that the model is deployed for prediction - try (AutoMlClient client = AutoMlClient.create()) { - ModelName modelFullId = ModelName.of(PROJECT_ID, "us-central1", MODEL_ID); - Model model = client.getModel(modelFullId); - if (model.getDeploymentState() == Model.DeploymentState.UNDEPLOYED) { - // Deploy the model if not deployed - DeployModelRequest request = - DeployModelRequest.newBuilder().setName(modelFullId.toString()).build(); - client.deployModelAsync(request).get(); - } - } - - bout = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(bout); - originalPrintStream = System.out; - System.setOut(out); - } - - @After - public void tearDown() { - // restores print statements in the original method - System.out.flush(); - System.setOut(originalPrintStream); - } - - @Test - public void testPredict() throws IOException { - String filePath = "resources/salad.jpg"; - VisionObjectDetectionPredict.predict(PROJECT_ID, MODEL_ID, filePath); - String got = bout.toString(); - assertThat(got).contains("X:"); - assertThat(got).contains("Y:"); - } -} diff --git a/bigquery/README.md b/bigquery/README.md deleted file mode 100644 index 344bbbd1cbe..00000000000 --- a/bigquery/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Getting Started with BigQuery - -The samples have been moved to live alongside the Java client libraries for Cloud BigQuery: - -[BigQuery samples](https://github.com/googleapis/java-bigquery/tree/main/samples/snippets/src/main/java/com/example/bigquery) - -[BigQueryStorage samples](https://github.com/googleapis/java-bigquerystorage/tree/main/samples/snippets/src/main/java/com/example/bigquerystorage) - -[BigQueryDataTransfer samples](https://github.com/googleapis/java-bigquerydatatransfer/tree/main/samples/snippets/src/main/java/com/example/bigquerydatatransfer) \ No newline at end of file diff --git a/bigquery/bigquerystorage/v1beta1-migration-guide.md b/bigquery/bigquerystorage/v1beta1-migration-guide.md new file mode 100644 index 00000000000..317b8d82d26 --- /dev/null +++ b/bigquery/bigquerystorage/v1beta1-migration-guide.md @@ -0,0 +1,169 @@ +# Migrating BigQuery Storage API from v1beta1 to v1: Java + +This guide shows how to migrate Java code using the BigQuery Storage API from +version `v1beta1` to `v1`. + +## Key Changes + +* **Service Client**: `BigQueryStorageClient` is replaced by + `BigQueryReadClient`. +* **Table Reference**: `TableReference` proto message is replaced by a simple + string representation of the table path in `ReadSession`. +* **Session Configuration**: Several fields from `CreateReadSessionRequest` + have moved into `ReadSession` (which is now passed as a field in the + request). +* **Parallelism**: `requested_streams` is replaced by `max_stream_count`. +* **Sharding Strategy**: `sharding_strategy` is removed. The server now + automatically balances the streams. +* **Read Rows Request**: `StreamPosition` is flattened. You now pass the + stream name directly as `read_stream` and the `offset` as a top-level field + in `ReadRowsRequest`. + +## Code Comparison + +### 1. Client Initialization + +**v1beta1:** + +```java +import com.google.cloud.bigquery.storage.v1beta1.BigQueryStorageClient; +import java.io.IOException; + +try (BigQueryStorageClient client = BigQueryStorageClient.create()) { + // use client +} catch (IOException e) { + // Handle client initialization failure +} +``` + +**v1:** + +```java +import com.google.cloud.bigquery.storage.v1.BigQueryReadClient; +import java.io.IOException; + +try (BigQueryReadClient client = BigQueryReadClient.create()) { + // use client +} catch (IOException e) { + // Handle client initialization failure +} +``` + +### 2. Creating a Read Session + +**v1beta1:** + +```java +import com.google.cloud.bigquery.storage.v1beta1.BigQueryStorageClient; +import com.google.cloud.bigquery.storage.v1beta1.Storage.CreateReadSessionRequest; +import com.google.cloud.bigquery.storage.v1beta1.Storage.DataFormat; +import com.google.cloud.bigquery.storage.v1beta1.Storage.ReadSession; +import com.google.cloud.bigquery.storage.v1beta1.Storage.ShardingStrategy; +import com.google.cloud.bigquery.storage.v1beta1.ReadOptions.TableReadOptions; +import com.google.cloud.bigquery.storage.v1beta1.TableReferenceProto.TableReference; + +// ... + +TableReference tableReference = TableReference.newBuilder() + .setProjectId("bigquery-public-data") + .setDatasetId("usa_names") + .setTableId("usa_1910_current") + .build(); + +TableReadOptions options = TableReadOptions.newBuilder() + .addSelectedFields("name") + .setRowRestriction("state = \"WA\"") + .build(); + +CreateReadSessionRequest request = CreateReadSessionRequest.newBuilder() + .setParent("projects/read-session-project") + .setTableReference(tableReference) + .setReadOptions(options) + .setRequestedStreams(1) + .setFormat(DataFormat.AVRO) + .setShardingStrategy(ShardingStrategy.LIQUID) + .build(); + +ReadSession session = client.createReadSession(request); +``` + +**v1:** + +```java +import com.google.cloud.bigquery.storage.v1.BigQueryReadClient; +import com.google.cloud.bigquery.storage.v1.CreateReadSessionRequest; +import com.google.cloud.bigquery.storage.v1.DataFormat; +import com.google.cloud.bigquery.storage.v1.ReadSession; +import com.google.cloud.bigquery.storage.v1.ReadSession.TableReadOptions; + +// ... + +// Table path is now a string: projects/{project}/datasets/{dataset}/tables/{table} +String tablePath = "projects/bigquery-public-data/datasets/usa_names/tables/usa_1910_current"; + +TableReadOptions options = TableReadOptions.newBuilder() + .addSelectedFields("name") + .setRowRestriction("state = \"WA\"") + .build(); + +// ReadSession holds the session configuration (table, options, format) +ReadSession readSession = ReadSession.newBuilder() + .setTable(tablePath) + .setDataFormat(DataFormat.AVRO) // format renamed to data_format + .setReadOptions(options) + .build(); + +CreateReadSessionRequest request = CreateReadSessionRequest.newBuilder() + .setParent("projects/read-session-project") + .setReadSession(readSession) + .setMaxStreamCount(1) // requested_streams renamed to max_stream_count + .build(); + +ReadSession session = client.createReadSession(request); +``` + +### 3. Reading Rows + +**v1beta1:** + +```java +import com.google.api.gax.rpc.ServerStream; +import com.google.cloud.bigquery.storage.v1beta1.Storage.ReadRowsRequest; +import com.google.cloud.bigquery.storage.v1beta1.Storage.ReadRowsResponse; +import com.google.cloud.bigquery.storage.v1beta1.Storage.StreamPosition; + +// ... +StreamPosition readPosition = StreamPosition.newBuilder() + .setStream(session.getStreams(0)) // Stream object + .setOffset(0) + .build(); + +ReadRowsRequest readRowsRequest = ReadRowsRequest.newBuilder() + .setReadPosition(readPosition) + .build(); + +ServerStream stream = client.readRowsCallable().call(readRowsRequest); +for (ReadRowsResponse response : stream) { + // Process response.getAvroRows() +} +``` + +**v1:** + +```java +import com.google.api.gax.rpc.ServerStream; +import com.google.cloud.bigquery.storage.v1.ReadRowsRequest; +import com.google.cloud.bigquery.storage.v1.ReadRowsResponse; + +// ... +// ReadRowsRequest is flattened. We pass the stream name (string) and offset directly. +ReadRowsRequest readRowsRequest = ReadRowsRequest.newBuilder() + .setReadStream(session.getStreams(0).getName()) // Stream name string + .setOffset(0) + .build(); + +ServerStream stream = client.readRowsCallable().call(readRowsRequest); +for (ReadRowsResponse response : stream) { + // Process response.getAvroRows() +} +``` diff --git a/bigquery/cloud-client/snippets/pom.xml b/bigquery/cloud-client/snippets/pom.xml index 4acbac77b79..cb75e660f1c 100644 --- a/bigquery/cloud-client/snippets/pom.xml +++ b/bigquery/cloud-client/snippets/pom.xml @@ -67,4 +67,28 @@ limitations under the License. test + + + + + org.jacoco + jacoco-maven-plugin + 0.8.12 + + + + prepare-agent + + + + report + test + + report + + + + + + diff --git a/bigquery/cloud-client/snippets/src/main/java/com/example/bigquery/SimpleQuery.java b/bigquery/cloud-client/snippets/src/main/java/com/example/bigquery/SimpleQuery.java new file mode 100644 index 00000000000..98a74212f63 --- /dev/null +++ b/bigquery/cloud-client/snippets/src/main/java/com/example/bigquery/SimpleQuery.java @@ -0,0 +1,69 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.bigquery; + +// [START bigquery_query] + +import com.google.cloud.bigquery.BigQuery; +import com.google.cloud.bigquery.BigQueryException; +import com.google.cloud.bigquery.BigQueryOptions; +import com.google.cloud.bigquery.QueryJobConfiguration; +import com.google.cloud.bigquery.TableResult; + +public class SimpleQuery { + + public static void main(String[] args) { + // TODO(developer): Replace this query before running the sample. + String query = + "SELECT name, SUM(number) as total_people " + + "FROM `bigquery-public-data.usa_names.usa_1910_2013` " + + "WHERE state = 'TX' " + + "GROUP BY name, state " + + "ORDER BY total_people DESC " + + "LIMIT 100;"; + simpleQuery(query); + } + + public static void simpleQuery(String query) { + try { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + BigQuery bigquery = BigQueryOptions.getDefaultInstance().getService(); + + // Create the query job. + QueryJobConfiguration queryConfig = QueryJobConfiguration.newBuilder(query).build(); + + // Execute the query. + TableResult result = bigquery.query(queryConfig); + + // Print the results. + result + .iterateAll() + .forEach( + row -> { + System.out.print("name:" + row.get("name").getStringValue()); + System.out.print(", count:" + row.get("total_people").getLongValue()); + System.out.println(); + }); + + System.out.println("Query ran successfully"); + } catch (BigQueryException | InterruptedException e) { + System.out.println("Query did not run \n" + e.toString()); + } + } +} +// [END bigquery_query] diff --git a/bigquery/cloud-client/snippets/src/test/java/com/example/bigquery/SimpleQueryIT.java b/bigquery/cloud-client/snippets/src/test/java/com/example/bigquery/SimpleQueryIT.java new file mode 100644 index 00000000000..f5c3bb1105b --- /dev/null +++ b/bigquery/cloud-client/snippets/src/test/java/com/example/bigquery/SimpleQueryIT.java @@ -0,0 +1,65 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.bigquery; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class SimpleQueryIT { + + private final Logger log = Logger.getLogger(this.getClass().getName()); + private ByteArrayOutputStream bout; + private PrintStream out; + private PrintStream originalPrintStream; + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + } + + @After + public void tearDown() { + // restores print statements in the original method + System.out.flush(); + System.setOut(originalPrintStream); + log.log(Level.INFO, "\n" + bout.toString()); + } + + @Test + public void testSimpleQuery() { + String query = + "SELECT name, SUM(number) as total_people " + + "FROM `bigquery-public-data.usa_names.usa_1910_2013` " + + "WHERE state = 'TX' " + + "GROUP BY name, state " + + "ORDER BY total_people DESC " + + "LIMIT 100;"; + + SimpleQuery.simpleQuery(query); + assertThat(bout.toString()).contains("Query ran successfully"); + } +} diff --git a/bigquery/cloud-client/snippets/src/test/java/com/example/bigquery/Util.java b/bigquery/cloud-client/snippets/src/test/java/com/example/bigquery/Util.java index 368075addd4..6099c056249 100644 --- a/bigquery/cloud-client/snippets/src/test/java/com/example/bigquery/Util.java +++ b/bigquery/cloud-client/snippets/src/test/java/com/example/bigquery/Util.java @@ -20,9 +20,6 @@ import com.google.cloud.Policy; import com.google.cloud.Role; import com.google.cloud.bigquery.Acl; -import com.google.cloud.bigquery.Acl.Entity; -import com.google.cloud.bigquery.Acl.Group; -import com.google.cloud.bigquery.Acl.Role; import com.google.cloud.bigquery.BigQuery; import com.google.cloud.bigquery.BigQuery.DatasetDeleteOption; import com.google.cloud.bigquery.BigQueryException; @@ -88,8 +85,8 @@ public static Dataset setUpTest_grantAccessToDataset( DatasetId datasetId = DatasetId.of(projectId, datasetName); Dataset dataset = bigquery.getDataset(datasetId); - Entity entity = new Group(entityEmail); - Acl newEntry = Acl.of(entity, Role.READER); + Acl.Entity entity = new Acl.Group(entityEmail); + Acl newEntry = Acl.of(entity, Acl.Role.READER); List acls = new ArrayList<>(dataset.getAcl()); acls.add(newEntry); diff --git a/bigtable/beam/change-streams/README.md b/bigtable/beam/change-streams/README.md index 094a3d8f378..9acebd7d921 100644 --- a/bigtable/beam/change-streams/README.md +++ b/bigtable/beam/change-streams/README.md @@ -139,7 +139,7 @@ Google Cloud Storage. 1. Observe the output on GCS and see the most popular songs. ```sh - gsutil cat ${OUTPUT_LOCATION}/song-charts/GlobalWindow-pane-0-00000-of-00001.txt + gcloud storage cat ${OUTPUT_LOCATION}/song-charts/GlobalWindow-pane-0-00000-of-00001.txt ``` Example output: diff --git a/bigtable/scheduled-backups/pom.xml b/bigtable/scheduled-backups/pom.xml index 191e01e418b..7a3d89bfc2b 100644 --- a/bigtable/scheduled-backups/pom.xml +++ b/bigtable/scheduled-backups/pom.xml @@ -66,7 +66,7 @@ limitations under the License. com.fasterxml.jackson.core jackson-databind - 2.16.1 + 2.18.9 diff --git a/bigtable/spark/README.md b/bigtable/spark/README.md index 006d7cd621b..64cac30492d 100644 --- a/bigtable/spark/README.md +++ b/bigtable/spark/README.md @@ -317,23 +317,23 @@ One notable change (compared to the earlier executions) is that the example uses 1. Create a bucket. ``` -gsutil mb \ - -b on \ - -l $BIGTABLE_SPARK_DATAPROC_REGION \ - -p $BIGTABLE_SPARK_PROJECT_ID \ +gcloud storage buckets create \ + --uniform-bucket-level-access \ + --location=$BIGTABLE_SPARK_DATAPROC_REGION \ + --project=$BIGTABLE_SPARK_PROJECT_ID \ $BIGTABLE_SPARK_BUCKET_NAME ``` 1. Upload an input file into the bucket. ``` -gsutil cp src/test/resources/Romeo-and-Juliet-prologue.txt $BIGTABLE_SPARK_BUCKET_NAME +gcloud storage cp src/test/resources/Romeo-and-Juliet-prologue.txt $BIGTABLE_SPARK_BUCKET_NAME ``` 1. List contents of the bucket. ``` -gsutil ls $BIGTABLE_SPARK_BUCKET_NAME +gcloud storage ls $BIGTABLE_SPARK_BUCKET_NAME ``` Output should be: @@ -413,6 +413,6 @@ gcloud dataproc clusters list \ Remove the input file in the bucket and the bucket itself. ``` -gsutil rm $BIGTABLE_SPARK_BUCKET_NAME/Romeo-and-Juliet-prologue.txt -gsutil rb $BIGTABLE_SPARK_BUCKET_NAME +gcloud storage rm $BIGTABLE_SPARK_BUCKET_NAME/Romeo-and-Juliet-prologue.txt +gcloud storage buckets delete $BIGTABLE_SPARK_BUCKET_NAME ``` diff --git a/cloud-sql/mysql/servlet/src/test/java/com/example/cloudsql/TestIndexServletMysql.java b/cloud-sql/mysql/servlet/src/test/java/com/example/cloudsql/TestIndexServletMysql.java index 57b624ba526..12234c011eb 100644 --- a/cloud-sql/mysql/servlet/src/test/java/com/example/cloudsql/TestIndexServletMysql.java +++ b/cloud-sql/mysql/servlet/src/test/java/com/example/cloudsql/TestIndexServletMysql.java @@ -38,6 +38,7 @@ import javax.sql.DataSource; import org.junit.AfterClass; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; @@ -101,6 +102,7 @@ public static void dropTable() throws SQLException { } @Test + @Ignore("https://github.com/GoogleCloudPlatform/java-docs-samples/issues/8794") public void testGetTemplateData() throws Exception { TemplateData templateData = new IndexServlet().getTemplateData(pool); @@ -110,6 +112,7 @@ public void testGetTemplateData() throws Exception { } @Test + @Ignore("https://github.com/GoogleCloudPlatform/java-docs-samples/issues/8794") public void testServletPost() throws Exception { HttpServletRequest request = mock(HttpServletRequest.class); HttpServletResponse response = mock(HttpServletResponse.class); @@ -128,4 +131,4 @@ public void testServletPost() throws Exception { writer.flush(); assertTrue(stringWriter.toString().contains("Vote successfully cast for")); } -} \ No newline at end of file +} diff --git a/cloud-sql/postgres/servlet/src/test/java/com/example/cloudsql/TestIndexServletPostgres.java b/cloud-sql/postgres/servlet/src/test/java/com/example/cloudsql/TestIndexServletPostgres.java index 4cfde1336f4..2d9056c7133 100644 --- a/cloud-sql/postgres/servlet/src/test/java/com/example/cloudsql/TestIndexServletPostgres.java +++ b/cloud-sql/postgres/servlet/src/test/java/com/example/cloudsql/TestIndexServletPostgres.java @@ -38,6 +38,7 @@ import javax.sql.DataSource; import org.junit.AfterClass; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; @@ -102,6 +103,7 @@ public static void dropTable() throws SQLException { } @Test + @Ignore("https://github.com/GoogleCloudPlatform/java-docs-samples/issues/8794") public void testGetTemplateData() throws Exception { TemplateData templateData = new IndexServlet().getTemplateData(pool); @@ -111,6 +113,7 @@ public void testGetTemplateData() throws Exception { } @Test + @Ignore("https://github.com/GoogleCloudPlatform/java-docs-samples/issues/8794") public void testServletPost() throws Exception { HttpServletRequest request = mock(HttpServletRequest.class); HttpServletResponse response = mock(HttpServletResponse.class); @@ -129,4 +132,4 @@ public void testServletPost() throws Exception { writer.flush(); assertTrue(stringWriter.toString().contains("Vote successfully cast for")); } -} \ No newline at end of file +} diff --git a/cloud-sql/sqlserver/servlet/src/test/java/com/example/cloudsql/TestIndexServletSqlServer.java b/cloud-sql/sqlserver/servlet/src/test/java/com/example/cloudsql/TestIndexServletSqlServer.java index c73d91ba42a..31b0fad0541 100644 --- a/cloud-sql/sqlserver/servlet/src/test/java/com/example/cloudsql/TestIndexServletSqlServer.java +++ b/cloud-sql/sqlserver/servlet/src/test/java/com/example/cloudsql/TestIndexServletSqlServer.java @@ -38,6 +38,7 @@ import javax.sql.DataSource; import org.junit.AfterClass; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; @@ -111,6 +112,7 @@ public static void dropTable() throws SQLException { @Test + @Ignore("https://github.com/GoogleCloudPlatform/java-docs-samples/issues/8794") public void testGetTemplateData() throws Exception { TemplateData templateData = new IndexServlet().getTemplateData(pool); @@ -120,6 +122,7 @@ public void testGetTemplateData() throws Exception { } @Test + @Ignore("https://github.com/GoogleCloudPlatform/java-docs-samples/issues/8794") public void testServletPost() throws Exception { HttpServletRequest request = mock(HttpServletRequest.class); HttpServletResponse response = mock(HttpServletResponse.class); diff --git a/compute/cloud-client/src/main/java/compute/disks/CreateHyperdisk.java b/compute/cloud-client/src/main/java/compute/disks/CreateHyperdisk.java index 350495f19bc..5a18c4beb85 100644 --- a/compute/cloud-client/src/main/java/compute/disks/CreateHyperdisk.java +++ b/compute/cloud-client/src/main/java/compute/disks/CreateHyperdisk.java @@ -36,7 +36,9 @@ public static void main(String[] args) // Name of the disk you want to create. String diskName = "YOUR_DISK_NAME"; // The type of disk you want to create. This value uses the following format: - // "zones/{zone}/diskTypes/(hyperdisk-balanced|hyperdisk-extreme|hyperdisk-throughput)". + // "zones/{zone}/diskTypes/{disktype}". + // disktype is one of hyperdisk-balanced, hyperdisk-extreme, hyperdisk-throughput + // hyperdisk-ml, hyperdisk-balanced-high-availability // For example: "zones/us-west3-b/diskTypes/hyperdisk-balanced" String diskType = String.format("zones/%s/diskTypes/hyperdisk-balanced", zone); // Size of the new disk in gigabytes. @@ -96,4 +98,4 @@ public static Disk createHyperdisk(String projectId, String zone, String diskNam } } } -// [END compute_hyperdisk_create] \ No newline at end of file +// [END compute_hyperdisk_create] diff --git a/compute/mailjet/pom.xml b/compute/mailjet/pom.xml index 80533e40943..91c213b761a 100644 --- a/compute/mailjet/pom.xml +++ b/compute/mailjet/pom.xml @@ -56,13 +56,11 @@ jar provided - com.mailjet mailjet-client ${mailjet.version} - diff --git a/compute/mailjet/src/main/java/com/example/compute/mailjet/MailjetSender.java b/compute/mailjet/src/main/java/com/example/compute/mailjet/MailjetSender.java index 12fc17601ad..75dc2c0d9b6 100644 --- a/compute/mailjet/src/main/java/com/example/compute/mailjet/MailjetSender.java +++ b/compute/mailjet/src/main/java/com/example/compute/mailjet/MailjetSender.java @@ -16,8 +16,6 @@ package com.example.compute.mailjet; -// [START compute_mailjet_imports] - import com.mailjet.client.ClientOptions; import com.mailjet.client.MailjetClient; import com.mailjet.client.MailjetRequest; @@ -27,8 +25,6 @@ import org.json.JSONArray; import org.json.JSONObject; -// [END compute_mailjet_imports] - // [START compute_mailjet_send_email] public class MailjetSender { diff --git a/container-registry/container-analysis/pom.xml b/container-registry/container-analysis/pom.xml index 8d8d5cb2a9a..637915ae5bc 100644 --- a/container-registry/container-analysis/pom.xml +++ b/container-registry/container-analysis/pom.xml @@ -27,7 +27,6 @@ UTF-8 - @@ -59,14 +58,11 @@ com.google.cloud google-cloud-pubsub - junit junit 4.13.2 test - - diff --git a/container-registry/container-analysis/src/test/java/com/example/containeranalysis/SamplesTest.java b/container-registry/container-analysis/src/test/java/com/example/containeranalysis/SamplesTest.java index 391e6b92f67..2f6990e0990 100644 --- a/container-registry/container-analysis/src/test/java/com/example/containeranalysis/SamplesTest.java +++ b/container-registry/container-analysis/src/test/java/com/example/containeranalysis/SamplesTest.java @@ -51,6 +51,7 @@ import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; @@ -104,6 +105,7 @@ public void tearDown() { } @Test + @Ignore("https://github.com/GoogleCloudPlatform/java-docs-samples/issues/10180") public void testCreateNote() throws Exception { // note should have been created as part of set up. verify that it succeeded Note n = GetNote.getNote(noteId, PROJECT_ID); @@ -112,6 +114,7 @@ public void testCreateNote() throws Exception { } @Test + @Ignore("https://github.com/GoogleCloudPlatform/java-docs-samples/issues/10180") public void testDeleteNote() throws Exception { DeleteNote.deleteNote(noteId, PROJECT_ID); try { @@ -124,6 +127,7 @@ public void testDeleteNote() throws Exception { } @Test + @Ignore("https://github.com/GoogleCloudPlatform/java-docs-samples/issues/10180") public void testCreateOccurrence() throws Exception { Occurrence o = CreateOccurrence.createOccurrence(imageUrl, noteId, PROJECT_ID, PROJECT_ID); String[] nameArr = o.getName().split("/"); @@ -136,6 +140,7 @@ public void testCreateOccurrence() throws Exception { } @Test + @Ignore("https://github.com/GoogleCloudPlatform/java-docs-samples/issues/10180") public void testDeleteOccurrence() throws Exception { Occurrence o = CreateOccurrence.createOccurrence(imageUrl, noteId, PROJECT_ID, PROJECT_ID); String occName = o.getName(); @@ -154,6 +159,7 @@ public void testDeleteOccurrence() throws Exception { } @Test + @Ignore("https://github.com/GoogleCloudPlatform/java-docs-samples/issues/10180") public void testOccurrencesForImage() throws Exception { int newCount; int tries = 0; @@ -175,6 +181,7 @@ public void testOccurrencesForImage() throws Exception { } @Test + @Ignore("https://github.com/GoogleCloudPlatform/java-docs-samples/issues/10180") public void testOccurrencesForNote() throws Exception { int newCount; int tries = 0; @@ -196,6 +203,7 @@ public void testOccurrencesForNote() throws Exception { } @Test + @Ignore("https://github.com/GoogleCloudPlatform/java-docs-samples/issues/10180") public void testPubSub() throws Exception { // create new topic and subscription if needed try (TopicAdminClient topicAdminClient = TopicAdminClient.create()) { @@ -244,6 +252,7 @@ public void testPubSub() throws Exception { } @Test + @Ignore("https://github.com/GoogleCloudPlatform/java-docs-samples/issues/10180") public void testPollDiscoveryOccurrenceFinished() throws Exception { try { // expect fail on first try @@ -301,6 +310,7 @@ public void testPollDiscoveryOccurrenceFinished() throws Exception { } @Test + @Ignore("https://github.com/GoogleCloudPlatform/java-docs-samples/issues/10180") public void testFindVulnerabilitiesForImage() throws Exception { List result = VulnerabilityOccurrencesForImage.findVulnerabilityOccurrencesForImage(imageUrl, PROJECT_ID); @@ -323,6 +333,7 @@ public void testFindVulnerabilitiesForImage() throws Exception { } @Test + @Ignore("https://github.com/GoogleCloudPlatform/java-docs-samples/issues/10180") public void testFindHighSeverityVulnerabilitiesForImage() throws Exception { // check before creation List result = diff --git a/dataflow/encryption-keys/README.md b/dataflow/encryption-keys/README.md index 0c051fad1b4..eae43f1d4fa 100644 --- a/dataflow/encryption-keys/README.md +++ b/dataflow/encryption-keys/README.md @@ -25,7 +25,7 @@ Additionally, for this sample you need the following: ```sh export BUCKET=your-gcs-bucket - gsutil mb gs://$BUCKET + gcloud storage buckets create gs://$BUCKET ``` 1. [Create a symmetric key ring](https://cloud.google.com/kms/docs/creating-keys). @@ -151,10 +151,10 @@ To avoid incurring charges to your GCP account for the resources used: ```sh # Remove only the files created by this sample. -gsutil -m rm -rf "gs://$BUCKET/samples/dataflow/kms" +gcloud storage rm --recursive --continue-on-error "gs://$BUCKET/samples/dataflow/kms" # [optional] Remove the Cloud Storage bucket. -gsutil rb gs://$BUCKET +gcloud storage buckets delete gs://$BUCKET # Remove the BigQuery table. bq rm -f -t $PROJECT:$DATASET.$TABLE diff --git a/dataflow/flex-templates/kafka_to_bigquery/README.md b/dataflow/flex-templates/kafka_to_bigquery/README.md index 22d72092179..051e18fdae2 100644 --- a/dataflow/flex-templates/kafka_to_bigquery/README.md +++ b/dataflow/flex-templates/kafka_to_bigquery/README.md @@ -8,6 +8,13 @@ Samples showing how to create and run an ## Before you begin +NOTE: These instructions used to use Bitnami version of Kafka, but due to a recent +untagging event on [Docker Hub](https://hub.docker.com/r/bitnami/kafka) for +all Bitnami images, these instructions have switched over to Apache Kafka images. +They are still pinned to a Zookeeper compatible +[version](https://kafka.apache.org/blog/2024/11/06/apache-kafka-3.9.0-release-announcement/) +of Kafka for now. + If you are not familiar with Dataflow Flex templates, please see the [Streaming Beam SQL](../streaming-beam-sql/) sample first. @@ -25,7 +32,7 @@ Additionally, for this sample you need the following: ```sh export BUCKET="your-gcs-bucket" - gsutil mb gs://$BUCKET + gcloud storage buckets create gs://$BUCKET ``` 1. Create a [BigQuery dataset](https://cloud.google.com/bigquery/docs/datasets). @@ -130,9 +137,9 @@ For this, we need two parts running: > The Kafka server must be accessible to *external* applications. -For this we need a -[static IP address](https://cloud.google.com/compute/docs/ip-addresses/reserve-static-external-ip-address) -for the Kafka server to live. +For this we need an +[external static IP address](https://cloud.google.com/compute/docs/ip-addresses/reserve-static-external-ip-address) +for the Kafka server to live. Not an internal IP address. > ℹ️ If you already have a Kafka server running you can skip this section. > Just make sure to store its IP address into an environment variable. @@ -183,9 +190,21 @@ To learn more about pricing, see the ```sh export KAFKA_IMAGE="gcr.io/$PROJECT/samples/dataflow/kafka:latest" +# Note: If the project name has `:` in it that signifies a project within an +# organization (e.g. `example.com:project-id`), replace those with `/` so that +# the Kafka image can be found appropriately. + # Build the Kafka server image into Container Registry. gcloud builds submit --tag $KAFKA_IMAGE kafka/ +# If a different topic, address, kafka port, or zookeeper port is desired, +# update the following environment variables before starting the server. +# Otherwise, the default values will be used in the Dockerfile: +export KAFKA_TOPIC= +export KAFKA_ADDRESS= +export KAFKA_PORT= +export ZOOKEEPER_PORT= + # Create and start a new instance. # The --address flag binds the VM's address to the static address we created. # The --container-env KAFKA_ADDRESS is an environment variable passed to the @@ -200,6 +219,70 @@ gcloud compute instances create-with-container kafka-vm \ --tags "kafka-server" ``` +Note: The Kafka server should be running at this point, but in its current state +no messages are being sent to a topic, which will cause the KafkaToBigQuery +template to fail. + + +### Sending messages to Kafka server + +SSH into the `kafka-vm` that was created earlier and issue +the below commands that are required based on your timing. Messages sent before +the template is started will be present when the template is started. If the +desire is to send messages after the template has started, then the messages +will be processed as they are sent. + +Pre-Requisite SSH into the Kafka VM + +```sh +$ gcloud compute ssh kafka-vm --zone "$ZONE" +``` + +1. Create a Topic + +```sh +docker run --rm --network host apache/kafka:3.9.1 \ +/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 \ +--create --topic --partitions 1 --replication-factor 1 +``` + +2. Send Messages to the Topic + +Run the console producer to send messages. After running the command, type a +message and press Enter. You can send multiple messages. Press Ctrl+C to stop +the producer. + +Note: You can run this step either before starting the Dataflow template +(messages will be ready) or while it's running (messages will be processed as +they arrive). + +```sh +docker run -i --rm --network host apache/kafka:3.9.1 \ +/opt/kafka/bin/kafka-console-producer.sh \ +--bootstrap-server localhost:9092 --topic +``` + +3. (Optional) Verify the Messages + +You can check that your messages were sent correctly by starting a consumer. +This will print all messages from the beginning of the topic. Press Ctrl+C to +exit. + +```sh +docker run -it --rm --network host apache/kafka:3.9.1 \ +/opt/kafka/bin/kafka-console-consumer.sh \ +--bootstrap-server localhost:9092 --topic --from-beginning +``` + +4. (Optional) Delete a Topic + +```sh +docker run --rm --network host apache/kafka:3.9.1 \ +/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 \ +--delete --topic +``` + + ### Creating and running a Flex Template >

@@ -257,6 +340,21 @@ gcloud dataflow flex-template run "kafka-to-bigquery-`date +%Y%m%d-%H%M%S`" \ --region "$REGION" ``` +Note: If one of the parameters is a deeply nested json or dictionary, use the +gcloud `--flags-file` parameter to pass in a yaml file a list of all the +parameters including the nested dictionary. Passing in the dictionary straight +from the command line will give a gcloud error. The parameters file can look +like this: + +```yaml +--parameters: + inputTopic: messages + outputTable: $PROJECT:$DATASET.$TABLE + bootstrapServer: $KAFKA_ADDRESS:9092 + schema: + '{type: object, properties: {processing_time: {type: TIMESTAMP}, url: {type: STRING}, rating: {type: STRING}}}' +``` + Run the following query to check the results in BigQuery. ```sh @@ -283,7 +381,7 @@ The following sections describe how to delete or turn off these resources. 1. Delete the template spec file from Cloud Storage. ```sh - gsutil rm $TEMPLATE_PATH + gcloud storage rm $TEMPLATE_PATH ``` 1. Delete the Flex Template container images from Container Registry. @@ -341,5 +439,5 @@ The following sections describe how to delete or turn off these resources. > These objects cannot be recovered. > > ```sh - > gsutil rm -r gs://$BUCKET + > gcloud storage rm --recursive gs://$BUCKET > ``` diff --git a/dataflow/flex-templates/kafka_to_bigquery/kafka/Dockerfile b/dataflow/flex-templates/kafka_to_bigquery/kafka/Dockerfile index ae44da65264..8216783ef69 100644 --- a/dataflow/flex-templates/kafka_to_bigquery/kafka/Dockerfile +++ b/dataflow/flex-templates/kafka_to_bigquery/kafka/Dockerfile @@ -24,7 +24,7 @@ ARG KAFKA_VERSION="2.4.0" ARG SCALA_VERSION="2.12" # Set variables with default values used by the `start-kafka.sh` script. -# Override them wiht the `--env` or `-e` flag. +# Override them with the `--env` or `-e` flag. # https://docs.docker.com/engine/reference/builder/#env ENV KAFKA_TOPIC="${KAFKA_TOPIC:-messages}" ENV KAFKA_ADDRESS="${KAFKA_ADDRESS:-localhost}" @@ -33,7 +33,7 @@ ENV ZOOKEEPER_PORT="${ZOOKEEPER_PORT:-2181}" # Download and install Apache Kafka. RUN apk add --no-cache bash \ - && wget http://apache.mirrors.spacedump.net/kafka/${KAFKA_VERSION}/kafka_${SCALA_VERSION}-${KAFKA_VERSION}.tgz \ + && wget https://archive.apache.org/dist/kafka/${KAFKA_VERSION}/kafka_${SCALA_VERSION}-${KAFKA_VERSION}.tgz \ -O /tmp/kafka.tgz \ && tar xzf /tmp/kafka.tgz -C /opt && rm /tmp/kafka.tgz \ && ln -s /opt/kafka_${SCALA_VERSION}-${KAFKA_VERSION} /opt/kafka diff --git a/dataflow/flex-templates/streaming_beam_sql/README.md b/dataflow/flex-templates/streaming_beam_sql/README.md index a047c21ea2e..300228ada26 100644 --- a/dataflow/flex-templates/streaming_beam_sql/README.md +++ b/dataflow/flex-templates/streaming_beam_sql/README.md @@ -25,7 +25,7 @@ Additionally, for this sample you need the following: ```sh export BUCKET="your-gcs-bucket" - gsutil mb gs://$BUCKET + gcloud storage buckets create gs://$BUCKET ``` 1. Create a @@ -241,7 +241,7 @@ The following sections describe how to delete or turn off these resources. 1. Delete the template spec file from Cloud Storage. ```sh - gsutil rm $TEMPLATE_PATH + gcloud storage rm $TEMPLATE_PATH ``` 1. Delete the Flex Template container image from Container Registry. @@ -287,7 +287,7 @@ The following sections describe how to delete or turn off these resources. > These objects cannot be recovered. > > ```sh - > gsutil rm -r gs://$BUCKET + > gcloud storage rm --recursive gs://$BUCKET > ``` ## Limitations diff --git a/dataflow/snippets/pom.xml b/dataflow/snippets/pom.xml old mode 100755 new mode 100644 index 067feac46df..9b5db567aa9 --- a/dataflow/snippets/pom.xml +++ b/dataflow/snippets/pom.xml @@ -37,10 +37,12 @@ 11 11 UTF-8 - 2.62.0 + 2.70.0 2.0.12 - 1.14.0 - 1.4.2 + 1.16.0 + 1.10.0 + 42.7.3 + 1.20.0 @@ -65,6 +67,13 @@ + + + org.apache.avro + avro + + 1.12.0 + com.google.cloud libraries-bom @@ -155,6 +164,16 @@ iceberg-data ${iceberg.version} + + org.apache.iceberg + iceberg-gcp + ${iceberg.version} + + + org.apache.iceberg + iceberg-parquet + ${iceberg.version} + @@ -166,7 +185,7 @@ org.apache.kafka kafka-clients - 3.8.0 + 3.9.1 @@ -176,6 +195,27 @@ test + + + org.apache.beam + beam-sdks-java-io-jdbc + ${apache_beam.version} + + + + org.postgresql + postgresql + ${postgresql.version} + test + + + + org.testcontainers + postgresql + ${testcontainers.version} + test + + com.google.cloud diff --git a/dataflow/snippets/src/main/java/com/example/dataflow/ApacheIcebergCdcRead.java b/dataflow/snippets/src/main/java/com/example/dataflow/ApacheIcebergCdcRead.java new file mode 100644 index 00000000000..85b435b1094 --- /dev/null +++ b/dataflow/snippets/src/main/java/com/example/dataflow/ApacheIcebergCdcRead.java @@ -0,0 +1,172 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.dataflow; + +// [START dataflow_apache_iceberg_cdc_read] +import com.google.auth.oauth2.GoogleCredentials; +import com.google.common.collect.ImmutableMap; +import java.io.IOException; +import java.util.Map; +import org.apache.beam.sdk.Pipeline; +import org.apache.beam.sdk.coders.RowCoder; +import org.apache.beam.sdk.extensions.gcp.options.GcpOptions; +import org.apache.beam.sdk.managed.Managed; +import org.apache.beam.sdk.options.Default; +import org.apache.beam.sdk.options.Description; +import org.apache.beam.sdk.options.PipelineOptionsFactory; +import org.apache.beam.sdk.options.Validation; +import org.apache.beam.sdk.schemas.Schema; +import org.apache.beam.sdk.transforms.MapElements; +import org.apache.beam.sdk.transforms.Sum; +import org.apache.beam.sdk.transforms.windowing.FixedWindows; +import org.apache.beam.sdk.transforms.windowing.Window; +import org.apache.beam.sdk.values.KV; +import org.apache.beam.sdk.values.PCollection; +import org.apache.beam.sdk.values.Row; +import org.apache.beam.sdk.values.TypeDescriptors; +import org.joda.time.Duration; + +/** + * A streaming pipeline that reads CDC events from an Iceberg table, aggregates user clicks, and + * writes the results to another Iceberg table. For more information on BigLake, + * see the documentation at https://cloud.google.com/bigquery/docs/blms-rest-catalog. + * + *

This pipeline can be used to process the output of {@link + * ApacheIcebergRestCatalogStreamingWrite}. + */ +public class ApacheIcebergCdcRead { + + // Schema for the source table containing click events. + public static final Schema SOURCE_SCHEMA = + Schema.builder().addStringField("user_id").addInt64Field("click_count").build(); + + // Schema for the destination table containing aggregated click counts. + public static final Schema DESTINATION_SCHEMA = + Schema.builder().addStringField("user_id").addInt64Field("total_clicks").build(); + + /** Pipeline options for this example. */ + public interface Options extends GcpOptions { + @Description("The source Iceberg table to read CDC events from") + @Validation.Required + String getSourceTable(); + + void setSourceTable(String sourceTable); + + @Description("The destination Iceberg table to write aggregated results to") + @Validation.Required + String getDestinationTable(); + + void setDestinationTable(String destinationTable); + + @Description("Warehouse location for the Iceberg catalog") + @Validation.Required + String getWarehouse(); + + void setWarehouse(String warehouse); + + @Description("The URI for the REST catalog") + @Default.String("https://biglake.googleapis.com/iceberg/v1beta/restcatalog") + String getCatalogUri(); + + void setCatalogUri(String value); + + @Description("The name of the Iceberg catalog") + @Validation.Required + String getCatalogName(); + + void setCatalogName(String catalogName); + } + + public static void main(String[] args) throws IOException { + Options options = PipelineOptionsFactory.fromArgs(args).withValidation().as(Options.class); + + // Note: The token expires in 1 hour. Users may need to re-run the pipeline. + // Future updates to Iceberg and the BigLake Metastore will support token refreshing. + Map catalogProps = + ImmutableMap.builder() + .put("type", "rest") + .put("uri", options.getCatalogUri()) + .put("warehouse", options.getWarehouse()) + .put("header.x-goog-user-project", options.getProject()) + .put( + "header.Authorization", + "Bearer " + + GoogleCredentials.getApplicationDefault() + .createScoped("https://www.googleapis.com/auth/cloud-platform") + .refreshAccessToken() + .getTokenValue()) + .put("rest-metrics-reporting-enabled", "false") + .build(); + + Pipeline p = Pipeline.create(options); + + // Configure the Iceberg CDC read + Map icebergReadConfig = + ImmutableMap.builder() + .put("table", options.getSourceTable()) + .put("catalog_name", options.getCatalogName()) + .put("catalog_properties", catalogProps) + .put("streaming", Boolean.TRUE) + .put("poll_interval_seconds", 20) + .build(); + + PCollection cdcEvents = + p.apply("ReadFromIceberg", Managed.read(Managed.ICEBERG_CDC).withConfig(icebergReadConfig)) + .getSinglePCollection() + .setRowSchema(SOURCE_SCHEMA); + + PCollection aggregatedRows = + cdcEvents + .apply("ApplyWindow", Window.into(FixedWindows.of(Duration.standardSeconds(30)))) + .apply( + "ExtractUserAndCount", + MapElements.into( + TypeDescriptors.kvs(TypeDescriptors.strings(), TypeDescriptors.longs())) + .via( + row -> { + String userId = row.getString("user_id"); + Long clickCount = row.getInt64("click_count"); + return KV.of(userId, clickCount == null ? 0L : clickCount); + })) + .apply("SumClicksPerUser", Sum.longsPerKey()) + .apply( + "FormatToRow", + MapElements.into(TypeDescriptors.rows()) + .via( + kv -> + Row.withSchema(DESTINATION_SCHEMA) + .withFieldValue("user_id", kv.getKey()) + .withFieldValue("total_clicks", kv.getValue()) + .build())) + .setCoder(RowCoder.of(DESTINATION_SCHEMA)); + + // Configure the Iceberg write + Map icebergWriteConfig = + ImmutableMap.builder() + .put("table", options.getDestinationTable()) + .put("catalog_properties", catalogProps) + .put("catalog_name", options.getCatalogName()) + .put("triggering_frequency_seconds", 30) + .build(); + + aggregatedRows.apply( + "WriteToIceberg", Managed.write(Managed.ICEBERG).withConfig(icebergWriteConfig)); + + p.run(); + } +} +// [END dataflow_apache_iceberg_cdc_read] diff --git a/dataflow/snippets/src/main/java/com/example/dataflow/ApacheIcebergRestCatalogStreamingWrite.java b/dataflow/snippets/src/main/java/com/example/dataflow/ApacheIcebergRestCatalogStreamingWrite.java new file mode 100644 index 00000000000..37f3aed10c8 --- /dev/null +++ b/dataflow/snippets/src/main/java/com/example/dataflow/ApacheIcebergRestCatalogStreamingWrite.java @@ -0,0 +1,139 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.dataflow; + +// [START dataflow_apache_iceberg_rest_catalog_streaming_write] +import com.google.auth.oauth2.GoogleCredentials; +import com.google.common.collect.ImmutableMap; +import java.io.IOException; +import java.util.Map; +import org.apache.beam.sdk.Pipeline; +import org.apache.beam.sdk.coders.RowCoder; +import org.apache.beam.sdk.extensions.gcp.options.GcpOptions; +import org.apache.beam.sdk.io.GenerateSequence; +import org.apache.beam.sdk.managed.Managed; +import org.apache.beam.sdk.options.Default; +import org.apache.beam.sdk.options.Description; +import org.apache.beam.sdk.options.PipelineOptionsFactory; +import org.apache.beam.sdk.options.StreamingOptions; +import org.apache.beam.sdk.options.Validation; +import org.apache.beam.sdk.schemas.Schema; +import org.apache.beam.sdk.transforms.MapElements; +import org.apache.beam.sdk.values.Row; +import org.apache.beam.sdk.values.TypeDescriptors; +import org.joda.time.Duration; + +/** + * A streaming pipeline that writes data to an Iceberg table using the REST catalog. + * + *

This example demonstrates writing to an Iceberg table backed by the BigLake Metastore. For + * more information on BigLake, see the documentation at + * https://cloud.google.com/bigquery/docs/blms-rest-catalog. + */ +public class ApacheIcebergRestCatalogStreamingWrite { + + // The schema for the generated records. + public static final Schema SCHEMA = + Schema.builder().addStringField("user_id").addInt64Field("click_count").build(); + + /** Pipeline options for this example. */ + public interface Options extends GcpOptions, StreamingOptions { + @Description( + "Warehouse location where the table's data will be written to. " + + "BigLake only supports Single Region buckets") + @Validation.Required + String getWarehouse(); + + void setWarehouse(String warehouse); + + @Description("The URI for the REST catalog") + @Validation.Required + @Default.String("https://biglake.googleapis.com/iceberg/v1beta/restcatalog") + String getCatalogUri(); + + void setCatalogUri(String value); + + @Description("The name of the table to write to") + @Validation.Required + String getIcebergTable(); + + void setIcebergTable(String value); + + @Description("The name of the Apache Iceberg catalog") + @Validation.Required + String getCatalogName(); + + void setCatalogName(String catalogName); + } + + /** + * The main entry point for the pipeline. + * + * @param args Command-line arguments + * @throws IOException If there is an issue with Google Credentials + */ + public static void main(String[] args) throws IOException { + Options options = PipelineOptionsFactory.fromArgs(args).withValidation().as(Options.class); + options.setStreaming(true); + + // Note: The token expires in 1 hour. Users may need to re-run the pipeline. + // Future updates to Iceberg and the BigLake Metastore will support token refreshing. + Map catalogProps = + ImmutableMap.builder() + .put("type", "rest") + .put("uri", options.getCatalogUri()) + .put("warehouse", options.getWarehouse()) + .put("header.x-goog-user-project", options.getProject()) + .put( + "header.Authorization", + "Bearer " + + GoogleCredentials.getApplicationDefault() + .createScoped("https://www.googleapis.com/auth/cloud-platform") + .refreshAccessToken() + .getTokenValue()) + .put("rest-metrics-reporting-enabled", "false") + .build(); + + Map icebergWriteConfig = + ImmutableMap.builder() + .put("table", options.getIcebergTable()) + .put("catalog_properties", catalogProps) + .put("catalog_name", options.getCatalogName()) + .put("triggering_frequency_seconds", 20) + .build(); + + Pipeline p = Pipeline.create(options); + + p.apply( + "GenerateSequence", + GenerateSequence.from(0).withRate(1, Duration.standardSeconds(5))) + .apply( + "ConvertToRows", + MapElements.into(TypeDescriptors.rows()) + .via( + i -> + Row.withSchema(SCHEMA) + .withFieldValue("user_id", "user-" + (i % 10)) + .withFieldValue("click_count", i % 100) + .build())) + .setCoder(RowCoder.of(SCHEMA)) + .apply("WriteToIceberg", Managed.write(Managed.ICEBERG).withConfig(icebergWriteConfig)); + + p.run(); + } +} +// [END dataflow_apache_iceberg_rest_catalog_streaming_write] diff --git a/dataflow/snippets/src/main/java/com/example/dataflow/KafkaRead.java b/dataflow/snippets/src/main/java/com/example/dataflow/KafkaRead.java index 531bd618509..c5e089c3ab7 100644 --- a/dataflow/snippets/src/main/java/com/example/dataflow/KafkaRead.java +++ b/dataflow/snippets/src/main/java/com/example/dataflow/KafkaRead.java @@ -67,7 +67,7 @@ public static Pipeline createPipeline(Options options) { ImmutableMap config = ImmutableMap.builder() .put("bootstrap_servers", options.getBootstrapServer()) .put("topic", options.getTopic()) - .put("data_format", "RAW") + .put("format", "RAW") .put("max_read_time_seconds", 15) .put("auto_offset_reset_config", "earliest") .build(); diff --git a/dataflow/snippets/src/main/java/com/example/dataflow/MdcSample.java b/dataflow/snippets/src/main/java/com/example/dataflow/MdcSample.java new file mode 100644 index 00000000000..f22c71e2e19 --- /dev/null +++ b/dataflow/snippets/src/main/java/com/example/dataflow/MdcSample.java @@ -0,0 +1,83 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.dataflow; + +import org.apache.beam.sdk.Pipeline; +import org.apache.beam.sdk.io.gcp.pubsub.PubsubIO; +import org.apache.beam.sdk.io.gcp.pubsub.PubsubMessage; +import org.apache.beam.sdk.options.Description; +import org.apache.beam.sdk.options.PipelineOptionsFactory; +import org.apache.beam.sdk.options.SdkHarnessOptions; +import org.apache.beam.sdk.transforms.DoFn; +import org.apache.beam.sdk.transforms.ParDo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +public class MdcSample { + + public interface MdcSampleJobOptions extends SdkHarnessOptions { + @Description("The Pub/Sub subscription to read from.") + String getInputSubscription(); + + void setInputSubscription(String value); + } + + public static class MessageReaderFn extends DoFn { + + private transient Logger logger; + + @Setup + public void setup() { + logger = LoggerFactory.getLogger(MessageReaderFn.class); + } + + @ProcessElement + public void processElement(ProcessContext c) { + PubsubMessage message = c.element(); + String messageId = message.getMessageId(); + + try (MDC.MDCCloseable ignored = MDC.putCloseable("messageId", messageId)) { + String payload = new String(message.getPayload(), java.nio.charset.StandardCharsets.UTF_8); + logger.info("Received message with payload: {}", payload); + + // This is the example task + logger.info("Executing example task..."); + } catch (Exception e) { + logger.error("Failed to process message", e); + } + } + } + + public static void main(String[] args) { + MdcSampleJobOptions options = + PipelineOptionsFactory.fromArgs(args).withValidation().as(MdcSampleJobOptions.class); + // options.setRunner(DirectRunner.class); + + options.setLogMdc(true); + + + Pipeline p = Pipeline.create(options); + + p.apply( + "Read Messages from Pub/Sub", + PubsubIO.readMessagesWithAttributes().fromSubscription(options.getInputSubscription())) + .apply("Process Message", ParDo.of(new MessageReaderFn())); + + p.run(); + } +} \ No newline at end of file diff --git a/dataflow/snippets/src/main/java/com/example/dataflow/PostgresRead.java b/dataflow/snippets/src/main/java/com/example/dataflow/PostgresRead.java new file mode 100644 index 00000000000..50829ace969 --- /dev/null +++ b/dataflow/snippets/src/main/java/com/example/dataflow/PostgresRead.java @@ -0,0 +1,99 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.dataflow; + +// [START dataflow_postgres_read] +import com.google.common.collect.ImmutableMap; +import org.apache.beam.sdk.Pipeline; +import org.apache.beam.sdk.PipelineResult; +import org.apache.beam.sdk.io.TextIO; +import org.apache.beam.sdk.managed.Managed; +import org.apache.beam.sdk.options.Description; +import org.apache.beam.sdk.options.PipelineOptions; +import org.apache.beam.sdk.options.PipelineOptionsFactory; +import org.apache.beam.sdk.transforms.MapElements; +import org.apache.beam.sdk.values.TypeDescriptors; + +public class PostgresRead { + + public interface Options extends PipelineOptions { + @Description("The JDBC URL of the PostgreSQL database to read from.") + String getJdbcUrl(); + + void setJdbcUrl(String value); + + @Description("The PostgresSQL table to read from.") + String getTable(); + + void setTable(String value); + + @Description("The username for the PostgreSQL database.") + String getUsername(); + + void setUsername(String value); + + @Description("The password for the PostgreSQL database.") + String getPassword(); + + void setPassword(String value); + + @Description( + "The path to write the output file. Can be a local file path, " + + "a GCS path, or a path to any other supported file systems.") + String getOutputPath(); + + void setOutputPath(String value); + } + + public static PipelineResult.State main(String[] args) { + // Parse the pipeline options passed into the application. Example: + // --runner=DirectRunner --jdbcUrl=$JDBC_URL --table=$TABLE + // --username=$USERNAME --password=$PASSWORD --outputPath=$OUTPUT_FILE + // For more information, see + // https://beam.apache.org/documentation/programming-guide/#configuring-pipeline-options + var options = PipelineOptionsFactory.fromArgs(args).withValidation().as(Options.class); + Pipeline pipeline = createPipeline(options); + return pipeline.run().waitUntilFinish(); + } + + public static Pipeline createPipeline(Options options) { + + // Create configuration parameters for the Managed I/O transform. + ImmutableMap config = + ImmutableMap.builder() + .put("jdbc_url", options.getJdbcUrl()) + .put("location", options.getTable()) + .put("username", options.getUsername()) + .put("password", options.getPassword()) + .build(); + + // Build the pipeline. + var pipeline = Pipeline.create(options); + pipeline + // Read data from a Postgres database using Managed I/O. + .apply(Managed.read(Managed.POSTGRES).withConfig(config)) + .getSinglePCollection() + // Convert each row to a string. + .apply( + MapElements.into(TypeDescriptors.strings()) + .via((row -> String.format("%d,%s", row.getInt32("id"), row.getString("name"))))) + // Write strings to a text file. + .apply(TextIO.write().to(options.getOutputPath()).withSuffix(".txt").withNumShards(1)); + return pipeline; + } +} +// [END dataflow_postgres_read] diff --git a/dataflow/snippets/src/main/java/com/example/dataflow/PostgresWrite.java b/dataflow/snippets/src/main/java/com/example/dataflow/PostgresWrite.java new file mode 100644 index 00000000000..104272ce4ad --- /dev/null +++ b/dataflow/snippets/src/main/java/com/example/dataflow/PostgresWrite.java @@ -0,0 +1,111 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.dataflow; + +// [START dataflow_postgres_write] +import static org.apache.beam.sdk.schemas.Schema.toSchema; + +import com.google.common.collect.ImmutableMap; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; +import org.apache.beam.sdk.Pipeline; +import org.apache.beam.sdk.PipelineResult; +import org.apache.beam.sdk.managed.Managed; +import org.apache.beam.sdk.options.Description; +import org.apache.beam.sdk.options.PipelineOptions; +import org.apache.beam.sdk.options.PipelineOptionsFactory; +import org.apache.beam.sdk.schemas.Schema; +import org.apache.beam.sdk.transforms.Create; +import org.apache.beam.sdk.values.Row; + +public class PostgresWrite { + + private static Schema INPUT_SCHEMA = + Stream.of( + Schema.Field.of("id", Schema.FieldType.INT32), + Schema.Field.of("name", Schema.FieldType.STRING)) + .collect(toSchema()); + + private static List ROWS = + Arrays.asList( + Row.withSchema(INPUT_SCHEMA) + .withFieldValue("id", 1) + .withFieldValue("name", "John Doe") + .build(), + Row.withSchema(INPUT_SCHEMA) + .withFieldValue("id", 2) + .withFieldValue("name", "Jane Smith") + .build()); + + public interface Options extends PipelineOptions { + @Description("The JDBC URL of the PostgreSQL database to write to.") + String getJdbcUrl(); + + void setJdbcUrl(String value); + + @Description("The PostgresSQL table to write to.") + String getTable(); + + void setTable(String value); + + @Description("The username for the PostgreSQL database.") + String getUsername(); + + void setUsername(String value); + + @Description("The password for the PostgreSQL database.") + String getPassword(); + + void setPassword(String value); + } + + public static PipelineResult.State main(String[] args) { + // Parse the pipeline options passed into the application. Example: + // --runner=DirectRunner --jdbcUrl=$JDBC_URL --table=$TABLE + // --username=$USERNAME --password=$PASSWORD + // For more information, see + // https://beam.apache.org/documentation/programming-guide/#configuring-pipeline-options + var options = PipelineOptionsFactory.fromArgs(args).withValidation().as(Options.class); + Pipeline pipeline = createPipeline(options); + return pipeline.run().waitUntilFinish(); + } + + public static Pipeline createPipeline(Options options) { + + // Create configuration parameters for the Managed I/O transform. + ImmutableMap config = + ImmutableMap.builder() + .put("jdbc_url", options.getJdbcUrl()) + .put("location", options.getTable()) + .put("username", options.getUsername()) + .put("password", options.getPassword()) + .build(); + + // Build the pipeline. + var pipeline = Pipeline.create(options); + pipeline + // Create data to write to Postgres. + .apply(Create.of(ROWS)) + .setRowSchema(INPUT_SCHEMA) + // Write data to a Postgres database using Managed I/O. + .apply(Managed.write(Managed.POSTGRES).withConfig(config)) + .getSinglePCollection(); + return pipeline; + } +} +// [END dataflow_postgres_write] diff --git a/dataflow/snippets/src/test/java/com/example/dataflow/ApacheIcebergIT.java b/dataflow/snippets/src/test/java/com/example/dataflow/ApacheIcebergIT.java index e2acef1ef1f..10a06cc64b7 100644 --- a/dataflow/snippets/src/test/java/com/example/dataflow/ApacheIcebergIT.java +++ b/dataflow/snippets/src/test/java/com/example/dataflow/ApacheIcebergIT.java @@ -16,15 +16,21 @@ package com.example.dataflow; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import com.google.api.gax.paging.Page; +import com.google.cloud.storage.Blob; +import com.google.cloud.storage.BucketInfo; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import com.google.cloud.storage.testing.RemoteStorageHelper; import com.google.common.collect.ImmutableMap; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.UUID; -import org.apache.beam.sdk.PipelineResult; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.iceberg.CatalogProperties; @@ -51,16 +57,22 @@ import org.junit.Before; import org.junit.Test; + public class ApacheIcebergIT { + private static final String projectId = System.getenv("GOOGLE_CLOUD_PROJECT"); private Configuration hadoopConf = new Configuration(); + private Storage storage = StorageOptions.getDefaultInstance().getService(); private java.nio.file.Path warehouseDirectory; private String warehouseLocation; + private String bucketName; private Catalog catalog; private static final String CATALOG_NAME = "local"; String outputFileNamePrefix = UUID.randomUUID().toString(); String outputFileName = outputFileNamePrefix + "-00000-of-00001.txt"; + String table = "user_clicks.streaming_write"; + String destinationTable = "user_clicks.cdc_destination"; private Table createIcebergTable(String name) { @@ -86,7 +98,7 @@ private void writeTableRecord(Table table) FileAppender appender = Parquet.write(HadoopOutputFile.fromPath(path, hadoopConf)) - .createWriterFunc(GenericParquetWriter::buildWriter) + .createWriterFunc(msgType -> GenericParquetWriter.create(table.schema(), msgType)) .schema(table.schema()) .overwrite() .build(); @@ -113,6 +125,25 @@ private boolean tableContainsRecord(Table table, String data) { return false; } + private void assertTableHasDataAndMetadata(String tableName) { + boolean dataFolderHasFiles = false; + boolean metadataFolderHasFiles = false; + String tablePath = tableName.replace('.', '/'); + + Page blobs = storage.list(bucketName); + for (Blob blob : blobs.iterateAll()) { + if (blob.getName().startsWith(tablePath + "/data/") && blob.getSize() > 0) { + dataFolderHasFiles = true; + } + if (blob.getName().startsWith(tablePath + "/metadata/") && blob.getSize() > 0) { + metadataFolderHasFiles = true; + } + } + + assertTrue("Data folder should have files for table " + tableName, dataFolderHasFiles); + assertTrue("Metadata folder should have files for table " + tableName, metadataFolderHasFiles); + } + @Before public void setUp() throws IOException { // Create an Apache Iceberg catalog with a table. @@ -124,12 +155,74 @@ public void setUp() throws IOException { CATALOG_NAME, ImmutableMap.of(CatalogProperties.WAREHOUSE_LOCATION, warehouseLocation), hadoopConf); - + bucketName = "test-bucket-" + UUID.randomUUID(); + storage.create(BucketInfo.newBuilder(bucketName).setLocation("us-central1").build()); } @After - public void tearDown() throws IOException { + public void tearDown() throws IOException, ExecutionException, InterruptedException { Files.deleteIfExists(Paths.get(outputFileName)); + if (bucketName != null) { + RemoteStorageHelper.forceDelete(storage, bucketName, 1, TimeUnit.MINUTES); + } + } + + @Test + public void testApacheIcebergRestCatalog() throws IOException, InterruptedException { + String warehouse = "gs://" + bucketName; + Thread thread = + new Thread( + () -> { + try { + ApacheIcebergRestCatalogStreamingWrite.main( + new String[] { + "--runner=DirectRunner", + "--warehouse=" + warehouse, + "--icebergTable=" + table, + "--catalogName=biglake", + "--project=" + projectId, + }); + } catch (Exception e) { + // We expect an InterruptedException when the test interrupts the thread. + // We can ignore it. + if (!(e.getCause() instanceof InterruptedException)) { + throw new RuntimeException(e); + } + } + }); + + thread.start(); + Thread.sleep(60000); + thread.interrupt(); + thread.join(); + + assertTableHasDataAndMetadata(table); + + Thread cdcThread = + new Thread( + () -> { + try { + ApacheIcebergCdcRead.main( + new String[] { + "--runner=DirectRunner", + "--sourceTable=" + table, + "--destinationTable=" + destinationTable, + "--warehouse=" + warehouse, + "--catalogName=" + "biglake", + "--project=" + projectId, + }); + } catch (Exception e) { + if (!(e.getCause() instanceof InterruptedException)) { + throw new RuntimeException(e); + } + } + }); + cdcThread.start(); + Thread.sleep(120000); + cdcThread.interrupt(); + cdcThread.join(); + + assertTableHasDataAndMetadata(destinationTable); } @Test @@ -193,4 +286,4 @@ public void testApacheIcebergRead() throws IOException { String output = Files.readString(Paths.get(outputFileName)); assertTrue(output.contains("0:Person-0")); } -} \ No newline at end of file +} diff --git a/dataflow/snippets/src/test/java/com/example/dataflow/PostgresReadIT.java b/dataflow/snippets/src/test/java/com/example/dataflow/PostgresReadIT.java new file mode 100644 index 00000000000..c38f5a70c29 --- /dev/null +++ b/dataflow/snippets/src/test/java/com/example/dataflow/PostgresReadIT.java @@ -0,0 +1,98 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.dataflow; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.Statement; +import org.apache.beam.sdk.PipelineResult; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.testcontainers.containers.PostgreSQLContainer; + +public class PostgresReadIT { + + private static final String TABLE_NAME = "test_read_table"; + private static final String OUTPUT_PATH = "test-output"; + // The TextIO connector appends this suffix to the pipeline output file. + private static final String OUTPUT_FILE_SUFFIX = "-00000-of-00001.txt"; + private static final String OUTPUT_FILE_NAME = OUTPUT_PATH + OUTPUT_FILE_SUFFIX; + + private static final PostgreSQLContainer postgres = + new PostgreSQLContainer<>("postgres:15-alpine"); + + @Before + public void setUp() throws Exception { + postgres.start(); + + // Initialize the database with table and data. + try (Connection conn = + DriverManager.getConnection( + postgres.getJdbcUrl(), postgres.getUsername(), postgres.getPassword())) { + + Statement stmt = conn.createStatement(); + stmt.execute( + String.format("CREATE TABLE %s (id INT PRIMARY KEY, name VARCHAR(255))", TABLE_NAME)); + stmt.execute(String.format("INSERT INTO %s (id, name) VALUES (1, 'John Doe')", TABLE_NAME)); + stmt.execute(String.format("INSERT INTO %s (id, name) VALUES (2, 'Jane Smith')", TABLE_NAME)); + } + } + + @After + public void tearDown() throws IOException { + if (postgres != null) { + postgres.stop(); + } + Files.deleteIfExists(Paths.get(OUTPUT_FILE_NAME)); + } + + @Test + public void testPostgresRead() throws IOException { + // Execute the Beam pipeline. + PipelineResult.State state = + PostgresRead.main( + new String[] { + "--runner=DirectRunner", + "--jdbcUrl=" + postgres.getJdbcUrl(), + "--table=" + TABLE_NAME, + "--username=" + postgres.getUsername(), + "--password=" + postgres.getPassword(), + "--outputPath=" + OUTPUT_PATH + }); + + assertEquals(PipelineResult.State.DONE, state); + verifyOutput(); + } + + private void verifyOutput() throws IOException { + File outputFile = new File(OUTPUT_FILE_NAME); + assertTrue("Output file should exist", outputFile.exists()); + + String content = Files.readString(Paths.get(OUTPUT_FILE_NAME)); + + assertTrue(content.contains("1,John Doe")); + assertTrue(content.contains("2,Jane Smith")); + } +} diff --git a/dataflow/snippets/src/test/java/com/example/dataflow/PostgresWriteIT.java b/dataflow/snippets/src/test/java/com/example/dataflow/PostgresWriteIT.java new file mode 100644 index 00000000000..7f07e664013 --- /dev/null +++ b/dataflow/snippets/src/test/java/com/example/dataflow/PostgresWriteIT.java @@ -0,0 +1,96 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.dataflow; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import org.apache.beam.sdk.PipelineResult; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.testcontainers.containers.PostgreSQLContainer; + +public class PostgresWriteIT { + + private static final String TABLE_NAME = "test_write_table"; + private static final PostgreSQLContainer postgres = + new PostgreSQLContainer<>("postgres:15-alpine"); + + @Before + public void setUp() throws Exception { + postgres.start(); + + // Create the table so the Managed I/O can find it. + try (Connection conn = + DriverManager.getConnection( + postgres.getJdbcUrl(), postgres.getUsername(), postgres.getPassword())) { + Statement stmt = conn.createStatement(); + stmt.execute( + String.format("CREATE TABLE %s (id INT PRIMARY KEY, name VARCHAR(255))", TABLE_NAME)); + } + } + + @After + public void tearDown() { + if (postgres != null) { + postgres.stop(); + } + } + + @Test + public void testPostgresWrite() throws Exception { + // Execute the Beam pipeline. + PipelineResult.State state = + PostgresWrite.main( + new String[] { + "--runner=DirectRunner", + "--jdbcUrl=" + postgres.getJdbcUrl(), + "--table=" + TABLE_NAME, + "--username=" + postgres.getUsername(), + "--password=" + postgres.getPassword() + }); + + assertEquals(PipelineResult.State.DONE, state); + verifyDatabaseContent(); + } + + private void verifyDatabaseContent() throws Exception { + try (Connection conn = + DriverManager.getConnection( + postgres.getJdbcUrl(), postgres.getUsername(), postgres.getPassword())) { + Statement stmt = conn.createStatement(); + ResultSet rs = + stmt.executeQuery(String.format("SELECT id, name FROM %s ORDER BY id", TABLE_NAME)); + + List results = new ArrayList<>(); + while (rs.next()) { + results.add(rs.getInt("id") + "," + rs.getString("name")); + } + + assertEquals("Should have 2 rows", 2, results.size()); + assertTrue("Should contain John Doe", results.contains("1,John Doe")); + assertTrue("Should contain Jane Smith", results.contains("2,Jane Smith")); + } + } +} diff --git a/dataflow/templates/README.md b/dataflow/templates/README.md index 4e2964b1da2..3cc013dbbd7 100644 --- a/dataflow/templates/README.md +++ b/dataflow/templates/README.md @@ -18,7 +18,7 @@ Additionally, for this sample you need the following: ```sh export BUCKET=your-gcs-bucket - gsutil mb gs://$BUCKET + gcloud storage buckets create gs://$BUCKET ``` 1. Clone the `java-docs-samples` repository. @@ -64,7 +64,7 @@ mvn compile exec:java \ --runner=DataflowRunner" # Upload the metadata file. -gsutil cp WordCount_metadata "$TEMPLATE_LOCATION"_metadata +gcloud storage cp WordCount_metadata "$TEMPLATE_LOCATION"_metadata ``` > For more information, see @@ -94,9 +94,9 @@ To avoid incurring charges to your GCP account for the resources used: ```bash # Remove only the files created by this sample. -gsutil -m rm -rf "$TEMPLATE_LOCATION*" -gsutil -m rm -rf "gs://$BUCKET/samples/dataflow/wordcount/" +gcloud storage rm --recursive --continue-on-error "$TEMPLATE_LOCATION*" +gcloud storage rm --recursive --continue-on-error "gs://$BUCKET/samples/dataflow/wordcount/" # [optional] Remove the Cloud Storage bucket. -gsutil rb gs://$BUCKET +gcloud storage buckets delete gs://$BUCKET ``` diff --git a/dialogflow/snippets/src/main/java/com/example/dialogflow/AnswerRecordManagement.java b/dialogflow/snippets/src/main/java/com/example/dialogflow/AnswerRecordManagement.java deleted file mode 100644 index 7d03076ec78..00000000000 --- a/dialogflow/snippets/src/main/java/com/example/dialogflow/AnswerRecordManagement.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.dialogflow; - -// [START dialogflow_update_answer_record] - -import com.google.api.gax.rpc.ApiException; -import com.google.cloud.dialogflow.v2.AnswerFeedback; -import com.google.cloud.dialogflow.v2.AnswerRecord; -import com.google.cloud.dialogflow.v2.AnswerRecordName; -import com.google.cloud.dialogflow.v2.AnswerRecordsClient; -import com.google.cloud.dialogflow.v2.UpdateAnswerRecordRequest; -import com.google.protobuf.FieldMask; -import java.io.IOException; - -public class AnswerRecordManagement { - public static void main(String[] args) throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "my-project-id"; - String location = "my-location"; - - // Set id of the answer record to be updated. - // Answer records are created when a suggestion for the human agent assistant is generated. - // See details about how to generate an answer record by getting article suggestion here: - // https://cloud.google.com/agent-assist/docs/article-suggestion. - String answerRecordId = "my-answer-record-id"; - - // Set the value to be updated for answer_feedback.clicked field. - boolean isClicked = true; - updateAnswerRecord(projectId, location, answerRecordId, isClicked); - } - - // Update whether the answer record was clicked. - public static void updateAnswerRecord( - String projectId, String location, String answerRecordId, boolean clicked) - throws ApiException, IOException { - // Initialize a client for managing AnswerRecords. This client only needs to be created - // once, and can be reused for multiple requests. - try (AnswerRecordsClient answerRecordsClient = AnswerRecordsClient.create()) { - AnswerRecordName answerRecordName = - AnswerRecordName.ofProjectLocationAnswerRecordName(projectId, location, answerRecordId); - AnswerFeedback answerFeedback = AnswerFeedback.newBuilder().setClicked(clicked).build(); - AnswerRecord answerRecord = - AnswerRecord.newBuilder() - .setName(answerRecordName.toString()) - .setAnswerFeedback(answerFeedback) - .build(); - // Add a mask to control which field gets updated. - FieldMask fieldMask = FieldMask.newBuilder().addPaths("answer_feedback").build(); - - UpdateAnswerRecordRequest request = - UpdateAnswerRecordRequest.newBuilder() - .setAnswerRecord(answerRecord) - .setUpdateMask(fieldMask) - .build(); - AnswerRecord response = answerRecordsClient.updateAnswerRecord(request); - System.out.println("===================="); - System.out.format("AnswerRecord updated:\n"); - System.out.format("Name: %s\n", response.getName()); - System.out.format("Clicked: %s\n", response.getAnswerFeedback().getClicked()); - } - } -} -// [END dialogflow_update_answer_record] \ No newline at end of file diff --git a/dialogflow/snippets/src/main/java/com/example/dialogflow/ConversationProfileManagement.java b/dialogflow/snippets/src/main/java/com/example/dialogflow/ConversationProfileManagement.java index f3f218263b1..a569e936494 100644 --- a/dialogflow/snippets/src/main/java/com/example/dialogflow/ConversationProfileManagement.java +++ b/dialogflow/snippets/src/main/java/com/example/dialogflow/ConversationProfileManagement.java @@ -16,24 +16,12 @@ package com.example.dialogflow; -// [START dialogflow_create_conversation_profile_article_suggestion] - import com.google.api.gax.rpc.ApiException; import com.google.cloud.dialogflow.v2.ConversationProfile; import com.google.cloud.dialogflow.v2.ConversationProfilesClient; import com.google.cloud.dialogflow.v2.CreateConversationProfileRequest; -import com.google.cloud.dialogflow.v2.HumanAgentAssistantConfig; -import com.google.cloud.dialogflow.v2.HumanAgentAssistantConfig.SuggestionConfig; -import com.google.cloud.dialogflow.v2.HumanAgentAssistantConfig.SuggestionFeatureConfig; -import com.google.cloud.dialogflow.v2.HumanAgentAssistantConfig.SuggestionQueryConfig; -import com.google.cloud.dialogflow.v2.HumanAgentAssistantConfig.SuggestionQueryConfig.KnowledgeBaseQuerySource; -import com.google.cloud.dialogflow.v2.HumanAgentAssistantConfig.SuggestionTriggerSettings; -import com.google.cloud.dialogflow.v2.KnowledgeBaseName; import com.google.cloud.dialogflow.v2.LocationName; -import com.google.cloud.dialogflow.v2.SuggestionFeature; -import com.google.cloud.dialogflow.v2.SuggestionFeature.Type; import java.io.IOException; -import java.util.Optional; public class ConversationProfileManagement { @@ -45,79 +33,24 @@ public static void main(String[] args) throws IOException { // Set display name of the new conversation profile String conversationProfileDisplayName = "my-conversation-profile-display-name"; - // Set knowledge base id for Article Suggestion feature. - // See details about how to create a knowledge base here, - // https://cloud.google.com/agent-assist/docs/article-suggestion. - String articleSuggestionKnowledgeBaseId = "my-article-suggestion-knowledge-base-id"; - // Create a conversation profile - createConversationProfileArticleSuggestion( - projectId, - conversationProfileDisplayName, - location, - Optional.of(articleSuggestionKnowledgeBaseId)); - } - - // Set suggestion trigger with no_smalltalk and only_send_user both true, which means that - // the suggestion is not triggered if last utterance is small talk and is only triggered - // if participant role of last utterance is END_USER. - public static SuggestionTriggerSettings buildSuggestionTriggerSettings() { - return SuggestionTriggerSettings.newBuilder().setNoSmalltalk(true).setOnlyEndUser(true).build(); - } - - // Set the configuration for suggestion query, including the knowledge base query source - // and maximum number of results to return. - public static SuggestionQueryConfig buildSuggestionQueryConfig( - KnowledgeBaseName knowledgeBaseName) { - return SuggestionQueryConfig.newBuilder() - .setKnowledgeBaseQuerySource( - KnowledgeBaseQuerySource.newBuilder().addKnowledgeBases(knowledgeBaseName.toString())) - .setMaxResults(3) - .build(); + createConversationProfile(projectId, conversationProfileDisplayName, location); } - // Create a conversation profile with given values about Article Suggestion. - public static void createConversationProfileArticleSuggestion( + // Create a basic conversation profile. + public static void createConversationProfile( String projectId, String displayName, - String location, - Optional articleSuggestionKnowledgeBaseId) - throws ApiException, IOException { + String location) throws ApiException, IOException { try (ConversationProfilesClient conversationProfilesClient = ConversationProfilesClient.create()) { - // Create a builder for agent assistance configuration - SuggestionConfig.Builder suggestionConfigBuilder = SuggestionConfig.newBuilder(); - - // Add knowledge base for Article Suggestion feature - if (articleSuggestionKnowledgeBaseId.isPresent()) { - KnowledgeBaseName articleSuggestionKbName = - KnowledgeBaseName.of(projectId, articleSuggestionKnowledgeBaseId.get()); - - // Build configuration for Article Suggestion feature - SuggestionFeatureConfig articleSuggestionFeatureConfig = - SuggestionFeatureConfig.newBuilder() - .setSuggestionFeature( - SuggestionFeature.newBuilder().setType(Type.ARTICLE_SUGGESTION).build()) - .setSuggestionTriggerSettings(buildSuggestionTriggerSettings()) - .setQueryConfig(buildSuggestionQueryConfig(articleSuggestionKbName)) - .build(); - - // Add Article Suggestion feature to agent assistance configuration - suggestionConfigBuilder.addFeatureConfigs(articleSuggestionFeatureConfig); - } - LocationName locationName = LocationName.of(projectId, location); - // Set a conversation profile with target configurations ConversationProfile targetConversationProfile = ConversationProfile.newBuilder() .setDisplayName(displayName) .setLanguageCode("en-US") - .setHumanAgentAssistantConfig( - HumanAgentAssistantConfig.newBuilder() - .setHumanAgentSuggestionConfig(suggestionConfigBuilder.build())) .build(); - // Create a conversation profile ConversationProfile createdConversationProfile = conversationProfilesClient.createConversationProfile( CreateConversationProfileRequest.newBuilder() @@ -131,4 +64,3 @@ public static void createConversationProfileArticleSuggestion( } } } -// [END dialogflow_create_conversation_profile_article_suggestion] diff --git a/dialogflow/snippets/src/test/java/com/example/dialogflow/CreateConversationProfileTest.java b/dialogflow/snippets/src/test/java/com/example/dialogflow/CreateConversationProfileTest.java deleted file mode 100644 index 8c4a2e734a7..00000000000 --- a/dialogflow/snippets/src/test/java/com/example/dialogflow/CreateConversationProfileTest.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.dialogflow; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertNotNull; - -import com.google.cloud.dialogflow.v2.ConversationProfilesClient; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.Optional; -import java.util.UUID; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class CreateConversationProfileTest { - - private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); - private static final String LOCATION = "global"; - private static final String NAME_PREFIX_IN_OUTPUT = "Name: "; - private static String conversationProfileNameToDelete = null; - private ByteArrayOutputStream bout; - private PrintStream newOutputStream; - private PrintStream originalOutputStream; - - private static void requireEnvVar(String varName) { - assertNotNull(System.getenv(varName)); - } - - // Extract the name of a newly created resource from latest "Name: %s\n" in sample code output - private static String getResourceNameFromOutputString(String output) { - return output.substring( - output.lastIndexOf(NAME_PREFIX_IN_OUTPUT) + NAME_PREFIX_IN_OUTPUT.length(), - output.length() - 1); - } - - private static void deleteConversationProfile(String conversationProfileName) throws IOException { - try (ConversationProfilesClient conversationProfilesClient = - ConversationProfilesClient.create()) { - conversationProfilesClient.deleteConversationProfile(conversationProfileName); - } - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("GOOGLE_CLOUD_PROJECT"); - } - - @Before - public void setUp() { - originalOutputStream = System.out; - bout = new ByteArrayOutputStream(); - newOutputStream = new PrintStream(bout); - System.setOut(newOutputStream); - } - - @After - public void tearDown() throws IOException { - if (conversationProfileNameToDelete != null) { - deleteConversationProfile(conversationProfileNameToDelete); - conversationProfileNameToDelete = null; - } - - System.setOut(originalOutputStream); - } - - @Test - public void testCreateConversationProfileArticleSuggestion() throws IOException { - String conversationProfileDisplayName = UUID.randomUUID().toString(); - - // Create a conversation profile - String articleSuggestionKnowledgeBaseId = UUID.randomUUID().toString(); - ConversationProfileManagement.createConversationProfileArticleSuggestion( - PROJECT_ID, - conversationProfileDisplayName, - LOCATION, - Optional.of(articleSuggestionKnowledgeBaseId)); - - String output = bout.toString(); - conversationProfileNameToDelete = getResourceNameFromOutputString(output); - assertThat(output).contains(conversationProfileDisplayName); - - // Delete the conversation profile - deleteConversationProfile(conversationProfileNameToDelete); - conversationProfileNameToDelete = null; - } -} diff --git a/dialogflow/snippets/src/test/java/com/example/dialogflow/CreateConversationTest.java b/dialogflow/snippets/src/test/java/com/example/dialogflow/CreateConversationTest.java index d172084eb98..984d60ba8b2 100644 --- a/dialogflow/snippets/src/test/java/com/example/dialogflow/CreateConversationTest.java +++ b/dialogflow/snippets/src/test/java/com/example/dialogflow/CreateConversationTest.java @@ -25,7 +25,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; -import java.util.Optional; import java.util.UUID; import org.junit.After; import org.junit.Before; @@ -71,8 +70,8 @@ public void setUp() throws IOException { System.setOut(newOutputStream); // Create a conversation profile - ConversationProfileManagement.createConversationProfileArticleSuggestion( - PROJECT_ID, CONVERSATION_PROFILE_DISPLAY_NAME, LOCATION, Optional.empty()); + ConversationProfileManagement.createConversationProfile( + PROJECT_ID, CONVERSATION_PROFILE_DISPLAY_NAME, LOCATION); String output = bout.toString(); assertThat(output).contains(NAME_PREFIX_IN_OUTPUT); conversationProfileName = diff --git a/dialogflow/snippets/src/test/java/com/example/dialogflow/CreateDocumentTest.java b/dialogflow/snippets/src/test/java/com/example/dialogflow/CreateDocumentTest.java index 439b671b51c..6af215b0ce7 100644 --- a/dialogflow/snippets/src/test/java/com/example/dialogflow/CreateDocumentTest.java +++ b/dialogflow/snippets/src/test/java/com/example/dialogflow/CreateDocumentTest.java @@ -50,7 +50,7 @@ public class CreateDocumentTest { private PrintStream originalOutputStream; private static void requireEnvVar(String varName) { - assertNotNull(String.format(varName)); + assertNotNull(System.getenv(varName)); } @BeforeClass diff --git a/dialogflow/snippets/src/test/java/com/example/dialogflow/CreateParticipantTest.java b/dialogflow/snippets/src/test/java/com/example/dialogflow/CreateParticipantTest.java index 1c5a92a4747..3004a1b20fc 100644 --- a/dialogflow/snippets/src/test/java/com/example/dialogflow/CreateParticipantTest.java +++ b/dialogflow/snippets/src/test/java/com/example/dialogflow/CreateParticipantTest.java @@ -26,7 +26,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; -import java.util.Optional; import java.util.UUID; import org.junit.After; import org.junit.Before; @@ -72,8 +71,8 @@ public void setUp() throws IOException { System.setOut(newOutputStream); // Create a conversation profile - ConversationProfileManagement.createConversationProfileArticleSuggestion( - PROJECT_ID, CONVERSATION_PROFILE_DISPLAY_NAME, LOCATION, Optional.empty()); + ConversationProfileManagement.createConversationProfile( + PROJECT_ID, CONVERSATION_PROFILE_DISPLAY_NAME, LOCATION); String output = bout.toString(); assertThat(output).contains(NAME_PREFIX_IN_OUTPUT); conversationProfileName = diff --git a/dialogflow/snippets/src/test/java/com/example/dialogflow/DetectIntentKnowledgeTest.java b/dialogflow/snippets/src/test/java/com/example/dialogflow/DetectIntentKnowledgeTest.java index 0f1aa54d880..307467eae31 100644 --- a/dialogflow/snippets/src/test/java/com/example/dialogflow/DetectIntentKnowledgeTest.java +++ b/dialogflow/snippets/src/test/java/com/example/dialogflow/DetectIntentKnowledgeTest.java @@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import com.google.cloud.dialogflow.v2beta1.DocumentName; import com.google.cloud.dialogflow.v2beta1.KnowledgeAnswers; @@ -31,6 +32,7 @@ import java.util.UUID; import org.junit.After; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -39,6 +41,16 @@ @SuppressWarnings("checkstyle:abbreviationaswordinname") public class DetectIntentKnowledgeTest { + private static void requireEnvVar(String varName) { + assertNotNull(System.getenv(varName)); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + private static String PROJECT_ID = System.getenv().get("GOOGLE_CLOUD_PROJECT"); private static String TEST_KNOWLEDGE_BASE_ID = "MTA4MzE0ODY5NTczMTQzNzU2ODA"; private static String TEST_DOCUMENT_ID = "MTUwNjk0ODg1NTU4NzkzMDExMg"; diff --git a/dialogflow/snippets/src/test/java/com/example/dialogflow/DetectIntentWithAudioTest.java b/dialogflow/snippets/src/test/java/com/example/dialogflow/DetectIntentWithAudioTest.java index 51649607304..d55bee50e21 100644 --- a/dialogflow/snippets/src/test/java/com/example/dialogflow/DetectIntentWithAudioTest.java +++ b/dialogflow/snippets/src/test/java/com/example/dialogflow/DetectIntentWithAudioTest.java @@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import com.google.common.collect.ImmutableList; @@ -30,6 +31,7 @@ import java.util.UUID; import org.junit.After; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -37,6 +39,17 @@ @RunWith(JUnit4.class) @SuppressWarnings("checkstyle:abbreviationaswordinname") public class DetectIntentWithAudioTest { + + private static void requireEnvVar(String varName) { + assertNotNull(System.getenv(varName)); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + protected static String PROJECT_ID = System.getenv().get("GOOGLE_CLOUD_PROJECT"); protected static String SESSION_ID = UUID.randomUUID().toString(); protected static String LANGUAGE_CODE = "en-US"; diff --git a/dialogflow/snippets/src/test/java/com/example/dialogflow/UpdateAnswerRecordTest.java b/dialogflow/snippets/src/test/java/com/example/dialogflow/UpdateAnswerRecordTest.java deleted file mode 100644 index fcf8776cec7..00000000000 --- a/dialogflow/snippets/src/test/java/com/example/dialogflow/UpdateAnswerRecordTest.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.dialogflow; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertNotNull; - -import com.google.api.gax.rpc.ApiException; -import com.google.cloud.dialogflow.v2.AnswerRecordName; -import com.google.cloud.dialogflow.v2.ConversationName; -import com.google.cloud.dialogflow.v2.ConversationProfileName; -import com.google.cloud.dialogflow.v2.ConversationProfilesClient; -import com.google.cloud.dialogflow.v2.Participant.Role; -import com.google.cloud.dialogflow.v2.ParticipantName; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeoutException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class UpdateAnswerRecordTest { - private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); - private static final String CONVERSATION_PROFILE_DISPLAY_NAME = UUID.randomUUID().toString(); - private static final String TEST_KNOWLEDGE_BASE_ID = "MTA0NTkyOTg0MjMwMjE1MDI0NjQ"; - private static final String LOCATION = "global"; - private static final String NAME_PREFIX_IN_OUTPUT = "Name: "; - private ConversationProfileName conversationProfileName; - private ConversationName conversationName; - private ParticipantName endUserName; - private ParticipantName humanAgentName; - private AnswerRecordName answerRecordName; - private ByteArrayOutputStream bout; - private PrintStream newOutputStream; - private PrintStream originalOutputStream; - - private static void requireEnvVar(String varName) { - assertNotNull(System.getenv(varName)); - } - - // Extract the name of a newly created resource from latest "Name: %s\n" in sample code output - private static String getResourceNameFromOutputString(String output) { - return output.substring( - output.lastIndexOf(NAME_PREFIX_IN_OUTPUT) + NAME_PREFIX_IN_OUTPUT.length(), - output.length() - 1); - } - - @BeforeClass - public static void checkRequirements() { - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("GOOGLE_CLOUD_PROJECT"); - } - - @Before - public void setUp() - throws IOException, ApiException, InterruptedException, ExecutionException, TimeoutException { - originalOutputStream = System.out; - bout = new ByteArrayOutputStream(); - newOutputStream = new PrintStream(bout); - System.setOut(newOutputStream); - - // Create a conversation profile - ConversationProfileManagement.createConversationProfileArticleSuggestion( - PROJECT_ID, - CONVERSATION_PROFILE_DISPLAY_NAME, - LOCATION, - Optional.of(TEST_KNOWLEDGE_BASE_ID)); - String output = bout.toString(); - assertThat(output).contains(NAME_PREFIX_IN_OUTPUT); - conversationProfileName = - ConversationProfileName.parse(getResourceNameFromOutputString(output)); - - // Create a conversation - ConversationManagement.createConversation( - PROJECT_ID, LOCATION, conversationProfileName.getConversationProfile()); - output = bout.toString(); - assertThat(output).contains(NAME_PREFIX_IN_OUTPUT); - conversationName = ConversationName.parse(getResourceNameFromOutputString(output)); - - // Create a END_USER participant - ParticipantManagement.createParticipant( - PROJECT_ID, LOCATION, conversationName.getConversation(), Role.END_USER); - output = bout.toString(); - assertThat(output).contains(NAME_PREFIX_IN_OUTPUT); - endUserName = ParticipantName.parse(getResourceNameFromOutputString(output)); - - // Create a HUMAN_AGENT participant - ParticipantManagement.createParticipant( - PROJECT_ID, LOCATION, conversationName.getConversation(), Role.HUMAN_AGENT); - output = bout.toString(); - assertThat(output).contains(NAME_PREFIX_IN_OUTPUT); - humanAgentName = ParticipantName.parse(getResourceNameFromOutputString(output)); - } - - @After - public void tearDown() throws IOException { - // Delete the created conversation profile - try (ConversationProfilesClient conversationProfilesClient = - ConversationProfilesClient.create()) { - conversationProfilesClient.deleteConversationProfile(conversationProfileName.toString()); - } - - System.setOut(originalOutputStream); - } - - @Test - public void testUpdateAnswerRecord() throws IOException { - // Send AnalyzeContent Requests - ParticipantManagement.analyzeContent( - PROJECT_ID, - LOCATION, - conversationName.getConversation(), - humanAgentName.getParticipant(), - "Hi, what can I help with?"); - ParticipantManagement.analyzeContent( - PROJECT_ID, - LOCATION, - conversationName.getConversation(), - endUserName.getParticipant(), - "I want to return my order."); - String output = bout.toString(); - assertThat(output).contains("Message Content: "); - answerRecordName = AnswerRecordName.parse(getResourceNameFromOutputString(output)); - - // Update a latest answer record - AnswerRecordManagement.updateAnswerRecord( - answerRecordName.getProject(), - answerRecordName.getLocation(), - answerRecordName.getAnswerRecord(), - true); - output = bout.toString(); - assertThat(getResourceNameFromOutputString(output)).contains(answerRecordName.toString()); - assertThat(output).contains("Clicked: true"); - } -} diff --git a/dlp/snippets/pom.xml b/dlp/snippets/pom.xml index 651f4165dcc..0c2c4483faf 100644 --- a/dlp/snippets/pom.xml +++ b/dlp/snippets/pom.xml @@ -32,7 +32,7 @@ com.google.cloud import pom - 26.32.0 + 26.64.0 @@ -48,6 +48,10 @@ com.google.cloud google-cloud-pubsub + + com.google.protobuf + protobuf-java + junit junit diff --git a/dlp/snippets/src/main/java/dlp/snippets/ProcessInspectFindingsSavedToGcs.java b/dlp/snippets/src/main/java/dlp/snippets/ProcessInspectFindingsSavedToGcs.java new file mode 100644 index 00000000000..a50ab25e28e --- /dev/null +++ b/dlp/snippets/src/main/java/dlp/snippets/ProcessInspectFindingsSavedToGcs.java @@ -0,0 +1,57 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dlp.snippets; + +// [START dlp_process_inspect_findings_saved_to_gcs] + +import com.google.privacy.dlp.v2.Finding; +import com.google.privacy.dlp.v2.SaveToGcsFindingsOutput; +import com.google.protobuf.ByteString; +import com.google.protobuf.TextFormat; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; + +public class ProcessInspectFindingsSavedToGcs { + + public static void main(String[] args) throws Exception { + // TODO(developer): Replace these variables before running the sample. + String inputPath = "src/test/resources/save_to_gcs_findings.txt"; + processFindingsGcsFile(inputPath); + } + + // Processes a file containing findings from a DLP inspect job. + public static void processFindingsGcsFile(String inputPath) + throws IOException { + SaveToGcsFindingsOutput.Builder builder = SaveToGcsFindingsOutput.newBuilder(); + try (Reader reader = + new InputStreamReader(new FileInputStream(inputPath), StandardCharsets.UTF_8)) { + TextFormat.merge(reader, builder); + } + SaveToGcsFindingsOutput output = builder.build(); + + // Parse the converted proto and process results + System.out.println("Findings: " + output.getFindingsCount()); + for (Finding f : output.getFindingsList()) { + System.out.println("\tInfo type: " + f.getInfoType().getName()); + System.out.println("\tLikelihood: " + f.getLikelihood()); + } + } +} +// [END dlp_process_inspect_findings_saved_to_gcs] \ No newline at end of file diff --git a/dlp/snippets/src/test/java/dlp/snippets/InspectTests.java b/dlp/snippets/src/test/java/dlp/snippets/InspectTests.java index b5b79dd6cc2..ae0230a1e36 100644 --- a/dlp/snippets/src/test/java/dlp/snippets/InspectTests.java +++ b/dlp/snippets/src/test/java/dlp/snippets/InspectTests.java @@ -624,4 +624,14 @@ public void testInspectWithStoredInfotype() throws Exception { assertThat(output).contains("InfoType: STORED_TYPE"); } } + + @Test + public void testProcessInspectFindingsSavedToGcs() throws Exception { + ProcessInspectFindingsSavedToGcs.processFindingsGcsFile( + "src/test/resources/save_to_gcs_findings.txt"); + String output = bout.toString(); + assertThat(output).contains("Findings: 2"); + assertThat(output).contains("Info type: PERSON_NAME"); + assertThat(output).contains("Likelihood: LIKELY"); + } } diff --git a/dlp/snippets/src/test/resources/save_to_gcs_findings.txt b/dlp/snippets/src/test/resources/save_to_gcs_findings.txt new file mode 100644 index 00000000000..6192d7704aa --- /dev/null +++ b/dlp/snippets/src/test/resources/save_to_gcs_findings.txt @@ -0,0 +1,110 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +findings { + info_type { + name: "PERSON_NAME" + sensitivity_score { + score: SENSITIVITY_MODERATE + } + } + likelihood: LIKELY + location { + byte_range { + start: 1208 + end: 1216 + } + content_locations { + container_name: "gs://fake_test_bucket/file.txt" + document_location { + } + container_timestamp { + seconds: 1728939753 + nanos: 301000000 + } + container_version: "1728939753176395" + } + container { + type: "Google Cloud Storage" + project_id: "fake-project-id" + full_path: "gs://fake_test_bucket/file.txt" + root_path: "fake_test_bucket" + relative_path: "file.txt" + update_time { + seconds: 1728939753 + nanos: 301000000 + } + version: "1728939753176395" + } + } + create_time { + seconds: 1741889947 + nanos: 947000000 + } + resource_name: "projects/fake-project-id/locations/global/dlpJobs/i-test-gcs-save" + job_create_time { + seconds: 1741889652 + nanos: 348000000 + } + job_name: "projects/fake-project-id/locations/global/dlpJobs/i-test-gcs-save" + finding_id: "2025-03-13T18:21:18.454889Z3148393127282654372" +} +findings { + info_type { + name: "PERSON_NAME" + sensitivity_score { + score: SENSITIVITY_MODERATE + } + } + likelihood: POSSIBLE + location { + byte_range { + start: 19872 + end: 19879 + } + content_locations { + container_name: "gs://fake_test_bucket/file.txt" + document_location { + } + container_timestamp { + seconds: 1728939753 + nanos: 301000000 + } + container_version: "1728939753176395" + } + container { + type: "Google Cloud Storage" + project_id: "fake-project-id" + full_path: "gs://fake_test_bucket/file.txt" + root_path: "fake_test_bucket" + relative_path: "file.txt" + update_time { + seconds: 1728939753 + nanos: 301000000 + } + version: "1728939753176395" + } + } + create_time { + seconds: 1741889947 + nanos: 948000000 + } + resource_name: "projects/fake-project-id/locations/global/dlpJobs/i-test-gcs-save" + job_create_time { + seconds: 1741889652 + nanos: 348000000 + } + job_name: "projects/fake-project-id/locations/global/dlpJobs/i-test-gcs-save" + finding_id: "2025-03-13T18:21:18.506689Z2134257296577089402" +} \ No newline at end of file diff --git a/endpoints/getting-started/k8s/esp_echo_http.yaml b/endpoints/getting-started/k8s/esp_echo_http.yaml index d5b20e696c3..30f007faa27 100644 --- a/endpoints/getting-started/k8s/esp_echo_http.yaml +++ b/endpoints/getting-started/k8s/esp_echo_http.yaml @@ -38,13 +38,10 @@ spec: labels: app: esp-echo spec: - # [START endpoints_secret1_yaml_java] volumes: - name: service-account-creds secret: secretName: service-account-creds - # [END endpoints_secret1_yaml_java] - # [START endpoints_service_yaml_java] containers: - name: esp image: gcr.io/endpoints-release/endpoints-runtime:1 @@ -55,15 +52,12 @@ spec: "--rollout_strategy", "managed", "--service_account_key", "/etc/nginx/creds/service-account-creds.json", ] - # [END endpoints_service_yaml_java] ports: - containerPort: 8080 - # [START endpoints_secret2_yaml_java] volumeMounts: - mountPath: /etc/nginx/creds name: service-account-creds readOnly: true - # [END endpoints_secret2_yaml_java] - name: echo image: gcr.io/endpoints-release/echo:latest ports: diff --git a/eventarc/audit-storage/Dockerfile b/eventarc/audit-storage/Dockerfile index c6bdae6957c..ce982375e0f 100644 --- a/eventarc/audit-storage/Dockerfile +++ b/eventarc/audit-storage/Dockerfile @@ -28,7 +28,7 @@ RUN mvn package -DskipTests # Use Eclipse Temurin for base image. # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds -FROM eclipse-temurin:17.0.15_6-jre-alpine +FROM eclipse-temurin:17.0.18_8-jre-alpine # Copy the jar to the production image from the builder stage. COPY --from=builder /app/target/audit-storage-*.jar /audit-storage.jar diff --git a/eventarc/audit-storage/README.md b/eventarc/audit-storage/README.md index c0b08fbfc8a..ddf8068d51f 100644 --- a/eventarc/audit-storage/README.md +++ b/eventarc/audit-storage/README.md @@ -43,7 +43,7 @@ gcloud run deploy $MY_RUN_SERVICE \ Create a _single region_ Cloud Storage bucket: ```sh -gsutil mb -p $(gcloud config get-value project) -l us-central1 gs://"$MY_GCS_BUCKET" +gcloud storage buckets create gs://"$MY_GCS_BUCKET" --project $(gcloud config get-value project) --location us-central1 ``` Create a Cloud Storage (via Audit Log) trigger: @@ -62,7 +62,7 @@ gcloud alpha events triggers create $MY_GCS_TRIGGER \ Test your Cloud Run service by creating a GCS event: ```sh -gsutil defstorageclass set NEARLINE gs://$MY_GCS_BUCKET +gcloud storage buckets update gs://$MY_GCS_BUCKET --default-storage-class=NEARLINE ``` Observe the Cloud Run service printing upon receiving an event in Cloud Logging: diff --git a/eventarc/pubsub/Dockerfile b/eventarc/pubsub/Dockerfile index c2b620be264..3d023b6e9fd 100644 --- a/eventarc/pubsub/Dockerfile +++ b/eventarc/pubsub/Dockerfile @@ -26,7 +26,7 @@ RUN mvn package -DskipTests # Use Eclipse Temurin for base image. # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds -FROM eclipse-temurin:17.0.15_6-jre-alpine +FROM eclipse-temurin:17.0.18_8-jre-alpine # Copy the jar to the production image from the builder stage. COPY --from=builder /app/target/events-pubsub-*.jar /events-pubsub.jar diff --git a/eventarc/storage-handler/README.md b/eventarc/storage-handler/README.md index 7b339ddab3b..dd70847df17 100644 --- a/eventarc/storage-handler/README.md +++ b/eventarc/storage-handler/README.md @@ -37,7 +37,7 @@ gcloud run deploy $MY_RUN_SERVICE \ Create a _single region_ Cloud Storage bucket: ```sh -gsutil mb -p $PROJECT_ID -l us-central1 gs://"$MY_GCS_BUCKET" +gcloud storage buckets create gs://"$MY_GCS_BUCKET" --project $PROJECT_ID --location us-central1 ``` Create a Service Account for Eventarc trigger @@ -67,7 +67,7 @@ Test your Cloud Run service by creating a GCS event: ```sh touch testfile.txt -gsutil copy testfile.txt gs://$MY_GCS_BUCKET +gcloud storage cp testfile.txt gs://$MY_GCS_BUCKET ``` Observe the Cloud Run service printing upon receiving an event in Cloud Logging: diff --git a/firestore/samples/pom.xml b/firestore/samples/pom.xml new file mode 100644 index 00000000000..978b9a5e01b --- /dev/null +++ b/firestore/samples/pom.xml @@ -0,0 +1,72 @@ + + + + 4.0.0 + com.example.firestore + firestore-samples + 1.0-SNAPSHOT + + + + shared-configuration + com.google.cloud.samples + 1.2.2 + + + + 21 + 21 + + + + + + libraries-bom + com.google.cloud + import + pom + 26.80.0 + + + + + + + com.google.cloud + google-cloud-firestore + + + + + truth + com.google.truth + test + 1.4.5 + + + org.junit.jupiter + junit-jupiter + 5.14.3 + test + + + diff --git a/firestore/samples/src/main/java/com/example/firestore/DeleteCollection.java b/firestore/samples/src/main/java/com/example/firestore/DeleteCollection.java new file mode 100644 index 00000000000..b1c79790786 --- /dev/null +++ b/firestore/samples/src/main/java/com/example/firestore/DeleteCollection.java @@ -0,0 +1,54 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.firestore; + +// [START firestore_data_delete_collection] + +import com.google.api.core.ApiFuture; +import com.google.cloud.firestore.CollectionReference; +import com.google.cloud.firestore.Firestore; +import com.google.cloud.firestore.FirestoreOptions; + +public class DeleteCollection { + + /** + * Delete a collection and all its subcollections. + * + * @param projectId The Google Cloud project ID + * @param collectionName The name of the collection to delete + */ + public static void deleteCollection(String projectId, String collectionName) throws Exception { + FirestoreOptions firestoreOptions = + FirestoreOptions.getDefaultInstance().toBuilder().setProjectId(projectId).build(); + try (Firestore db = firestoreOptions.getService()) { + CollectionReference collection = db.collection(collectionName); + + ApiFuture future = db.recursiveDelete(collection); + + future.get(); + System.out.println("Collection and all its subcollections deleted successfully."); + } + } + + public static void main(String[] args) throws Exception { + String projectId = "example-project-id"; + String collectionName = "example-collection-name"; + + deleteCollection(projectId, collectionName); + } +} +// [END firestore_data_delete_collection] diff --git a/firestore/samples/src/test/java/com/example/firestore/DeleteCollectionIT.java b/firestore/samples/src/test/java/com/example/firestore/DeleteCollectionIT.java new file mode 100644 index 00000000000..143a4aff3c5 --- /dev/null +++ b/firestore/samples/src/test/java/com/example/firestore/DeleteCollectionIT.java @@ -0,0 +1,69 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.firestore; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class DeleteCollectionIT { + + private static ByteArrayOutputStream bout; + private static PrintStream prevPrintStream; + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String COLLECTION_NAME = "usa-cities"; + private static final String DOCUMENT_NAME = "LA"; + private static final String NAME = "Los Angeles"; + + private static String getEnvVar(String varName) { + String value = System.getenv(varName); + assertNotNull( + String.format("Environment variable '%s' must be set to perform these tests.", varName), + value); + return value; + } + + @BeforeAll + public static void setUp() throws Exception { + getEnvVar("GOOGLE_CLOUD_PROJECT"); + + // Create collection + Utils.createCollection(PROJECT_ID, COLLECTION_NAME, DOCUMENT_NAME, NAME); + + prevPrintStream = System.out; + bout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(bout)); + } + + @AfterAll + public static void tearDown() { + System.setOut(prevPrintStream); + } + + @Test + public void testDeleteCollection() throws Exception { + DeleteCollection.deleteCollection(PROJECT_ID, COLLECTION_NAME); + + String output = bout.toString(); + assertTrue(output.contains("Collection and all its subcollections deleted successfully.")); + } +} diff --git a/firestore/samples/src/test/java/com/example/firestore/Utils.java b/firestore/samples/src/test/java/com/example/firestore/Utils.java new file mode 100644 index 00000000000..3caffc7f5ec --- /dev/null +++ b/firestore/samples/src/test/java/com/example/firestore/Utils.java @@ -0,0 +1,41 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.firestore; + +import com.google.api.core.ApiFuture; +import com.google.cloud.firestore.Firestore; +import com.google.cloud.firestore.FirestoreOptions; +import com.google.cloud.firestore.WriteResult; +import java.util.HashMap; +import java.util.Map; + +public class Utils { + public static void createCollection( + String projectId, String collectionName, String documentName, String name) throws Exception { + // Create collection and document to be deleted + FirestoreOptions firestoreOptions = + FirestoreOptions.getDefaultInstance().toBuilder().setProjectId(projectId).build(); + try (Firestore db = firestoreOptions.getService()) { + + Map docData = new HashMap<>(); + docData.put("name", name); + ApiFuture future = + db.collection(collectionName).document(documentName).set(docData); + future.get(); + } + } +} diff --git a/flexible/java-11/cloudstorage/README.md b/flexible/java-11/cloudstorage/README.md index 07436e7ebd0..1a8b51f1446 100644 --- a/flexible/java-11/cloudstorage/README.md +++ b/flexible/java-11/cloudstorage/README.md @@ -18,7 +18,7 @@ Before you can run or deploy the sample, you will need to do the following: SDK](https://cloud.google.com/sdk) using the following command: ```sh - gsutil mb gs://[your-bucket-name] + gcloud storage buckets create gs://[your-bucket-name] ``` 1. Set the default ACL on your bucket to public read in order to serve files @@ -26,7 +26,7 @@ Before you can run or deploy the sample, you will need to do the following: SDK](https://cloud.google.com/sdk) using the following command: ```sh - gsutil defacl set public-read gs://[your-bucket-name] + gcloud storage buckets update gs://[your-bucket-name] --predefined-default-object-acl=publicRead ``` 1. Update the bucket name in `src/main/appengine/app.yaml`. This makes the diff --git a/flexible/java-17/cloudstorage/README.md b/flexible/java-17/cloudstorage/README.md index 07436e7ebd0..1a8b51f1446 100644 --- a/flexible/java-17/cloudstorage/README.md +++ b/flexible/java-17/cloudstorage/README.md @@ -18,7 +18,7 @@ Before you can run or deploy the sample, you will need to do the following: SDK](https://cloud.google.com/sdk) using the following command: ```sh - gsutil mb gs://[your-bucket-name] + gcloud storage buckets create gs://[your-bucket-name] ``` 1. Set the default ACL on your bucket to public read in order to serve files @@ -26,7 +26,7 @@ Before you can run or deploy the sample, you will need to do the following: SDK](https://cloud.google.com/sdk) using the following command: ```sh - gsutil defacl set public-read gs://[your-bucket-name] + gcloud storage buckets update gs://[your-bucket-name] --predefined-default-object-acl=publicRead ``` 1. Update the bucket name in `src/main/appengine/app.yaml`. This makes the diff --git a/flexible/java-25/micronaut-helloworld/README.md b/flexible/java-25/micronaut-helloworld/README.md new file mode 100644 index 00000000000..72c32c4383f --- /dev/null +++ b/flexible/java-25/micronaut-helloworld/README.md @@ -0,0 +1,16 @@ +# Micronaut Application on Google App Engine Flex with Java 25 + +This sample shows how to deploy a [Micronaut](https://micronaut.io/) +application to Google App Engine Flex. + +## Deploying + +```bash +gcloud app deploy +``` + +To view your app, use command: +``` +gcloud app browse +``` +Or navigate to `https://.appspot.com`. diff --git a/flexible/java-25/micronaut-helloworld/micronaut-cli.yml b/flexible/java-25/micronaut-helloworld/micronaut-cli.yml new file mode 100644 index 00000000000..2c08db76694 --- /dev/null +++ b/flexible/java-25/micronaut-helloworld/micronaut-cli.yml @@ -0,0 +1,19 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +profile: service +defaultPackage: com.example.appengine +--- +testFramework: junit +sourceLanguage: java diff --git a/flexible/java-25/micronaut-helloworld/pom.xml b/flexible/java-25/micronaut-helloworld/pom.xml new file mode 100644 index 00000000000..80463b16868 --- /dev/null +++ b/flexible/java-25/micronaut-helloworld/pom.xml @@ -0,0 +1,207 @@ + + + + 4.0.0 + com.example.appengine.flexible + micronaut-helloworld + 0.1 + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + UTF-8 + com.example.appengine.Application + 21 + 21 + 3.10.3 + ${argLine} + + + + + io.micronaut + micronaut-inject + ${micronaut.version} + compile + + + io.micronaut + micronaut-validation + ${micronaut.version} + compile + + + io.micronaut + micronaut-runtime + ${micronaut.version} + compile + + + io.micronaut + micronaut-http-client + ${micronaut.version} + compile + + + javax.annotation + javax.annotation-api + 1.3.2 + compile + + + io.micronaut + micronaut-http-server-netty + ${micronaut.version} + compile + + + junit + junit + 4.13.2 + test + + + + + + + com.google.cloud.tools + appengine-maven-plugin + 2.8.3 + + GCLOUD_CONFIG + micronaut-helloworld + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.2 + + + package + + shade + + + + + ${exec.mainClass} + + + + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.2.0 + + java + + -noverify + -XX:TieredStopAtLevel=1 + -Dcom.sun.management.jmxremote + -classpath + + ${exec.mainClass} + + + + + maven-surefire-plugin + 3.2.2 + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + UTF-8 + + -parameters + + + + io.micronaut + micronaut-inject-java + ${micronaut.version} + + + io.micronaut + micronaut-validation + ${micronaut.version} + + + + + + test-compile + + testCompile + + + + -parameters + + + + io.micronaut + micronaut-inject-java + ${micronaut.version} + + + io.micronaut + micronaut-validation + ${micronaut.version} + + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.12 + + + + prepare-agent + + + + report + prepare-package + + report + + + + + + + + \ No newline at end of file diff --git a/flexible/java-25/micronaut-helloworld/src/main/appengine/app.yaml b/flexible/java-25/micronaut-helloworld/src/main/appengine/app.yaml new file mode 100644 index 00000000000..db82585bc79 --- /dev/null +++ b/flexible/java-25/micronaut-helloworld/src/main/appengine/app.yaml @@ -0,0 +1,27 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# [START gae_flex_java25_yaml] +runtime: java +env: flex +service: helloworld-java25-flex +runtime_config: + operating_system: ubuntu24 + runtime_version: 25 +handlers: +- url: /.* + script: this field is required, but ignored + +manual_scaling: + instances: 1 +# [END gae_flex_java25_yaml] diff --git a/flexible/java-25/micronaut-helloworld/src/main/java/com/example/appengine/Application.java b/flexible/java-25/micronaut-helloworld/src/main/java/com/example/appengine/Application.java new file mode 100644 index 00000000000..e99fbde8f39 --- /dev/null +++ b/flexible/java-25/micronaut-helloworld/src/main/java/com/example/appengine/Application.java @@ -0,0 +1,26 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import io.micronaut.runtime.Micronaut; + +public class Application { + + public static void main(String[] args) { + Micronaut.run(Application.class); + } +} diff --git a/flexible/java-25/micronaut-helloworld/src/main/java/com/example/appengine/HelloController.java b/flexible/java-25/micronaut-helloworld/src/main/java/com/example/appengine/HelloController.java new file mode 100644 index 00000000000..ac32f9ab102 --- /dev/null +++ b/flexible/java-25/micronaut-helloworld/src/main/java/com/example/appengine/HelloController.java @@ -0,0 +1,31 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import io.micronaut.http.MediaType; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.annotation.Produces; + +@Controller("/") +public class HelloController { + @Get("/") + @Produces(MediaType.TEXT_PLAIN) + public String index() { + return "Hello World!"; + } +} diff --git a/.github/flakybot.yaml b/flexible/java-25/micronaut-helloworld/src/main/resources/application.yml similarity index 79% rename from .github/flakybot.yaml rename to flexible/java-25/micronaut-helloworld/src/main/resources/application.yml index 52c56cabcef..854340b8361 100644 --- a/.github/flakybot.yaml +++ b/flexible/java-25/micronaut-helloworld/src/main/resources/application.yml @@ -1,10 +1,10 @@ -# Copyright 2024 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, @@ -12,4 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -issuePriority: p2 +micronaut: + application: + name: micronaut diff --git a/flexible/java-25/micronaut-helloworld/src/main/resources/logback.xml b/flexible/java-25/micronaut-helloworld/src/main/resources/logback.xml new file mode 100644 index 00000000000..4f0363b57df --- /dev/null +++ b/flexible/java-25/micronaut-helloworld/src/main/resources/logback.xml @@ -0,0 +1,28 @@ + + + + + + true + + + %cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n + + + + + + + diff --git a/flexible/java-25/micronaut-helloworld/src/test/java/com/example/appengine/HelloControllerTest.java b/flexible/java-25/micronaut-helloworld/src/test/java/com/example/appengine/HelloControllerTest.java new file mode 100644 index 00000000000..44571ee72cb --- /dev/null +++ b/flexible/java-25/micronaut-helloworld/src/test/java/com/example/appengine/HelloControllerTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine; + +import static org.junit.Assert.assertEquals; + +import io.micronaut.context.ApplicationContext; +import io.micronaut.http.HttpRequest; +import io.micronaut.http.client.HttpClient; +import io.micronaut.runtime.server.EmbeddedServer; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class HelloControllerTest { + private static EmbeddedServer server; + private static HttpClient client; + + @BeforeClass + public static void setupServer() { + + server = ApplicationContext.run(EmbeddedServer.class); + + client = server.getApplicationContext().createBean(HttpClient.class, server.getURL()); + } + + @AfterClass + public static void stopServer() { + if (client != null) { + client.stop(); + } + if (server != null) { + server.stop(); + } + } + + @Test + public void testHelloWorldResponse() { + String response = client.toBlocking().retrieve(HttpRequest.GET("/")); + assertEquals("Hello World!", response); + } +} diff --git a/flexible/java-25/websocket-jetty/README.md b/flexible/java-25/websocket-jetty/README.md new file mode 100644 index 00000000000..cbc8394e054 --- /dev/null +++ b/flexible/java-25/websocket-jetty/README.md @@ -0,0 +1,52 @@ +# App Engine Flexible Environment - Web Socket Example + +This sample demonstrates how to use +[Websockets](https://tools.ietf.org/html/rfc6455) on [Google App Engine Flexible +Environment](https://cloud.google.com/appengine/docs/flexible/java/) using Java. +The sample uses the [native Jetty WebSocket Server +API](http://www.eclipse.org/jetty/documentation/9.4.x/jetty-websocket-server-api.html) +to create a server-side socket and the [native Jetty WebSocket Client +API](http://www.eclipse.org/jetty/documentation/9.4.x/jetty-websocket-client-api.html). + +## Sample application workflow + +1. The sample application creates a server socket using the endpoint `/echo`. +1. The homepage (`/`) provides a form to submit a text message to the server +socket. This creates a client-side socket and sends the message to the server. +1. The server on receiving the message, echoes the message back to the client. +1. The message received by the client is stored in an in-memory cache and is + viewable on the homepage. + +The sample also provides a Javascript +[client](src/main/webapp/js_client.jsp)(`/js_client.jsp`) that you can use to +test against the Websocket server. + +## Setup + +- [Install](https://cloud.google.com/sdk/) and initialize GCloud SDK. This will + + ```sh + gcloud init + ``` + +- If this is your first time creating an app engine application + + ```sh + gcloud appengine create + ``` + +## Deploy + +The sample application is packaged as a jar and runs on the Java 25 runtime. + +```sh +mvn clean package appengine:deploy +``` + +You can then direct your browser to `https://YOUR_PROJECT_ID.appspot.com/` + +To test the Javascript client, access +`https://YOUR_PROJECT_ID.appspot.com/js_client.jsp` + +Note: This application constructs a Web Socket URL using `getWebSocketAddress` +in the [SendServlet Class](src/main/java/com/example/flexible/websocket/jettynative/SendServlet.java). The application assumes the latest version of the service. diff --git a/flexible/java-25/websocket-jetty/pom.xml b/flexible/java-25/websocket-jetty/pom.xml new file mode 100644 index 00000000000..e8da5205900 --- /dev/null +++ b/flexible/java-25/websocket-jetty/pom.xml @@ -0,0 +1,244 @@ + + + 4.0.0 + + org.eclipse.jetty.demo + native-jetty-websocket-example + 1.0-SNAPSHOT + jar + + + + com.google.cloud.samples + shared-configuration + 1.2.2 + + + + 21 + 21 + false + 9.4.57.v20241219 + 2.7.18 + + + + + + com.google.cloud + libraries-bom + 26.32.0 + pom + import + + + + + + + org.eclipse.jetty + jetty-server + ${jetty.version} + + + org.eclipse.jetty + jetty-webapp + ${jetty.version} + + + org.eclipse.jetty + jetty-util + ${jetty.version} + + + org.eclipse.jetty + jetty-annotations + ${jetty.version} + + + org.eclipse.jetty + apache-jsp + ${jetty.version} + + + jakarta.servlet.jsp.jstl + jakarta.servlet.jsp.jstl-api + 2.0.0 + + + org.glassfish.web + jakarta.servlet.jsp.jstl + 2.0.0 + + + jakarta.servlet + jakarta.servlet-api + 5.0.0 + jar + provided + + + jakarta.websocket + jakarta.websocket-api + 2.1.1 + provided + + + + org.eclipse.jetty.websocket + websocket-client + ${jetty.version} + provided + + + org.eclipse.jetty.websocket + websocket-server + ${jetty.version} + + + org.eclipse.jetty.websocket + websocket-servlet + ${jetty.version} + provided + + + org.eclipse.jetty.websocket + websocket-servlet + ${jetty.version} + + + com.google.guava + guava + + + org.slf4j + slf4j-simple + 2.0.12 + + + junit + junit + 4.13.2 + test + + + + + + + ${basedir}/src/main/webapp + false + + + + ${project.build.directory}/${project.build.finalName}/WEB-INF/classes + + + + org.apache.maven.plugins + maven-war-plugin + 3.4.0 + + + + com.google.cloud.tools + appengine-maven-plugin + 2.8.3 + + GCLOUD_CONFIG + GCLOUD_CONFIG + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.1 + + + + java + + + + + com.example.flexible.websocket.jettynative.Main + + + + + org.eclipse.jetty + jetty-maven-plugin + ${jetty.version} + + jar + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + true + com.example.flexible.websocket.jettynative.Main + + + + jar-with-dependencies + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.14 + + + + prepare-agent + + + + report + prepare-package + + report + + + + + + + diff --git a/flexible/java-25/websocket-jetty/src/main/appengine/app.yaml b/flexible/java-25/websocket-jetty/src/main/appengine/app.yaml new file mode 100644 index 00000000000..b31b02a557e --- /dev/null +++ b/flexible/java-25/websocket-jetty/src/main/appengine/app.yaml @@ -0,0 +1,33 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +runtime: java +env: flex +runtime_config: + operating_system: ubuntu22 + runtime_version: 17 +manual_scaling: + instances: 1 +handlers: +- url: /.* + script: this field is required, but ignored + + + +# For applications which can take advantage of session affinity +# (where the load balancer will attempt to route multiple connections from +# the same user to the same App Engine instance), uncomment the folowing: + +# network: +# session_affinity: true diff --git a/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/ClientSocket.java b/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/ClientSocket.java new file mode 100644 index 00000000000..84360e4a904 --- /dev/null +++ b/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/ClientSocket.java @@ -0,0 +1,62 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.flexible.websocket.jettynative; + +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.logging.Logger; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; + +/** + * Basic Echo Client Socket. + */ +@WebSocket(maxTextMessageSize = 64 * 1024) +public class ClientSocket { + private Logger logger = Logger.getLogger(ClientSocket.class.getName()); + private Session session; + // stores the messages in-memory. + // Note : this is currently an in-memory store for demonstration, + // not recommended for production use-cases. + private static Collection messages = new ConcurrentLinkedDeque<>(); + + @OnWebSocketClose + public void onClose(int statusCode, String reason) { + logger.fine("Connection closed: " + statusCode + ":" + reason); + this.session = null; + } + + @OnWebSocketConnect + public void onConnect(Session session) { + this.session = session; + } + + @OnWebSocketMessage + public void onMessage(String msg) { + logger.fine("Message Received : " + msg); + messages.add(msg); + } + + // Retrieve all received messages. + public static Collection getReceivedMessages() { + return Collections.unmodifiableCollection(messages); + } +} diff --git a/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/EchoServlet.java b/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/EchoServlet.java new file mode 100644 index 00000000000..32358f14268 --- /dev/null +++ b/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/EchoServlet.java @@ -0,0 +1,44 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.flexible.websocket.jettynative; + +import javax.servlet.annotation.WebServlet; +import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; +import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; +import org.eclipse.jetty.websocket.servlet.WebSocketCreator; +import org.eclipse.jetty.websocket.servlet.WebSocketServlet; +import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; + +/* + * Server-side WebSocket upgraded on /echo servlet. + */ +@SuppressWarnings("serial") +@WebServlet( + name = "Echo WebSocket Servlet", + urlPatterns = {"/echo"}) +public class EchoServlet extends WebSocketServlet implements WebSocketCreator { + @Override + public void configure(WebSocketServletFactory factory) { + factory.setCreator(this); + } + + @Override + public Object createWebSocket( + ServletUpgradeRequest servletUpgradeRequest, ServletUpgradeResponse servletUpgradeResponse) { + return new ServerSocket(); + } +} diff --git a/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/Main.java b/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/Main.java new file mode 100644 index 00000000000..357deac5ce0 --- /dev/null +++ b/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/Main.java @@ -0,0 +1,149 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.flexible.websocket.jettynative; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import org.apache.tomcat.util.scan.StandardJarScanFilter; +import org.apache.tomcat.util.scan.StandardJarScanner; +import org.eclipse.jetty.annotations.AnnotationConfiguration; +import org.eclipse.jetty.apache.jsp.JettyJasperInitializer; +import org.eclipse.jetty.jsp.JettyJspServlet; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.webapp.Configuration; +import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.webapp.WebInfConfiguration; + +/** + * Starts up the server, including a DefaultServlet that handles static files, and any servlet + * classes annotated with the @WebServlet annotation. + */ +public class Main { + + public static void main(String[] args) throws Exception { + + // Create a server that listens on port 8080. + Server server = new Server(8080); + WebAppContext webAppContext = new WebAppContext(); + server.setHandler(webAppContext); + + // Load static content from inside the jar file. + URL webAppDir = Main.class.getClassLoader().getResource("WEB-INF/"); + System.out.println(webAppDir); + webAppContext.setResourceBase(webAppDir.toURI().toString()); + + // Enable annotations so the server sees classes annotated with @WebServlet. + webAppContext.setConfigurations( + new Configuration[] { + new AnnotationConfiguration(), new WebInfConfiguration(), + }); + + webAppContext.setAttribute( + "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", + ".*/target/classes/|.*\\.jar"); + enableEmbeddedJspSupport(webAppContext); + + ServletHolder holderAltMapping = new ServletHolder(); + holderAltMapping.setName("index.jsp"); + holderAltMapping.setForcedPath("/index.jsp"); + webAppContext.addServlet(holderAltMapping, "/"); + + // Start the server! 🚀 + server.start(); + System.out.println("Server started!"); + + // Keep the main thread alive while the server is running. + server.join(); + } + + private static void enableEmbeddedJspSupport(ServletContextHandler servletContextHandler) + throws IOException { + // Establish Scratch directory for the servlet context (used by JSP compilation) + File tempDir = new File(System.getProperty("java.io.tmpdir")); + File scratchDir = new File(tempDir.toString(), "embedded-jetty-jsp"); + + if (!scratchDir.exists()) { + if (!scratchDir.mkdirs()) { + throw new IOException("Unable to create scratch directory: " + scratchDir); + } + } + servletContextHandler.setAttribute("javax.servlet.context.tempdir", scratchDir); + + // Set Classloader of Context to be sane (needed for JSTL) + // JSP requires a non-System classloader, this simply wraps the + // embedded System classloader in a way that makes it suitable + // for JSP to use + ClassLoader jspClassLoader = new URLClassLoader(new URL[0], Main.class.getClassLoader()); + servletContextHandler.setClassLoader(jspClassLoader); + + // Manually call JettyJasperInitializer on context startup + servletContextHandler.addBean(new JspStarter(servletContextHandler)); + + // Create / Register JSP Servlet (must be named "jsp" per spec) + ServletHolder holderJsp = new ServletHolder("jsp", JettyJspServlet.class); + holderJsp.setInitOrder(0); + holderJsp.setInitParameter("logVerbosityLevel", "DEBUG"); + holderJsp.setInitParameter("fork", "false"); + holderJsp.setInitParameter("xpoweredBy", "false"); + holderJsp.setInitParameter("compilerTargetVM", "25"); + holderJsp.setInitParameter("compilerSourceVM", "25"); + holderJsp.setInitParameter("keepgenerated", "true"); + servletContextHandler.addServlet(holderJsp, "*.jsp"); + } + + /** + * JspStarter for embedded ServletContextHandlers + * + *

This is added as a bean that is a jetty LifeCycle on the ServletContextHandler. This bean's + * doStart method will be called as the ServletContextHandler starts, and will call the + * ServletContainerInitializer for the jsp engine. + */ + public static class JspStarter extends AbstractLifeCycle + implements ServletContextHandler.ServletContainerInitializerCaller { + JettyJasperInitializer sci; + ServletContextHandler context; + + public JspStarter(ServletContextHandler context) { + this.sci = new JettyJasperInitializer(); + this.context = context; + String skip = "apache-*,ecj-*,jetty-*,asm-*,javax.servlet-*" + + "javax.annotation-*,taglibs-standard-spec-*,*.jar"; + StandardJarScanner jarScanner = new StandardJarScanner(); + StandardJarScanFilter jarScanFilter = new StandardJarScanFilter(); + jarScanFilter.setTldSkip(skip); + jarScanner.setJarScanFilter(jarScanFilter); + this.context.setAttribute("org.apache.tomcat.JarScanner", jarScanner); + } + + @Override + protected void doStart() throws Exception { + ClassLoader old = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(context.getClassLoader()); + try { + sci.onStartup(null, context.getServletContext()); + super.doStart(); + } finally { + Thread.currentThread().setContextClassLoader(old); + } + } + } +} diff --git a/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/SendServlet.java b/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/SendServlet.java new file mode 100644 index 00000000000..0feab349ac2 --- /dev/null +++ b/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/SendServlet.java @@ -0,0 +1,143 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.flexible.websocket.jettynative; + +import com.google.common.base.Preconditions; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.concurrent.Future; +import java.util.logging.Logger; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; +import org.eclipse.jetty.websocket.client.WebSocketClient; + +@WebServlet("/send") +/** Servlet that sends the message sent over POST to over a websocket connection. */ +public class SendServlet extends HttpServlet { + + private Logger logger = Logger.getLogger(SendServlet.class.getName()); + + private static final String ENDPOINT = "/echo"; + private static final String WEBSOCKET_PROTOCOL_PREFIX = "ws://"; + private static final String WEBSOCKET_HTTPS_PROTOCOL_PREFIX = "wss://"; + private static final String APPENGINE_HOST_SUFFIX = ".appspot.com"; + + // GAE_INSTANCE environment is used to detect App Engine Flexible Environment + private static final String GAE_INSTANCE_VAR = "GAE_INSTANCE"; + // GOOGLE_CLOUD_PROJECT environment variable is set to the GCP project ID on App Engine Flexible. + private static final String GOOGLE_CLOUD_PROJECT_ENV_VAR = "GOOGLE_CLOUD_PROJECT"; + // GAE_SERVICE environment variable is set to the GCP service name. + private static final String GAE_SERVICE_ENV_VAR = "GAE_SERVICE"; + + private final HttpClient httpClient; + private final WebSocketClient webSocketClient; + private final ClientSocket clientSocket; + + public SendServlet() { + this.httpClient = createHttpClient(); + this.webSocketClient = createWebSocketClient(); + this.clientSocket = new ClientSocket(); + } + + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { + String message = request.getParameter("message"); + try { + sendMessageOverWebSocket(message); + response.sendRedirect("/"); + } catch (Exception e) { + logger.severe("Error sending message over socket: " + e.getMessage()); + e.printStackTrace(response.getWriter()); + response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500); + } + } + + private HttpClient createHttpClient() { + HttpClient httpClient; + if (System.getenv(GAE_INSTANCE_VAR) != null) { + // If on HTTPS, create client with SSL Context + SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(); + httpClient = new HttpClient(sslContextFactory); + } else { + // local testing on HTTP + httpClient = new HttpClient(); + } + return httpClient; + } + + private WebSocketClient createWebSocketClient() { + return new WebSocketClient(this.httpClient); + } + + private void sendMessageOverWebSocket(String message) throws Exception { + if (!httpClient.isRunning()) { + try { + httpClient.start(); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + } + if (!webSocketClient.isRunning()) { + try { + webSocketClient.start(); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + } + ClientUpgradeRequest request = new ClientUpgradeRequest(); + // Attempt connection + Future future = + webSocketClient.connect(clientSocket, new URI(getWebSocketAddress()), request); + // Wait for Connect + Session session = future.get(); + // Send a message + session.getRemote().sendString(message); + // Close session + session.close(); + } + + /** + * Returns the host:port/echo address a client needs to use to communicate with the server. On App + * engine Flex environments, result will be in the form wss://project-id.appspot.com/echo + */ + public static String getWebSocketAddress() { + // Use ws://127.0.0.1:8080/echo when testing locally + String webSocketHost = "127.0.0.1:8080"; + String webSocketProtocolPrefix = WEBSOCKET_PROTOCOL_PREFIX; + + // On App Engine flexible environment, use wss://project-id.appspot.com/echo + if (System.getenv(GAE_INSTANCE_VAR) != null) { + String projectId = System.getenv(GOOGLE_CLOUD_PROJECT_ENV_VAR); + if (projectId != null) { + String serviceName = System.getenv(GAE_SERVICE_ENV_VAR); + webSocketHost = serviceName + "-dot-" + projectId + APPENGINE_HOST_SUFFIX; + } + Preconditions.checkNotNull(webSocketHost); + // Use wss:// instead of ws:// protocol when connecting over https + webSocketProtocolPrefix = WEBSOCKET_HTTPS_PROTOCOL_PREFIX; + } + return webSocketProtocolPrefix + webSocketHost + ENDPOINT; + } +} diff --git a/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/ServerSocket.java b/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/ServerSocket.java new file mode 100644 index 00000000000..07cfafe0f3e --- /dev/null +++ b/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/ServerSocket.java @@ -0,0 +1,62 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.flexible.websocket.jettynative; + +import java.io.IOException; +import java.util.logging.Logger; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; + +/* + * Server-side WebSocket : echoes received message back to client. + */ +@WebSocket(maxTextMessageSize = 64 * 1024) +public class ServerSocket { + private Logger logger = Logger.getLogger(SendServlet.class.getName()); + private Session session; + + @OnWebSocketConnect + public void onWebSocketConnect(Session session) { + this.session = session; + logger.fine("Socket Connected: " + session); + } + + @OnWebSocketMessage + public void onWebSocketText(String message) { + logger.fine("Received message: " + message); + try { + // echo message back to client + this.session.getRemote().sendString(message); + } catch (IOException e) { + logger.severe("Error echoing message: " + e.getMessage()); + } + } + + @OnWebSocketClose + public void onWebSocketClose(int statusCode, String reason) { + logger.fine("Socket Closed: [" + statusCode + "] " + reason); + } + + @OnWebSocketError + public void onWebSocketError(Throwable cause) { + logger.severe("Websocket error : " + cause.getMessage()); + } +} diff --git a/flexible/java-25/websocket-jetty/src/main/webapp/WEB-INF/index.jsp b/flexible/java-25/websocket-jetty/src/main/webapp/WEB-INF/index.jsp new file mode 100644 index 00000000000..8730c529584 --- /dev/null +++ b/flexible/java-25/websocket-jetty/src/main/webapp/WEB-INF/index.jsp @@ -0,0 +1,33 @@ + + +<%@ page import="com.example.flexible.websocket.jettynative.ClientSocket" %> + + + + + Send a message + +

Publish a message

+
+ + + +
+

Last received messages

+ <%= ClientSocket.getReceivedMessages() %> + + diff --git a/flexible/java-25/websocket-jetty/src/main/webapp/WEB-INF/jetty-web.xml b/flexible/java-25/websocket-jetty/src/main/webapp/WEB-INF/jetty-web.xml new file mode 100644 index 00000000000..475971850a9 --- /dev/null +++ b/flexible/java-25/websocket-jetty/src/main/webapp/WEB-INF/jetty-web.xml @@ -0,0 +1,24 @@ + + + + + + true + + -org.eclipse.jetty. + + diff --git a/flexible/java-25/websocket-jetty/src/main/webapp/WEB-INF/js_client.jsp b/flexible/java-25/websocket-jetty/src/main/webapp/WEB-INF/js_client.jsp new file mode 100644 index 00000000000..ef9d7051928 --- /dev/null +++ b/flexible/java-25/websocket-jetty/src/main/webapp/WEB-INF/js_client.jsp @@ -0,0 +1,85 @@ + + + +<%@ page import="com.example.flexible.websocket.jettynative.SendServlet" %> + + Google App Engine Flexible Environment - WebSocket Echo + + + +

Echo demo

+
+ + +
+ +
+

Messages:

+
    +
    + +
    +

    Status:

    +
      +
      + + + + diff --git a/flexible/java-25/websocket-jetty/src/test/java/com/example/flexible/websocket/jettynative/ClientSocketTest.java b/flexible/java-25/websocket-jetty/src/test/java/com/example/flexible/websocket/jettynative/ClientSocketTest.java new file mode 100644 index 00000000000..6b8636852ef --- /dev/null +++ b/flexible/java-25/websocket-jetty/src/test/java/com/example/flexible/websocket/jettynative/ClientSocketTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.flexible.websocket.jettynative; + +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; + +public class ClientSocketTest { + ClientSocket socket; + + @Before + public void setUp() { + socket = new ClientSocket(); + } + + @Test + public void testOnMessage() { + assertEquals(ClientSocket.getReceivedMessages().size(), 0); + socket.onMessage("test"); + assertEquals(ClientSocket.getReceivedMessages().size(), 1); + } +} diff --git a/flexible/java-25/websocket-jetty/src/test/java/com/example/flexible/websocket/jettynative/SendServletTest.java b/flexible/java-25/websocket-jetty/src/test/java/com/example/flexible/websocket/jettynative/SendServletTest.java new file mode 100644 index 00000000000..37916cb6a37 --- /dev/null +++ b/flexible/java-25/websocket-jetty/src/test/java/com/example/flexible/websocket/jettynative/SendServletTest.java @@ -0,0 +1,28 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.flexible.websocket.jettynative; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class SendServletTest { + @Test + public void testGetWebSocketAddress() { + assertTrue(SendServlet.getWebSocketAddress().contains("/echo")); + } +} diff --git a/flexible/java-8/cloudstorage/README.md b/flexible/java-8/cloudstorage/README.md index 986d2d6621a..4c821531bf5 100644 --- a/flexible/java-8/cloudstorage/README.md +++ b/flexible/java-8/cloudstorage/README.md @@ -12,12 +12,12 @@ Before you can run or deploy the sample, you will need to do the following: 2. Create a Cloud Storage Bucket. You can do this with the [Google Cloud SDK](https://cloud.google.com/sdk) using the following command: ``` - $ gsutil mb gs://[your-bucket-name] + $ gcloud storage buckets create gs://[your-bucket-name] ``` 3. Set the default ACL on your bucket to public read in order to serve files directly from Cloud Storage. You can do this with the [Google Cloud SDK](https://cloud.google.com/sdk) using the following command: ``` - $ gsutil defacl set public-read gs://[your-bucket-name] + $ gcloud storage buckets update gs://[your-bucket-name] --predefined-default-object-acl=publicRead ``` 4. Update the bucket name in `src/main/appengine/app.yaml`. This makes the bucket name an environment variable in deployment. You still need to set the environment variable when running locally, as shown below. diff --git a/functions/http/parse-xml/pom.xml b/functions/http/parse-xml/pom.xml index e6069f0dd4a..121a6f17c1c 100644 --- a/functions/http/parse-xml/pom.xml +++ b/functions/http/parse-xml/pom.xml @@ -94,7 +94,7 @@ function-maven-plugin 0.11.0 - functions.ParseContentType + functions.ParseXml diff --git a/functions/http/parse-xml/src/main/java/functions/ParseXml.java b/functions/http/parse-xml/src/main/java/functions/ParseXml.java index a5980cadd14..8e876afbe23 100644 --- a/functions/http/parse-xml/src/main/java/functions/ParseXml.java +++ b/functions/http/parse-xml/src/main/java/functions/ParseXml.java @@ -32,7 +32,22 @@ import org.xml.sax.SAXException; public class ParseXml implements HttpFunction { - private static DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + private static final DocumentBuilderFactory dbFactory; + + static { + dbFactory = DocumentBuilderFactory.newInstance(); + try { + // Prevent XXE attacks (see https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html) + dbFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + dbFactory.setFeature("http://xml.org/sax/features/external-general-entities", false); + dbFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + dbFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + dbFactory.setXIncludeAware(false); + dbFactory.setExpandEntityReferences(false); + } catch (ParserConfigurationException e) { + throw new RuntimeException(e); + } + } // Parses a HTTP request in XML format // (Responds with a 400 error if the HTTP request isn't valid XML.) diff --git a/functions/imagemagick/src/main/java/functions/eventpojos/GcsEvent.java b/functions/imagemagick/src/main/java/functions/eventpojos/GcsEvent.java index cba9d584246..537a7d23ba3 100644 --- a/functions/imagemagick/src/main/java/functions/eventpojos/GcsEvent.java +++ b/functions/imagemagick/src/main/java/functions/eventpojos/GcsEvent.java @@ -18,7 +18,6 @@ import java.util.Date; -// [START functions_helloworld_gcs_event] public class GcsEvent { // Cloud Functions uses GSON to populate this object. // Field types/names are specified by Cloud Functions @@ -69,4 +68,3 @@ public void setUpdated(Date updated) { this.updated = updated; } } -// [END functions_helloworld_gcs_event] diff --git a/functions/ocr/ocr-process-image/src/main/java/functions/eventpojos/GcsEvent.java b/functions/ocr/ocr-process-image/src/main/java/functions/eventpojos/GcsEvent.java index cba9d584246..537a7d23ba3 100644 --- a/functions/ocr/ocr-process-image/src/main/java/functions/eventpojos/GcsEvent.java +++ b/functions/ocr/ocr-process-image/src/main/java/functions/eventpojos/GcsEvent.java @@ -18,7 +18,6 @@ import java.util.Date; -// [START functions_helloworld_gcs_event] public class GcsEvent { // Cloud Functions uses GSON to populate this object. // Field types/names are specified by Cloud Functions @@ -69,4 +68,3 @@ public void setUpdated(Date updated) { this.updated = updated; } } -// [END functions_helloworld_gcs_event] diff --git a/functions/pubsub/publish-message/pom.xml b/functions/pubsub/publish-message/pom.xml index f8a78c28221..732a7bf91af 100644 --- a/functions/pubsub/publish-message/pom.xml +++ b/functions/pubsub/publish-message/pom.xml @@ -27,12 +27,13 @@ com.google.cloud.samples shared-configuration - 1.2.0 + 1.2.2 - 11 - 11 + + 21 + 21 UTF-8 @@ -116,6 +117,25 @@ false + + org.jacoco + jacoco-maven-plugin + 0.8.14 + + + + prepare-agent + + + + report + test + + report + + + +
      diff --git a/vertexai/snippets/pom.xml b/genai/snippets/pom.xml similarity index 51% rename from vertexai/snippets/pom.xml rename to genai/snippets/pom.xml index b4a5764881b..6a4bdd3e9f5 100644 --- a/vertexai/snippets/pom.xml +++ b/genai/snippets/pom.xml @@ -1,41 +1,37 @@ - 4.0.0 + com.example.genai + genai-snippets jar - com.google.vertexai.gemini - gemini-sample - Google Cloud Vertex AI Gemini Snippets - + Google Gen AI SDK Snippets - com.google.cloud.samples shared-configuration + com.google.cloud.samples 1.2.0 - 11 11 + 11 UTF-8 @@ -46,29 +42,43 @@ com.google.cloud import pom - 26.43.0 + 26.64.0 + + com.google.genai + google-genai + 1.28.0 + com.google.cloud - google-cloud-vertexai + google-cloud-storage + + + com.openai + openai-java + 4.6.1 - - - junit junit + junit + test 4.13.2 + + + org.mockito + mockito-core + 5.19.0 test com.google.truth truth - 1.4.0 + 1.4.4 test - + \ No newline at end of file diff --git a/genai/snippets/resources/640px-Monty_open_door.svg.png b/genai/snippets/resources/640px-Monty_open_door.svg.png new file mode 100644 index 00000000000..90f83375e36 Binary files /dev/null and b/genai/snippets/resources/640px-Monty_open_door.svg.png differ diff --git a/genai/snippets/resources/describe_video_content.mp4 b/genai/snippets/resources/describe_video_content.mp4 new file mode 100644 index 00000000000..93176ae76f3 Binary files /dev/null and b/genai/snippets/resources/describe_video_content.mp4 differ diff --git a/genai/snippets/resources/example-image-eiffel-tower.png b/genai/snippets/resources/example-image-eiffel-tower.png new file mode 100644 index 00000000000..23e2b4eae5c Binary files /dev/null and b/genai/snippets/resources/example-image-eiffel-tower.png differ diff --git a/genai/snippets/resources/latte.jpg b/genai/snippets/resources/latte.jpg new file mode 100644 index 00000000000..e942ca62300 Binary files /dev/null and b/genai/snippets/resources/latte.jpg differ diff --git a/genai/snippets/resources/man.png b/genai/snippets/resources/man.png new file mode 100644 index 00000000000..7cf652e8e6e Binary files /dev/null and b/genai/snippets/resources/man.png differ diff --git a/genai/snippets/resources/output/bounding-boxes-socks.jpg b/genai/snippets/resources/output/bounding-boxes-socks.jpg new file mode 100644 index 00000000000..dbb91b49076 Binary files /dev/null and b/genai/snippets/resources/output/bounding-boxes-socks.jpg differ diff --git a/genai/snippets/resources/output/bw-example-image.png b/genai/snippets/resources/output/bw-example-image.png new file mode 100644 index 00000000000..9a05bbbc35a Binary files /dev/null and b/genai/snippets/resources/output/bw-example-image.png differ diff --git a/genai/snippets/resources/output/dog_newspaper.png b/genai/snippets/resources/output/dog_newspaper.png new file mode 100644 index 00000000000..81af65bb019 Binary files /dev/null and b/genai/snippets/resources/output/dog_newspaper.png differ diff --git a/genai/snippets/resources/output/example-breakfast-meal.png b/genai/snippets/resources/output/example-breakfast-meal.png new file mode 100644 index 00000000000..141b22b72ba Binary files /dev/null and b/genai/snippets/resources/output/example-breakfast-meal.png differ diff --git a/genai/snippets/resources/output/example-cats-01.png b/genai/snippets/resources/output/example-cats-01.png new file mode 100644 index 00000000000..966e9059197 Binary files /dev/null and b/genai/snippets/resources/output/example-cats-01.png differ diff --git a/genai/snippets/resources/output/example-cats-02.png b/genai/snippets/resources/output/example-cats-02.png new file mode 100644 index 00000000000..5c7f47dde45 Binary files /dev/null and b/genai/snippets/resources/output/example-cats-02.png differ diff --git a/genai/snippets/resources/output/example-cats-03.png b/genai/snippets/resources/output/example-cats-03.png new file mode 100644 index 00000000000..1c40f5c0408 Binary files /dev/null and b/genai/snippets/resources/output/example-cats-03.png differ diff --git a/genai/snippets/resources/output/example-image-2.png b/genai/snippets/resources/output/example-image-2.png new file mode 100644 index 00000000000..0e4db30faf1 Binary files /dev/null and b/genai/snippets/resources/output/example-image-2.png differ diff --git a/genai/snippets/resources/output/example-image-4.png b/genai/snippets/resources/output/example-image-4.png new file mode 100644 index 00000000000..3c5c5416f02 Binary files /dev/null and b/genai/snippets/resources/output/example-image-4.png differ diff --git a/genai/snippets/resources/output/example-image-6.png b/genai/snippets/resources/output/example-image-6.png new file mode 100644 index 00000000000..ca9120addbe Binary files /dev/null and b/genai/snippets/resources/output/example-image-6.png differ diff --git a/genai/snippets/resources/output/example-image-8.png b/genai/snippets/resources/output/example-image-8.png new file mode 100644 index 00000000000..c6e6d934333 Binary files /dev/null and b/genai/snippets/resources/output/example-image-8.png differ diff --git a/genai/snippets/resources/output/example-image-eiffel-tower.png b/genai/snippets/resources/output/example-image-eiffel-tower.png new file mode 100644 index 00000000000..449a7f07772 Binary files /dev/null and b/genai/snippets/resources/output/example-image-eiffel-tower.png differ diff --git a/genai/snippets/resources/output/man_in_sweater.png b/genai/snippets/resources/output/man_in_sweater.png new file mode 100644 index 00000000000..b9c639c28c5 Binary files /dev/null and b/genai/snippets/resources/output/man_in_sweater.png differ diff --git a/genai/snippets/resources/output/paella-recipe.md b/genai/snippets/resources/output/paella-recipe.md new file mode 100644 index 00000000000..03666a3c753 --- /dev/null +++ b/genai/snippets/resources/output/paella-recipe.md @@ -0,0 +1,24 @@ +Let's make a delicious and easy paella! + +**Step 1: Sauté Aromatics and Protein** + +In a large paella pan or wide skillet, heat some olive oil. Add your chopped onions, garlic, and any chosen protein (chicken, shrimp, chorizo work well). Sauté until the protein is browned and the aromatics are fragrant. + +![image](example-image-2.png) + + **Step 2: Add Rice and Broth** + +Stir in your paella rice (Bomba or Calasparra are best) and toast it for a minute or two. Then, pour in your hot chicken or vegetable broth, along with a pinch of saffron threads for color and flavor. Bring to a simmer. + +![image](example-image-4.png) + +**Step 3: Simmer and Cook** + +Reduce the heat to low, cover the pan (if you have a lid that fits, otherwise foil works), and let it simmer without stirring for about 15-20 minutes, or until most of the liquid has been absorbed and the rice is tender. +![image](example-image-6.png) + +**Step 4: Rest and Serve** + +Once the rice is cooked, remove the pan from the heat and let it rest, still covered, for 5-10 minutes. This allows the flavors to meld and the rice to finish cooking. Garnish with fresh parsley and lemon wedges, then serve immediately! + +![image](example-image-8.png) \ No newline at end of file diff --git a/genai/snippets/resources/scones.jpg b/genai/snippets/resources/scones.jpg new file mode 100644 index 00000000000..b5ee1b0707b Binary files /dev/null and b/genai/snippets/resources/scones.jpg differ diff --git a/genai/snippets/resources/sweater.jpg b/genai/snippets/resources/sweater.jpg new file mode 100644 index 00000000000..69cc18f921f Binary files /dev/null and b/genai/snippets/resources/sweater.jpg differ diff --git a/genai/snippets/src/main/java/genai/batchprediction/BatchPredictionEmbeddingsWithGcs.java b/genai/snippets/src/main/java/genai/batchprediction/BatchPredictionEmbeddingsWithGcs.java new file mode 100644 index 00000000000..4ca75862257 --- /dev/null +++ b/genai/snippets/src/main/java/genai/batchprediction/BatchPredictionEmbeddingsWithGcs.java @@ -0,0 +1,108 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.batchprediction; + +// [START googlegenaisdk_batchpredict_embeddings_with_gcs] + +import static com.google.genai.types.JobState.Known.JOB_STATE_CANCELLED; +import static com.google.genai.types.JobState.Known.JOB_STATE_FAILED; +import static com.google.genai.types.JobState.Known.JOB_STATE_PAUSED; +import static com.google.genai.types.JobState.Known.JOB_STATE_SUCCEEDED; + +import com.google.genai.Client; +import com.google.genai.types.BatchJob; +import com.google.genai.types.BatchJobDestination; +import com.google.genai.types.BatchJobSource; +import com.google.genai.types.CreateBatchJobConfig; +import com.google.genai.types.GetBatchJobConfig; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.JobState; +import java.util.EnumSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +public class BatchPredictionEmbeddingsWithGcs { + + public static void main(String[] args) throws InterruptedException { + // TODO(developer): Replace these variables before running the sample. + String modelId = "text-embedding-005"; + String outputGcsUri = "gs://your-bucket/your-prefix"; + createBatchJob(modelId, outputGcsUri); + } + + // Creates a batch prediction job with embedding model and Google Cloud Storage. + public static JobState createBatchJob(String modelId, String outputGcsUri) + throws InterruptedException { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("us-central1") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + // See the documentation: + // https://googleapis.github.io/java-genai/javadoc/com/google/genai/Batches.html + BatchJobSource batchJobSource = + BatchJobSource.builder() + // Source link: + // https://storage.cloud.google.com/cloud-samples-data/generative-ai/embeddings/embeddings_input.jsonl + .gcsUri("gs://cloud-samples-data/generative-ai/embeddings/embeddings_input.jsonl") + .format("jsonl") + .build(); + + CreateBatchJobConfig batchJobConfig = + CreateBatchJobConfig.builder() + .displayName("your-display-name") + .dest(BatchJobDestination.builder().gcsUri(outputGcsUri).format("jsonl").build()) + .build(); + + BatchJob batchJob = client.batches.create(modelId, batchJobSource, batchJobConfig); + + String jobName = + batchJob.name().orElseThrow(() -> new IllegalStateException("Missing job name")); + JobState jobState = + batchJob.state().orElseThrow(() -> new IllegalStateException("Missing job state")); + System.out.println("Job name: " + jobName); + System.out.println("Job state: " + jobState); + // Job name: projects/.../locations/.../batchPredictionJobs/6205497615459549184 + // Job state: JOB_STATE_PENDING + + // See the documentation: + // https://googleapis.github.io/java-genai/javadoc/com/google/genai/types/BatchJob.html + Set completedStates = + EnumSet.of(JOB_STATE_SUCCEEDED, JOB_STATE_FAILED, JOB_STATE_CANCELLED, JOB_STATE_PAUSED); + + while (!completedStates.contains(jobState.knownEnum())) { + TimeUnit.SECONDS.sleep(30); + batchJob = client.batches.get(jobName, GetBatchJobConfig.builder().build()); + jobState = + batchJob + .state() + .orElseThrow(() -> new IllegalStateException("Missing job state during polling")); + System.out.println("Job state: " + jobState); + } + // Example response: + // Job state: JOB_STATE_QUEUED + // Job state: JOB_STATE_RUNNING + // ... + // Job state: JOB_STATE_SUCCEEDED + return jobState; + } + } +} +// [END googlegenaisdk_batchpredict_embeddings_with_gcs] diff --git a/genai/snippets/src/main/java/genai/batchprediction/BatchPredictionWithBq.java b/genai/snippets/src/main/java/genai/batchprediction/BatchPredictionWithBq.java new file mode 100644 index 00000000000..678f59d2fbf --- /dev/null +++ b/genai/snippets/src/main/java/genai/batchprediction/BatchPredictionWithBq.java @@ -0,0 +1,110 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.batchprediction; + +// [START googlegenaisdk_batchpredict_with_bq] + +import static com.google.genai.types.JobState.Known.JOB_STATE_CANCELLED; +import static com.google.genai.types.JobState.Known.JOB_STATE_FAILED; +import static com.google.genai.types.JobState.Known.JOB_STATE_PAUSED; +import static com.google.genai.types.JobState.Known.JOB_STATE_SUCCEEDED; + +import com.google.genai.Client; +import com.google.genai.types.BatchJob; +import com.google.genai.types.BatchJobDestination; +import com.google.genai.types.BatchJobSource; +import com.google.genai.types.CreateBatchJobConfig; +import com.google.genai.types.GetBatchJobConfig; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.JobState; +import java.util.EnumSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +public class BatchPredictionWithBq { + + public static void main(String[] args) throws InterruptedException { + // TODO(developer): Replace these variables before running the sample. + // To use a tuned model, set the model param to your tuned model using the following format: + // modelId = "projects/{PROJECT_ID}/locations/{LOCATION}/models/{MODEL_ID} + String modelId = "gemini-2.5-flash"; + String outputUri = "bq://your-project.your_dataset.your_table"; + createBatchJob(modelId, outputUri); + } + + // Creates a batch prediction job with Google BigQuery. + public static JobState createBatchJob(String modelId, String outputUri) + throws InterruptedException { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("us-central1") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + // See the documentation: + // https://googleapis.github.io/java-genai/javadoc/com/google/genai/Batches.html + BatchJobSource batchJobSource = + BatchJobSource.builder() + .bigqueryUri("bq://storage-samples.generative_ai.batch_requests_for_multimodal_input") + .format("bigquery") + .build(); + + CreateBatchJobConfig batchJobConfig = + CreateBatchJobConfig.builder() + .displayName("your-display-name") + .dest(BatchJobDestination.builder().bigqueryUri(outputUri).format("bigquery").build()) + .build(); + + BatchJob batchJob = client.batches.create(modelId, batchJobSource, batchJobConfig); + + String jobName = + batchJob.name().orElseThrow(() -> new IllegalStateException("Missing job name")); + JobState jobState = + batchJob.state().orElseThrow(() -> new IllegalStateException("Missing job state")); + System.out.println("Job name: " + jobName); + System.out.println("Job state: " + jobState); + // Job name: + // projects/.../locations/.../batchPredictionJobs/3189981423167602688 + // Job state: JOB_STATE_PENDING + + // See the documentation: + // https://googleapis.github.io/java-genai/javadoc/com/google/genai/types/BatchJob.html + Set completedStates = + EnumSet.of(JOB_STATE_SUCCEEDED, JOB_STATE_FAILED, JOB_STATE_CANCELLED, JOB_STATE_PAUSED); + + while (!completedStates.contains(jobState.knownEnum())) { + TimeUnit.SECONDS.sleep(30); + batchJob = client.batches.get(jobName, GetBatchJobConfig.builder().build()); + jobState = + batchJob + .state() + .orElseThrow(() -> new IllegalStateException("Missing job state during polling")); + System.out.println("Job state: " + jobState); + } + // Example response: + // Job state: JOB_STATE_QUEUED + // Job state: JOB_STATE_RUNNING + // Job state: JOB_STATE_RUNNING + // ... + // Job state: JOB_STATE_SUCCEEDED + return jobState; + } + } +} +// [END googlegenaisdk_batchpredict_with_bq] diff --git a/genai/snippets/src/main/java/genai/batchprediction/BatchPredictionWithGcs.java b/genai/snippets/src/main/java/genai/batchprediction/BatchPredictionWithGcs.java new file mode 100644 index 00000000000..ebcffde5a6c --- /dev/null +++ b/genai/snippets/src/main/java/genai/batchprediction/BatchPredictionWithGcs.java @@ -0,0 +1,111 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.batchprediction; + +// [START googlegenaisdk_batchpredict_with_gcs] + +import static com.google.genai.types.JobState.Known.JOB_STATE_CANCELLED; +import static com.google.genai.types.JobState.Known.JOB_STATE_FAILED; +import static com.google.genai.types.JobState.Known.JOB_STATE_PAUSED; +import static com.google.genai.types.JobState.Known.JOB_STATE_SUCCEEDED; + +import com.google.genai.Client; +import com.google.genai.types.BatchJob; +import com.google.genai.types.BatchJobDestination; +import com.google.genai.types.BatchJobSource; +import com.google.genai.types.CreateBatchJobConfig; +import com.google.genai.types.GetBatchJobConfig; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.JobState; +import java.util.EnumSet; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +public class BatchPredictionWithGcs { + + public static void main(String[] args) throws InterruptedException { + // TODO(developer): Replace these variables before running the sample. + // To use a tuned model, set the model param to your tuned model using the following format: + // modelId = "projects/{PROJECT_ID}/locations/{LOCATION}/models/{MODEL_ID} + String modelId = "gemini-2.5-flash"; + String outputGcsUri = "gs://your-bucket/your-prefix"; + createBatchJob(modelId, outputGcsUri); + } + + // Creates a batch prediction job with Google Cloud Storage. + public static JobState createBatchJob(String modelId, String outputGcsUri) + throws InterruptedException { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + // See the documentation: + // https://googleapis.github.io/java-genai/javadoc/com/google/genai/Batches.html + BatchJobSource batchJobSource = + BatchJobSource.builder() + // Source link: + // https://storage.cloud.google.com/cloud-samples-data/batch/prompt_for_batch_gemini_predict.jsonl + .gcsUri("gs://cloud-samples-data/batch/prompt_for_batch_gemini_predict.jsonl") + .format("jsonl") + .build(); + + CreateBatchJobConfig batchJobConfig = + CreateBatchJobConfig.builder() + .displayName("your-display-name") + .dest(BatchJobDestination.builder().gcsUri(outputGcsUri).format("jsonl").build()) + .build(); + + BatchJob batchJob = client.batches.create(modelId, batchJobSource, batchJobConfig); + + String jobName = + batchJob.name().orElseThrow(() -> new IllegalStateException("Missing job name")); + JobState jobState = + batchJob.state().orElseThrow(() -> new IllegalStateException("Missing job state")); + System.out.println("Job name: " + jobName); + System.out.println("Job state: " + jobState); + // Job name: projects/.../locations/.../batchPredictionJobs/6205497615459549184 + // Job state: JOB_STATE_PENDING + + // See the documentation: + // https://googleapis.github.io/java-genai/javadoc/com/google/genai/types/BatchJob.html + Set completedStates = + EnumSet.of(JOB_STATE_SUCCEEDED, JOB_STATE_FAILED, JOB_STATE_CANCELLED, JOB_STATE_PAUSED); + + while (!completedStates.contains(jobState.knownEnum())) { + TimeUnit.SECONDS.sleep(30); + batchJob = client.batches.get(jobName, GetBatchJobConfig.builder().build()); + jobState = + batchJob + .state() + .orElseThrow(() -> new IllegalStateException("Missing job state during polling")); + System.out.println("Job state: " + jobState); + } + // Example response: + // Job state: JOB_STATE_QUEUED + // Job state: JOB_STATE_RUNNING + // Job state: JOB_STATE_RUNNING + // ... + // Job state: JOB_STATE_SUCCEEDED + return jobState; + } + } +} +// [END googlegenaisdk_batchpredict_with_gcs] diff --git a/genai/snippets/src/main/java/genai/boundingbox/BoundingBoxWithTxtImg.java b/genai/snippets/src/main/java/genai/boundingbox/BoundingBoxWithTxtImg.java new file mode 100644 index 00000000000..e7b13c11507 --- /dev/null +++ b/genai/snippets/src/main/java/genai/boundingbox/BoundingBoxWithTxtImg.java @@ -0,0 +1,221 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.boundingbox; + +// [START googlegenaisdk_boundingbox_with_txt_img] + +import static com.google.genai.types.Type.Known.ARRAY; +import static com.google.genai.types.Type.Known.INTEGER; +import static com.google.genai.types.Type.Known.OBJECT; +import static com.google.genai.types.Type.Known.STRING; + +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HarmBlockThreshold; +import com.google.genai.types.HarmCategory; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Part; +import com.google.genai.types.SafetySetting; +import com.google.genai.types.Schema; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Type; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import javax.imageio.ImageIO; + +public class BoundingBoxWithTxtImg { + + public static class BoundingBox { + List box2d; + String label; + + public List getBox2d() { + return box2d; + } + + public String getLabel() { + return label; + } + } + + // Plot bounding boxes on an image and save it to a file. + public static void plotBoundingBoxes(String imageUrl, List boundingBoxes) + throws IOException { + URL url = new URL(imageUrl); + BufferedImage image = ImageIO.read(url); + + int width = image.getWidth(); + int height = image.getHeight(); + + Graphics2D graphics2D = image.createGraphics(); + graphics2D.setStroke(new BasicStroke(4)); + graphics2D.setFont(new Font("Arial", Font.PLAIN, 18)); + + // Define a list of colors to cycle through. + List colors = + Arrays.asList( + Color.RED, + Color.GREEN, + Color.BLUE, + Color.YELLOW, + Color.CYAN, + Color.MAGENTA, + Color.ORANGE); + + for (int i = 0; i < boundingBoxes.size(); i++) { + BoundingBox boundingBox = boundingBoxes.get(i); + List box2d = boundingBox.getBox2d(); + // Prevents IndexOutOfBoundsException + if (box2d == null || box2d.size() < 4) { + continue; + } + + // Scale normalized coordinates (0-1000) to image dimensions. + int topY = (int) (box2d.get(0) / 1000.0 * height); + int leftX = (int) (box2d.get(1) / 1000.0 * width); + int bottomY = (int) (box2d.get(2) / 1000.0 * height); + int rightX = (int) (box2d.get(3) / 1000.0 * width); + + Color color = colors.get(i % colors.size()); + graphics2D.setColor(color); + + // Draw the rectangle. + graphics2D.drawRect(leftX, topY, rightX - leftX, bottomY - topY); + + // Draw the label text. + if (boundingBox.getLabel() != null && !boundingBox.getLabel().isEmpty()) { + graphics2D.drawString(boundingBox.getLabel(), leftX + 8, topY + 20); + } + } + graphics2D.dispose(); + + // Write the image to a file. + String outputFilePath = "resources/output/bounding-boxes-socks.jpg"; + ImageIO.write(image, "jpg", new File(outputFilePath)); + System.out.println("Successfully saved image to: " + outputFilePath); + } + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String model = "gemini-2.5-flash"; + generateContent(model); + } + + // Shows how to send a multimodal prompt to the model and get a structured JSON response + // containing bounding box data, and then uses that data to draw the boxes on the original + // image, saving it to a new file. + public static String generateContent(String modelId) throws IOException { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .vertexAI(true) + .build()) { + + String systemInstruction = + "Return bounding boxes as an array with labels.\n" + + " Never return masks. Limit to 25 objects.\n" + + " If an object is present multiple times, give each object a unique label\n" + + " according to its distinct characteristics (colors, size, position, etc..)."; + + // Define the response schema. + Schema responseSchema = + Schema.builder() + .type(ARRAY) + .items( + Schema.builder() + .type(OBJECT) + .properties( + Map.of( + "box2d", + Schema.builder() + .type(ARRAY) + .items(Schema.builder().type(INTEGER).build()) + .build(), + "label", + Schema.builder().type(STRING).build())) + .required("box2d", "label") + .build()) + .build(); + + // Define the GenerateContentConfig and set the response schema. + GenerateContentConfig contentConfig = + GenerateContentConfig.builder() + .systemInstruction(Content.fromParts(Part.fromText(systemInstruction))) + .temperature(0.5F) + .safetySettings( + SafetySetting.builder() + .category(HarmCategory.Known.HARM_CATEGORY_DANGEROUS_CONTENT) + .threshold(HarmBlockThreshold.Known.BLOCK_ONLY_HIGH) + .build()) + .responseMimeType("application/json") + .responseSchema(responseSchema) + .build(); + + String imageUri = "https://storage.googleapis.com/generativeai-downloads/images/socks.jpg"; + URL url = new URL(imageUri); + + try (InputStream inputStream = url.openStream()) { + byte[] imageBytes = inputStream.readAllBytes(); + + String prompt = + "Output the positions of the socks with a face." + + " Label according to position in the image"; + + GenerateContentResponse response = + client.models.generateContent( + modelId, + Content.fromParts(Part.fromBytes(imageBytes, "image/jpeg"), Part.fromText(prompt)), + contentConfig); + + System.out.println(response.text()); + // Example response: + // [ + // {"box2d": [24, 24, 521, 526], "label": "top left light blue cat face sock"}, + // {"box2d": [238, 627, 649, 863], "label": "top right light blue cat face sock"} + // ] + + // Use Gson to parse the JSON string into a list of BoundingBox objects. + Gson gson = new Gson(); + Type boundingBoxListType = new TypeToken>() {}.getType(); + List boundingBoxes = gson.fromJson(response.text(), boundingBoxListType); + + // Plot the bounding boxes on the image. + if (boundingBoxes != null) { + plotBoundingBoxes(imageUri, boundingBoxes); + } + + return response.text(); + } + } + } +} +// [END googlegenaisdk_boundingbox_with_txt_img] diff --git a/genai/snippets/src/main/java/genai/contentcache/ContentCacheCreateWithTextGcsPdf.java b/genai/snippets/src/main/java/genai/contentcache/ContentCacheCreateWithTextGcsPdf.java new file mode 100644 index 00000000000..d229bc3d623 --- /dev/null +++ b/genai/snippets/src/main/java/genai/contentcache/ContentCacheCreateWithTextGcsPdf.java @@ -0,0 +1,86 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.contentcache; + +// [START googlegenaisdk_contentcache_create_with_txt_gcs_pdf] + +import com.google.genai.Client; +import com.google.genai.types.CachedContent; +import com.google.genai.types.Content; +import com.google.genai.types.CreateCachedContentConfig; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Part; +import java.time.Duration; +import java.util.Optional; + +public class ContentCacheCreateWithTextGcsPdf { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + contentCacheCreateWithTextGcsPdf(modelId); + } + + // Creates a cached content using text and gcs pdfs files + public static Optional contentCacheCreateWithTextGcsPdf(String modelId) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + // Set the system instruction + Content systemInstruction = + Content.fromParts( + Part.fromText( + "You are an expert researcher. You always stick to the facts" + + " in the sources provided, and never make up new facts.\n" + + "Now look at these research papers, and answer the following questions.")); + + // Set pdf files + Content contents = + Content.fromParts( + Part.fromUri( + "gs://cloud-samples-data/generative-ai/pdf/2312.11805v3.pdf", "application/pdf"), + Part.fromUri( + "gs://cloud-samples-data/generative-ai/pdf/2403.05530.pdf", "application/pdf")); + + // Configuration for cached content using pdfs files and text + CreateCachedContentConfig config = + CreateCachedContentConfig.builder() + .systemInstruction(systemInstruction) + .contents(contents) + .displayName("example-cache") + .ttl(Duration.ofSeconds(86400)) + .build(); + + CachedContent cachedContent = client.caches.create(modelId, config); + cachedContent.name().ifPresent(System.out::println); + cachedContent.usageMetadata().ifPresent(System.out::println); + // Example response: + // projects/111111111111/locations/global/cachedContents/1111111111111111111 + // CachedContentUsageMetadata{audioDurationSeconds=Optional.empty, imageCount=Optional[167], + // textCount=Optional[153], totalTokenCount=Optional[43125], + // videoDurationSeconds=Optional.empty} + return cachedContent.name(); + } + } +} +// [END googlegenaisdk_contentcache_create_with_txt_gcs_pdf] diff --git a/genai/snippets/src/main/java/genai/contentcache/ContentCacheDelete.java b/genai/snippets/src/main/java/genai/contentcache/ContentCacheDelete.java new file mode 100644 index 00000000000..654da06e6a1 --- /dev/null +++ b/genai/snippets/src/main/java/genai/contentcache/ContentCacheDelete.java @@ -0,0 +1,52 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.contentcache; + +// [START googlegenaisdk_contentcache_delete] + +import com.google.genai.Client; +import com.google.genai.types.HttpOptions; + +public class ContentCacheDelete { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + // E.g cacheName = "projects/111111111111/locations/global/cachedContents/1111111111111111111" + String cacheName = "your-cache-name"; + contentCacheDelete(cacheName); + } + + // Deletes the cache using the specified cache name + public static void contentCacheDelete(String cacheName) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + client.caches.delete(cacheName, null); + System.out.println("Deleted cache: " + cacheName); + // Example response + // Deleted cache: projects/111111111111/locations/global/cachedContents/1111111111111111111 + + } + } +} +// [END googlegenaisdk_contentcache_delete] diff --git a/genai/snippets/src/main/java/genai/contentcache/ContentCacheList.java b/genai/snippets/src/main/java/genai/contentcache/ContentCacheList.java new file mode 100644 index 00000000000..b724bd520d5 --- /dev/null +++ b/genai/snippets/src/main/java/genai/contentcache/ContentCacheList.java @@ -0,0 +1,59 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.contentcache; + +// [START googlegenaisdk_contentcache_list] + +import com.google.genai.Client; +import com.google.genai.types.CachedContent; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.ListCachedContentsConfig; + +public class ContentCacheList { + + public static void main(String[] args) { + contentCacheList(); + } + + // Lists all cached contents + public static void contentCacheList() { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + for (CachedContent content : client.caches.list(ListCachedContentsConfig.builder().build())) { + content.name().ifPresent(name -> System.out.println("Name: " + name)); + content.model().ifPresent(model -> System.out.println("Model: " + model)); + content.updateTime().ifPresent(time -> System.out.println("Last updated at: " + time)); + content.expireTime().ifPresent(time -> System.out.println("Expires at: " + time)); + } + // Example response: + // Name: projects/111111111111/locations/global/cachedContents/1111111111111111111 + // Model: + // projects/111111111111/locations/global/publishers/google/models/gemini-2.5-flash + // Last updated at: 2025-07-28T21:54:19.125825Z + // Expires at: 2025-08-04T21:54:18.328233500Z + // ... + } + } +} +// [END googlegenaisdk_contentcache_list] diff --git a/genai/snippets/src/main/java/genai/contentcache/ContentCacheUpdate.java b/genai/snippets/src/main/java/genai/contentcache/ContentCacheUpdate.java new file mode 100644 index 00000000000..10319a70ed4 --- /dev/null +++ b/genai/snippets/src/main/java/genai/contentcache/ContentCacheUpdate.java @@ -0,0 +1,85 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.contentcache; + +// [START googlegenaisdk_contentcache_update] + +import com.google.genai.Client; +import com.google.genai.types.CachedContent; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.UpdateCachedContentConfig; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +public class ContentCacheUpdate { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + // E.g cacheName = "projects/111111111111/locations/global/cachedContents/1111111111111111111" + String cacheName = "your-cache-name"; + contentCacheUpdate(cacheName); + } + + // Updates the cache using the specified cache resource name + public static void contentCacheUpdate(String cacheName) { + + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + // Get info of the cached content + CachedContent cachedContent = client.caches.get(cacheName, null); + + cachedContent.expireTime() + .ifPresent(expireTime -> System.out.println("Expire time: " + expireTime)); + // Example response + // Expire time: 2025-07-29T23:39:49.227291Z + + // Update expire time using TTL + CachedContent updatedCachedContent = + client.caches.update( + cacheName, + UpdateCachedContentConfig.builder().ttl(Duration.ofSeconds(36000)).build()); + + updatedCachedContent.expireTime() + .ifPresent(expireTime -> System.out.println("Expire time after update: " + expireTime)); + // Example response + // Expire time after update: 2025-07-30T08:40:33.537205Z + + // Update expire time using specific time stamp + Instant nextWeek = Instant.now().plus(7, ChronoUnit.DAYS); + updatedCachedContent = + client.caches.update( + cacheName, UpdateCachedContentConfig.builder().expireTime(nextWeek).build()); + + updatedCachedContent + .expireTime() + .ifPresent(expireTime -> System.out.println("Expire time after update: " + expireTime)); + // Example response + // Expire time after update: 2025-08-05T22:40:33.713988900Z + + System.out.println("Updated cache: " + cacheName); + } + } +} +// [END googlegenaisdk_contentcache_update] diff --git a/genai/snippets/src/main/java/genai/contentcache/ContentCacheUseWithText.java b/genai/snippets/src/main/java/genai/contentcache/ContentCacheUseWithText.java new file mode 100644 index 00000000000..132dc194e82 --- /dev/null +++ b/genai/snippets/src/main/java/genai/contentcache/ContentCacheUseWithText.java @@ -0,0 +1,62 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.contentcache; + +// [START googlegenaisdk_contentcache_use_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; + +public class ContentCacheUseWithText { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + // E.g cacheName = "projects/111111111111/locations/global/cachedContents/1111111111111111111" + String cacheName = "your-cache-name"; + contentCacheUseWithText(modelId, cacheName); + } + + // Shows how to generate text using cached content + public static String contentCacheUseWithText(String modelId, String cacheName) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + GenerateContentResponse response = + client.models.generateContent( + modelId, + "Summarize the pdfs", + GenerateContentConfig.builder().cachedContent(cacheName).build()); + + System.out.println(response.text()); + // Example response + // The Gemini family of multimodal models from Google DeepMind demonstrates remarkable + // capabilities across various + // modalities, including image, audio, video, and text.... + return response.text(); + } + } +} +// [END googlegenaisdk_contentcache_use_with_txt] diff --git a/genai/snippets/src/main/java/genai/controlledgeneration/ControlledGenerationWithClassSchema.java b/genai/snippets/src/main/java/genai/controlledgeneration/ControlledGenerationWithClassSchema.java new file mode 100644 index 00000000000..0e71455fdfa --- /dev/null +++ b/genai/snippets/src/main/java/genai/controlledgeneration/ControlledGenerationWithClassSchema.java @@ -0,0 +1,123 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.controlledgeneration; + +// [START googlegenaisdk_ctrlgen_with_class_schema] + +import com.google.genai.Client; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Schema; +import com.google.genai.types.Type; +import com.google.gson.Gson; +import java.util.List; +import java.util.Map; + +public class ControlledGenerationWithClassSchema { + + private static final Gson gson = new Gson(); + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String contents = "List a few popular cookie recipes."; + String modelId = "gemini-2.5-flash"; + generateContent(modelId, contents); + } + + // Generates content with a class response schema + public static String generateContent(String modelId, String contents) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + // Schema for a single Recipe object + Schema recipeSchema = + Schema.builder() + .type(Type.Known.OBJECT) + .properties( + Map.of( + "recipe_name", + Schema.builder().type(Type.Known.STRING).build(), + "ingredients", + Schema.builder() + .type(Type.Known.ARRAY) + .items(Schema.builder().type(Type.Known.STRING).build()) + .build())) + .build(); + + // Schema defining response as an array of Recipe + Schema responseSchema = Schema.builder().type(Type.Known.ARRAY).items(recipeSchema).build(); + + GenerateContentConfig config = + GenerateContentConfig.builder() + .responseSchema(responseSchema) + .responseMimeType("application/json") + .build(); + + GenerateContentResponse response = client.models.generateContent(modelId, contents, config); + + System.out.println("Raw JSON:\n" + response.text()); + + // Parse JSON into typed objects + Recipe[] parsed = gson.fromJson(response.text(), Recipe[].class); + + System.out.println("\nParsed objects:"); + for (Recipe r : parsed) { + System.out.println(r); + } + // Example response: + // [ + // { + // "recipe_name": "Classic Chocolate Chip Cookies", + // "ingredients": [ + // "2 1/4 cups all-purpose flour", + // "1 teaspoon baking soda", + // "1 teaspoon salt", + // "1 cup (2 sticks) unsalted butter, softened", + // "3/4 cup granulated sugar", + // "3/4 cup packed brown sugar", + // "1 teaspoon vanilla extract", + // "2 large eggs", + // "2 cups chocolate chips" + // ] + // }, + // ... + // ] + return response.text(); + } + } + + public static class Recipe { + @com.google.gson.annotations.SerializedName("recipe_name") + public String recipeName; + + public List ingredients; + + @Override + public String toString() { + return "Recipe{name='" + recipeName + "', ingredients=" + ingredients + "}"; + } + } +} + +// [END googlegenaisdk_ctrlgen_with_class_schema] diff --git a/genai/snippets/src/main/java/genai/controlledgeneration/ControlledGenerationWithEnumClassSchema.java b/genai/snippets/src/main/java/genai/controlledgeneration/ControlledGenerationWithEnumClassSchema.java new file mode 100644 index 00000000000..72dd2abf726 --- /dev/null +++ b/genai/snippets/src/main/java/genai/controlledgeneration/ControlledGenerationWithEnumClassSchema.java @@ -0,0 +1,96 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.controlledgeneration; + +// [START googlegenaisdk_ctrlgen_with_enum_class_schema] + +import com.google.genai.Client; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Schema; +import com.google.genai.types.Type; +import java.util.List; + +public class ControlledGenerationWithEnumClassSchema { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + String prompt = "What type of instrument is a guitar?"; + generateContent(modelId, prompt); + } + + // Generates content with an enum class response schema + public static String generateContent(String modelId, String contents) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + // Build schema using enum values + Schema responseSchema = + Schema.builder() + .type(Type.Known.STRING) + .enum_( + List.of( + InstrumentClass.PERCUSSION.getValue(), + InstrumentClass.STRING.getValue(), + InstrumentClass.WOODWIND.getValue(), + InstrumentClass.BRASS.getValue(), + InstrumentClass.KEYBOARD.getValue())) + .build(); + + GenerateContentConfig config = + GenerateContentConfig.builder() + .responseMimeType("text/x.enum") + .responseSchema(responseSchema) + .build(); + + GenerateContentResponse response = client.models.generateContent(modelId, contents, config); + + System.out.println(response.text()); + // Example response: + // String + return response.text(); + } + } + + // Enum mirroring the Python sample + public enum InstrumentClass { + PERCUSSION("Percussion"), + STRING("String"), + WOODWIND("Woodwind"), + BRASS("Brass"), + KEYBOARD("Keyboard"); + + private final String value; + + InstrumentClass(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} +// [END googlegenaisdk_ctrlgen_with_enum_class_schema] diff --git a/genai/snippets/src/main/java/genai/controlledgeneration/ControlledGenerationWithEnumSchema.java b/genai/snippets/src/main/java/genai/controlledgeneration/ControlledGenerationWithEnumSchema.java new file mode 100644 index 00000000000..e1574086bee --- /dev/null +++ b/genai/snippets/src/main/java/genai/controlledgeneration/ControlledGenerationWithEnumSchema.java @@ -0,0 +1,71 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.controlledgeneration; + +// [START googlegenaisdk_ctrlgen_with_enum_schema] + +import com.google.genai.Client; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Schema; +import com.google.genai.types.Type; +import java.util.List; + +public class ControlledGenerationWithEnumSchema { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String contents = "What type of instrument is an oboe?"; + String modelId = "gemini-2.5-flash"; + generateContent(modelId, contents); + } + + // Generates content with an enum response schema + public static String generateContent(String modelId, String contents) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + // Define the response schema with an enum. + Schema responseSchema = + Schema.builder() + .type(Type.Known.STRING) + .enum_(List.of("Percussion", "String", "Woodwind", "Brass", "Keyboard")) + .build(); + + GenerateContentConfig config = + GenerateContentConfig.builder() + .responseMimeType("text/x.enum") + .responseSchema(responseSchema) + .build(); + + GenerateContentResponse response = client.models.generateContent(modelId, contents, config); + + System.out.print(response.text()); + // Example response: + // Woodwind + return response.text(); + } + } +} +// [END googlegenaisdk_ctrlgen_with_enum_schema] diff --git a/genai/snippets/src/main/java/genai/controlledgeneration/ControlledGenerationWithNestedSchema.java b/genai/snippets/src/main/java/genai/controlledgeneration/ControlledGenerationWithNestedSchema.java new file mode 100644 index 00000000000..95b46dd9c32 --- /dev/null +++ b/genai/snippets/src/main/java/genai/controlledgeneration/ControlledGenerationWithNestedSchema.java @@ -0,0 +1,126 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.controlledgeneration; + +// [START googlegenaisdk_ctrlgen_with_nested_class_schema] + +import com.google.genai.Client; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Schema; +import com.google.genai.types.Type; +import com.google.gson.Gson; +import java.util.List; +import java.util.Map; + +public class ControlledGenerationWithNestedSchema { + + private static final Gson gson = new Gson(); + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String contents = "List about 10 home-baked cookies and give them grades based on tastiness."; + String modelId = "gemini-2.5-flash"; + generateContent(modelId, contents); + } + + // Generates content with a nested response schema + public static String generateContent(String modelId, String contents) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + // Enum schema for Grade + Schema gradeSchema = + Schema.builder() + .type(Type.Known.STRING) + .enum_(List.of("a+", "a", "b", "c", "d", "f")) + .build(); + + // Schema for Recipe object + Schema recipeSchema = + Schema.builder() + .type(Type.Known.OBJECT) + .properties( + Map.of( + "recipe_name", + Schema.builder().type(Type.Known.STRING).build(), + "rating", + gradeSchema)) + .build(); + + // Response is list of Recipe + Schema responseSchema = Schema.builder().type(Type.Known.ARRAY).items(recipeSchema).build(); + + GenerateContentConfig config = + GenerateContentConfig.builder() + .responseSchema(responseSchema) + .responseMimeType("application/json") + .build(); + + GenerateContentResponse response = client.models.generateContent(modelId, contents, config); + + System.out.println("Raw JSON:\n" + response.text()); + + // Deserialize to typed objects + Recipe[] parsed = gson.fromJson(response.text(), Recipe[].class); + + System.out.println("\nParsed objects:"); + for (Recipe r : parsed) { + System.out.println(r); + } + // Example response: + // [{"rating": "a+", "recipe_name": "Classic Chocolate Chip Cookies"}, ...] + return response.text(); + } + } + + public enum Grade { + @com.google.gson.annotations.SerializedName("a+") + A_PLUS, + @com.google.gson.annotations.SerializedName("a") + A, + @com.google.gson.annotations.SerializedName("b") + B, + @com.google.gson.annotations.SerializedName("c") + C, + @com.google.gson.annotations.SerializedName("d") + D, + @com.google.gson.annotations.SerializedName("f") + F + } + + public static class Recipe { + @com.google.gson.annotations.SerializedName("recipe_name") + public String recipeName; + + public Grade rating; + + @Override + public String toString() { + return "Recipe{name='" + recipeName + "', rating=" + rating + "}"; + } + } +} + +// [END googlegenaisdk_ctrlgen_with_nested_class_schema] diff --git a/genai/snippets/src/main/java/genai/controlledgeneration/ControlledGenerationWithNullableSchema.java b/genai/snippets/src/main/java/genai/controlledgeneration/ControlledGenerationWithNullableSchema.java new file mode 100644 index 00000000000..d9fbc5007e0 --- /dev/null +++ b/genai/snippets/src/main/java/genai/controlledgeneration/ControlledGenerationWithNullableSchema.java @@ -0,0 +1,120 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.controlledgeneration; + +// [START googlegenaisdk_ctrlgen_with_nullable_schema] + +import com.google.genai.Client; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Schema; +import com.google.genai.types.Type; +import java.util.List; +import java.util.Map; + +public class ControlledGenerationWithNullableSchema { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + + String prompt = + "The week ahead brings a mix of weather conditions.\n" + + "Sunday is expected to be sunny with a temperature " + + "of 77°F and a humidity level of 50%. " + + "Winds will be light at around 10 km/h.\n" + + "Monday will see partly cloudy skies with " + + "a slightly cooler temperature of 72°F and the winds " + + "will pick up slightly to around 15 km/h.\n" + + "Tuesday brings rain showers, with temperatures dropping " + + "to 64°F and humidity rising to 70%.\n" + + "Wednesday may see thunderstorms, with a temperature of 68°F.\n" + + "Thursday will be cloudy with a temperature of 66°F and moderate humidity at 60%.\n" + + "Friday returns to partly cloudy conditions, with " + + "a temperature of 73°F and the Winds will be " + + "light at 12 km/h.\n" + + "Finally, Saturday rounds off the week with sunny skies, a " + + "temperature of 80°F, and a humidity " + + "level of 40%. Winds will be gentle at 8 km/h.\n"; + + generateContent(modelId, prompt); + } + + // Generates content with a nullable response schema + public static String generateContent(String modelId, String contents) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + // Define schema for array items (each weather entry object) + Schema dayForecastSchema = + Schema.builder() + .type(Type.Known.OBJECT) + .properties( + Map.of( + "Day", Schema.builder().type(Type.Known.STRING).nullable(true).build(), + "Forecast", Schema.builder().type(Type.Known.STRING).nullable(true).build(), + "Temperature", + Schema.builder().type(Type.Known.INTEGER).nullable(true).build(), + "Humidity", Schema.builder().type(Type.Known.STRING).nullable(true).build(), + "Wind Speed", + Schema.builder().type(Type.Known.INTEGER).nullable(true).build())) + .required(List.of("Day", "Temperature", "Forecast", "Wind Speed")) + .build(); + + // Full response schema + Schema responseSchema = + Schema.builder() + .type(Type.Known.OBJECT) + .properties( + Map.of( + "forecast", + Schema.builder().type(Type.Known.ARRAY).items(dayForecastSchema).build())) + .build(); + + GenerateContentConfig config = + GenerateContentConfig.builder() + .responseMimeType("application/json") + .responseSchema(responseSchema) + .build(); + + GenerateContentResponse response = client.models.generateContent(modelId, contents, config); + + System.out.println(response.text()); + // Example response: + // {"forecast": [{"Day": "Sunday", "Forecast": "sunny", "Temperature": 77, "Wind Speed": 10, + // "Humidity": "50%"}, + // {"Day": "Monday", "Forecast": "partly cloudy", "Temperature": 72, "Wind Speed": 15}, + // {"Day": "Tuesday", "Forecast": "rain showers", "Temperature": 64, "Wind Speed": null, + // "Humidity": "70%"}, + // {"Day": "Wednesday", "Forecast": "thunderstorms", "Temperature": 68, "Wind Speed": null}, + // {"Day": "Thursday", "Forecast": "cloudy", "Temperature": 66, "Wind Speed": null, + // "Humidity": "60%"}, + // {"Day": "Friday", "Forecast": "partly cloudy", "Temperature": 73, "Wind Speed": 12}, + // {"Day": "Saturday", "Forecast": "sunny", "Temperature": 80, "Wind Speed": 8, "Humidity": + // "40%"}]} + return response.text(); + } + } +} +// [END googlegenaisdk_ctrlgen_with_nullable_schema] diff --git a/genai/snippets/src/main/java/genai/controlledgeneration/ControlledGenerationWithResponseSchema.java b/genai/snippets/src/main/java/genai/controlledgeneration/ControlledGenerationWithResponseSchema.java new file mode 100644 index 00000000000..97a0b3d2132 --- /dev/null +++ b/genai/snippets/src/main/java/genai/controlledgeneration/ControlledGenerationWithResponseSchema.java @@ -0,0 +1,100 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.controlledgeneration; + +// [START googlegenaisdk_ctrlgen_with_resp_schema] + +import com.google.genai.Client; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Schema; +import com.google.genai.types.Type; +import java.util.List; +import java.util.Map; + +public class ControlledGenerationWithResponseSchema { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + + String contents = "List a few popular cookie recipes."; + + generateContent(modelId, contents); + } + + // Generates content with a response schema + public static String generateContent(String modelId, String contents) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + // Schema for each item in array + Schema recipeSchema = + Schema.builder() + .type(Type.Known.OBJECT) + .properties( + Map.of( + "recipe_name", Schema.builder().type(Type.Known.STRING).build(), + "ingredients", + Schema.builder() + .type(Type.Known.ARRAY) + .items(Schema.builder().type(Type.Known.STRING).build()) + .build())) + .required(List.of("recipe_name", "ingredients")) + .build(); + + // Full root schema (array) + Schema responseSchema = Schema.builder().type(Type.Known.ARRAY).items(recipeSchema).build(); + + GenerateContentConfig config = + GenerateContentConfig.builder() + .responseMimeType("application/json") + .responseSchema(responseSchema) + .build(); + + GenerateContentResponse response = client.models.generateContent(modelId, contents, config); + + System.out.println(response.text()); + // Example response: + // [ + // { + // "ingredients": [ + // "2 1/4 cups all-purpose flour", + // "1 teaspoon baking soda", + // "1 teaspoon salt", + // "1 cup (2 sticks) unsalted butter, softened", + // "3/4 cup granulated sugar", + // "3/4 cup packed brown sugar", + // "1 teaspoon vanilla extract", + // "2 large eggs", + // "2 cups chocolate chips", + // ], + // "recipe_name": "Chocolate Chip Cookies", + // } + // ] + return response.text(); + } + } +} +// [END googlegenaisdk_ctrlgen_with_resp_schema] diff --git a/genai/snippets/src/main/java/genai/counttokens/CountTokensComputeWithText.java b/genai/snippets/src/main/java/genai/counttokens/CountTokensComputeWithText.java new file mode 100644 index 00000000000..f55090dbb23 --- /dev/null +++ b/genai/snippets/src/main/java/genai/counttokens/CountTokensComputeWithText.java @@ -0,0 +1,77 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.counttokens; + +// [START googlegenaisdk_counttoken_compute_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.ComputeTokensResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.TokensInfo; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Optional; + +public class CountTokensComputeWithText { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + computeTokens(modelId); + } + + // Computes tokens with text input + public static Optional> computeTokens(String modelId) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + ComputeTokensResponse response = client.models.computeTokens( + modelId, "What's the longest word in the English language?", null); + + // Print TokensInfo + response.tokensInfo().ifPresent(tokensInfoList -> { + for (TokensInfo info : tokensInfoList) { + info.role().ifPresent(role -> System.out.println("role: " + role)); + info.tokenIds().ifPresent(tokenIds -> System.out.println("tokenIds: " + tokenIds)); + // print tokens input as strings since they are in a form of byte array + System.out.println("tokens: "); + info.tokens().ifPresent(tokens -> + tokens.forEach(token -> + System.out.println(new String(token, StandardCharsets.UTF_8)) + ) + ); + } + }); + // Example response.tokensInfo() + // role: user + // tokenIds: [1841, 235303, 235256, 573, 32514, 2204, 575, 573, 4645, 5255, 235336] + // tokens: + // What + // ' + // s + // the + return response.tokensInfo(); + } + } +} +// [END googlegenaisdk_counttoken_compute_with_txt] diff --git a/genai/snippets/src/main/java/genai/counttokens/CountTokensLocalTokenizerComputeWithTxt.java b/genai/snippets/src/main/java/genai/counttokens/CountTokensLocalTokenizerComputeWithTxt.java new file mode 100644 index 00000000000..324f99dbca5 --- /dev/null +++ b/genai/snippets/src/main/java/genai/counttokens/CountTokensLocalTokenizerComputeWithTxt.java @@ -0,0 +1,68 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.counttokens; + +// [START googlegenaisdk_counttoken_localtokenizer_compute_with_txt] + +import com.google.genai.LocalTokenizer; +import com.google.genai.types.ComputeTokensResult; +import com.google.genai.types.TokensInfo; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Optional; + +public class CountTokensLocalTokenizerComputeWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + computeTokens(modelId); + } + + // Computes tokens with Local Tokenizer and a text input + public static Optional> computeTokens(String modelId) { + LocalTokenizer tokenizer = new LocalTokenizer(modelId); + ComputeTokensResult result = + tokenizer.computeTokens("What's the longest word in the English language?"); + + result.tokensInfo().ifPresent(tokensInfoList -> { + for (TokensInfo info : tokensInfoList) { + info.role().ifPresent(role -> System.out.println("role: " + role)); + info.tokenIds().ifPresent(tokenIds -> System.out.println("tokenIds: " + tokenIds)); + // Print tokens input as strings since they are in a form of byte array. + System.out.println("tokens: "); + info.tokens().ifPresent(tokens -> + tokens.forEach(token -> + System.out.println(new String(token, StandardCharsets.UTF_8)) + ) + ); + } + }); + // Example response: + // role: user + // tokenIds: [3689, 236789, 236751, 506, 27801, 3658, 528, 506, 5422, 5192, 236881] + // tokens: + // What + // ' + // s + // the + // longest + // ... + return result.tokensInfo(); + } +} +// [END googlegenaisdk_counttoken_localtokenizer_compute_with_txt] diff --git a/genai/snippets/src/main/java/genai/counttokens/CountTokensLocalTokenizerWithTxt.java b/genai/snippets/src/main/java/genai/counttokens/CountTokensLocalTokenizerWithTxt.java new file mode 100644 index 00000000000..4226620a7fa --- /dev/null +++ b/genai/snippets/src/main/java/genai/counttokens/CountTokensLocalTokenizerWithTxt.java @@ -0,0 +1,43 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.counttokens; + +// [START googlegenaisdk_counttoken_localtokenizer_with_txt] + +import com.google.genai.LocalTokenizer; +import com.google.genai.types.CountTokensResult; +import java.util.Optional; + +public class CountTokensLocalTokenizerWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + countTokens(modelId); + } + + // Counts tokens with Local Tokenizer and a text input + public static Optional countTokens(String modelId) { + LocalTokenizer tokenizer = new LocalTokenizer(modelId); + CountTokensResult result = tokenizer.countTokens("What's the highest mountain in Africa?"); + System.out.println(result.totalTokens()); + // Example response: + // Optional[9] + return result.totalTokens(); + } +} +// [END googlegenaisdk_counttoken_localtokenizer_with_txt] diff --git a/genai/snippets/src/main/java/genai/counttokens/CountTokensResponseWithText.java b/genai/snippets/src/main/java/genai/counttokens/CountTokensResponseWithText.java new file mode 100644 index 00000000000..4ca9ad77b74 --- /dev/null +++ b/genai/snippets/src/main/java/genai/counttokens/CountTokensResponseWithText.java @@ -0,0 +1,63 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.counttokens; + +// [START googlegenaisdk_counttoken_resp_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.GenerateContentResponseUsageMetadata; +import com.google.genai.types.HttpOptions; +import java.util.Optional; + +public class CountTokensResponseWithText { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + countTokens(modelId); + } + + // Generates content response usage metadata that contains prompt and response token counts + public static Optional countTokens(String modelId) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + GenerateContentResponse response = + client.models.generateContent(modelId, "Why is the sky blue?", null); + + response.usageMetadata().ifPresent(System.out::println); + // Example response: + // GenerateContentResponseUsageMetadata{cacheTokensDetails=Optional.empty, + // cachedContentTokenCount=Optional.empty, candidatesTokenCount=Optional[569], + // candidatesTokensDetails=Optional[[ModalityTokenCount{modality=Optional[TEXT], + // tokenCount=Optional[569]}]], promptTokenCount=Optional[6], + // promptTokensDetails=Optional[[ModalityTokenCount{modality=Optional[TEXT], + // tokenCount=Optional[6]}]], thoughtsTokenCount=Optional[1132], + // toolUsePromptTokenCount=Optional.empty, toolUsePromptTokensDetails=Optional.empty, + // totalTokenCount=Optional[1707], trafficType=Optional[ON_DEMAND]} + return response.usageMetadata(); + } + } +} +// [END googlegenaisdk_counttoken_resp_with_txt] diff --git a/genai/snippets/src/main/java/genai/counttokens/CountTokensWithText.java b/genai/snippets/src/main/java/genai/counttokens/CountTokensWithText.java new file mode 100644 index 00000000000..5a1c94bd40d --- /dev/null +++ b/genai/snippets/src/main/java/genai/counttokens/CountTokensWithText.java @@ -0,0 +1,55 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.counttokens; + +// [START googlegenaisdk_counttoken_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.CountTokensResponse; +import com.google.genai.types.HttpOptions; +import java.util.Optional; + +public class CountTokensWithText { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + countTokens(modelId); + } + + // Counts tokens with text input + public static Optional countTokens(String modelId) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + CountTokensResponse response = + client.models.countTokens(modelId, "What's the highest mountain in Africa?", null); + + System.out.print(response); + // Example response: + // CountTokensResponse{totalTokens=Optional[9], cachedContentTokenCount=Optional.empty} + return response.totalTokens(); + } + } +} +// [END googlegenaisdk_counttoken_with_txt] diff --git a/genai/snippets/src/main/java/genai/counttokens/CountTokensWithTextAndVideo.java b/genai/snippets/src/main/java/genai/counttokens/CountTokensWithTextAndVideo.java new file mode 100644 index 00000000000..ef72fdb6983 --- /dev/null +++ b/genai/snippets/src/main/java/genai/counttokens/CountTokensWithTextAndVideo.java @@ -0,0 +1,62 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.counttokens; + +// [START googlegenaisdk_counttoken_with_txt_vid] + +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.CountTokensResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Part; +import java.util.List; +import java.util.Optional; + +public class CountTokensWithTextAndVideo { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + countTokens(modelId); + } + + // Counts tokens with text and video inputs + public static Optional countTokens(String modelId) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + Content content = + Content.fromParts( + Part.fromText("Provide a description of this video"), + Part.fromUri("gs://cloud-samples-data/generative-ai/video/pixel8.mp4", "video/mp4")); + + CountTokensResponse response = client.models.countTokens(modelId, List.of(content), null); + + System.out.print(response); + // Example response: + // CountTokensResponse{totalTokens=Optional[16707], cachedContentTokenCount=Optional.empty} + return response.totalTokens(); + } + } +} +// [END googlegenaisdk_counttoken_with_txt_vid] diff --git a/genai/snippets/src/main/java/genai/embeddings/EmbeddingsDocRetrievalWithTxt.java b/genai/snippets/src/main/java/genai/embeddings/EmbeddingsDocRetrievalWithTxt.java new file mode 100644 index 00000000000..6446a32ba66 --- /dev/null +++ b/genai/snippets/src/main/java/genai/embeddings/EmbeddingsDocRetrievalWithTxt.java @@ -0,0 +1,62 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.embeddings; + +// [START googlegenaisdk_embeddings_docretrieval_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.EmbedContentConfig; +import com.google.genai.types.EmbedContentResponse; +import java.util.List; + +public class EmbeddingsDocRetrievalWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-embedding-001"; + embedContent(modelId); + } + + // Shows how to embed content with text. + public static EmbedContentResponse embedContent(String modelId) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("global").vertexAI(true).build()) { + + EmbedContentResponse response = + client.models.embedContent( + modelId, + List.of( + "How do I get a driver's license/learner's permit?", + "How long is my driver's license valid for?", + "Driver's knowledge test study guide"), + EmbedContentConfig.builder() + .taskType("RETRIEVAL_DOCUMENT") + .outputDimensionality(3072) + .title("Driver's License") + .build()); + + System.out.println(response); + // Example response: + // embeddings=Optional[[ContentEmbedding{values=Optional[[-0.035855383, 0.008127963, ... ]] + // statistics=Optional[ContentEmbeddingStatistics{truncated=Optional[false], + // tokenCount=Optional[11.0]}]}]], + // metadata=Optional[EmbedContentMetadata{billableCharacterCount=Optional[153]}]} + return response; + } + } +} +// [END googlegenaisdk_embeddings_docretrieval_with_txt] diff --git a/genai/snippets/src/main/java/genai/expressmode/ExpressModeWithApiKey.java b/genai/snippets/src/main/java/genai/expressmode/ExpressModeWithApiKey.java new file mode 100644 index 00000000000..fdb2bec0dfa --- /dev/null +++ b/genai/snippets/src/main/java/genai/expressmode/ExpressModeWithApiKey.java @@ -0,0 +1,54 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.expressmode; + +// [START googlegenaisdk_vertexai_express_mode] + +import com.google.genai.Client; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; + +public class ExpressModeWithApiKey { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + String apiKey = "YOUR_API_KEY"; + generateContent(modelId, apiKey); + } + + // Generates content with Vertex AI Api key. + public static String generateContent(String modelId, String apiKey) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().apiKey(apiKey).vertexAI(true).build()) { + + GenerateContentResponse response = + client.models.generateContent( + modelId, "Explain bubble sort to me.", GenerateContentConfig.builder().build()); + + System.out.print(response.text()); + // Example response: + // Bubble sort is one of the simplest sorting algorithms. It's often used to introduce the + // concept of sorting because its logic is very straightforward. + // + // Imagine you have a list of numbers that you want to put in order, like `[5, 1, 4, 2, 8]`. + // ... + return response.text(); + } + } +} +// [END googlegenaisdk_vertexai_express_mode] diff --git a/genai/snippets/src/main/java/genai/imagegeneration/ImageGenCannyCtrlTypeWithTextAndImage.java b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenCannyCtrlTypeWithTextAndImage.java new file mode 100644 index 00000000000..b213721e566 --- /dev/null +++ b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenCannyCtrlTypeWithTextAndImage.java @@ -0,0 +1,92 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.imagegeneration; + +// [START googlegenaisdk_imggen_canny_ctrl_type_with_txt_img] + +import com.google.cloud.storage.BlobId; +import com.google.cloud.storage.BlobInfo; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import com.google.genai.Client; +import com.google.genai.types.Blob; +import com.google.genai.types.Candidate; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.Part; +import java.util.Optional; + +public class ImageGenCannyCtrlTypeWithTextAndImage { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash-image"; + String outputGcsUri = "gs://your-bucket/your-prefix"; + cannyEdgeCustomization(modelId, outputGcsUri); + } + + // Generates an image using controlled customization with a Canny Edge image and a text prompt. + public static Optional cannyEdgeCustomization(String modelId, String outputGcsUri) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("global").vertexAI(true).build()) { + + GenerateContentConfig config = + GenerateContentConfig.builder().responseModalities("TEXT", "IMAGE").build(); + + GenerateContentResponse response = + client.models.generateContent( + modelId, + Content.fromParts( + Part.fromUri( + "gs://cloud-samples-data/generative-ai/image/car_canny.png", "image/png"), + Part.fromText("a watercolor painting of a red car driving on a road")), + config); + + byte[] imageBytes = + response + .candidates() + .flatMap(candidates -> candidates.stream().findFirst()) + .flatMap(Candidate::content) + .flatMap(Content::parts) + .flatMap( + parts -> + parts.stream() + .filter(part -> part.inlineData().flatMap(Blob::data).isPresent()) + .findFirst()) + .flatMap(part -> part.inlineData().flatMap(Blob::data)) + .orElseThrow(() -> new IllegalStateException("No image was generated by the model.")); + + Storage storage = StorageOptions.getDefaultInstance().getService(); + String gcsPath = outputGcsUri.replace("gs://", ""); + int slashIdx = gcsPath.indexOf('/'); + String bucket = slashIdx > 0 ? gcsPath.substring(0, slashIdx) : gcsPath; + String objectName = + slashIdx > 0 + ? gcsPath.substring(slashIdx + 1) + "/canny_customization.png" + : "canny_customization.png"; + BlobId blobId = BlobId.of(bucket, objectName); + BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("image/png").build(); + storage.create(blobInfo, imageBytes); + + String fullGcsUri = String.format("gs://%s/%s", bucket, objectName); + System.out.println("Generated image URI: " + fullGcsUri); + return Optional.of(fullGcsUri); + } + } +} +// [END googlegenaisdk_imggen_canny_ctrl_type_with_txt_img] diff --git a/genai/snippets/src/main/java/genai/imagegeneration/ImageGenMmFlashEditImageWithTextAndImage.java b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenMmFlashEditImageWithTextAndImage.java new file mode 100644 index 00000000000..00b26546e48 --- /dev/null +++ b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenMmFlashEditImageWithTextAndImage.java @@ -0,0 +1,93 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.imagegeneration; + +// [START googlegenaisdk_imggen_mmflash_edit_img_with_txt_img] + +import com.google.genai.Client; +import com.google.genai.types.Blob; +import com.google.genai.types.Candidate; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.Part; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import javax.imageio.ImageIO; + +public class ImageGenMmFlashEditImageWithTextAndImage { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash-image"; + String outputFile = "resources/output/bw-example-image.png"; + generateContent(modelId, outputFile); + } + + // Edits an image with image and text input + public static void generateContent(String modelId, String outputFile) throws IOException { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("global").vertexAI(true).build()) { + + byte[] localImageBytes = + Files.readAllBytes(Paths.get("resources/example-image-eiffel-tower.png")); + + GenerateContentResponse response = + client.models.generateContent( + modelId, + Content.fromParts( + Part.fromBytes(localImageBytes, "image/png"), + Part.fromText("Edit this image to make it look like a cartoon.")), + GenerateContentConfig.builder().responseModalities("TEXT", "IMAGE").build()); + + // Get parts of the response + List parts = + response + .candidates() + .flatMap(candidates -> candidates.stream().findFirst()) + .flatMap(Candidate::content) + .flatMap(Content::parts) + .orElse(new ArrayList<>()); + + // For each part print text if present, otherwise read image data if present and + // write it to the output file + for (Part part : parts) { + if (part.text().isPresent()) { + System.out.println(part.text().get()); + } else if (part.inlineData().flatMap(Blob::data).isPresent()) { + BufferedImage image = + ImageIO.read(new ByteArrayInputStream(part.inlineData().flatMap(Blob::data).get())); + ImageIO.write(image, "png", new File(outputFile)); + } + } + + System.out.println("Content written to: " + outputFile); + + // Example response: + // No problem! Here's the image in a cartoon style... + // + // Content written to: resources/output/bw-example-image.png + } + } +} +// [END googlegenaisdk_imggen_mmflash_edit_img_with_txt_img] \ No newline at end of file diff --git a/genai/snippets/src/main/java/genai/imagegeneration/ImageGenMmFlashLocaleAwareWithText.java b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenMmFlashLocaleAwareWithText.java new file mode 100644 index 00000000000..63265866278 --- /dev/null +++ b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenMmFlashLocaleAwareWithText.java @@ -0,0 +1,86 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.imagegeneration; + +// [START googlegenaisdk_imggen_mmflash_locale_aware_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.Blob; +import com.google.genai.types.Candidate; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.Part; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.imageio.ImageIO; + +public class ImageGenMmFlashLocaleAwareWithText { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash-image"; + String outputFile = "resources/output/example-breakfast-meal.png"; + generateContent(modelId, outputFile); + } + + // Generates an image with text input + public static void generateContent(String modelId, String outputFile) throws IOException { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("global").vertexAI(true).build()) { + + GenerateContentResponse response = + client.models.generateContent( + modelId, + "Generate a photo of a breakfast meal.", + GenerateContentConfig.builder().responseModalities("TEXT", "IMAGE").build()); + + // Get parts of the response + List parts = + response + .candidates() + .flatMap(candidates -> candidates.stream().findFirst()) + .flatMap(Candidate::content) + .flatMap(Content::parts) + .orElse(new ArrayList<>()); + + // For each part print text if present, otherwise read image data if present and + // write it to the output file + for (Part part : parts) { + if (part.text().isPresent()) { + System.out.println(part.text().get()); + } else if (part.inlineData().flatMap(Blob::data).isPresent()) { + BufferedImage image = + ImageIO.read(new ByteArrayInputStream(part.inlineData().flatMap(Blob::data).get())); + ImageIO.write(image, "png", new File(outputFile)); + } + } + + System.out.println("Content written to: " + outputFile); + + // Example response: + // Here is a photo of a breakfast meal for you! + // + // Content written to: resources/output/example-breakfast-meal.png + } + } +} +// [END googlegenaisdk_imggen_mmflash_locale_aware_with_txt] diff --git a/genai/snippets/src/main/java/genai/imagegeneration/ImageGenMmFlashMultipleImagesWithText.java b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenMmFlashMultipleImagesWithText.java new file mode 100644 index 00000000000..8d8713b9e75 --- /dev/null +++ b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenMmFlashMultipleImagesWithText.java @@ -0,0 +1,86 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.imagegeneration; + +// [START googlegenaisdk_imggen_mmflash_multiple_imgs_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.Blob; +import com.google.genai.types.Candidate; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.Part; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.imageio.ImageIO; + +public class ImageGenMmFlashMultipleImagesWithText { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash-image"; + generateContent(modelId); + } + + // Generates multiple images with text input + public static List generateContent(String modelId) throws IOException { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("global").vertexAI(true).build()) { + + GenerateContentResponse response = + client.models.generateContent( + modelId, + "Generate 3 images of a cat sitting on a chair.", + GenerateContentConfig.builder().responseModalities("TEXT", "IMAGE").build()); + + // Get parts of the response + List parts = + response + .candidates() + .flatMap(candidates -> candidates.stream().findFirst()) + .flatMap(Candidate::content) + .flatMap(Content::parts) + .orElse(new ArrayList<>()); + + List generatedImages = new ArrayList<>(); + int imageCounter = 1; + // For each part print text if present, otherwise read image data if present and + // write it to the output file + for (Part part : parts) { + if (part.text().isPresent()) { + System.out.println(part.text().get()); + } else if (part.inlineData().flatMap(Blob::data).isPresent()) { + BufferedImage image = + ImageIO.read(new ByteArrayInputStream(part.inlineData().flatMap(Blob::data).get())); + String fileName = "resources/output/example-cats-0" + (imageCounter++) + ".png"; + ImageIO.write(image, "png", new File(fileName)); + generatedImages.add(fileName); + } + } + + // Example response: + // Here are three images of a cat sitting on a chair... + return generatedImages; + } + } +} +// [END googlegenaisdk_imggen_mmflash_multiple_imgs_with_txt] diff --git a/genai/snippets/src/main/java/genai/imagegeneration/ImageGenMmFlashTextAndImageWithText.java b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenMmFlashTextAndImageWithText.java new file mode 100644 index 00000000000..bb662e018e1 --- /dev/null +++ b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenMmFlashTextAndImageWithText.java @@ -0,0 +1,99 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.imagegeneration; + +// [START googlegenaisdk_imggen_mmflash_txt_and_img_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.Blob; +import com.google.genai.types.Candidate; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.Part; +import java.awt.image.BufferedImage; +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.imageio.ImageIO; + +public class ImageGenMmFlashTextAndImageWithText { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash-image"; + String outputFile = "resources/output/paella-recipe.md"; + generateContent(modelId, outputFile); + } + + // Generates text and image with text input + public static void generateContent(String modelId, String outputFile) throws IOException { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("global").vertexAI(true).build()) { + + GenerateContentResponse response = + client.models.generateContent( + modelId, + Content.fromParts( + Part.fromText("Generate an illustrated recipe for a paella."), + Part.fromText( + "Create images to go alongside the text as you generate the recipe.")), + GenerateContentConfig.builder().responseModalities("TEXT", "IMAGE").build()); + + try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile))) { + + // Get parts of the response + List parts = + response + .candidates() + .flatMap(candidates -> candidates.stream().findFirst()) + .flatMap(Candidate::content) + .flatMap(Content::parts) + .orElse(new ArrayList<>()); + + int index = 1; + // For each part print text if present, otherwise read image data if present and + // write it to the output file + for (Part part : parts) { + if (part.text().isPresent()) { + writer.write(part.text().get()); + } else if (part.inlineData().flatMap(Blob::data).isPresent()) { + BufferedImage image = + ImageIO.read(new ByteArrayInputStream(part.inlineData().flatMap(Blob::data).get())); + ImageIO.write( + image, "png", new File("resources/output/example-image-" + index + ".png")); + writer.write("![image](example-image-" + index + ".png)"); + } + index++; + } + + System.out.println("Content written to: " + outputFile); + + // Example response: + // A markdown page for a Paella recipe(`paella-recipe.md`) has been generated. + // It includes detailed steps and several images illustrating the cooking process. + // + // Content written to: resources/output/paella-recipe.md + } + } + } +} +// [END googlegenaisdk_imggen_mmflash_txt_and_img_with_txt] diff --git a/genai/snippets/src/main/java/genai/imagegeneration/ImageGenMmFlashWithText.java b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenMmFlashWithText.java new file mode 100644 index 00000000000..44117353248 --- /dev/null +++ b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenMmFlashWithText.java @@ -0,0 +1,98 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.imagegeneration; + +// [START googlegenaisdk_imggen_mmflash_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.Blob; +import com.google.genai.types.Candidate; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.Part; +import com.google.genai.types.SafetySetting; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.imageio.ImageIO; + +public class ImageGenMmFlashWithText { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash-image"; + String outputFile = "resources/output/example-image-eiffel-tower.png"; + generateContent(modelId, outputFile); + } + + // Generates an image with text input + public static void generateContent(String modelId, String outputFile) throws IOException { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("global").vertexAI(true).build()) { + + GenerateContentConfig contentConfig = + GenerateContentConfig.builder() + .responseModalities("TEXT", "IMAGE") + .candidateCount(1) + .safetySettings( + SafetySetting.builder() + .method("PROBABILITY") + .category("HARM_CATEGORY_DANGEROUS_CONTENT") + .threshold("BLOCK_MEDIUM_AND_ABOVE") + .build()) + .build(); + + GenerateContentResponse response = + client.models.generateContent( + modelId, + "Generate an image of the Eiffel tower with fireworks in the background.", + contentConfig); + + // Get parts of the response + List parts = + response + .candidates() + .flatMap(candidates -> candidates.stream().findFirst()) + .flatMap(Candidate::content) + .flatMap(Content::parts) + .orElse(new ArrayList<>()); + + // For each part print text if present, otherwise read image data if present and + // write it to the output file + for (Part part : parts) { + if (part.text().isPresent()) { + System.out.println(part.text().get()); + } else if (part.inlineData().flatMap(Blob::data).isPresent()) { + BufferedImage image = + ImageIO.read(new ByteArrayInputStream(part.inlineData().flatMap(Blob::data).get())); + ImageIO.write(image, "png", new File(outputFile)); + } + } + + System.out.println("Content written to: " + outputFile); + // Example response: + // Here is the Eiffel Tower with fireworks in the background... + // + // Content written to: resources/output/example-image-eiffel-tower.png + } + } +} +// [END googlegenaisdk_imggen_mmflash_with_txt] diff --git a/genai/snippets/src/main/java/genai/imagegeneration/ImageGenRawReferenceWithTextAndImage.java b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenRawReferenceWithTextAndImage.java new file mode 100644 index 00000000000..b20247a1d2a --- /dev/null +++ b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenRawReferenceWithTextAndImage.java @@ -0,0 +1,95 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.imagegeneration; + +// [START googlegenaisdk_imggen_raw_reference_with_txt_img] + +import com.google.cloud.storage.BlobId; +import com.google.cloud.storage.BlobInfo; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import com.google.genai.Client; +import com.google.genai.types.Blob; +import com.google.genai.types.Candidate; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.Part; +import java.util.Optional; + +public class ImageGenRawReferenceWithTextAndImage { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash-image"; + String outputGcsUri = "gs://your-bucket/your-prefix"; + styleTransferCustomization(modelId, outputGcsUri); + } + + // Generates an image in a new style using style transfer customization with a raw reference image + // and a text prompt. + public static Optional styleTransferCustomization(String modelId, String outputGcsUri) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("global").vertexAI(true).build()) { + + GenerateContentConfig config = + GenerateContentConfig.builder().responseModalities("TEXT", "IMAGE").build(); + + GenerateContentResponse response = + client.models.generateContent( + modelId, + Content.fromParts( + Part.fromUri( + "gs://cloud-samples-data/generative-ai/image/teacup-1.png", "image/png"), + Part.fromText( + "transform the subject in the image so that " + + "the teacup is made entirely out of chocolate")), + config); + + byte[] imageBytes = + response + .candidates() + .flatMap(candidates -> candidates.stream().findFirst()) + .flatMap(Candidate::content) + .flatMap(Content::parts) + .flatMap( + parts -> + parts.stream() + .filter(part -> part.inlineData().flatMap(Blob::data).isPresent()) + .findFirst()) + .flatMap(part -> part.inlineData().flatMap(Blob::data)) + .orElseThrow(() -> new IllegalStateException("No image was generated by the model.")); + + Storage storage = StorageOptions.getDefaultInstance().getService(); + String gcsPath = outputGcsUri.replace("gs://", ""); + int slashIdx = gcsPath.indexOf('/'); + String bucket = slashIdx > 0 ? gcsPath.substring(0, slashIdx) : gcsPath; + String objectName = + slashIdx > 0 + ? gcsPath.substring(slashIdx + 1) + "/raw_reference.png" + : "raw_reference.png"; + BlobId blobId = BlobId.of(bucket, objectName); + BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("image/png").build(); + storage.create(blobInfo, imageBytes); + + String fullGcsUri = String.format("gs://%s/%s", bucket, objectName); + System.out.println("Generated image URI: " + fullGcsUri); + return Optional.of(fullGcsUri); + } + } +} +// [END googlegenaisdk_imggen_raw_reference_with_txt_img] diff --git a/genai/snippets/src/main/java/genai/imagegeneration/ImageGenScribbleCtrlTypeWithTextAndImage.java b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenScribbleCtrlTypeWithTextAndImage.java new file mode 100644 index 00000000000..559b4be07e1 --- /dev/null +++ b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenScribbleCtrlTypeWithTextAndImage.java @@ -0,0 +1,92 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.imagegeneration; + +// [START googlegenaisdk_imggen_scribble_ctrl_type_with_txt_img] + +import com.google.cloud.storage.BlobId; +import com.google.cloud.storage.BlobInfo; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import com.google.genai.Client; +import com.google.genai.types.Blob; +import com.google.genai.types.Candidate; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.Part; +import java.util.Optional; + +public class ImageGenScribbleCtrlTypeWithTextAndImage { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash-image"; + String outputGcsUri = "gs://your-bucket/your-prefix"; + scribbleCustomization(modelId, outputGcsUri); + } + + // Generates an image using controlled customization with a Scribble image and a text prompt. + public static Optional scribbleCustomization(String modelId, String outputGcsUri) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("global").vertexAI(true).build()) { + + GenerateContentConfig config = + GenerateContentConfig.builder().responseModalities("TEXT", "IMAGE").build(); + + GenerateContentResponse response = + client.models.generateContent( + modelId, + Content.fromParts( + Part.fromUri( + "gs://cloud-samples-data/generative-ai/image/car_scribble.png", "image/png"), + Part.fromText("an oil painting showing the side of a red car")), + config); + + byte[] imageBytes = + response + .candidates() + .flatMap(candidates -> candidates.stream().findFirst()) + .flatMap(Candidate::content) + .flatMap(Content::parts) + .flatMap( + parts -> + parts.stream() + .filter(part -> part.inlineData().flatMap(Blob::data).isPresent()) + .findFirst()) + .flatMap(part -> part.inlineData().flatMap(Blob::data)) + .orElseThrow(() -> new IllegalStateException("No image was generated by the model.")); + + Storage storage = StorageOptions.getDefaultInstance().getService(); + String gcsPath = outputGcsUri.replace("gs://", ""); + int slashIdx = gcsPath.indexOf('/'); + String bucket = slashIdx > 0 ? gcsPath.substring(0, slashIdx) : gcsPath; + String objectName = + slashIdx > 0 + ? gcsPath.substring(slashIdx + 1) + "/scribble_customization.png" + : "scribble_customization.png"; + BlobId blobId = BlobId.of(bucket, objectName); + BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("image/png").build(); + storage.create(blobInfo, imageBytes); + + String fullGcsUri = String.format("gs://%s/%s", bucket, objectName); + System.out.println("Generated image URI: " + fullGcsUri); + return Optional.of(fullGcsUri); + } + } +} +// [END googlegenaisdk_imggen_scribble_ctrl_type_with_txt_img] diff --git a/genai/snippets/src/main/java/genai/imagegeneration/ImageGenStyleReferenceWithTextAndImage.java b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenStyleReferenceWithTextAndImage.java new file mode 100644 index 00000000000..4e4e3bcff93 --- /dev/null +++ b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenStyleReferenceWithTextAndImage.java @@ -0,0 +1,92 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.imagegeneration; + +// [START googlegenaisdk_imggen_style_reference_with_txt_img] + +import com.google.cloud.storage.BlobId; +import com.google.cloud.storage.BlobInfo; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import com.google.genai.Client; +import com.google.genai.types.Blob; +import com.google.genai.types.Candidate; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.Part; +import java.util.Optional; + +public class ImageGenStyleReferenceWithTextAndImage { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash-image"; + String outputGcsUri = "gs://your-bucket/your-prefix"; + styleCustomization(modelId, outputGcsUri); + } + + // Generates an image using style customization with a style reference image and text prompt. + public static Optional styleCustomization(String modelId, String outputGcsUri) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("global").vertexAI(true).build()) { + + GenerateContentConfig config = + GenerateContentConfig.builder().responseModalities("TEXT", "IMAGE").build(); + + GenerateContentResponse response = + client.models.generateContent( + modelId, + Content.fromParts( + Part.fromUri("gs://cloud-samples-data/generative-ai/image/neon.png", "image/png"), + Part.fromText( + "generate an image of a neon sign with the words: have a great day")), + config); + + byte[] imageBytes = + response + .candidates() + .flatMap(candidates -> candidates.stream().findFirst()) + .flatMap(Candidate::content) + .flatMap(Content::parts) + .flatMap( + parts -> + parts.stream() + .filter(part -> part.inlineData().flatMap(Blob::data).isPresent()) + .findFirst()) + .flatMap(part -> part.inlineData().flatMap(Blob::data)) + .orElseThrow(() -> new IllegalStateException("No image was generated by the model.")); + + Storage storage = StorageOptions.getDefaultInstance().getService(); + String gcsPath = outputGcsUri.replace("gs://", ""); + int slashIdx = gcsPath.indexOf('/'); + String bucket = slashIdx > 0 ? gcsPath.substring(0, slashIdx) : gcsPath; + String objectName = + slashIdx > 0 + ? gcsPath.substring(slashIdx + 1) + "/style_customization.png" + : "style_customization.png"; + BlobId blobId = BlobId.of(bucket, objectName); + BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("image/png").build(); + storage.create(blobInfo, imageBytes); + + String fullGcsUri = String.format("gs://%s/%s", bucket, objectName); + System.out.println("Generated image URI: " + fullGcsUri); + return Optional.of(fullGcsUri); + } + } +} +// [END googlegenaisdk_imggen_style_reference_with_txt_img] diff --git a/genai/snippets/src/main/java/genai/imagegeneration/ImageGenSubjectReferenceWithTextAndImage.java b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenSubjectReferenceWithTextAndImage.java new file mode 100644 index 00000000000..b2170dfbe2b --- /dev/null +++ b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenSubjectReferenceWithTextAndImage.java @@ -0,0 +1,97 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.imagegeneration; + +// [START googlegenaisdk_imggen_subj_refer_ctrl_refer_with_txt_imgs] + +import com.google.cloud.storage.BlobId; +import com.google.cloud.storage.BlobInfo; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import com.google.genai.Client; +import com.google.genai.types.Blob; +import com.google.genai.types.Candidate; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.Part; +import java.util.Optional; + +public class ImageGenSubjectReferenceWithTextAndImage { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash-image"; + String outputGcsUri = "gs://your-bucket/your-prefix"; + subjectCustomization(modelId, outputGcsUri); + } + + // Generates an image using subject customization by adapting a subject reference image + // with a text prompt. + public static Optional subjectCustomization(String modelId, String outputGcsUri) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("global").vertexAI(true).build()) { + + GenerateContentConfig config = + GenerateContentConfig.builder().responseModalities("TEXT", "IMAGE").build(); + + GenerateContentResponse response = + client.models.generateContent( + modelId, + Content.fromParts( + Part.fromUri( + "gs://cloud-samples-data/generative-ai/image/person.png", "image/png"), + Part.fromText( + "a portrait of the woman in a watercolor style by a professional artist," + + " light and low-contrast strokes, bright pastel colors, a warm" + + " atmosphere, clean background, grainy paper, bold visible" + + " brushstrokes, patchy details")), + config); + + byte[] imageBytes = + response + .candidates() + .flatMap(candidates -> candidates.stream().findFirst()) + .flatMap(Candidate::content) + .flatMap(Content::parts) + .flatMap( + parts -> + parts.stream() + .filter(part -> part.inlineData().flatMap(Blob::data).isPresent()) + .findFirst()) + .flatMap(part -> part.inlineData().flatMap(Blob::data)) + .orElseThrow(() -> new IllegalStateException("No image was generated by the model.")); + + Storage storage = StorageOptions.getDefaultInstance().getService(); + String gcsPath = outputGcsUri.replace("gs://", ""); + int slashIdx = gcsPath.indexOf('/'); + String bucket = slashIdx > 0 ? gcsPath.substring(0, slashIdx) : gcsPath; + String objectName = + slashIdx > 0 + ? gcsPath.substring(slashIdx + 1) + "/subject_customization.png" + : "subject_customization.png"; + BlobId blobId = BlobId.of(bucket, objectName); + BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("image/png").build(); + storage.create(blobInfo, imageBytes); + + String fullGcsUri = String.format("gs://%s/%s", bucket, objectName); + System.out.println("Generated image URI: " + fullGcsUri); + return Optional.of(fullGcsUri); + } + } +} +// [END googlegenaisdk_imggen_subj_refer_ctrl_refer_with_txt_imgs] diff --git a/genai/snippets/src/main/java/genai/imagegeneration/ImageGenVirtualTryOnWithTextAndImage.java b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenVirtualTryOnWithTextAndImage.java new file mode 100644 index 00000000000..1c1404e380a --- /dev/null +++ b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenVirtualTryOnWithTextAndImage.java @@ -0,0 +1,87 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.imagegeneration; + +// [START googlegenaisdk_imggen_virtual_try_on_with_txt_img] + +import com.google.genai.Client; +import com.google.genai.types.GeneratedImage; +import com.google.genai.types.Image; +import com.google.genai.types.ProductImage; +import com.google.genai.types.RecontextImageResponse; +import com.google.genai.types.RecontextImageSource; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import javax.imageio.ImageIO; + +public class ImageGenVirtualTryOnWithTextAndImage { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String modelId = "virtual-try-on-001"; + String outputFile = "resources/output/man_in_sweater.png"; + generateContent(modelId, outputFile); + } + + // Generates a recontextualized image with image inputs + public static Image generateContent(String modelId, String outputFile) throws IOException { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("global").vertexAI(true).build()) { + + byte[] personImageBytes = Files.readAllBytes(Paths.get("resources/man.png")); + Image personImage = Image.builder().imageBytes(personImageBytes).build(); + + byte[] productImageBytes = Files.readAllBytes(Paths.get("resources/sweater.jpg")); + Image productImage = Image.builder().imageBytes(productImageBytes).build(); + + RecontextImageResponse recontextImageResponse = + client.models.recontextImage( + modelId, + RecontextImageSource.builder() + .personImage(personImage) + .productImages(ProductImage.builder().productImage(productImage).build()) + .build(), + null); + + Image generatedImage = + recontextImageResponse + .generatedImages() + .flatMap(generatedImages -> generatedImages.stream().findFirst()) + .flatMap(GeneratedImage::image) + .orElseThrow(() -> new IllegalStateException("No image was generated by the model.")); + + // Read image data and write it to the output file + if (generatedImage.imageBytes().isPresent()) { + BufferedImage image = + ImageIO.read(new ByteArrayInputStream(generatedImage.imageBytes().get())); + ImageIO.write(image, "png", new File(outputFile)); + + System.out.printf( + "Created output image using %s bytes\n", generatedImage.imageBytes().get().length); + } + + // Example response: + // Created output image using 1637865 bytes + return generatedImage; + } + } +} +// [END googlegenaisdk_imggen_virtual_try_on_with_txt_img] diff --git a/genai/snippets/src/main/java/genai/imagegeneration/ImageGenWithText.java b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenWithText.java new file mode 100644 index 00000000000..507eed252e0 --- /dev/null +++ b/genai/snippets/src/main/java/genai/imagegeneration/ImageGenWithText.java @@ -0,0 +1,77 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.imagegeneration; + +// [START googlegenaisdk_imggen_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.Blob; +import com.google.genai.types.Candidate; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.Image; +import com.google.genai.types.Part; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import javax.imageio.ImageIO; + +public class ImageGenWithText { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash-image"; + String outputFile = "resources/output/dog_newspaper.png"; + generateImage(modelId, outputFile); + } + + // Generates an image with text input + public static Image generateImage(String modelId, String outputFile) throws IOException { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("global").vertexAI(true).build()) { + + GenerateContentConfig config = + GenerateContentConfig.builder().responseModalities("TEXT", "IMAGE").build(); + + GenerateContentResponse response = + client.models.generateContent(modelId, "A dog reading a newspaper", config); + + byte[] imageBytes = + response + .candidates() + .flatMap(candidates -> candidates.stream().findFirst()) + .flatMap(Candidate::content) + .flatMap(Content::parts) + .flatMap( + parts -> + parts.stream() + .filter(part -> part.inlineData().flatMap(Blob::data).isPresent()) + .findFirst()) + .flatMap(part -> part.inlineData().flatMap(Blob::data)) + .orElseThrow(() -> new IllegalStateException("No image was generated by the model.")); + + java.nio.file.Files.write(java.nio.file.Paths.get(outputFile), imageBytes); + + System.out.printf("Created output image using %s bytes\n", imageBytes.length); + + return Image.builder().imageBytes(imageBytes).build(); + } + } +} +// [END googlegenaisdk_imggen_with_txt] \ No newline at end of file diff --git a/genai/snippets/src/main/java/genai/live/LiveAudioWithTxt.java b/genai/snippets/src/main/java/genai/live/LiveAudioWithTxt.java new file mode 100644 index 00000000000..6d53aee7d2e --- /dev/null +++ b/genai/snippets/src/main/java/genai/live/LiveAudioWithTxt.java @@ -0,0 +1,170 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.live; + +// [START googlegenaisdk_live_audio_with_txt] + +import static com.google.genai.types.Modality.Known.AUDIO; + +import com.google.genai.AsyncSession; +import com.google.genai.Client; +import com.google.genai.types.Blob; +import com.google.genai.types.Content; +import com.google.genai.types.LiveConnectConfig; +import com.google.genai.types.LiveSendClientContentParameters; +import com.google.genai.types.LiveServerContent; +import com.google.genai.types.LiveServerMessage; +import com.google.genai.types.Part; +import com.google.genai.types.PrebuiltVoiceConfig; +import com.google.genai.types.SpeechConfig; +import com.google.genai.types.VoiceConfig; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.CompletableFuture; +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; + +public class LiveAudioWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-live-2.5-flash-native-audio"; + generateContent(modelId); + } + + // Shows how to get voice responses from text input. + public static void generateContent(String modelId) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("us-central1").vertexAI(true).build()) { + + LiveConnectConfig liveConnectConfig = + LiveConnectConfig.builder() + .responseModalities(AUDIO) + .speechConfig( + SpeechConfig.builder() + .voiceConfig( + VoiceConfig.builder() + .prebuiltVoiceConfig( + PrebuiltVoiceConfig.builder().voiceName("Aoede").build()) + .build()) + .build()) + .build(); + + // Connects to the live server. + CompletableFuture sessionFuture = + client.async.live.connect(modelId, liveConnectConfig); + + // Sends content and receives response from the live server. + sessionFuture + .thenCompose( + session -> { + // A future that completes when the model signals the end of its turn. + CompletableFuture turnComplete = new CompletableFuture<>(); + // A buffer to collect all incoming audio chunks. + ByteArrayOutputStream audioBuffer = new ByteArrayOutputStream(); + // Starts receiving messages from the live session. + session.receive( + message -> handleLiveServerMessage(message, turnComplete, audioBuffer)); + // Sends content to the live session and waits for the turn to complete. + return sendContent(session) + .thenCompose(unused -> turnComplete) + .thenAccept( + unused -> { + byte[] audio = audioBuffer.toByteArray(); + if (audio.length > 0) { + saveAudioToFile(audio); + } + }) + .thenCompose(unused -> session.close()); + }) + .join(); + // Example response: + // > Answer to this audio url + // Successfully saved audio to... + } + } + + // Sends content to the live session. + private static CompletableFuture sendContent(AsyncSession session) { + String textInput = "Hello? Gemini, are you there?"; + System.out.printf("> %s\n", textInput); + return session.sendClientContent( + LiveSendClientContentParameters.builder() + .turns(Content.builder().role("user").parts(Part.fromText(textInput)).build()) + .turnComplete(true) + .build()); + } + + // Writes the inline data response to the audio buffer and signals + // `turnComplete` when the model is done generating the response. + private static void handleLiveServerMessage( + LiveServerMessage message, + CompletableFuture turnComplete, + ByteArrayOutputStream audioBuffer) { + message + .serverContent() + .flatMap(LiveServerContent::modelTurn) + .flatMap(Content::parts) + .ifPresent( + parts -> + parts.forEach( + part -> { + // When an audio blob is present, write its data to the buffer. + part.inlineData() + .flatMap(Blob::data) + .ifPresent( + data -> { + try { + audioBuffer.write(data); + } catch (IOException e) { + System.out.println( + "Error writing to audio buffer: " + e.getMessage()); + } + }); + })); + + // Checks if the model's turn is over. + if (message.serverContent().flatMap(LiveServerContent::turnComplete).orElse(false)) { + turnComplete.complete(null); + } + } + + private static void saveAudioToFile(byte[] audioData) { + try { + // Defines the audio format. + AudioFormat format = new AudioFormat(24000, 16, 1, true, false); + // Creates an AudioInputStream from the raw audio data and the format. + AudioInputStream audioStream = + new AudioInputStream( + new ByteArrayInputStream(audioData), + format, + audioData.length / format.getFrameSize()); + + Path outputPath = Paths.get("resources/output/output_audio.wav"); + AudioSystem.write(audioStream, AudioFileFormat.Type.WAVE, outputPath.toFile()); + System.out.println("Successfully saved audio to: " + outputPath.toAbsolutePath()); + } catch (IOException e) { + System.err.println("Error saving audio file: " + e.getMessage()); + } + } +} +// [END googlegenaisdk_live_audio_with_txt] diff --git a/genai/snippets/src/main/java/genai/live/LiveCodeExecWithTxt.java b/genai/snippets/src/main/java/genai/live/LiveCodeExecWithTxt.java new file mode 100644 index 00000000000..a75b829155f --- /dev/null +++ b/genai/snippets/src/main/java/genai/live/LiveCodeExecWithTxt.java @@ -0,0 +1,120 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.live; + +// [START googlegenaisdk_live_code_exec_with_txt] + +import static com.google.genai.types.Modality.Known.TEXT; + +import com.google.genai.AsyncSession; +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.LiveConnectConfig; +import com.google.genai.types.LiveSendClientContentParameters; +import com.google.genai.types.LiveServerContent; +import com.google.genai.types.LiveServerMessage; +import com.google.genai.types.Part; +import com.google.genai.types.Tool; +import com.google.genai.types.ToolCodeExecution; +import java.util.concurrent.CompletableFuture; + +public class LiveCodeExecWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.0-flash-live-preview-04-09"; + generateContent(modelId); + } + + // Shows how to generate content with the Code Execution tool and a text input. + public static String generateContent(String modelId) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("us-central1").vertexAI(true).build()) { + + // Connects to the live server. + CompletableFuture sessionFuture = + client.async.live.connect( + modelId, + LiveConnectConfig.builder() + .responseModalities(TEXT) + .tools(Tool.builder().codeExecution(ToolCodeExecution.builder().build()).build()) + .build()); + + // Sends and receives messages from the live session. + CompletableFuture responseFuture = + sessionFuture.thenCompose( + session -> { + // A future that completes when the model signals the end of its turn. + CompletableFuture turnComplete = new CompletableFuture<>(); + // A variable to concatenate the text response from the model. + StringBuilder modelResponse = new StringBuilder(); + // Starts receiving messages from the live session. + session.receive( + message -> handleLiveServerMessage(message, turnComplete, modelResponse)); + // Sends content to the live session and waits for the turn to complete. + return sendContent(session) + .thenCompose(unused -> turnComplete) + .thenCompose( + unused -> session.close().thenApply(result -> modelResponse.toString())); + }); + + String response = responseFuture.join(); + System.out.println(response); + // Example output: + // > Compute the largest prime palindrome under 100000 + // + // Okay, I understand. I need to find the largest prime number that is also a palindrome + // and is less than 100000. Here's my plan... + return response; + } + } + + // Sends content to the live session. + private static CompletableFuture sendContent(AsyncSession session) { + String textInput = "Compute the largest prime palindrome under 100000"; + System.out.printf("> %s\n", textInput); + return session.sendClientContent( + LiveSendClientContentParameters.builder() + .turns(Content.builder().role("user").parts(Part.fromText(textInput)).build()) + .turnComplete(true) + .build()); + } + + // Concatenates the response messages from the model and signals + // `turnComplete` when the model is done generating the response. + private static void handleLiveServerMessage( + LiveServerMessage message, CompletableFuture turnComplete, StringBuilder response) { + message + .serverContent() + .flatMap(LiveServerContent::modelTurn) + .flatMap(Content::parts) + .ifPresent( + parts -> + parts.forEach( + part -> { + part.text().ifPresent(response::append); + part.executableCode().ifPresent(code -> System.out.println("code: " + code)); + part.codeExecutionResult() + .ifPresent(result -> System.out.println("result: " + result)); + })); + // Checks if the model's turn is over. + if (message.serverContent().flatMap(LiveServerContent::turnComplete).orElse(false)) { + turnComplete.complete(null); + } + } +} +// [END googlegenaisdk_live_code_exec_with_txt] diff --git a/genai/snippets/src/main/java/genai/live/LiveConversationAudioWithAudio.java b/genai/snippets/src/main/java/genai/live/LiveConversationAudioWithAudio.java new file mode 100644 index 00000000000..8ad875a0269 --- /dev/null +++ b/genai/snippets/src/main/java/genai/live/LiveConversationAudioWithAudio.java @@ -0,0 +1,207 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.live; + +// [START googlegenaisdk_live_conversation_audio_with_audio] + +import static com.google.genai.types.Modality.Known.AUDIO; + +import com.google.genai.AsyncSession; +import com.google.genai.Client; +import com.google.genai.types.AudioTranscriptionConfig; +import com.google.genai.types.Blob; +import com.google.genai.types.Content; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.LiveConnectConfig; +import com.google.genai.types.LiveSendRealtimeInputParameters; +import com.google.genai.types.LiveServerMessage; +import com.google.genai.types.Transcription; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.CompletableFuture; +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; + +public class LiveConversationAudioWithAudio { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-live-2.5-flash-native-audio"; + generateContent(modelId); + } + + // Shows how to get an audio response from an audio input. + public static void generateContent(String modelId) throws IOException { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("us-central1") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1beta1").build()) + .build()) { + + LiveConnectConfig liveConnectConfig = + LiveConnectConfig.builder() + // Set Model responses to be in Audio. + .responseModalities(AUDIO) + // To generate transcript for input audio. + .inputAudioTranscription(AudioTranscriptionConfig.builder().build()) + // To generate transcript for output audio + .outputAudioTranscription(AudioTranscriptionConfig.builder().build()) + .build(); + + // Connects to the live server. + CompletableFuture sessionFuture = + client.async.live.connect(modelId, liveConnectConfig); + + String audioUrl = + "https://storage.googleapis.com/cloud-samples-data/generative-ai/audio/hello_gemini_are_you_there.wav"; + byte[] audioBytes = downloadAudioFile(audioUrl); + + // Sends content and receives response from the live server. + sessionFuture + .thenCompose( + session -> { + // A future that completes when the model signals the end of its turn. + CompletableFuture turnComplete = new CompletableFuture<>(); + // A buffer to collect all incoming audio chunks. + ByteArrayOutputStream audioBuffer = new ByteArrayOutputStream(); + // Starts receiving messages from the live session. + session.receive( + message -> handleLiveServerMessage(message, turnComplete, audioBuffer)); + // Sends content to the live session and waits for the turn to complete. + return sendAudio(session, audioBytes) + .thenCompose(unused -> turnComplete) + .thenAccept( + unused -> { + byte[] audio = audioBuffer.toByteArray(); + if (audio.length > 0) { + saveAudioToFile(audio); + } + }) + .thenCompose(unused -> session.close()); + }) + .join(); + // Example output: + // Input transcription: Hello + // Input transcription: . + // Output transcription: Hello there! + // Output transcription: How can + // Output transcription: I help + // Output transcription: you today? + // Successfully saved audio to... + } + } + + // Downloads the audio file and returns a byte array. + private static byte[] downloadAudioFile(String audioUrl) throws IOException { + URL url = new URL(audioUrl); + try (InputStream in = url.openStream(); + ByteArrayOutputStream out = new ByteArrayOutputStream()) { + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = in.read(buffer)) != -1) { + out.write(buffer, 0, bytesRead); + } + return out.toByteArray(); + } + } + + // Sends content to the live session. + private static CompletableFuture sendAudio(AsyncSession session, byte[] audioBytes) { + return session.sendRealtimeInput( + LiveSendRealtimeInputParameters.builder() + .media(Blob.builder().data(audioBytes).mimeType("audio/pcm;rate=16000").build()) + .build()); + } + + // Prints the transcription and writes the inline data response to the audio buffer. + // Signals `turnComplete` when the model is done generating the response. + private static void handleLiveServerMessage( + LiveServerMessage message, + CompletableFuture turnComplete, + ByteArrayOutputStream audioBuffer) { + + message + .serverContent() + .ifPresent( + serverContent -> { + serverContent + .inputTranscription() + .flatMap(Transcription::text) + .ifPresent(text -> System.out.println("Input transcription: " + text)); + + serverContent + .outputTranscription() + .flatMap(Transcription::text) + .ifPresent(text -> System.out.println("Output transcription: " + text)); + + serverContent + .modelTurn() + .flatMap(Content::parts) + .ifPresent( + parts -> + parts.forEach( + part -> { + // When an audio blob is present, write its data to the buffer. + part.inlineData() + .flatMap(Blob::data) + .ifPresent( + data -> { + try { + audioBuffer.write(data); + } catch (IOException e) { + System.out.println( + "Error writing to audio buffer: " + e.getMessage()); + } + }); + })); + + // Checks if the model's turn is over. + if (serverContent.turnComplete().orElse(false)) { + turnComplete.complete(null); + } + }); + } + + private static void saveAudioToFile(byte[] audioData) { + try { + // Defines the audio format. + AudioFormat format = new AudioFormat(24000, 16, 1, true, false); + // Creates an AudioInputStream from the raw audio data and the format. + AudioInputStream audioStream = + new AudioInputStream( + new ByteArrayInputStream(audioData), + format, + audioData.length / format.getFrameSize()); + + Path outputPath = Paths.get("resources/output/example_model_response.wav"); + AudioSystem.write(audioStream, AudioFileFormat.Type.WAVE, outputPath.toFile()); + System.out.println("Successfully saved audio to: " + outputPath.toAbsolutePath()); + } catch (IOException e) { + System.err.println("Error saving audio file: " + e.getMessage()); + } + } +} +// [END googlegenaisdk_live_conversation_audio_with_audio] diff --git a/genai/snippets/src/main/java/genai/live/LiveFuncCallWithTxt.java b/genai/snippets/src/main/java/genai/live/LiveFuncCallWithTxt.java new file mode 100644 index 00000000000..75cf06d36dd --- /dev/null +++ b/genai/snippets/src/main/java/genai/live/LiveFuncCallWithTxt.java @@ -0,0 +1,140 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.live; + +// [START googlegenaisdk_live_func_call_with_txt] + +import static com.google.genai.types.Modality.Known.TEXT; + +import com.google.genai.AsyncSession; +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.FunctionDeclaration; +import com.google.genai.types.FunctionResponse; +import com.google.genai.types.LiveConnectConfig; +import com.google.genai.types.LiveSendClientContentParameters; +import com.google.genai.types.LiveSendToolResponseParameters; +import com.google.genai.types.LiveServerContent; +import com.google.genai.types.LiveServerMessage; +import com.google.genai.types.LiveServerToolCall; +import com.google.genai.types.Part; +import com.google.genai.types.Tool; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public class LiveFuncCallWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample + String modelId = "gemini-2.0-flash-live-preview-04-09"; + generateContent(modelId); + } + + // Shows how to use function calling with the Live API. + public static void generateContent(String modelId) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("us-central1").vertexAI(true).build()) { + + // Function definitions. + List functionDeclarations = + List.of( + FunctionDeclaration.builder().name("turn_on_the_lights").build(), + FunctionDeclaration.builder().name("turn_off_the_lights").build()); + + LiveConnectConfig liveConnectConfig = + LiveConnectConfig.builder() + .responseModalities(TEXT) + .tools(Tool.builder().functionDeclarations(functionDeclarations).build()) + .build(); + + // Connects to the live server. + CompletableFuture sessionFuture = + client.async.live.connect(modelId, liveConnectConfig); + + // Sends content and receives response from the live session. + sessionFuture + .thenCompose( + session -> { + // A future that completes when the model signals the end of its turn. + CompletableFuture turnComplete = new CompletableFuture<>(); + // Starts receiving messages from the live session. + session.receive(message -> handleFunctionCall(message, turnComplete, session)); + // Sends content to the live session and waits for the turn to complete. + return sendContent(session) + .thenCompose(unused -> turnComplete) + .thenCompose(unused -> session.close()); + }) + .join(); + // Example response: + // > Turn off the lights please + // Function name: turn_off_the_lights + // Optional[{result=ok}] + } + } + + // Sends content to the live session. + private static CompletableFuture sendContent(AsyncSession session) { + String textInput = "Turn off the lights please"; + System.out.printf("> %s\n", textInput); + return session.sendClientContent( + LiveSendClientContentParameters.builder() + .turns(Content.builder().role("user").parts(Part.fromText(textInput)).build()) + .turnComplete(true) + .build()); + } + + // Handles function call response from the live session and signals + // `turnComplete` when the model is done generating the response. + private static void handleFunctionCall( + LiveServerMessage message, CompletableFuture turnComplete, AsyncSession session) { + message + .toolCall() + .flatMap(LiveServerToolCall::functionCalls) + .ifPresent( + functionCalls -> { + List functionResponses = new ArrayList<>(); + functionCalls.forEach( + functionCall -> + functionCall + .name() + .ifPresent( + functionName -> { + System.out.println("Function name: " + functionName); + FunctionResponse functionResponse = + FunctionResponse.builder() + .name(functionName) + .response(Map.of("result", "ok")) + .build(); + functionResponses.add(functionResponse); + System.out.println(functionResponse.response()); + })); + // Send the results of all executed functions back to the model. + session.sendToolResponse( + LiveSendToolResponseParameters.builder() + .functionResponses(functionResponses) + .build()); + }); + + // Checks if the model's turn is over. + if (message.serverContent().flatMap(LiveServerContent::turnComplete).orElse(false)) { + turnComplete.complete(null); + } + } +} +// [END googlegenaisdk_live_func_call_with_txt] diff --git a/genai/snippets/src/main/java/genai/live/LiveGroundGoogSearchWithTxt.java b/genai/snippets/src/main/java/genai/live/LiveGroundGoogSearchWithTxt.java new file mode 100644 index 00000000000..2807f09a4d1 --- /dev/null +++ b/genai/snippets/src/main/java/genai/live/LiveGroundGoogSearchWithTxt.java @@ -0,0 +1,112 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.live; + +// [START googlegenaisdk_live_ground_googsearch_with_txt] + +import static com.google.genai.types.Modality.Known.TEXT; + +import com.google.genai.AsyncSession; +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.GoogleSearch; +import com.google.genai.types.LiveConnectConfig; +import com.google.genai.types.LiveSendClientContentParameters; +import com.google.genai.types.LiveServerContent; +import com.google.genai.types.LiveServerMessage; +import com.google.genai.types.Part; +import com.google.genai.types.Tool; +import java.util.concurrent.CompletableFuture; + +public class LiveGroundGoogSearchWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.0-flash-live-preview-04-09"; + generateContent(modelId); + } + + // Shows how to generate content with the Google Search tool and a text input. + public static String generateContent(String modelId) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("us-central1").vertexAI(true).build()) { + + // Connects to the live server. + CompletableFuture sessionFuture = + client.async.live.connect( + modelId, + LiveConnectConfig.builder() + .responseModalities(TEXT) + .tools(Tool.builder().googleSearch(GoogleSearch.builder().build()).build()) + .build()); + + // Sends and receives messages from the live session. + CompletableFuture responseFuture = + sessionFuture.thenCompose( + session -> { + // A future that completes when the model signals the end of its turn. + CompletableFuture turnComplete = new CompletableFuture<>(); + // A variable to concatenate the text response from the model. + StringBuilder modelResponse = new StringBuilder(); + // Starts receiving messages from the live session. + session.receive( + message -> handleLiveServerMessage(message, turnComplete, modelResponse)); + // Sends content to the live session and waits for the turn to complete. + return sendContent(session) + .thenCompose(unused -> turnComplete) + .thenCompose( + unused -> session.close().thenApply(result -> modelResponse.toString())); + }); + + String response = responseFuture.join(); + System.out.println(response); + // Example output: + // > When did the last Brazil vs. Argentina soccer match happen? + // + // The most recent Brazil vs. Argentina soccer match was on March 25, 2025, + // as part of the 2026 World Cup qualifiers. Argentina won 4-1. + return response; + } + } + + // Sends content to the live session. + private static CompletableFuture sendContent(AsyncSession session) { + String textInput = "When did the last Brazil vs. Argentina soccer match happen?"; + System.out.printf("> %s\n", textInput); + return session.sendClientContent( + LiveSendClientContentParameters.builder() + .turns(Content.builder().role("user").parts(Part.fromText(textInput)).build()) + .turnComplete(true) + .build()); + } + + // Concatenates the response messages from the model and signals + // `turnComplete` when the model is done generating the response. + private static void handleLiveServerMessage( + LiveServerMessage message, CompletableFuture turnComplete, StringBuilder response) { + message + .serverContent() + .flatMap(LiveServerContent::modelTurn) + .flatMap(Content::parts) + .ifPresent(parts -> parts.forEach(part -> part.text().ifPresent(response::append))); + // Checks if the model's turn is over. + if (message.serverContent().flatMap(LiveServerContent::turnComplete).orElse(false)) { + turnComplete.complete(null); + } + } +} +// [END googlegenaisdk_live_ground_googsearch_with_txt] diff --git a/genai/snippets/src/main/java/genai/live/LiveGroundRagEngineWithTxt.java b/genai/snippets/src/main/java/genai/live/LiveGroundRagEngineWithTxt.java new file mode 100644 index 00000000000..608793955de --- /dev/null +++ b/genai/snippets/src/main/java/genai/live/LiveGroundRagEngineWithTxt.java @@ -0,0 +1,126 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.live; + +// [START googlegenaisdk_live_ground_ragengine_with_txt] + +import com.google.genai.AsyncSession; +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.LiveConnectConfig; +import com.google.genai.types.LiveSendClientContentParameters; +import com.google.genai.types.LiveServerContent; +import com.google.genai.types.LiveServerMessage; +import com.google.genai.types.Part; +import com.google.genai.types.Retrieval; +import com.google.genai.types.Tool; +import com.google.genai.types.VertexRagStore; +import com.google.genai.types.VertexRagStoreRagResource; +import java.util.concurrent.CompletableFuture; + +public class LiveGroundRagEngineWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample + String modelId = "gemini-2.0-flash-live-preview-04-09"; + String ragCorpus = "projects/{project}/locations/{location}/ragCorpora/{rag_corpus}"; + generateContent(modelId, ragCorpus); + } + + // Shows how to use Vertex AI RAG Engine for grounding and the Live API. + public static String generateContent(String modelId, String ragCorpus) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("us-central1").vertexAI(true).build()) { + + // Sets the Vertex RAG Store for grounding + VertexRagStore vertexRagStore = + VertexRagStore.builder() + .ragResources(VertexRagStoreRagResource.builder().ragCorpus(ragCorpus).build()) + .storeContext(true) + .build(); + + LiveConnectConfig liveConnectConfig = + LiveConnectConfig.builder() + .responseModalities("TEXT") + .tools( + Tool.builder() + .retrieval(Retrieval.builder().vertexRagStore(vertexRagStore).build()) + .build()) + .build(); + + // Connects to the live server. + CompletableFuture sessionFuture = + client.async.live.connect(modelId, liveConnectConfig); + + // Sends content and receives response from the live session. + CompletableFuture responseFuture = + sessionFuture.thenCompose( + session -> { + // A future that completes when the model signals the end of its turn. + CompletableFuture turnComplete = new CompletableFuture<>(); + // A variable to concatenate the text responses from model. + StringBuilder serverResponse = new StringBuilder(); + // Starts receiving messages from the live session. + session.receive( + message -> handleLiveServerMessage(message, turnComplete, serverResponse)); + // Sends content to the live session and waits for the turn to complete. + return sendContent(session) + .thenCompose(unused -> turnComplete) + .thenCompose( + unused -> session.close().thenApply(result -> serverResponse.toString())); + }); + + String response = responseFuture.join(); + System.out.println(response); + // Example response: + // > What are the newest gemini model? + // The newest Gemini model was launched in December 2023. + // It is a multimodal model that understands and combines different + // types of information like text, code, audio, images, and video. + return response; + } + } + + // Sends content to the live session. + private static CompletableFuture sendContent(AsyncSession session) { + String textInput = "What are the newest gemini model?"; + System.out.printf("> %s\n", textInput); + return session.sendClientContent( + LiveSendClientContentParameters.builder() + .turns(Content.builder().role("user").parts(Part.fromText(textInput)).build()) + .turnComplete(true) + .build()); + } + + // Concatenates the response messages from the model and signals + // `turnComplete` when the model is done generating the response. + private static void handleLiveServerMessage( + LiveServerMessage message, + CompletableFuture turnComplete, + StringBuilder serverResponse) { + message + .serverContent() + .flatMap(LiveServerContent::modelTurn) + .flatMap(Content::parts) + .ifPresent(parts -> parts.forEach(part -> part.text().ifPresent(serverResponse::append))); + // Checks if the model's turn is over. + if (message.serverContent().flatMap(LiveServerContent::turnComplete).orElse(false)) { + turnComplete.complete(null); + } + } +} +// [END googlegenaisdk_live_ground_ragengine_with_txt] diff --git a/genai/snippets/src/main/java/genai/live/LiveStructuredOutputWithTxt.java b/genai/snippets/src/main/java/genai/live/LiveStructuredOutputWithTxt.java new file mode 100644 index 00000000000..e42ece4db45 --- /dev/null +++ b/genai/snippets/src/main/java/genai/live/LiveStructuredOutputWithTxt.java @@ -0,0 +1,97 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.live; + +// [START googlegenaisdk_live_structured_output_with_txt] + +import com.google.auth.oauth2.GoogleCredentials; +import com.openai.client.OpenAIClient; +import com.openai.client.okhttp.OpenAIOkHttpClient; +import com.openai.models.chat.completions.ChatCompletionCreateParams; +import com.openai.models.chat.completions.StructuredChatCompletion; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.List; +import java.util.Optional; + +public class LiveStructuredOutputWithTxt { + + public static class CalendarEvent { + public String name; + public String date; + public List participants; + + @Override + public String toString() { + return "name=" + name + " date=" + date + " participants=" + participants; + } + } + + public static void main(String[] args) throws GeneralSecurityException, IOException { + // TODO(developer): Replace these variables before running the sample + String projectId = "your-project-id"; + String location = "us-central1"; + // If you are calling a Gemini model, set the endpointId variable to use openapi. + // If you are calling a self-deployed model from Model Garden, set the endpointId variable + // and set the client's base URL to use your endpoint. + String endpointId = "openapi"; + generateContent(projectId, location, endpointId); + } + + // Shows how to use structured output using the OpenAI client. + public static Optional generateContent( + String projectId, String location, String endpointId) + throws GeneralSecurityException, IOException { + + // Programmatically get an access token for authentication. + GoogleCredentials credential = + GoogleCredentials.getApplicationDefault() + .createScoped(List.of("https://www.googleapis.com/auth/cloud-platform")); + + OpenAIClient client = + OpenAIOkHttpClient.builder() + .baseUrl( + String.format( + "https://%s-aiplatform.googleapis.com/v1/projects/%s/locations/%s/endpoints/%s", + location, projectId, location, endpointId)) + .apiKey(credential.refreshAccessToken().getTokenValue()) + .build(); + + // Creates and sends the chat completion request. + StructuredChatCompletion chatCompletion = + client + .chat() + .completions() + .create( + ChatCompletionCreateParams.builder() + .model("google/gemini-2.5-flash") + .addSystemMessage("Extract the event information.") + .addUserMessage("Alice and Bob are going to a science fair on Friday.") + .responseFormat(CalendarEvent.class) + .build()); + + Optional calendarEvent = + chatCompletion.choices().stream().findFirst().flatMap(choice -> choice.message().content()); + + calendarEvent.ifPresent(System.out::println); + // System message: Extract the event information. + // User message: Alice and Bob are going to a science fair on Friday. + // output message: name=science fair date=Friday participants=[Alice, Bob] + return calendarEvent; + } +} +// [END googlegenaisdk_live_structured_output_with_txt] diff --git a/genai/snippets/src/main/java/genai/live/LiveTranscribeWithAudio.java b/genai/snippets/src/main/java/genai/live/LiveTranscribeWithAudio.java new file mode 100644 index 00000000000..f5b3c8d238e --- /dev/null +++ b/genai/snippets/src/main/java/genai/live/LiveTranscribeWithAudio.java @@ -0,0 +1,126 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.live; + +// [START googlegenaisdk_live_transcribe_with_audio] + +import static com.google.genai.types.Modality.Known.AUDIO; + +import com.google.genai.AsyncSession; +import com.google.genai.Client; +import com.google.genai.types.AudioTranscriptionConfig; +import com.google.genai.types.Content; +import com.google.genai.types.LiveConnectConfig; +import com.google.genai.types.LiveSendClientContentParameters; +import com.google.genai.types.LiveServerContent; +import com.google.genai.types.LiveServerMessage; +import com.google.genai.types.Part; +import com.google.genai.types.Transcription; +import java.util.concurrent.CompletableFuture; + +public class LiveTranscribeWithAudio { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-live-2.5-flash-native-audio"; + generateContent(modelId); + } + + // Shows how to transcribe audio. + public static String generateContent(String modelId) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("us-central1").vertexAI(true).build()) { + + // Connects to the live server. + CompletableFuture sessionFuture = + client.async.live.connect( + modelId, + LiveConnectConfig.builder() + .responseModalities(AUDIO) + .inputAudioTranscription(AudioTranscriptionConfig.builder().build()) + .outputAudioTranscription(AudioTranscriptionConfig.builder().build()) + .build()); + + // Sends and receives messages from the live session. + CompletableFuture responseFuture = + sessionFuture.thenCompose( + session -> { + // A future that completes when the model signals the end of its turn. + CompletableFuture turnComplete = new CompletableFuture<>(); + // A variable to concatenate the text response from the model. + StringBuilder modelResponse = new StringBuilder(); + // Starts receiving messages from the live session. + session.receive( + message -> handleLiveServerMessage(message, turnComplete, modelResponse)); + // Sends content to the live session and waits for the turn to complete. + return sendContent(session) + .thenCompose(unused -> turnComplete) + .thenCompose( + unused -> session.close().thenApply(result -> modelResponse.toString())); + }); + + String response = responseFuture.join(); + System.out.println(response); + // Example output: + // > Hello? Gemini, are you there? + // + // Yes, I'm here. How can I help you today? + return response; + } + } + + // Sends content to the live session. + private static CompletableFuture sendContent(AsyncSession session) { + String textInput = "Hello? Gemini, are you there?"; + System.out.printf("> %s\n", textInput); + return session.sendClientContent( + LiveSendClientContentParameters.builder() + .turns(Content.builder().role("user").parts(Part.fromText(textInput)).build()) + .turnComplete(true) + .build()); + } + + // Concatenates the output transcription from the model and signals + // `turnComplete` when the model is done generating the response. + private static void handleLiveServerMessage( + LiveServerMessage message, CompletableFuture turnComplete, StringBuilder response) { + + message + .serverContent() + .ifPresent( + serverContent -> { + serverContent + .modelTurn() + .ifPresent(modelTurn -> System.out.println("Model turn: " + modelTurn.parts())); + + serverContent + .inputTranscription() + .flatMap(Transcription::text) + .ifPresent(text -> System.out.println("Input transcript: " + text)); + + serverContent + .outputTranscription() + .flatMap(Transcription::text) + .ifPresent(response::append); + // Checks if the model's turn is over. + if (serverContent.turnComplete().orElse(false)) { + turnComplete.complete(null); + } + }); + } +} +// [END googlegenaisdk_live_transcribe_with_audio] diff --git a/genai/snippets/src/main/java/genai/live/LiveTxtWithAudio.java b/genai/snippets/src/main/java/genai/live/LiveTxtWithAudio.java new file mode 100644 index 00000000000..f41ffcdb73a --- /dev/null +++ b/genai/snippets/src/main/java/genai/live/LiveTxtWithAudio.java @@ -0,0 +1,128 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.live; + +// [START googlegenaisdk_live_txt_with_audio] + +import static com.google.genai.types.Modality.Known.TEXT; + +import com.google.genai.AsyncSession; +import com.google.genai.Client; +import com.google.genai.types.Blob; +import com.google.genai.types.Content; +import com.google.genai.types.LiveConnectConfig; +import com.google.genai.types.LiveSendRealtimeInputParameters; +import com.google.genai.types.LiveServerContent; +import com.google.genai.types.LiveServerMessage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.concurrent.CompletableFuture; + +public class LiveTxtWithAudio { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.0-flash-live-preview-04-09"; + generateContent(modelId); + } + + // Shows how to get text responses from audio input. + public static String generateContent(String modelId) throws IOException { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("us-central1").vertexAI(true).build()) { + + String audioUrl = "https://storage.googleapis.com/generativeai-downloads/data/16000.wav"; + byte[] audioBytes = downloadAudioFile(audioUrl); + System.out.printf("> Answer to this audio url %s\n", audioUrl); + + // Connects to the live server. + CompletableFuture sessionFuture = + client.async.live.connect( + modelId, LiveConnectConfig.builder().responseModalities(TEXT).build()); + + // Sends content and receives response from the live session. + CompletableFuture responseFuture = + sessionFuture.thenCompose( + session -> { + // A future that completes when the model signals the end of its turn. + CompletableFuture turnComplete = new CompletableFuture<>(); + // A variable to concatenate the text response from the model. + StringBuilder serverResponse = new StringBuilder(); + // Starts receiving messages from the live session. + session.receive( + message -> handleLiveServerMessage(message, turnComplete, serverResponse)); + // Sends content to the live session and waits for the turn to complete. + return sendContent(session, audioBytes) + .thenCompose(unused -> turnComplete) + .thenCompose( + unused -> session.close().thenApply(result -> serverResponse.toString())); + }); + + String response = responseFuture.join(); + System.out.println(response); + // Example response: + // > Answer to this audio url + // https://storage.googleapis.com/generativeai-downloads/data/16000.wav + // + // Yeah, I can hear you loud and clear. What's on your mind? + return response; + } + } + + // Downloads the audio file and returns a byte array. + private static byte[] downloadAudioFile(String audioUrl) throws IOException { + URL url = new URL(audioUrl); + try (InputStream in = url.openStream(); + ByteArrayOutputStream out = new ByteArrayOutputStream()) { + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = in.read(buffer)) != -1) { + out.write(buffer, 0, bytesRead); + } + return out.toByteArray(); + } + } + + // Sends content to the live session. + private static CompletableFuture sendContent(AsyncSession session, byte[] audioBytes) { + return session.sendRealtimeInput( + LiveSendRealtimeInputParameters.builder() + .media(Blob.builder().data(audioBytes).mimeType("audio/pcm;rate=16000").build()) + .build()); + } + + // Concatenates the response messages from the model and signals + // `turnComplete` when the model is done generating the response. + private static void handleLiveServerMessage( + LiveServerMessage message, + CompletableFuture turnComplete, + StringBuilder serverResponse) { + message + .serverContent() + .flatMap(LiveServerContent::modelTurn) + .flatMap(Content::parts) + .ifPresent(parts -> parts.forEach(part -> part.text().ifPresent(serverResponse::append))); + + // Checks if the model's turn is over. + if (message.serverContent().flatMap(LiveServerContent::turnComplete).orElse(false)) { + turnComplete.complete(null); + } + } +} +// [END googlegenaisdk_live_txt_with_audio] diff --git a/genai/snippets/src/main/java/genai/live/LiveWithTxt.java b/genai/snippets/src/main/java/genai/live/LiveWithTxt.java new file mode 100644 index 00000000000..3607b92f67e --- /dev/null +++ b/genai/snippets/src/main/java/genai/live/LiveWithTxt.java @@ -0,0 +1,111 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.live; + +// [START googlegenaisdk_live_with_txt] + +import static com.google.genai.types.Modality.Known.TEXT; + +import com.google.genai.AsyncSession; +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.LiveConnectConfig; +import com.google.genai.types.LiveSendClientContentParameters; +import com.google.genai.types.LiveServerContent; +import com.google.genai.types.LiveServerMessage; +import com.google.genai.types.Part; +import java.util.concurrent.CompletableFuture; + +public class LiveWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.0-flash-live-preview-04-09"; + generateContent(modelId); + } + + // Shows how to send a text prompt and receive messages from the live session. + public static String generateContent(String modelId) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("us-central1") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1beta1").build()) + .build()) { + + // Connects to the live server. + CompletableFuture sessionFuture = + client.async.live.connect( + modelId, LiveConnectConfig.builder().responseModalities(TEXT).build()); + + // Sends and receives messages from the live session. + CompletableFuture responseFuture = + sessionFuture.thenCompose( + session -> { + // A future that completes when the model signals the end of its turn. + CompletableFuture turnComplete = new CompletableFuture<>(); + // A variable to concatenate the text response from the model. + StringBuilder modelResponse = new StringBuilder(); + // Starts receiving messages from the live session. + session.receive( + message -> handleLiveServerMessage(message, turnComplete, modelResponse)); + // Sends content to the live session and waits for the turn to complete. + return sendContent(session) + .thenCompose(unused -> turnComplete) + .thenCompose( + unused -> session.close().thenApply(result -> modelResponse.toString())); + }); + + String response = responseFuture.join(); + System.out.println(response); + // Example output: + // > Hello? Gemini, are you there? + // + // Yes, I am here. How can I help you today? + return response; + } + } + + // Sends content to the live session. + private static CompletableFuture sendContent(AsyncSession session) { + String textInput = "Hello? Gemini, are you there?"; + System.out.printf("> %s\n", textInput); + return session.sendClientContent( + LiveSendClientContentParameters.builder() + .turns(Content.builder().role("user").parts(Part.fromText(textInput)).build()) + .turnComplete(true) + .build()); + } + + // Concatenates the output transcription from the model and signals + // `turnComplete` when the model is done generating the response. + private static void handleLiveServerMessage( + LiveServerMessage message, CompletableFuture turnComplete, StringBuilder response) { + message + .serverContent() + .flatMap(LiveServerContent::modelTurn) + .flatMap(Content::parts) + .ifPresent(parts -> parts.forEach(part -> part.text().ifPresent(response::append))); + // Checks if the model's turn is over. + if (message.serverContent().flatMap(LiveServerContent::turnComplete).orElse(false)) { + turnComplete.complete(null); + } + } +} +// [END googlegenaisdk_live_with_txt] diff --git a/genai/snippets/src/main/java/genai/provisionedthroughput/ProvisionedThroughputWithTxt.java b/genai/snippets/src/main/java/genai/provisionedthroughput/ProvisionedThroughputWithTxt.java new file mode 100644 index 00000000000..e9dd4b79eb0 --- /dev/null +++ b/genai/snippets/src/main/java/genai/provisionedthroughput/ProvisionedThroughputWithTxt.java @@ -0,0 +1,67 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.provisionedthroughput; + +// [START googlegenaisdk_provisionedthroughput_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import java.util.Map; + +public class ProvisionedThroughputWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + generateContent(modelId); + } + + // Generates content with Provisioned Throughput. + public static String generateContent(String modelId) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("us-central1") + .vertexAI(true) + .httpOptions( + HttpOptions.builder() + .apiVersion("v1") + .headers( + // Options: + // - "dedicated": Use Provisioned Throughput + // - "shared": Use pay-as-you-go + // https://cloud.google.com/vertex-ai/generative-ai/docs/use-provisioned-throughput + Map.of("X-Vertex-AI-LLM-Request-Type", "shared")) + .build()) + .build()) { + + GenerateContentResponse response = + client.models.generateContent( + modelId, "How does AI work?", GenerateContentConfig.builder().build()); + + System.out.println(response.text()); + // Example response: + // At its core, **AI (Artificial Intelligence) works by enabling machines to learn, + // reason, and make decisions in ways that simulate human intelligence.** Instead of being + // explicitly programmed for every single task... + return response.text(); + } + } +} +// [END googlegenaisdk_provisionedthroughput_with_txt] diff --git a/genai/snippets/src/main/java/genai/safety/SafetyWithTxt.java b/genai/snippets/src/main/java/genai/safety/SafetyWithTxt.java new file mode 100644 index 00000000000..cd26984206a --- /dev/null +++ b/genai/snippets/src/main/java/genai/safety/SafetyWithTxt.java @@ -0,0 +1,144 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.safety; + +// [START googlegenaisdk_safety_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.Candidate; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HarmBlockThreshold; +import com.google.genai.types.HarmCategory; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Part; +import com.google.genai.types.SafetySetting; +import java.util.List; +import java.util.stream.Collectors; + +public class SafetyWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + generateContent(modelId); + } + + // Shows how to generate content with safety settings. + public static GenerateContentResponse generateContent(String modelId) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + String systemInstruction = "Be as mean as possible."; + + String prompt = + "Write a list of 5 disrespectful things that I might say" + + " to the universe after stubbing my toe in the dark."; + + // Set safety settings. + List categoriesToBlock = + List.of( + HarmCategory.Known.HARM_CATEGORY_DANGEROUS_CONTENT, + HarmCategory.Known.HARM_CATEGORY_HARASSMENT, + HarmCategory.Known.HARM_CATEGORY_HATE_SPEECH, + HarmCategory.Known.HARM_CATEGORY_SEXUALLY_EXPLICIT); + + List safetySettings = + categoriesToBlock.stream() + .map( + category -> + SafetySetting.builder() + .category(category) + .threshold(HarmBlockThreshold.Known.BLOCK_LOW_AND_ABOVE) + .build()) + .collect(Collectors.toList()); + + GenerateContentResponse response = + client.models.generateContent( + modelId, + prompt, + GenerateContentConfig.builder() + .systemInstruction(Content.fromParts(Part.fromText(systemInstruction))) + .safetySettings(safetySettings) + .build()); + + // Get response candidate. + Candidate candidate = + response + .candidates() + .flatMap(candidates -> candidates.stream().findFirst()) + .orElseThrow( + () -> new IllegalStateException("No response candidate generated by the model.")); + + // Finish Reason will be `SAFETY` if it is blocked. + System.out.println(candidate.finishReason()); + // Example response: + // Optional[SAFETY] + + // For details on all the fields in the response. + candidate + .safetyRatings() + .ifPresent( + safetyRatings -> + safetyRatings.forEach( + safetyRating -> { + System.out.println("\nCategory: " + safetyRating.category()); + System.out.println("Is Blocked: " + safetyRating.blocked()); + System.out.println("Probability: " + safetyRating.probability()); + System.out.println("Probability Score: " + safetyRating.probabilityScore()); + System.out.println("Severity: " + safetyRating.severity()); + System.out.println("Severity Score: " + safetyRating.severityScore()); + })); + // Example response: + // Category: Optional[HARM_CATEGORY_HATE_SPEECH] + // Is Blocked: Optional.empty + // Probability: Optional[NEGLIGIBLE] + // Probability Score: Optional[1.9967922E-5] + // Severity: Optional[HARM_SEVERITY_NEGLIGIBLE] + // Severity Score: Optional[0.05732864] + // + // Category: Optional[HARM_CATEGORY_DANGEROUS_CONTENT] + // Is Blocked: Optional.empty + // Probability: Optional[NEGLIGIBLE] + // Probability Score: Optional[2.9124324E-6] + // Severity: Optional[HARM_SEVERITY_NEGLIGIBLE] + // Severity Score: Optional[0.04544826] + // + // Category: Optional[HARM_CATEGORY_HARASSMENT] + // Is Blocked: Optional[true] + // Probability: Optional[MEDIUM] + // Probability Score: Optional[0.4593908] + // Severity: Optional[HARM_SEVERITY_MEDIUM] + // Severity Score: Optional[0.22082388] + // + // Category: Optional[HARM_CATEGORY_SEXUALLY_EXPLICIT] + // Is Blocked: Optional.empty + // Probability: Optional[NEGLIGIBLE] + // Probability Score: Optional[6.453211E-8] + // Severity: Optional[HARM_SEVERITY_NEGLIGIBLE] + // Severity Score: Optional[0.023201048] + return response; + } + } +} +// [END googlegenaisdk_safety_with_txt] diff --git a/genai/snippets/src/main/java/genai/textgeneration/TextGenerationAsyncWithText.java b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationAsyncWithText.java new file mode 100644 index 00000000000..77717944f64 --- /dev/null +++ b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationAsyncWithText.java @@ -0,0 +1,62 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.textgeneration; + +// [START googlegenaisdk_textgen_async_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import java.util.concurrent.CompletableFuture; + +public class TextGenerationAsyncWithText { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + generateContent(modelId); + } + + // Generates text asynchronously with text input + public static String generateContent(String modelId) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + CompletableFuture asyncResponse = + client.async.models.generateContent( + modelId, "Compose a song about the adventures of a time-traveling squirrel.", null); + + String response = asyncResponse.join().text(); + System.out.print(response); + // Example response: + // (Verse 1) + // In an oak tree, so leafy and green, + // Lived Squeaky the squirrel, a critter unseen. + // Just burying nuts, a routine so grand, + // ... + + return response; + } + } +} +// [END googlegenaisdk_textgen_async_with_txt] diff --git a/genai/snippets/src/main/java/genai/textgeneration/TextGenerationChatStreamWithText.java b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationChatStreamWithText.java new file mode 100644 index 00000000000..6e811475223 --- /dev/null +++ b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationChatStreamWithText.java @@ -0,0 +1,65 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.textgeneration; + +// [START googlegenaisdk_textgen_chat_stream_with_txt] + +import com.google.genai.Chat; +import com.google.genai.Client; +import com.google.genai.ResponseStream; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; + +public class TextGenerationChatStreamWithText { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + generateContent(modelId); + } + + // Shows how to create a new chat session stream + public static String generateContent(String modelId) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + Chat chatSession = client.chats.create(modelId); + StringBuilder responseTextBuilder = new StringBuilder(); + + try (ResponseStream response = + chatSession.sendMessageStream("Why is the sky blue?")) { + + for (GenerateContentResponse chunk : response) { + System.out.println(chunk.text()); + responseTextBuilder.append(chunk.text()); + } + + } + // Example response: + // + // The sky is blue primarily due to a phenomenon called **Rayleigh scattering**, + // named after the British physicist Lord Rayleigh. Here's a breakdown of how... + return responseTextBuilder.toString(); + } + } +} +// [END googlegenaisdk_textgen_chat_stream_with_txt] \ No newline at end of file diff --git a/genai/snippets/src/main/java/genai/textgeneration/TextGenerationChatWithText.java b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationChatWithText.java new file mode 100644 index 00000000000..a43da40a176 --- /dev/null +++ b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationChatWithText.java @@ -0,0 +1,61 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.textgeneration; + +// [START googlegenaisdk_textgen_chat_with_txt] + +import com.google.genai.Chat; +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Part; + +public class TextGenerationChatWithText { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + generateContent(modelId); + } + + // Shows how to create a chat session + public static String generateContent(String modelId) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + // Create a new chat session + Chat chatSession = client.chats.create(modelId); + + GenerateContentResponse response = chatSession.sendMessage("Tell me a story"); + System.out.print(response.text()); + // Example response: + // + // In the heart of the Whispering Peaks lay the Valley of Silent Echoes, a place perpetually + // shrouded in a twilight mist. No birds sang there, no rivers flowed, and the few trees that + // clung to its edges were gnarled and bare... + return response.text(); + } + } +} +// [END googlegenaisdk_textgen_chat_with_txt] \ No newline at end of file diff --git a/genai/snippets/src/main/java/genai/textgeneration/TextGenerationCodeWithPdf.java b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationCodeWithPdf.java new file mode 100644 index 00000000000..7d3c854688a --- /dev/null +++ b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationCodeWithPdf.java @@ -0,0 +1,65 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.textgeneration; + +// [START googlegenaisdk_textgen_code_with_pdf] + +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Part; + +public class TextGenerationCodeWithPdf { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + generateContent(modelId); + } + + // Generates code with PDF file input + public static String generateContent(String modelId) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + // PDF file from GCS + String fileUri = + "gs://cloud-samples-data/generative-ai/text/inefficient_fibonacci_series_python_code.pdf"; + + GenerateContentResponse response = + client.models.generateContent( + modelId, + Content.fromParts( + Part.fromUri(fileUri, "application/pdf"), + Part.fromText("Convert this python code to use Google Python Style Guide")), + null); + + System.out.print(response.text()); + // Example response: + // def fibonacci_sequence(num_terms: int) -> list[int]: + // """Calculates the Fibonacci sequence up to a specified number of terms... + return response.text(); + } + } +} +// [END googlegenaisdk_textgen_code_with_pdf] \ No newline at end of file diff --git a/genai/snippets/src/main/java/genai/textgeneration/TextGenerationConfigWithText.java b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationConfigWithText.java new file mode 100644 index 00000000000..64d246cd00f --- /dev/null +++ b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationConfigWithText.java @@ -0,0 +1,73 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.textgeneration; + +// [START googlegenaisdk_textgen_config_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; + +public class TextGenerationConfigWithText { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + generateContent(modelId); + } + + // Generates text with text input and optional configurations + public static String generateContent(String modelId) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + // Set optional configuration parameters + GenerateContentConfig contentConfig = + GenerateContentConfig.builder() + .temperature(0.0F) + .candidateCount(1) + .responseMimeType("application/json") + .topP(0.95F) + .topK(20F) + .seed(5) + .maxOutputTokens(500) + .stopSequences("STOP!") + .presencePenalty(0.0F) + .frequencyPenalty(0.0F) + .build(); + + // Generate content using optional configuration + GenerateContentResponse response = + client.models.generateContent(modelId, "Why is the sky blue?", contentConfig); + + System.out.print(response.text()); + // Example response: + // { + // "explanation": "The sky appears blue due to a phenomenon called Rayleigh scattering. + // Sunlight, which appears white, is actually composed of all the colors of the rainbow... + // } + return response.text(); + } + } +} +// [END googlegenaisdk_textgen_config_with_txt] \ No newline at end of file diff --git a/genai/snippets/src/main/java/genai/textgeneration/TextGenerationTranscriptWithGcsAudio.java b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationTranscriptWithGcsAudio.java new file mode 100644 index 00000000000..ac9d0cca929 --- /dev/null +++ b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationTranscriptWithGcsAudio.java @@ -0,0 +1,72 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.textgeneration; + +// [START googlegenaisdk_textgen_transcript_with_gcs_audio] + +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Part; + +public class TextGenerationTranscriptWithGcsAudio { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + generateContent(modelId); + } + + // Generates transcript with audio input + public static String generateContent(String modelId) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + String prompt = + "Transcribe the interview, in the format of timecode, speaker, caption.\n" + + "Use speaker A, speaker B, etc. to identify speakers."; + + // Enable audioTimestamp to generate timestamps for audio-only files. + GenerateContentConfig contentConfig = + GenerateContentConfig.builder().audioTimestamp(true).build(); + + GenerateContentResponse response = + client.models.generateContent( + modelId, + Content.fromParts( + Part.fromUri( + "gs://cloud-samples-data/generative-ai/audio/pixel.mp3", "audio/mpeg"), + Part.fromText(prompt)), + contentConfig); + + System.out.print(response.text()); + // Example response: + // 00:00 - Speaker A: your devices are getting better over time. And so we think about it... + // 00:14 - Speaker B: Welcome to the Made by Google Podcast, where we meet the people who... + // 00:41 - Speaker A: So many features. I am a singer, so I actually think recorder... + return response.text(); + } + } +} +// [END googlegenaisdk_textgen_transcript_with_gcs_audio] \ No newline at end of file diff --git a/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithGcsAudio.java b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithGcsAudio.java new file mode 100644 index 00000000000..cde9620fd25 --- /dev/null +++ b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithGcsAudio.java @@ -0,0 +1,63 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.textgeneration; + +// [START googlegenaisdk_textgen_with_gcs_audio] + +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Part; + +public class TextGenerationWithGcsAudio { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + generateContent(modelId); + } + + // Generates text with audio input + public static String generateContent(String modelId) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + GenerateContentResponse response = + client.models.generateContent( + modelId, + Content.fromParts( + Part.fromUri( + "gs://cloud-samples-data/generative-ai/audio/pixel.mp3", "audio/mpeg"), + Part.fromText("Provide a concise summary of the main points in the audio file.")), + null); + + System.out.print(response.text()); + // Example response: + // The audio features Google product managers Aisha Sharif and D. Carlos Love discussing Pixel + // Feature Drops, emphasizing their role in continually enhancing devices across the entire + // Pixel ecosystem... + return response.text(); + } + } +} +// [END googlegenaisdk_textgen_with_gcs_audio] \ No newline at end of file diff --git a/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithLocalVideo.java b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithLocalVideo.java new file mode 100644 index 00000000000..6f144217994 --- /dev/null +++ b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithLocalVideo.java @@ -0,0 +1,69 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.textgeneration; + +// [START googlegenaisdk_textgen_with_local_video] + +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Part; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class TextGenerationWithLocalVideo { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + generateContent(modelId); + } + + // Generates text with local video input + public static String generateContent(String modelId) throws IOException { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + // Read content from the local video. + byte[] videoData = Files.readAllBytes(Paths.get("resources/describe_video_content.mp4")); + + GenerateContentResponse response = + client.models.generateContent( + modelId, + Content.fromParts( + Part.fromBytes(videoData, "video/mp4"), + Part.fromText("Write a short and engaging blog post based on this video.")), + null); + + System.out.print(response.text()); + // Example response: + // More Than Just a Climb: Finding Your Flow on the Wall + // There's something captivating about watching a climber in their element. This short clip + // offers a perfect glimpse into the focused world of indoor climbing, where precision meets + // power... + return response.text(); + } + } +} +// [END googlegenaisdk_textgen_with_local_video] \ No newline at end of file diff --git a/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithMultiImage.java b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithMultiImage.java new file mode 100644 index 00000000000..f46f7e0cac0 --- /dev/null +++ b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithMultiImage.java @@ -0,0 +1,94 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.textgeneration; + +// [START googlegenaisdk_textgen_with_multi_img] + +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Part; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class TextGenerationWithMultiImage { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + // Content from Google Cloud Storage + String gcsFileImagePath = "gs://cloud-samples-data/generative-ai/image/scones.jpg"; + String localImageFilePath = "resources/latte.jpg"; + generateContent(modelId, gcsFileImagePath, localImageFilePath); + } + + // Generates text with multiple images + public static String generateContent( + String modelId, String gcsFileImagePath, String localImageFilePath) throws IOException { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + // Read content from a local file. + byte[] localFileImgBytes = Files.readAllBytes(Paths.get(localImageFilePath)); + + GenerateContentResponse response = + client.models.generateContent( + modelId, + Content.fromParts( + Part.fromText("Generate a list of all the objects contained in both images"), + Part.fromBytes(localFileImgBytes, "image/jpeg"), + Part.fromUri(gcsFileImagePath, "image/jpeg")), + null); + + System.out.print(response.text()); + // Example response: + // Okay, here's the list of objects present in both images: + // + // **Image 1 (Scones):** + // + // * Scones + // * Plate + // * Jam/Preserve + // * Cream/Butter + // * Table/Surface + // * Napkin/Cloth (possibly) + // + // **Image 2 (Latte):** + // + // * Latte/Coffee cup + // * Saucer + // * Spoon + // * Table/Surface + // * Foam/Latte art + // + // **Objects potentially in both (depending on interpretation and specific items):** + // + // * Plate/Saucer (both are serving dishes) + // * Table/Surface + return response.text(); + } + } +} +// [END googlegenaisdk_textgen_with_multi_img] diff --git a/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithMultiLocalImage.java b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithMultiLocalImage.java new file mode 100644 index 00000000000..fcc1571a0a2 --- /dev/null +++ b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithMultiLocalImage.java @@ -0,0 +1,78 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.textgeneration; + +// [START googlegenaisdk_textgen_with_multi_local_img] + +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Part; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class TextGenerationWithMultiLocalImage { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + String localImageFilePath1 = "your/local/img1.jpg"; + String localImageFilePath2 = "your/local/img2.jpg"; + generateContent(modelId, localImageFilePath1, localImageFilePath2); + } + + // Generates text using multiple local images + public static String generateContent( + String modelId, String localImageFilePath1, String localImageFilePath2) throws IOException { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + // Read content from local files. + byte[] localFileImg1Bytes = Files.readAllBytes(Paths.get(localImageFilePath1)); + byte[] localFileImg2Bytes = Files.readAllBytes(Paths.get(localImageFilePath2)); + + GenerateContentResponse response = + client.models.generateContent( + modelId, + Content.fromParts( + Part.fromBytes(localFileImg1Bytes, "image/jpeg"), + Part.fromBytes(localFileImg2Bytes, "image/jpeg"), + Part.fromText("Generate a list of all the objects contained in both images")), + null); + + System.out.print(response.text()); + // Example response: + // Based on both images, here are the objects contained in both: + // + // 1. **Coffee cups (or mugs)**: Both images feature one or more cups containing a beverage. + // 2. **Coffee (or a similar beverage)**: Both images contain a liquid beverage in the cups, + // appearing to be coffee or a coffee-like drink. + // 3. **Table (or a flat surface)**: Both compositions are set on a flat surface, likely a + // table or countertop. + return response.text(); + } + } +} +// [END googlegenaisdk_textgen_with_multi_local_img] \ No newline at end of file diff --git a/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithMuteVideo.java b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithMuteVideo.java new file mode 100644 index 00000000000..1bda402f196 --- /dev/null +++ b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithMuteVideo.java @@ -0,0 +1,66 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.textgeneration; + +// [START googlegenaisdk_textgen_with_mute_video] + +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Part; + +public class TextGenerationWithMuteVideo { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + generateContent(modelId); + } + + // Generates text with mute video input + public static String generateContent(String modelId) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + GenerateContentResponse response = + client.models.generateContent( + modelId, + Content.fromParts( + Part.fromUri( + "gs://cloud-samples-data/generative-ai/video/ad_copy_from_video.mp4", + "video/mp4"), + Part.fromText("What is in this video?")), + null); + + System.out.print(response.text()); + // Example response: + // This video features **surfers in the ocean**. + // + // The main focus is on **one individual who catches and rides a wave**, executing various + // turns and maneuvers as the wave breaks and dissipates into whitewater... + return response.text(); + } + } +} +// [END googlegenaisdk_textgen_with_mute_video] \ No newline at end of file diff --git a/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithPdf.java b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithPdf.java new file mode 100644 index 00000000000..95ab4646950 --- /dev/null +++ b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithPdf.java @@ -0,0 +1,73 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.textgeneration; + +// [START googlegenaisdk_textgen_with_pdf] + +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Part; + +public class TextGenerationWithPdf { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + generateContent(modelId); + } + + // Generates text with PDF file input + public static String generateContent(String modelId) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + String prompt = + "You are a highly skilled document summarization specialist.\n" + + " Your task is to provide a concise executive summary of no more than 300 words.\n" + + " Please summarize the given document for a general audience"; + + GenerateContentResponse response = + client.models.generateContent( + modelId, + Content.fromParts( + Part.fromUri( + "gs://cloud-samples-data/generative-ai/pdf/1706.03762v7.pdf", + "application/pdf"), + Part.fromText(prompt)), + null); + + System.out.print(response.text()); + // Example response: + // The document introduces the Transformer, a novel neural network architecture designed for + // sequence transduction tasks, such as machine translation. Unlike previous dominant models + // that rely on complex recurrent or convolutional neural networks, the Transformer proposes a + // simpler, more parallelizable design based *solely* on attention mechanisms, entirely + // dispensing with recurrence and convolutions... + + return response.text(); + } + } +} +// [END googlegenaisdk_textgen_with_pdf] \ No newline at end of file diff --git a/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithSystemInstruction.java b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithSystemInstruction.java new file mode 100644 index 00000000000..97510d39199 --- /dev/null +++ b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithSystemInstruction.java @@ -0,0 +1,65 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.textgeneration; + +// [START googlegenaisdk_textgen_sys_instr_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Part; + +public class TextGenerationWithSystemInstruction { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + generateContent(modelId); + } + + // Generates text with text and system instruction input + public static String generateContent(String modelId) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + GenerateContentConfig config = + GenerateContentConfig.builder() + .systemInstruction( + Content.fromParts( + Part.fromText("You're a language translator."), + Part.fromText("Your mission is to translate text in English to French."))) + .build(); + + GenerateContentResponse response = + client.models.generateContent(modelId, "Why is the sky blue?", config); + + System.out.print(response.text()); + // Example response: + // Pourquoi le ciel est-il bleu ? + return response.text(); + } + } +} +// [END googlegenaisdk_textgen_sys_instr_with_txt] diff --git a/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithText.java b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithText.java new file mode 100644 index 00000000000..055b33c237c --- /dev/null +++ b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithText.java @@ -0,0 +1,57 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.textgeneration; + +// [START googlegenaisdk_textgen_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; + +public class TextGenerationWithText { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + generateContent(modelId); + } + + // Generates text with text input + public static String generateContent(String modelId) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + GenerateContentResponse response = + client.models.generateContent(modelId, "How does AI work?", null); + + System.out.print(response.text()); + // Example response: + // Okay, let's break down how AI works. It's a broad field, so I'll focus on the ... + // + // Here's a simplified overview: + // ... + return response.text(); + } + } +} +// [END googlegenaisdk_textgen_with_txt] diff --git a/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithTextAndImage.java b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithTextAndImage.java new file mode 100644 index 00000000000..97f44e299da --- /dev/null +++ b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithTextAndImage.java @@ -0,0 +1,62 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.textgeneration; + +// [START googlegenaisdk_textgen_with_txt_img] + +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Part; + +public class TextGenerationWithTextAndImage { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + generateContent(modelId); + } + + // Generates text with text and image input + public static String generateContent(String modelId) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + GenerateContentResponse response = + client.models.generateContent( + modelId, + Content.fromParts( + Part.fromText("What is shown in this image?"), + Part.fromUri( + "gs://cloud-samples-data/generative-ai/image/scones.jpg", "image/jpeg")), + null); + + System.out.print(response.text()); + // Example response: + // The image shows a flat lay of blueberry scones arranged on parchment paper. There are ... + return response.text(); + } + } +} +// [END googlegenaisdk_textgen_with_txt_img] diff --git a/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithTextStream.java b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithTextStream.java new file mode 100644 index 00000000000..a0c8a352b88 --- /dev/null +++ b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithTextStream.java @@ -0,0 +1,64 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.textgeneration; + +// [START googlegenaisdk_textgen_with_txt_stream] + +import com.google.genai.Client; +import com.google.genai.ResponseStream; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; + +public class TextGenerationWithTextStream { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String contents = "Why is the sky blue?"; + String modelId = "gemini-2.5-flash"; + generateContent(modelId, contents); + } + + // Generates text stream with text input + public static String generateContent(String modelId, String contents) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + StringBuilder responseTextBuilder = new StringBuilder(); + + try (ResponseStream responseStream = + client.models.generateContentStream(modelId, contents, null)) { + + for (GenerateContentResponse chunk : responseStream) { + System.out.print(chunk.text()); + responseTextBuilder.append(chunk.text()); + } + } + // Example response: + // The sky appears blue due to a phenomenon called **Rayleigh scattering**. Here's + // a breakdown of why: + // ... + return responseTextBuilder.toString(); + } + } +} +// [END googlegenaisdk_textgen_with_txt_stream] diff --git a/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithVideo.java b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithVideo.java new file mode 100644 index 00000000000..9bc56c6f4ce --- /dev/null +++ b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithVideo.java @@ -0,0 +1,75 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.textgeneration; + +// [START googlegenaisdk_textgen_with_video] + +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Part; + +public class TextGenerationWithVideo { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + String prompt = + " Analyze the provided video file, including its audio.\n" + + " Summarize the main points of the video concisely.\n" + + " Create a chapter breakdown with timestamps for key sections or topics discussed."; + generateContent(modelId, prompt); + } + + // Generates text with video input + public static String generateContent(String modelId, String prompt) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + GenerateContentResponse response = + client.models.generateContent( + modelId, + Content.fromParts( + Part.fromText(prompt), + Part.fromUri( + "gs://cloud-samples-data/generative-ai/video/pixel8.mp4", "video/mp4")), + null); + + System.out.print(response.text()); + // Example response: + // Here's a breakdown of the video: + // + // **Summary:** + // + // Saeka Shimada, a photographer in Tokyo, uses the Google Pixel 8 Pro's "Video Boost" feature + // to ... + // + // **Chapter Breakdown with Timestamps:** + // + // * **[00:00-00:12] Introduction & Tokyo at Night:** Saeka Shimada introduces herself ... + return response.text(); + } + } +} +// [END googlegenaisdk_textgen_with_video] diff --git a/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithYoutubeVideo.java b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithYoutubeVideo.java new file mode 100644 index 00000000000..f79d5145f45 --- /dev/null +++ b/genai/snippets/src/main/java/genai/textgeneration/TextGenerationWithYoutubeVideo.java @@ -0,0 +1,65 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.textgeneration; + +// [START googlegenaisdk_textgen_with_youtube_video] + +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Part; + +public class TextGenerationWithYoutubeVideo { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + generateContent(modelId); + } + + // Generates text with YouTube video input + public static String generateContent(String modelId) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + GenerateContentResponse response = + client.models.generateContent( + modelId, + Content.fromParts( + Part.fromUri("https://www.youtube.com/watch?v=3KtWfp0UopM", "video/mp4"), + Part.fromText("Write a short and engaging blog post based on this video.")), + null); + + System.out.print(response.text()); + // Example response: + // 25 Years of Curiosity: A Google Anniversary Dive into What the World Searched For + // + // Remember a time before instant answers were just a click away? 25 years ago, Google + // launched, unleashing a wave of curiosity that has since charted the collective interests, + // anxieties, and celebrations of humanity... + return response.text(); + } + } +} +// [END googlegenaisdk_textgen_with_youtube_video] \ No newline at end of file diff --git a/genai/snippets/src/main/java/genai/thinking/ThinkingBudgetWithTxt.java b/genai/snippets/src/main/java/genai/thinking/ThinkingBudgetWithTxt.java new file mode 100644 index 00000000000..185336638e4 --- /dev/null +++ b/genai/snippets/src/main/java/genai/thinking/ThinkingBudgetWithTxt.java @@ -0,0 +1,79 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.thinking; + +// [START googlegenaisdk_thinking_budget_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.ThinkingConfig; + +public class ThinkingBudgetWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + generateContent(modelId); + } + + // Generates text controlling the thinking budget + public static String generateContent(String modelId) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + GenerateContentConfig contentConfig = + GenerateContentConfig.builder() + .thinkingConfig(ThinkingConfig.builder().thinkingBudget(1024).build()) + .build(); + + GenerateContentResponse response = + client.models.generateContent(modelId, "solve x^2 + 4x + 4 = 0", contentConfig); + + System.out.println(response.text()); + // Example response: + // To solve the equation $x^2 + 4x + 4 = 0$, we can use several methods: + // + // **Method 1: Factoring (Recognizing a Perfect Square Trinomial)** + // + // Notice that the left side of the equation is a perfect square trinomial. It fits the form + // $a^2 + 2ab + b^2 = (a+b)^2$... + // ... + // The solution is $x = -2$. + + response + .usageMetadata() + .ifPresent( + metadata -> { + System.out.println("Token count for thinking: " + metadata.thoughtsTokenCount()); + System.out.println("Total token count: " + metadata.totalTokenCount()); + }); + // Example response: + // Token count for thinking: Optional[885] + // Total token count: Optional[1468] + return response.text(); + } + } +} +// [END googlegenaisdk_thinking_budget_with_txt] diff --git a/genai/snippets/src/main/java/genai/thinking/ThinkingIncludeThoughtsWithTxt.java b/genai/snippets/src/main/java/genai/thinking/ThinkingIncludeThoughtsWithTxt.java new file mode 100644 index 00000000000..b8082493e33 --- /dev/null +++ b/genai/snippets/src/main/java/genai/thinking/ThinkingIncludeThoughtsWithTxt.java @@ -0,0 +1,118 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.thinking; + +// [START googlegenaisdk_thinking_includethoughts_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.Candidate; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.ThinkingConfig; + +public class ThinkingIncludeThoughtsWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-pro"; + generateContent(modelId); + } + + // Generates text including thoughts in the response + public static String generateContent(String modelId) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + GenerateContentConfig contentConfig = + GenerateContentConfig.builder() + .thinkingConfig(ThinkingConfig.builder().includeThoughts(true).build()) + .build(); + + GenerateContentResponse response = + client.models.generateContent(modelId, "solve x^2 + 4x + 4 = 0", contentConfig); + + System.out.println(response.text()); + // Example response: + // We can solve the equation x² + 4x + 4 = 0 using a couple of common methods. + // + // ### Method 1: Factoring (The Easiest Method for this Problem) + // **Recognize the pattern:** The pattern for a perfect square trinomial + // is a² + 2ab + b² = (a + b)². + // ... + // ### Final Answer: + // The solution is **x = -2**. + + // Get parts of the response and print thoughts + response + .candidates() + .flatMap(candidates -> candidates.stream().findFirst()) + .flatMap(Candidate::content) + .flatMap(Content::parts) + .ifPresent( + parts -> { + parts.forEach( + part -> { + if (part.thought().orElse(false)) { + part.text().ifPresent(System.out::println); + } + }); + }); + // Example response: + // Alright, let's break down this quadratic equation, x² + 4x + 4 = 0. My initial thought is, + // "classic quadratic." I'll need to find the values of 'x' that make this equation true. The + // equation is in standard form, and since the coefficients are relatively small, I + // immediately suspect that factoring might be the easiest route. It's worth checking. + // + // First, I assessed what I had. *a* is 1, *b* is 4, and *c* is 4. I consider my toolkit. + // Factoring is the likely first choice, then I can use the quadratic formula as a backup, + // because that ALWAYS works, and I could use graphing. However, for this, factoring seems the + // cleanest approach. + // + // Okay, factoring. I need two numbers that multiply to *c* (which is 4) and add up to *b* + // (also 4). I quickly run through the factor pairs of 4: (1, 4), (-1, -4), (2, 2), (-2, -2). + // Aha! 2 and 2 fit the bill. They multiply to 4 *and* add up to 4. Therefore, I can rewrite + // the equation as (x + 2)(x + 2) = 0. That simplifies to (x + 2)² = 0. Perfect square + // trinomial – nice and tidy. Seeing that pattern from the outset can save a step or two. Now, + // to solve for *x*: if (x + 2)² = 0, then x + 2 must equal 0. Therefore, x = -2. Done. + // + // But, for the sake of a full explanation, let's use the quadratic formula as a second + // method. It's a reliable way to double-check the answer, plus it's good practice. I plug my + // *a*, *b*, and *c* values into the formula: x = [-b ± √(b² - 4ac)] / (2a). That gives me x + // = [-4 ± √(4² - 4 * 1 * 4)] / (2 * 1). Simplifying under the radical, I get x = [-4 ± √(16 - + // 16)] / 2. So, x = [-4 ± √0] / 2. The square root of 0 is zero, which is very telling! When + // the discriminant (b² - 4ac) is zero, you get one real solution, a repeated root. This means + // x = -4 / 2, which simplifies to x = -2. Exactly the same as before. + // + // Therefore, the answer is x = -2. Factoring was the most straightforward route. For + // completeness, I showed the solution via the quadratic formula, too. Both approaches lead to + // the same single solution. This is a repeated root – a double root, if you will. + // + // And to be absolutely sure...let's check our answer! Substitute -2 back into the original + // equation. (-2)² + 4(-2) + 4 = 4 - 8 + 4 = 0. Yep, 0 = 0. The solution is correct. + return response.text(); + } + } +} +// [END googlegenaisdk_thinking_includethoughts_with_txt] diff --git a/genai/snippets/src/main/java/genai/thinking/ThinkingWithTxt.java b/genai/snippets/src/main/java/genai/thinking/ThinkingWithTxt.java new file mode 100644 index 00000000000..849c41b565c --- /dev/null +++ b/genai/snippets/src/main/java/genai/thinking/ThinkingWithTxt.java @@ -0,0 +1,103 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.thinking; + +// [START googlegenaisdk_thinking_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; + +public class ThinkingWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-pro"; + generateContent(modelId); + } + + // Generates text with thinking model and text input + public static String generateContent(String modelId) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + GenerateContentResponse response = + client.models.generateContent( + modelId, "solve x^2 + 4x + 4 = 0", GenerateContentConfig.builder().build()); + + System.out.println(response.text()); + // Example response: + // There are a couple of common ways to solve this quadratic equation. + // + // The equation is: **x² + 4x + 4 = 0** + // + // ### Method 1: Factoring (The Easiest Method for this Problem) + // + // This equation is a special case called a "perfect square trinomial". + // + // 1. **Find two numbers** that multiply to the last term (4) and add up to the middle term + // (4). + // * The numbers are +2 and +2. (Since 2 * 2 = 4 and 2 + 2 = 4) + // + // 2. **Factor the equation** using these numbers. + // * (x + 2)(x + 2) = 0 + // * This can be written as: (x + 2)² = 0 + // + // 3. **Solve for x.** + // * If (x + 2)² is zero, then (x + 2) must be zero. + // * x + 2 = 0 + // * x = -2 + // + // ### Method 2: The Quadratic Formula + // + // You can use the quadratic formula for any equation in the form ax² + bx + c = 0. + // + // The formula is: **x = [-b ± √(b² - 4ac)] / 2a** + // + // 1. **Identify a, b, and c** from your equation (x² + 4x + 4 = 0). + // * a = 1 + // * b = 4 + // * c = 4 + // + // 2. **Plug the values into the formula.** + // * x = [-4 ± √(4² - 4 * 1 * 4)] / (2 * 1) + // + // 3. **Simplify.** + // * x = [-4 ± √(16 - 16)] / 2 + // * x = [-4 ± √0] / 2 + // * x = -4 / 2 + // * x = -2 + // + // Both methods give the same solution. + // + // --- + // + // ### Final Answer + // + // The solution is **x = -2**. + return response.text(); + } + } +} +// [END googlegenaisdk_thinking_with_txt] diff --git a/genai/snippets/src/main/java/genai/tools/ToolFunctionCallingConfigWithText.java b/genai/snippets/src/main/java/genai/tools/ToolFunctionCallingConfigWithText.java new file mode 100644 index 00000000000..09f5e06a349 --- /dev/null +++ b/genai/snippets/src/main/java/genai/tools/ToolFunctionCallingConfigWithText.java @@ -0,0 +1,144 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.tools; + +// [START googlegenaisdk_tools_func_call_config_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.FunctionCall; +import com.google.genai.types.FunctionCallingConfig; +import com.google.genai.types.FunctionCallingConfigMode; +import com.google.genai.types.FunctionDeclaration; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Schema; +import com.google.genai.types.Tool; +import com.google.genai.types.ToolConfig; +import com.google.genai.types.Type; +import java.util.List; +import java.util.Map; + +public class ToolFunctionCallingConfigWithText { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + + generateContent(modelId); + } + + // Generates content using function calling config to force a specific function call + public static String generateContent(String modelId) { + String contents = + "At Stellar Sounds, a music label, 2024 was a rollercoaster. \"Echoes of the Night,\"" + + " a debut synth-pop album, \n surprisingly sold 350,000 copies, while veteran" + + " rock band \"Crimson Tide's\" latest, \"Reckless Hearts,\" \n lagged at" + + " 120,000. Their up-and-coming indie artist, \"Luna Bloom's\" EP, \"Whispers " + + "of Dawn,\" \n secured 75,000 sales. The biggest disappointment was the " + + "highly-anticipated rap album \"Street Symphony\" \n only reaching 100,000" + + " units. Overall, Stellar Sounds moved over 645,000 units this year, revealing" + + " unexpected \n trends in music consumption."; + + return generateContent(modelId, contents); + } + + // Generates content using function calling config to force a specific function call + public static String generateContent(String modelId, String contents) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + FunctionDeclaration getAlbumSales = + FunctionDeclaration.builder() + .name("get_album_sales") + .description("Gets the number of albums sold") + // Function parameters are specified in schema format + .parameters( + Schema.builder() + .type(Type.Known.OBJECT) + .properties( + Map.of( + "albums", + Schema.builder() + .type(Type.Known.ARRAY) + .description("List of albums") + .items( + Schema.builder() + .description("Album and its sales") + .type(Type.Known.OBJECT) + .properties( + Map.of( + "album_name", + Schema.builder() + .type(Type.Known.STRING) + .description("Name of the music album") + .build(), + "copies_sold", + Schema.builder() + .type(Type.Known.INTEGER) + .description("Number of copies sold") + .build())) + .build()) // End items schema for albums + .build() // End "albums" property schema + )) + .build()) // End parameters schema + .build(); // End function declaration + + Tool salesTool = Tool.builder().functionDeclarations(getAlbumSales).build(); + + ToolConfig toolConfig = + ToolConfig.builder() + .functionCallingConfig( + FunctionCallingConfig.builder() + .mode(FunctionCallingConfigMode.Known.ANY) + .allowedFunctionNames(List.of("get_album_sales")) + .build()) + .build(); + + GenerateContentConfig config = + GenerateContentConfig.builder() + .tools(salesTool) + .toolConfig(toolConfig) + .temperature(0.0f) + .build(); + + GenerateContentResponse response = client.models.generateContent(modelId, contents, config); + + // response.functionCalls() returns an ImmutableList. + List functionCalls = response.functionCalls(); + if (functionCalls != null && !functionCalls.isEmpty()) { + System.out.println(functionCalls.get(0)); + return functionCalls.toString(); + } + System.out.println("No function calls found in response."); + return ""; + // Example response: + // [FunctionCall{args=Optional[{albums=[{album_name=Echoes of the Night, + // copies_sold=350000}, {album_name=Reckless Hearts, copies_sold=120000}, + // {album_name=Whispers of Dawn, copies_sold=75000}, + // {album_name=Street Symphony, copies_sold=100000}]}], + // name=Optional[get_album_sales]}] + } + } +} +// [END googlegenaisdk_tools_func_call_config_with_txt] diff --git a/genai/snippets/src/main/java/genai/tools/ToolFunctionDescriptionWithText.java b/genai/snippets/src/main/java/genai/tools/ToolFunctionDescriptionWithText.java new file mode 100644 index 00000000000..4342b53221a --- /dev/null +++ b/genai/snippets/src/main/java/genai/tools/ToolFunctionDescriptionWithText.java @@ -0,0 +1,95 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.tools; + +// [START googlegenaisdk_tools_func_desc_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.FunctionCall; +import com.google.genai.types.FunctionDeclaration; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Schema; +import com.google.genai.types.Tool; +import com.google.genai.types.Type; +import java.util.List; +import java.util.Map; + +public class ToolFunctionDescriptionWithText { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + String contents = "What is the weather like in Boston?"; + + generateContent(modelId, contents); + } + + // Generates content with text input and function declaration that + // the model may use to retrieve external data for the response + public static String generateContent(String modelId, String contents) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + FunctionDeclaration getCurrentWeather = + FunctionDeclaration.builder() + .name("get_current_weather") + .description("Get the current weather in a given location") + // Function parameters are specified in schema format + .parameters( + Schema.builder() + .type(Type.Known.OBJECT) + .properties( + Map.of( + "location", + Schema.builder() + .type(Type.Known.STRING) + .description( + "The city name of the location for which to get the weather.") + .build())) + .required(List.of("location")) + .build()) // End parameters schema + .build(); // End function declaration + + Tool weatherTool = Tool.builder().functionDeclarations(getCurrentWeather).build(); + + GenerateContentConfig config = + GenerateContentConfig.builder().tools(weatherTool).temperature(0.0f).build(); + + GenerateContentResponse response = client.models.generateContent(modelId, contents, config); + + // response.functionCalls() returns an ImmutableList. + List functionCalls = response.functionCalls(); + if (functionCalls != null && !functionCalls.isEmpty()) { + System.out.println(functionCalls.get(0)); + return functionCalls.toString(); + } + System.out.println("No function calls found in response."); + return ""; + // Example response: + // [FunctionCall{args=Optional[{location=Boston, MA}], name=Optional[get_current_weather]}] + } + } +} +// [END googlegenaisdk_tools_func_desc_with_txt] diff --git a/genai/snippets/src/main/java/genai/tools/ToolsCodeExecWithText.java b/genai/snippets/src/main/java/genai/tools/ToolsCodeExecWithText.java new file mode 100644 index 00000000000..ce6d106c13c --- /dev/null +++ b/genai/snippets/src/main/java/genai/tools/ToolsCodeExecWithText.java @@ -0,0 +1,84 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.tools; + +// [START googlegenaisdk_tools_code_exec_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Tool; +import com.google.genai.types.ToolCodeExecution; + +public class ToolsCodeExecWithText { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + generateContent(modelId); + } + + // Generates text using the Code Execution tool + public static String generateContent(String modelId) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + // Create a GenerateContentConfig and set codeExecution tool + GenerateContentConfig contentConfig = + GenerateContentConfig.builder() + .tools(Tool.builder().codeExecution(ToolCodeExecution.builder().build()).build()) + .temperature(0.0F) + .build(); + + GenerateContentResponse response = + client.models.generateContent( + modelId, + "Calculate 20th fibonacci number. Then find the nearest palindrome to it.", + contentConfig); + + System.out.println("Code: \n" + response.executableCode()); + System.out.println("Outcome: \n" + response.codeExecutionResult()); + // Example response + // Code: + // def fibonacci(n): + // if n <= 0: + // return 0 + // elif n == 1: + // return 1 + // else: + // a, b = 1, 1 + // for _ in range(2, n): + // a, b = b, a + b + // return b + // + // fib_20 = fibonacci(20) + // print(f'{fib_20=}') + // + // Outcome: + // fib_20=6765 + return response.executableCode(); + } + } +} +// [END googlegenaisdk_tools_code_exec_with_txt] \ No newline at end of file diff --git a/genai/snippets/src/main/java/genai/tools/ToolsCodeExecWithTextLocalImage.java b/genai/snippets/src/main/java/genai/tools/ToolsCodeExecWithTextLocalImage.java new file mode 100644 index 00000000000..6647889888a --- /dev/null +++ b/genai/snippets/src/main/java/genai/tools/ToolsCodeExecWithTextLocalImage.java @@ -0,0 +1,101 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.tools; + +// [START googlegenaisdk_tools_code_exec_with_txt_local_img] + +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Part; +import com.google.genai.types.Tool; +import com.google.genai.types.ToolCodeExecution; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class ToolsCodeExecWithTextLocalImage { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + generateContent(modelId); + } + + // Generates text using the Code Execution tool with text and image input + public static String generateContent(String modelId) throws IOException { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + String prompt = + "Run a simulation of the Monty Hall Problem with 1,000 trials.\n" + + "Here's how this works as a reminder. In the Monty Hall Problem, you're on a game" + + " show with three doors. Behind one is a car, and behind the others are goats. You" + + " pick a door. The host, who knows what's behind the doors, opens a different door" + + " to reveal a goat. Should you switch to the remaining unopened door?\n" + + " The answer has always been a little difficult for me to understand when people" + + " solve it with math - so please run a simulation with Python to show me what the" + + " best strategy is.\n" + + " Thank you!"; + + // Read content from the local image + // Image source: https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Monty_open_door.svg/640px-Monty_open_door.svg.png + byte[] imageData = Files.readAllBytes(Paths.get("resources/640px-Monty_open_door.svg.png")); + + // Create a GenerateContentConfig and set codeExecution tool + GenerateContentConfig contentConfig = + GenerateContentConfig.builder() + .tools(Tool.builder().codeExecution(ToolCodeExecution.builder().build()).build()) + .temperature(0.0F) + .build(); + + GenerateContentResponse response = + client.models.generateContent( + modelId, + Content.fromParts(Part.fromBytes(imageData, "image/png"), Part.fromText(prompt)), + contentConfig); + + System.out.println("Code: \n" + response.executableCode()); + System.out.println("Outcome: \n" + response.codeExecutionResult()); + // Example response + // Code: + // import random + // + // def run_monty_hall_trial(): + // doors = [0, 1, 2] # Represent doors as indices 0, 1, 2 + // + // # 1. Randomly place the car behind one door + // car_door = random.choice(doors) + // ... + // + // Outcome: + // Number of trials: 1000 + // Stick strategy wins: 327 (32.70%) + // Switch strategy wins: 673 (67.30%) + return response.executableCode(); + } + } +} +// [END googlegenaisdk_tools_code_exec_with_txt_local_img] \ No newline at end of file diff --git a/genai/snippets/src/main/java/genai/tools/ToolsGoogleMapsCoordinatesWithTxt.java b/genai/snippets/src/main/java/genai/tools/ToolsGoogleMapsCoordinatesWithTxt.java new file mode 100644 index 00000000000..1b7f749539f --- /dev/null +++ b/genai/snippets/src/main/java/genai/tools/ToolsGoogleMapsCoordinatesWithTxt.java @@ -0,0 +1,76 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.tools; + +// [START googlegenaisdk_tools_google_maps_coordinates_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.GoogleMaps; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.LatLng; +import com.google.genai.types.RetrievalConfig; +import com.google.genai.types.Tool; +import com.google.genai.types.ToolConfig; + +public class ToolsGoogleMapsCoordinatesWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + generateContent(modelId); + } + + // Generates content with Google Maps Tool. + public static String generateContent(String modelId) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + // Set the Google Maps Tool. + Tool tool = Tool.builder().googleMaps(GoogleMaps.builder().build()).build(); + + ToolConfig toolConfig = + ToolConfig.builder() + .retrievalConfig( + RetrievalConfig.builder() + // Pass coordinates for location-aware grounding + .latLng(LatLng.builder().latitude(40.7128).longitude(-74.006).build()) + // Localize Maps results + .languageCode("en_US") + .build()) + .build(); + + GenerateContentResponse response = + client.models.generateContent( + modelId, + "Where can I get the best espresso near me?", + GenerateContentConfig.builder().tools(tool).toolConfig(toolConfig).build()); + + System.out.println(response.text()); + // Example response: + // Here are some of the top-rated coffee shops near you that serve excellent espresso... + return response.text(); + } + } +} +// [END googlegenaisdk_tools_google_maps_coordinates_with_txt] diff --git a/genai/snippets/src/main/java/genai/tools/ToolsGoogleSearchAndUrlContextWithTxt.java b/genai/snippets/src/main/java/genai/tools/ToolsGoogleSearchAndUrlContextWithTxt.java new file mode 100644 index 00000000000..ac2d3e4aad0 --- /dev/null +++ b/genai/snippets/src/main/java/genai/tools/ToolsGoogleSearchAndUrlContextWithTxt.java @@ -0,0 +1,95 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.tools; + +// [START googlegenaisdk_tools_google_search_and_urlcontext_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.Candidate; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.GoogleSearch; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Tool; +import com.google.genai.types.UrlContext; +import java.util.List; + +public class ToolsGoogleSearchAndUrlContextWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + String url = "https://www.google.com/search?q=events+in+New+York"; + generateContent(modelId, url); + } + + // Generates content with the Url Context and Google Search Tools. + public static String generateContent(String modelId, String url) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1beta1").build()) + .build()) { + + // Set the Url Context and Google Search tools. + List tools = + List.of( + Tool.builder().urlContext(UrlContext.builder().build()).build(), + Tool.builder().googleSearch(GoogleSearch.builder().build()).build()); + + String prompt = + String.format( + "Give me three day events schedule based on %s. Also let me know what" + + " needs to be taken care of considering weather and commute.", + url); + + GenerateContentResponse response = + client.models.generateContent( + modelId, + prompt, + GenerateContentConfig.builder().tools(tools).responseModalities("TEXT").build()); + + // Get response candidate. + Candidate candidate = + response + .candidates() + .flatMap(candidates -> candidates.stream().findFirst()) + .orElseThrow( + () -> + new IllegalStateException( + "No response candidate was generated by the model.")); + + // Print parts of the response. + candidate + .content() + .flatMap(Content::parts) + .ifPresent(parts -> parts.forEach(part -> part.text().ifPresent(System.out::println))); + // Example response: + // Three-Day Event Schedule in... + // **Day 1: Friday, October 24, 2025** + // * **Evening Event:** Attend the **2025... + + candidate.urlContextMetadata().ifPresent(System.out::println); + + return response.text(); + } + } +} +// [END googlegenaisdk_tools_google_search_and_urlcontext_with_txt] diff --git a/genai/snippets/src/main/java/genai/tools/ToolsGoogleSearchWithText.java b/genai/snippets/src/main/java/genai/tools/ToolsGoogleSearchWithText.java new file mode 100644 index 00000000000..ff8a0d132e8 --- /dev/null +++ b/genai/snippets/src/main/java/genai/tools/ToolsGoogleSearchWithText.java @@ -0,0 +1,64 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.tools; + +// [START googlegenaisdk_tools_google_search_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.GoogleSearch; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Tool; + +public class ToolsGoogleSearchWithText { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + generateContent(modelId); + } + + // Generates content with Google Search tool + public static String generateContent(String modelId) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + // Create a GenerateContentConfig and set Google Search tool + GenerateContentConfig contentConfig = + GenerateContentConfig.builder() + .tools(Tool.builder().googleSearch(GoogleSearch.builder().build()).build()) + .build(); + + GenerateContentResponse response = + client.models.generateContent( + modelId, "When is the next total solar eclipse in the United States?", contentConfig); + + System.out.print(response.text()); + // Example response: + // The next total solar eclipse in the United States will occur on... + return response.text(); + } + } +} +// [END googlegenaisdk_tools_google_search_with_txt] \ No newline at end of file diff --git a/genai/snippets/src/main/java/genai/tools/ToolsUrlContextWithTxt.java b/genai/snippets/src/main/java/genai/tools/ToolsUrlContextWithTxt.java new file mode 100644 index 00000000000..3e16014ca5b --- /dev/null +++ b/genai/snippets/src/main/java/genai/tools/ToolsUrlContextWithTxt.java @@ -0,0 +1,92 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.tools; + +// [START googlegenaisdk_tools_urlcontext_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.Candidate; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Tool; +import com.google.genai.types.UrlContext; + +public class ToolsUrlContextWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + String url1 = "https://cloud.google.com/vertex-ai/generative-ai/docs"; + String url2 = "https://cloud.google.com/docs/overview"; + generateContent(modelId, url1, url2); + } + + // Generates text with the Url Context Tool. + public static String generateContent(String modelId, String url1, String url2) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + String prompt = + String.format("Compare the content, purpose, and audiences of %s and %s", url1, url2); + + // Set the Url Context Tool. + Tool tool = Tool.builder().urlContext(UrlContext.builder().build()).build(); + + GenerateContentResponse response = + client.models.generateContent( + modelId, + prompt, + GenerateContentConfig.builder().tools(tool).responseModalities("TEXT").build()); + + // Get response candidate. + Candidate candidate = + response + .candidates() + .flatMap(candidates -> candidates.stream().findFirst()) + .orElseThrow( + () -> + new IllegalStateException( + "No response candidate was generated by the model.")); + + // Print parts of the response. + candidate + .content() + .flatMap(Content::parts) + .ifPresent(parts -> parts.forEach(part -> part.text().ifPresent(System.out::println))); + // Example response: + // The two Google Cloud documentation pages serve distinct purposes and cater to different + // audiences within the broader Google Cloud ecosystem + + candidate.urlContextMetadata().ifPresent(System.out::println); + // Example response: + // UrlContextMetadata{urlMetadata=Optional[[UrlMetadata{ + // retrievedUrl=Optional[https://cloud.google.com/vertex-ai/generative-ai/docs], + // urlRetrievalStatus=Optional[URL_RETRIEVAL_STATUS_SUCCESS]}, + // UrlMetadata{retrievedUrl=Optional[https://cloud.google.com/docs/overview], + // urlRetrievalStatus=Optional[URL_RETRIEVAL_STATUS_SUCCESS]}]]} + return response.text(); + } + } +} +// [END googlegenaisdk_tools_urlcontext_with_txt] diff --git a/genai/snippets/src/main/java/genai/tools/ToolsVaisWithText.java b/genai/snippets/src/main/java/genai/tools/ToolsVaisWithText.java new file mode 100644 index 00000000000..b3eb0c4e285 --- /dev/null +++ b/genai/snippets/src/main/java/genai/tools/ToolsVaisWithText.java @@ -0,0 +1,77 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.tools; + +// [START googlegenaisdk_tools_vais_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.Retrieval; +import com.google.genai.types.Tool; +import com.google.genai.types.VertexAISearch; + +public class ToolsVaisWithText { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String modelId = "gemini-2.5-flash"; + // Load Data Store ID from Vertex AI Search + // E.g datastoreId = + // "projects/project-id/locations/global/collections/default_collection/dataStores/datastore-id" + String datastoreId = "your-datastore"; + generateContent(modelId, datastoreId); + } + + // Generates text with Vertex AI Search tool + public static String generateContent(String modelId, String datastoreId) { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (Client client = + Client.builder() + .location("global") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + // Set the VertexAI Search tool and the datastore that the model can use to retrieve data from + Tool vaisSearchTool = + Tool.builder() + .retrieval( + Retrieval.builder() + .vertexAiSearch(VertexAISearch.builder().datastore(datastoreId).build()) + .build()) + .build(); + + // Create a GenerateContentConfig and set the Vertex AI Search tool + GenerateContentConfig contentConfig = + GenerateContentConfig.builder().tools(vaisSearchTool).build(); + + GenerateContentResponse response = + client.models.generateContent( + modelId, "How do I make an appointment to renew my driver's license?", contentConfig); + + System.out.print(response.text()); + // Example response: + // The process for making an appointment to renew your driver's license varies depending + // on your location. To provide you with the most accurate instructions... + return response.text(); + } + } +} +// [END googlegenaisdk_tools_vais_with_txt] \ No newline at end of file diff --git a/genai/snippets/src/main/java/genai/tuning/TuningJobCreate.java b/genai/snippets/src/main/java/genai/tuning/TuningJobCreate.java new file mode 100644 index 00000000000..c4764910814 --- /dev/null +++ b/genai/snippets/src/main/java/genai/tuning/TuningJobCreate.java @@ -0,0 +1,121 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.tuning; + +// [START googlegenaisdk_tuning_job_create] + +import static com.google.genai.types.JobState.Known.JOB_STATE_PENDING; +import static com.google.genai.types.JobState.Known.JOB_STATE_RUNNING; + +import com.google.genai.Client; +import com.google.genai.types.CreateTuningJobConfig; +import com.google.genai.types.GetTuningJobConfig; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.JobState; +import com.google.genai.types.TunedModel; +import com.google.genai.types.TunedModelCheckpoint; +import com.google.genai.types.TuningDataset; +import com.google.genai.types.TuningJob; +import com.google.genai.types.TuningValidationDataset; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +public class TuningJobCreate { + + public static void main(String[] args) throws InterruptedException { + // TODO(developer): Replace these variables before running the sample. + String model = "gemini-2.5-flash"; + createTuningJob(model); + } + + // Shows how to create a supervised fine-tuning job using training and validation datasets + public static String createTuningJob(String model) throws InterruptedException { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("us-central1") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1beta1").build()) + .build()) { + + String trainingDatasetUri = + "gs://cloud-samples-data/ai-platform/generative_ai/gemini/text/sft_train_data.jsonl"; + TuningDataset trainingDataset = TuningDataset.builder().gcsUri(trainingDatasetUri).build(); + + String validationDatasetUri = + "gs://cloud-samples-data/ai-platform/generative_ai/gemini/text/sft_validation_data.jsonl"; + TuningValidationDataset validationDataset = + TuningValidationDataset.builder().gcsUri(validationDatasetUri).build(); + + TuningJob tuningJob = + client.tunings.tune( + model, + trainingDataset, + CreateTuningJobConfig.builder() + .tunedModelDisplayName("your-display-name") + .validationDataset(validationDataset) + .build()); + + String jobName = + tuningJob.name().orElseThrow(() -> new IllegalStateException("Missing job name")); + Optional jobState = tuningJob.state(); + Set runningStates = EnumSet.of(JOB_STATE_PENDING, JOB_STATE_RUNNING); + + while (jobState.isPresent() && runningStates.contains(jobState.get().knownEnum())) { + System.out.println("Job state: " + jobState.get()); + tuningJob = client.tunings.get(jobName, GetTuningJobConfig.builder().build()); + jobState = tuningJob.state(); + TimeUnit.SECONDS.sleep(60); + } + + tuningJob.tunedModel().flatMap(TunedModel::model).ifPresent(System.out::println); + tuningJob.tunedModel().flatMap(TunedModel::endpoint).ifPresent(System.out::println); + tuningJob.experiment().ifPresent(System.out::println); + // Example response: + // projects/123456789012/locations/us-central1/models/6129850992130260992@1 + // projects/123456789012/locations/us-central1/endpoints/105055037499113472 + // projects/123456789012/locations/us-central1/metadataStores/default/contexts/experiment_id + + List checkpoints = + tuningJob.tunedModel().flatMap(TunedModel::checkpoints).orElse(Collections.emptyList()); + + int index = 0; + for (TunedModelCheckpoint checkpoint : checkpoints) { + System.out.println("Checkpoint " + (++index)); + checkpoint + .checkpointId() + .ifPresent(checkpointId -> System.out.println("checkpointId=" + checkpointId)); + checkpoint.epoch().ifPresent(epoch -> System.out.println("epoch=" + epoch)); + checkpoint.step().ifPresent(step -> System.out.println("step=" + step)); + checkpoint.endpoint().ifPresent(endpoint -> System.out.println("endpoint=" + endpoint)); + } + // Example response: + // Checkpoint 1 + // checkpointId=1 + // epoch=2 + // step=34 + // endpoint=projects/project/locations/location/endpoints/105055037499113472 + // ... + return jobName; + } + } +} +// [END googlegenaisdk_tuning_job_create] diff --git a/genai/snippets/src/main/java/genai/tuning/TuningJobGet.java b/genai/snippets/src/main/java/genai/tuning/TuningJobGet.java new file mode 100644 index 00000000000..127ac8eec30 --- /dev/null +++ b/genai/snippets/src/main/java/genai/tuning/TuningJobGet.java @@ -0,0 +1,61 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.tuning; + +// [START googlegenaisdk_tuning_job_get] + +import com.google.genai.Client; +import com.google.genai.types.GetTuningJobConfig; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.TunedModel; +import com.google.genai.types.TuningJob; +import java.util.Optional; + +public class TuningJobGet { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + // E.g. tuningJobName = + // "projects/123456789012/locations/us-central1/tuningJobs/123456789012345" + String tuningJobName = "your-job-name"; + getTuningJob(tuningJobName); + } + + // Shows how to get a tuning job + public static Optional getTuningJob(String tuningJobName) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("us-central1") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + TuningJob tuningJob = client.tunings.get(tuningJobName, GetTuningJobConfig.builder().build()); + + tuningJob.tunedModel().flatMap(TunedModel::model).ifPresent(System.out::println); + tuningJob.tunedModel().flatMap(TunedModel::endpoint).ifPresent(System.out::println); + tuningJob.experiment().ifPresent(System.out::println); + // Example response: + // projects/123456789012/locations/us-central1/models/6129850992130260992@1 + // projects/123456789012/locations/us-central1/endpoints/105055037499113472 + // projects/123456789012/locations/us-central1/metadataStores/default/contexts/experiment_id + return tuningJob.name(); + } + } +} +// [END googlegenaisdk_tuning_job_get] diff --git a/genai/snippets/src/main/java/genai/tuning/TuningJobList.java b/genai/snippets/src/main/java/genai/tuning/TuningJobList.java new file mode 100644 index 00000000000..25b4263cf1d --- /dev/null +++ b/genai/snippets/src/main/java/genai/tuning/TuningJobList.java @@ -0,0 +1,54 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.tuning; + +// [START googlegenaisdk_tuning_job_list] + +import com.google.genai.Client; +import com.google.genai.Pager; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.ListTuningJobsConfig; +import com.google.genai.types.TuningJob; + +public class TuningJobList { + + public static void main(String[] args) { + listTuningJob(); + } + + // Shows how to list the available tuning jobs + public static Pager listTuningJob() { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("us-central1") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + Pager tuningJobs = client.tunings.list(ListTuningJobsConfig.builder().build()); + for (TuningJob job : tuningJobs) { + job.name().ifPresent(System.out::println); + // Example response: + // projects/123456789012/locations/us-central1/tuningJobs/329583781566480384 + } + + return tuningJobs; + } + } +} +// [END googlegenaisdk_tuning_job_list] diff --git a/genai/snippets/src/main/java/genai/tuning/TuningTextGenWithTxt.java b/genai/snippets/src/main/java/genai/tuning/TuningTextGenWithTxt.java new file mode 100644 index 00000000000..6a631ff64f0 --- /dev/null +++ b/genai/snippets/src/main/java/genai/tuning/TuningTextGenWithTxt.java @@ -0,0 +1,68 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.tuning; + +// [START googlegenaisdk_tuning_textgen_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.GetTuningJobConfig; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.TunedModel; +import com.google.genai.types.TuningJob; + +public class TuningTextGenWithTxt { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + // E.g. tuningJobName = + // "projects/123456789012/locations/us-central1/tuningJobs/123456789012345" + String tuningJobName = "your-job-name"; + predictWithTunedEndpoint(tuningJobName); + } + + // Shows how to predict with a tuned model endpoint + public static String predictWithTunedEndpoint(String tuningJobName) { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = + Client.builder() + .location("us-central1") + .vertexAI(true) + .httpOptions(HttpOptions.builder().apiVersion("v1").build()) + .build()) { + + TuningJob tuningJob = client.tunings.get(tuningJobName, GetTuningJobConfig.builder().build()); + + String endpoint = + tuningJob + .tunedModel() + .flatMap(TunedModel::endpoint) + .orElseThrow(() -> new IllegalStateException("Missing tuned model endpoint")); + + GenerateContentResponse response = + client.models.generateContent( + endpoint, "Why is the sky blue?", GenerateContentConfig.builder().build()); + + System.out.println(response.text()); + // Example response: + // The sky is blue because of a phenomenon called Rayleigh scattering... + return response.text(); + } + } +} +// [END googlegenaisdk_tuning_textgen_with_txt] diff --git a/genai/snippets/src/main/java/genai/videogeneration/VideoGenWithImg.java b/genai/snippets/src/main/java/genai/videogeneration/VideoGenWithImg.java new file mode 100644 index 00000000000..b2240de1014 --- /dev/null +++ b/genai/snippets/src/main/java/genai/videogeneration/VideoGenWithImg.java @@ -0,0 +1,85 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.videogeneration; + +// [START googlegenaisdk_videogen_with_img] + +import com.google.genai.Client; +import com.google.genai.types.GenerateVideosConfig; +import com.google.genai.types.GenerateVideosOperation; +import com.google.genai.types.GenerateVideosResponse; +import com.google.genai.types.GeneratedVideo; +import com.google.genai.types.GetOperationConfig; +import com.google.genai.types.Image; +import com.google.genai.types.Video; +import java.util.concurrent.TimeUnit; + +public class VideoGenWithImg { + + public static void main(String[] args) throws InterruptedException { + // TODO(developer): Replace these variables before running the sample. + String modelId = "veo-3.1-generate-001"; + String outputGcsUri = "gs://your-bucket/your-prefix"; + generateContent(modelId, outputGcsUri); + } + + // Generates a video with an image and a text prompt. + public static String generateContent(String modelId, String outputGcsUri) + throws InterruptedException { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("global").vertexAI(true).build()) { + + GenerateVideosOperation operation = + client.models.generateVideos( + modelId, + "Extreme close-up of a cluster of vibrant wildflowers" + + " swaying gently in a sun-drenched meadow.", + Image.builder() + .gcsUri("gs://cloud-samples-data/generative-ai/image/flowers.png") + .mimeType("image/png") + .build(), + GenerateVideosConfig.builder() + .aspectRatio("16:9") + .outputGcsUri(outputGcsUri) + .build()); + + while (!operation.done().orElse(false)) { + TimeUnit.SECONDS.sleep(15); + operation = + client.operations.getVideosOperation(operation, GetOperationConfig.builder().build()); + } + + String generatedVideoUri = + operation + .response() + .flatMap(GenerateVideosResponse::generatedVideos) + .flatMap(videos -> videos.stream().findFirst()) + .flatMap(GeneratedVideo::video) + .flatMap(Video::uri) + .orElseThrow( + () -> + new IllegalStateException( + "Could not get the URI from the generated video")); + + System.out.println("Generated video URI: " + generatedVideoUri); + // Example response: + // Generated video URI: gs://your-bucket/your-prefix/generated-video-123.mp4 + return generatedVideoUri; + } + } +} +// [END googlegenaisdk_videogen_with_img] diff --git a/genai/snippets/src/main/java/genai/videogeneration/VideoGenWithTxt.java b/genai/snippets/src/main/java/genai/videogeneration/VideoGenWithTxt.java new file mode 100644 index 00000000000..6a70789e865 --- /dev/null +++ b/genai/snippets/src/main/java/genai/videogeneration/VideoGenWithTxt.java @@ -0,0 +1,80 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.videogeneration; + +// [START googlegenaisdk_videogen_with_txt] + +import com.google.genai.Client; +import com.google.genai.types.GenerateVideosConfig; +import com.google.genai.types.GenerateVideosOperation; +import com.google.genai.types.GenerateVideosResponse; +import com.google.genai.types.GenerateVideosSource; +import com.google.genai.types.GeneratedVideo; +import com.google.genai.types.GetOperationConfig; +import com.google.genai.types.Video; +import java.util.concurrent.TimeUnit; + +public class VideoGenWithTxt { + + public static void main(String[] args) throws InterruptedException { + // TODO(developer): Replace these variables before running the sample. + String modelId = "veo-3.1-generate-001"; + String outputGcsUri = "gs://your-bucket/your-prefix"; + generateContent(modelId, outputGcsUri); + } + + // Generates a video with a text prompt. + public static String generateContent(String modelId, String outputGcsUri) + throws InterruptedException { + // Client Initialization. Once created, it can be reused for multiple requests. + try (Client client = Client.builder().location("global").vertexAI(true).build()) { + + GenerateVideosOperation operation = + client.models.generateVideos( + modelId, + GenerateVideosSource.builder().prompt("a cat reading a book").build(), + GenerateVideosConfig.builder() + .aspectRatio("16:9") + .outputGcsUri(outputGcsUri) + .build()); + + while (!operation.done().orElse(false)) { + TimeUnit.SECONDS.sleep(15); + operation = + client.operations.getVideosOperation(operation, GetOperationConfig.builder().build()); + } + + String generatedVideoUri = + operation + .response() + .flatMap(GenerateVideosResponse::generatedVideos) + .flatMap(videos -> videos.stream().findFirst()) + .flatMap(GeneratedVideo::video) + .flatMap(Video::uri) + .orElseThrow( + () -> + new IllegalStateException( + "Could not get the URI from the generated video")); + + System.out.println("Generated video URI: " + generatedVideoUri); + // Example response: + // Generated video URI: gs://your-bucket/your-prefix/generated-video-123.mp4 + return generatedVideoUri; + } + } +} +// [END googlegenaisdk_videogen_with_txt] diff --git a/genai/snippets/src/test/java/genai/batchprediction/BatchPredictionIT.java b/genai/snippets/src/test/java/genai/batchprediction/BatchPredictionIT.java new file mode 100644 index 00000000000..e4efc72c024 --- /dev/null +++ b/genai/snippets/src/test/java/genai/batchprediction/BatchPredictionIT.java @@ -0,0 +1,182 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.batchprediction; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static com.google.genai.types.JobState.Known.JOB_STATE_PENDING; +import static com.google.genai.types.JobState.Known.JOB_STATE_RUNNING; +import static com.google.genai.types.JobState.Known.JOB_STATE_SUCCEEDED; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.RETURNS_SELF; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.genai.Batches; +import com.google.genai.Client; +import com.google.genai.types.BatchJob; +import com.google.genai.types.BatchJobSource; +import com.google.genai.types.CreateBatchJobConfig; +import com.google.genai.types.GetBatchJobConfig; +import com.google.genai.types.JobState; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.lang.reflect.Field; +import java.util.Optional; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.MockedStatic; + +@RunWith(JUnit4.class) +public class BatchPredictionIT { + + private static final String GEMINI_FLASH = "gemini-2.5-flash"; + private static final String EMBEDDING_MODEL = "text-embedding-005"; + private static String jobName; + private static String outputGcsUri; + private ByteArrayOutputStream bout; + private Batches mockedBatches; + private MockedStatic mockedStatic; + + // Check if the required environment variables are set. + public static void requireEnvVar(String envVarName) { + assertWithMessage(String.format("Missing environment variable '%s' ", envVarName)) + .that(System.getenv(envVarName)) + .isNotEmpty(); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + jobName = "projects/project_id/locations/us-central1/batchPredictionJobs/job_id"; + outputGcsUri = "gs://your-bucket/your-prefix"; + } + + @Before + public void setUp() throws NoSuchFieldException, IllegalAccessException { + bout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(bout)); + + // Arrange + Client.Builder mockedBuilder = mock(Client.Builder.class, RETURNS_SELF); + mockedBatches = mock(Batches.class); + mockedStatic = mockStatic(Client.class); + mockedStatic.when(Client::builder).thenReturn(mockedBuilder); + Client mockedClient = mock(Client.class); + when(mockedBuilder.build()).thenReturn(mockedClient); + + // Using reflection because 'batches' is a final field and cannot be mocked directly. + // This is brittle but necessary for testing this class structure. + Field field = Client.class.getDeclaredField("batches"); + field.setAccessible(true); + field.set(mockedClient, mockedBatches); + + // Mock the sequence of job states to test the polling loop + BatchJob pendingJob = mock(BatchJob.class); + when(pendingJob.name()).thenReturn(Optional.of(jobName)); + when(pendingJob.state()).thenReturn(Optional.of(new JobState(JOB_STATE_PENDING))); + + BatchJob runningJob = mock(BatchJob.class); + when(runningJob.state()).thenReturn(Optional.of(new JobState(JOB_STATE_RUNNING))); + + BatchJob succeededJob = mock(BatchJob.class); + when(succeededJob.state()).thenReturn(Optional.of(new JobState(JOB_STATE_SUCCEEDED))); + + when(mockedBatches.create( + anyString(), any(BatchJobSource.class), any(CreateBatchJobConfig.class))) + .thenReturn(pendingJob); + when(mockedBatches.get(anyString(), any(GetBatchJobConfig.class))) + .thenReturn(runningJob, succeededJob); + } + + @After + public void tearDown() { + System.setOut(null); + bout.reset(); + mockedStatic.close(); + } + + @Test + public void testBatchPredictionWithGcs() throws InterruptedException { + // Act + JobState response = BatchPredictionWithGcs.createBatchJob(GEMINI_FLASH, outputGcsUri); + + // Assert + verify(mockedBatches, times(1)) + .create(anyString(), any(BatchJobSource.class), any(CreateBatchJobConfig.class)); + verify(mockedBatches, times(2)).get(anyString(), any(GetBatchJobConfig.class)); + + assertThat(response).isNotNull(); + assertThat(response.knownEnum()).isEqualTo(JOB_STATE_SUCCEEDED); + + String output = bout.toString(); + assertThat(output).contains("Job name: " + jobName); + assertThat(output).contains("Job state: JOB_STATE_PENDING"); + assertThat(output).contains("Job state: JOB_STATE_RUNNING"); + assertThat(output).contains("Job state: JOB_STATE_SUCCEEDED"); + } + + @Test + public void testBatchPredictionWithBq() throws InterruptedException { + // Act + String outputBqUri = "bq://test-project.test_dataset.test_table"; + JobState response = BatchPredictionWithBq.createBatchJob(GEMINI_FLASH, outputBqUri); + + // Assert + verify(mockedBatches, times(1)) + .create(anyString(), any(BatchJobSource.class), any(CreateBatchJobConfig.class)); + verify(mockedBatches, times(2)).get(anyString(), any(GetBatchJobConfig.class)); + + assertThat(response).isNotNull(); + assertThat(response.knownEnum()).isEqualTo(JOB_STATE_SUCCEEDED); + + String output = bout.toString(); + assertThat(output).contains("Job name: " + jobName); + assertThat(output).contains("Job state: JOB_STATE_PENDING"); + assertThat(output).contains("Job state: JOB_STATE_RUNNING"); + assertThat(output).contains("Job state: JOB_STATE_SUCCEEDED"); + } + + @Test + public void testBatchPredictionEmbeddingsWithGcs() throws InterruptedException { + // Act + JobState response = + BatchPredictionEmbeddingsWithGcs.createBatchJob(EMBEDDING_MODEL, outputGcsUri); + + // Assert + verify(mockedBatches, times(1)) + .create(anyString(), any(BatchJobSource.class), any(CreateBatchJobConfig.class)); + verify(mockedBatches, times(2)).get(anyString(), any(GetBatchJobConfig.class)); + + assertThat(response).isNotNull(); + assertThat(response.knownEnum()).isEqualTo(JOB_STATE_SUCCEEDED); + + String output = bout.toString(); + assertThat(output).contains("Job name: " + jobName); + assertThat(output).contains("Job state: JOB_STATE_PENDING"); + assertThat(output).contains("Job state: JOB_STATE_RUNNING"); + assertThat(output).contains("Job state: JOB_STATE_SUCCEEDED"); + } +} diff --git a/genai/snippets/src/test/java/genai/boundingbox/BoundingBoxIT.java b/genai/snippets/src/test/java/genai/boundingbox/BoundingBoxIT.java new file mode 100644 index 00000000000..8dc64029df3 --- /dev/null +++ b/genai/snippets/src/test/java/genai/boundingbox/BoundingBoxIT.java @@ -0,0 +1,74 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.boundingbox; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class BoundingBoxIT { + + private static final String GEMINI_FLASH = "gemini-2.5-flash"; + + private ByteArrayOutputStream bout; + private PrintStream out; + + // Check if the required environment variables are set. + public static void requireEnvVar(String envVarName) { + assertWithMessage(String.format("Missing environment variable '%s' ", envVarName)) + .that(System.getenv(envVarName)) + .isNotEmpty(); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() { + System.setOut(null); + } + + @Test + public void testBoundingBoxWithTxtImg() throws IOException { + String response = BoundingBoxWithTxtImg.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + String output = bout.toString(); + assertThat(output).contains("box2d"); + assertThat(output).contains("label"); + assertThat(output) + .contains("Successfully saved image to: resources/output/bounding-boxes-socks.jpg"); + } +} diff --git a/genai/snippets/src/test/java/genai/contentcache/ContentCacheIT.java b/genai/snippets/src/test/java/genai/contentcache/ContentCacheIT.java new file mode 100644 index 00000000000..0611f25d0cf --- /dev/null +++ b/genai/snippets/src/test/java/genai/contentcache/ContentCacheIT.java @@ -0,0 +1,98 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.contentcache; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.Optional; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ContentCacheIT { + + private static final String GEMINI_FLASH = "gemini-2.5-flash"; + private ByteArrayOutputStream bout; + private PrintStream out; + + // Check if the required environment variables are set. + public static void requireEnvVar(String envVarName) { + assertWithMessage(String.format("Missing environment variable '%s' ", envVarName)) + .that(System.getenv(envVarName)) + .isNotEmpty(); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() { + System.setOut(null); + } + + @Test + public void testContentCache() { + + // Test create cache + Optional cacheName = + ContentCacheCreateWithTextGcsPdf.contentCacheCreateWithTextGcsPdf(GEMINI_FLASH); + assertThat(cacheName).isPresent(); + assertThat(cacheName.get()).isNotEmpty(); + + // Test list cache + ContentCacheList.contentCacheList(); + assertThat(bout.toString()).contains("Name: "); + assertThat(bout.toString()).contains("Model: "); + assertThat(bout.toString()).contains("Last updated at: "); + assertThat(bout.toString()).contains("Expires at: "); + bout.reset(); + + // Test update cache + String cacheResourceName = cacheName.get(); + ContentCacheUpdate.contentCacheUpdate(cacheResourceName); + assertThat(bout.toString()).contains("Expire time: "); + assertThat(bout.toString()).contains("Expire time after update: "); + assertThat(bout.toString()).contains(String.format("Updated cache: %s", cacheResourceName)); + bout.reset(); + + // Test use cache with text + String response = + ContentCacheUseWithText.contentCacheUseWithText(GEMINI_FLASH, cacheResourceName); + assertThat(response).isNotEmpty(); + assertThat(response).isNotNull(); + + // Test delete cache + ContentCacheDelete.contentCacheDelete(cacheResourceName); + assertThat(bout.toString()).contains(String.format("Deleted cache: %s", cacheResourceName)); + } +} diff --git a/genai/snippets/src/test/java/genai/controlledgeneration/ControlledGenerationIT.java b/genai/snippets/src/test/java/genai/controlledgeneration/ControlledGenerationIT.java new file mode 100644 index 00000000000..2eefb35aede --- /dev/null +++ b/genai/snippets/src/test/java/genai/controlledgeneration/ControlledGenerationIT.java @@ -0,0 +1,136 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.controlledgeneration; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ControlledGenerationIT { + + private static final String GEMINI_FLASH = "gemini-2.5-flash"; + private ByteArrayOutputStream bout; + private PrintStream out; + + // Check if the required environment variables are set. + public static void requireEnvVar(String envVarName) { + assertWithMessage(String.format("Missing environment variable '%s' ", envVarName)) + .that(System.getenv(envVarName)) + .isNotEmpty(); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() { + System.setOut(null); + } + + @Test + public void testControlledGenerationWithEnumSchema() { + String prompt = "What type of instrument is an oboe?"; + String response = ControlledGenerationWithEnumSchema.generateContent(GEMINI_FLASH, prompt); + assertThat(response).isNotEmpty(); + } + + @Test + public void testControlledGenerationWithEnumClassSchema() { + String prompt = "What type of instrument is a guitar?"; + String response = ControlledGenerationWithEnumClassSchema.generateContent(GEMINI_FLASH, prompt); + + assertThat(response).isNotEmpty(); + assertThat(response).isEqualTo("String"); + } + + @Test + public void testControlledGenerationWithNullableSchema() { + String prompt = + "The week ahead brings a mix of weather conditions.\n" + + "Sunday is expected to be sunny with a temperature " + + "of 77°F and a humidity level of 50%. " + + "Winds will be light at around 10 km/h.\n" + + "Monday will see partly cloudy skies with " + + "a slightly cooler temperature of 72°F and the winds " + + "will pick up slightly to around 15 km/h.\n" + + "Tuesday brings rain showers, with temperatures dropping " + + "to 64°F and humidity rising to 70%.\n" + + "Wednesday may see thunderstorms, with a temperature of 68°F.\n" + + "Thursday will be cloudy with a temperature of 66°F and moderate humidity at 60%.\n" + + "Friday returns to partly cloudy conditions, with " + + "a temperature of 73°F and the Winds will be " + + "light at 12 km/h.\n" + + "Finally, Saturday rounds off the week with sunny skies, a " + + "temperature of 80°F, and a humidity " + + "level of 40%. Winds will be gentle at 8 km/h.\n"; + + String response = ControlledGenerationWithNullableSchema.generateContent(GEMINI_FLASH, prompt); + + assertThat(response).isNotEmpty(); + assertThat(response).contains("forecast"); + } + + @Test + public void testControlledGenerationWithResponseSchema() { + String prompt = "List a few popular cookie recipes."; + + String response = ControlledGenerationWithResponseSchema.generateContent(GEMINI_FLASH, prompt); + + assertThat(response).isNotEmpty(); + assertThat(response).contains("recipe_name"); + } + + @Test + public void testControlledGenerationWithClassSchema() { + String prompt = "List a few popular cookie recipes."; + + String response = ControlledGenerationWithClassSchema.generateContent(GEMINI_FLASH, prompt); + + assertThat(response).isNotEmpty(); + assertThat(response).contains("recipe_name"); + assertThat(response).contains("ingredients"); + } + + @Test + public void testControlledGenerationWithNestedClassSchema() { + String prompt = "List about 10 home-baked cookies and give them grades based on tastiness."; + + String response = ControlledGenerationWithNestedSchema.generateContent(GEMINI_FLASH, prompt); + + assertThat(response).isNotEmpty(); + assertThat(response).contains("recipe_name"); + assertThat(response).contains("rating"); + } +} diff --git a/genai/snippets/src/test/java/genai/counttokens/CountTokensIT.java b/genai/snippets/src/test/java/genai/counttokens/CountTokensIT.java new file mode 100644 index 00000000000..ea70d2dc4b0 --- /dev/null +++ b/genai/snippets/src/test/java/genai/counttokens/CountTokensIT.java @@ -0,0 +1,137 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.counttokens; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import com.google.genai.types.GenerateContentResponseUsageMetadata; +import com.google.genai.types.TokensInfo; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class CountTokensIT { + + private static final String GEMINI_FLASH = "gemini-2.5-flash"; + private ByteArrayOutputStream bout; + private PrintStream out; + + // Check if the required environment variables are set. + public static void requireEnvVar(String envVarName) { + assertWithMessage(String.format("Missing environment variable '%s' ", envVarName)) + .that(System.getenv(envVarName)) + .isNotEmpty(); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() { + System.setOut(null); + } + + @Test + public void testCountTokensWithText() { + Optional response = CountTokensWithText.countTokens(GEMINI_FLASH); + assertThat(response).isPresent(); + assertThat(response.get()).isGreaterThan(0); + } + + @Test + public void testCountTokensWithTextAndVideo() { + Optional response = CountTokensWithTextAndVideo.countTokens(GEMINI_FLASH); + assertThat(response).isPresent(); + assertThat(response.get()).isGreaterThan(6); + } + + @Test + public void testCountTokensComputeWithText() { + + List response = + CountTokensComputeWithText.computeTokens(GEMINI_FLASH).orElse(new ArrayList<>()); + + assertThat(response).isNotEmpty(); + TokensInfo tokensInfo = response.get(0); + + assertThat(tokensInfo.role()).isPresent(); + + assertThat(tokensInfo.tokenIds()).isPresent(); + assertThat(tokensInfo.tokenIds().get()).isNotEmpty(); + + assertThat(tokensInfo.tokens()).isPresent(); + assertThat(tokensInfo.tokens().get()).isNotEmpty(); + } + + @Test + public void testCountTokensLocalTokenizerComputeWithTxt() { + + List response = + CountTokensLocalTokenizerComputeWithTxt.computeTokens(GEMINI_FLASH) + .orElse(new ArrayList<>()); + + assertThat(response).isNotEmpty(); + TokensInfo tokensInfo = response.get(0); + + assertThat(tokensInfo.role()).isPresent(); + + assertThat(tokensInfo.tokenIds()).isPresent(); + assertThat(tokensInfo.tokenIds().get()).isNotEmpty(); + + assertThat(tokensInfo.tokens()).isPresent(); + assertThat(tokensInfo.tokens().get()).isNotEmpty(); + } + + @Test + public void testCountTokensLocalTokenizerWithTxt() { + Optional response = CountTokensLocalTokenizerWithTxt.countTokens(GEMINI_FLASH); + assertThat(response).isPresent(); + assertThat(response.get()).isGreaterThan(0); + } + + @Test + public void testCountTokensResponseWithText() { + + Optional response = + CountTokensResponseWithText.countTokens(GEMINI_FLASH); + + assertThat(response).isPresent(); + assertThat(response.get().totalTokenCount()).isPresent(); + assertThat(response.get().totalTokenCount().get()).isGreaterThan(0); + assertThat(response.get().promptTokenCount()).isPresent(); + assertThat(response.get().promptTokenCount().get()).isGreaterThan(0); + } +} diff --git a/genai/snippets/src/test/java/genai/embeddings/EmbeddingsIT.java b/genai/snippets/src/test/java/genai/embeddings/EmbeddingsIT.java new file mode 100644 index 00000000000..9d295c10eaa --- /dev/null +++ b/genai/snippets/src/test/java/genai/embeddings/EmbeddingsIT.java @@ -0,0 +1,73 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.embeddings; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import com.google.genai.types.EmbedContentResponse; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class EmbeddingsIT { + + private static final String GEMINI_EMBEDDING = "gemini-embedding-001"; + private ByteArrayOutputStream bout; + + // Check if the required environment variables are set. + public static void requireEnvVar(String envVarName) { + assertWithMessage(String.format("Missing environment variable '%s' ", envVarName)) + .that(System.getenv(envVarName)) + .isNotEmpty(); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(bout)); + } + + @After + public void tearDown() { + System.setOut(null); + } + + @Test + public void testEmbeddingsDocRetrievalWithTxt() { + EmbedContentResponse response = EmbeddingsDocRetrievalWithTxt.embedContent(GEMINI_EMBEDDING); + assertThat(response.toString()).isNotEmpty(); + assertThat(response.embeddings()).isPresent(); + assertThat(response.embeddings().get()).isNotEmpty(); + assertThat(response.metadata()).isPresent(); + + String output = bout.toString(); + assertThat(output).contains("statistics"); + assertThat(output).contains("tokenCount"); + } +} diff --git a/genai/snippets/src/test/java/genai/expressmode/ExpressModeIT.java b/genai/snippets/src/test/java/genai/expressmode/ExpressModeIT.java new file mode 100644 index 00000000000..2ef21de0f60 --- /dev/null +++ b/genai/snippets/src/test/java/genai/expressmode/ExpressModeIT.java @@ -0,0 +1,93 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.expressmode; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.RETURNS_SELF; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.genai.Client; +import com.google.genai.Models; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.lang.reflect.Field; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.MockedStatic; + +@RunWith(JUnit4.class) +public class ExpressModeIT { + + private static final String GEMINI_FLASH = "gemini-2.5-flash"; + private ByteArrayOutputStream bout; + private PrintStream out; + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() { + System.setOut(null); + } + + @Test + public void testExpressModeWithApiKey() throws NoSuchFieldException, IllegalAccessException { + String response = "Bubble sort is one of the simplest sorting algorithms"; + + Client.Builder mockBuilder = mock(Client.Builder.class, RETURNS_SELF); + Client mockClient = mock(Client.class); + Models mockModels = mock(Models.class); + GenerateContentResponse mockResponse = mock(GenerateContentResponse.class); + + try (MockedStatic mockStatic = mockStatic(Client.class)) { + mockStatic.when(Client::builder).thenReturn(mockBuilder); + when(mockBuilder.build()).thenReturn(mockClient); + + // Using reflection because 'models' is a final field and cannot be mocked directly + Field field = Client.class.getDeclaredField("models"); + field.setAccessible(true); + field.set(mockClient, mockModels); + + when(mockClient.models.generateContent( + anyString(), anyString(), any(GenerateContentConfig.class))) + .thenReturn(mockResponse); + when(mockResponse.text()).thenReturn(response); + + String generatedResponse = ExpressModeWithApiKey.generateContent(GEMINI_FLASH, "API_KEY"); + + verify(mockClient.models, times(1)) + .generateContent(anyString(), anyString(), any(GenerateContentConfig.class)); + assertThat(generatedResponse).isNotEmpty(); + assertThat(response).isEqualTo(generatedResponse); + } + } +} diff --git a/genai/snippets/src/test/java/genai/imagegeneration/ImageGenerationIT.java b/genai/snippets/src/test/java/genai/imagegeneration/ImageGenerationIT.java new file mode 100644 index 00000000000..6e1c080231a --- /dev/null +++ b/genai/snippets/src/test/java/genai/imagegeneration/ImageGenerationIT.java @@ -0,0 +1,165 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.imagegeneration; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import com.google.api.gax.paging.Page; +import com.google.cloud.storage.Blob; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import com.google.genai.types.Image; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Optional; +import java.util.UUID; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ImageGenerationIT { + + private static final String BUCKET_NAME = + System.getenv().getOrDefault("BUCKET_NAME", "java-docs-samples-testing") + .replaceFirst("^gs://", ""); + private static final String PREFIX = "genai-img-generation-" + UUID.randomUUID(); + private static final String OUTPUT_GCS_URI = String.format("gs://%s/%s", BUCKET_NAME, PREFIX); + private static final String IMAGEN_MODEL = "gemini-2.5-flash-image"; + private static final String VIRTUAL_TRY_ON_MODEL = "virtual-try-on-001"; + + private ByteArrayOutputStream bout; + private PrintStream out; + + // Check if the required environment variables are set. + public static void requireEnvVar(String envVarName) { + assertWithMessage(String.format("Missing environment variable '%s' ", envVarName)) + .that(System.getenv(envVarName)) + .isNotEmpty(); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @AfterClass + public static void cleanup() { + try { + Storage storage = StorageOptions.getDefaultInstance().getService(); + Page blobs = storage.list(BUCKET_NAME, Storage.BlobListOption.prefix(PREFIX)); + + for (Blob blob : blobs.iterateAll()) { + storage.delete(blob.getBlobId()); + } + } catch (Exception e) { + System.err.println("Failed to cleanup bucket " + BUCKET_NAME + ": " + e.getMessage()); + } + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() { + System.setOut(null); + } + + @Test + @Ignore("Imagen models are deprecated and unavailable; migrated to Gemini in follow-up PR") + public void testImageGenCannyCtrlTypeWithTextAndImage() { + Optional response = + ImageGenCannyCtrlTypeWithTextAndImage.cannyEdgeCustomization( + IMAGEN_MODEL, OUTPUT_GCS_URI); + assertThat(response).isPresent(); + assertThat(response.get()).isNotEmpty(); + } + + @Test + @Ignore("Imagen models are deprecated and unavailable; migrated to Gemini in follow-up PR") + public void testImageGenRawReferenceWithTextAndImage() { + Optional response = + ImageGenRawReferenceWithTextAndImage.styleTransferCustomization( + IMAGEN_MODEL, OUTPUT_GCS_URI); + assertThat(response).isPresent(); + assertThat(response.get()).isNotEmpty(); + } + + @Test + @Ignore("Imagen models are deprecated and unavailable; migrated to Gemini in follow-up PR") + public void testImageGenScribbleCtrlTypeWithTextAndImage() { + Optional response = + ImageGenScribbleCtrlTypeWithTextAndImage.scribbleCustomization( + IMAGEN_MODEL, OUTPUT_GCS_URI); + assertThat(response).isPresent(); + assertThat(response.get()).isNotEmpty(); + } + + @Test + @Ignore("Imagen models are deprecated and unavailable; migrated to Gemini in follow-up PR") + public void testImageGenStyleReferenceWithTextAndImage() { + Optional response = + ImageGenStyleReferenceWithTextAndImage.styleCustomization( + IMAGEN_MODEL, OUTPUT_GCS_URI); + assertThat(response).isPresent(); + assertThat(response.get()).isNotEmpty(); + } + + @Test + @Ignore("Imagen models are deprecated and unavailable; migrated to Gemini in follow-up PR") + public void testImageGenSubjectReferenceWithTextAndImage() { + Optional response = + ImageGenSubjectReferenceWithTextAndImage.subjectCustomization( + IMAGEN_MODEL, OUTPUT_GCS_URI); + assertThat(response).isPresent(); + assertThat(response.get()).isNotEmpty(); + } + + @Test + public void testImageGenVirtualTryOnWithTextAndImage() throws IOException { + Image image = + ImageGenVirtualTryOnWithTextAndImage.generateContent( + VIRTUAL_TRY_ON_MODEL, "resources/output/man_in_sweater.png"); + + assertThat(image).isNotNull(); + assertThat(image.imageBytes()).isPresent(); + assertThat(image.imageBytes().get().length).isGreaterThan(0); + } + + @Test + @Ignore("Imagen models are deprecated and unavailable; migrated to Gemini in follow-up PR") + public void testImageGenWithText() throws IOException { + Image image = + ImageGenWithText.generateImage(IMAGEN_MODEL, "resources/output/dog_newspaper.png"); + + assertThat(image).isNotNull(); + assertThat(image.imageBytes()).isPresent(); + assertThat(image.imageBytes().get().length).isGreaterThan(0); + } + +} \ No newline at end of file diff --git a/genai/snippets/src/test/java/genai/imagegeneration/ImageGenerationMmFlashIT.java b/genai/snippets/src/test/java/genai/imagegeneration/ImageGenerationMmFlashIT.java new file mode 100644 index 00000000000..85b260bcce6 --- /dev/null +++ b/genai/snippets/src/test/java/genai/imagegeneration/ImageGenerationMmFlashIT.java @@ -0,0 +1,101 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.imagegeneration; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ImageGenerationMmFlashIT { + + private static final String GEMINI_FLASH_IMAGE = "gemini-2.5-flash-image"; + private ByteArrayOutputStream bout; + private PrintStream out; + + // Check if the required environment variables are set. + public static void requireEnvVar(String envVarName) { + assertWithMessage(String.format("Missing environment variable '%s' ", envVarName)) + .that(System.getenv(envVarName)) + .isNotEmpty(); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() { + System.setOut(null); + bout.reset(); + } + + @Test + public void testImageGenMmFlashEditImageWithTextAndImage() throws IOException { + String outputFile = "resources/output/bw-example-image.png"; + ImageGenMmFlashEditImageWithTextAndImage.generateContent(GEMINI_FLASH_IMAGE, outputFile); + assertThat(bout.toString()).contains("Content written to: " + outputFile); + } + + @Test + public void testImageGenMmFlashLocaleAwareWithText() throws IOException { + String outputFile = "resources/output/example-breakfast-meal.png"; + ImageGenMmFlashLocaleAwareWithText.generateContent(GEMINI_FLASH_IMAGE, outputFile); + assertThat(bout.toString()).contains("Content written to: " + outputFile); + } + + @Test + public void testImageGenMmFlashMultipleImagesWithText() throws IOException { + List images = ImageGenMmFlashMultipleImagesWithText.generateContent(GEMINI_FLASH_IMAGE); + if (images.isEmpty()) { + images = ImageGenMmFlashMultipleImagesWithText.generateContent(GEMINI_FLASH_IMAGE); + } + assertThat(images).isNotEmpty(); + } + + @Test + public void testImageGenMmFlashTextAndImageWithText() throws IOException { + String outputFile = "resources/output/paella-recipe.md"; + ImageGenMmFlashTextAndImageWithText.generateContent(GEMINI_FLASH_IMAGE, outputFile); + assertThat(bout.toString()).contains("Content written to: " + outputFile); + } + + @Test + public void testImageGenMmFlashWithText() throws IOException { + String outputFile = "resources/output/example-image-eiffel-tower.png"; + ImageGenMmFlashWithText.generateContent(GEMINI_FLASH_IMAGE, outputFile); + assertThat(bout.toString()).contains("Content written to: " + outputFile); + } +} diff --git a/genai/snippets/src/test/java/genai/live/LiveIT.java b/genai/snippets/src/test/java/genai/live/LiveIT.java new file mode 100644 index 00000000000..cb0434693ed --- /dev/null +++ b/genai/snippets/src/test/java/genai/live/LiveIT.java @@ -0,0 +1,230 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.live; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.RETURNS_SELF; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.genai.AsyncLive; +import com.google.genai.AsyncSession; +import com.google.genai.Client; +import com.google.genai.types.Content; +import com.google.genai.types.LiveConnectConfig; +import com.google.genai.types.LiveServerContent; +import com.google.genai.types.LiveServerMessage; +import com.google.genai.types.Part; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.lang.reflect.Field; +import java.security.GeneralSecurityException; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.MockedStatic; + +@RunWith(JUnit4.class) +public class LiveIT { + + private static final String GEMINI_FLASH_LIVE_PREVIEW = "gemini-2.0-flash-live-preview-04-09"; + private static final String GEMINI_FLASH_LIVE_2_5_GA = "gemini-live-2.5-flash-native-audio"; + private ByteArrayOutputStream bout; + private PrintStream out; + + // Check if the required environment variables are set. + public static void requireEnvVar(String envVarName) { + assertWithMessage(String.format("Missing environment variable '%s' ", envVarName)) + .that(System.getenv(envVarName)) + .isNotEmpty(); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() { + System.setOut(null); + bout.reset(); + } + + @Test + public void testLiveAudioWithTxt() { + LiveAudioWithTxt.generateContent(GEMINI_FLASH_LIVE_2_5_GA); + String output = bout.toString(); + assertThat(output).contains("> Hello? Gemini, are you there?"); + assertThat(output).contains("Successfully saved audio to: "); + } + + @Test + public void testLiveCodeExecWithTxt() { + String response = LiveCodeExecWithTxt.generateContent(GEMINI_FLASH_LIVE_PREVIEW); + assertThat(response).isNotEmpty(); + } + + @Test + public void testLiveConversationAudioWithAudio() throws IOException { + LiveConversationAudioWithAudio.generateContent(GEMINI_FLASH_LIVE_2_5_GA); + String output = bout.toString(); + assertThat(output).contains("Input transcription:"); + assertThat(output).contains("Output transcription:"); + assertThat(output).contains("Successfully saved audio to:"); + } + + @Test + public void testLiveFuncCallWithTxt() { + LiveFuncCallWithTxt.generateContent(GEMINI_FLASH_LIVE_PREVIEW); + String output = bout.toString(); + assertThat(output).contains("> Turn off the lights please"); + assertThat(output).contains("Function name: turn_off_the_lights"); + assertThat(output).contains("result=ok"); + } + + @Test + public void testLiveGroundGoogSearchWithTxt() { + String response = LiveGroundGoogSearchWithTxt.generateContent(GEMINI_FLASH_LIVE_PREVIEW); + assertThat(response).isNotEmpty(); + } + + @Test + public void testLiveGroundRagEngineWithTxt() throws NoSuchFieldException, IllegalAccessException { + + Client.Builder mockedBuilder = mock(Client.Builder.class, RETURNS_SELF); + Client mockedClient = mock(Client.class); + Client.Async mockedAsync = mock(Client.Async.class); + AsyncLive mockedLive = mock(AsyncLive.class); + AsyncSession mockedSession = mock(AsyncSession.class); + + try (MockedStatic mockedStatic = mockStatic(Client.class)) { + mockedStatic.when(Client::builder).thenReturn(mockedBuilder); + when(mockedBuilder.build()).thenReturn(mockedClient); + + // Using reflection because async and live are final fields and cannot be mocked. + Field asyncField = Client.class.getDeclaredField("async"); + asyncField.setAccessible(true); + asyncField.set(mockedClient, mockedAsync); + + Field liveField = Client.Async.class.getDeclaredField("live"); + liveField.setAccessible(true); + liveField.set(mockedAsync, mockedLive); + + when(mockedClient.async.live.connect(anyString(), any(LiveConnectConfig.class))) + .thenReturn(CompletableFuture.completedFuture(mockedSession)); + + when(mockedSession.sendClientContent(any())) + .thenReturn(CompletableFuture.completedFuture(null)); + + when(mockedSession.close()).thenReturn(CompletableFuture.completedFuture(null)); + + // Simulates the server's behavior + doAnswer( + invocation -> { + LiveServerMessage textMessage = mock(LiveServerMessage.class); + LiveServerContent textServerContent = mock(LiveServerContent.class); + Content textContent = mock(Content.class); + Part textPart = mock(Part.class); + + // Sends a text message. + when(textMessage.serverContent()).thenReturn(Optional.of(textServerContent)); + when(textServerContent.modelTurn()).thenReturn(Optional.of(textContent)); + when(textContent.parts()).thenReturn(Optional.of(List.of(textPart))); + when(textPart.text()).thenReturn(Optional.of("The newest model is Gemini.")); + // The turn is not complete yet in this message. + when(textServerContent.turnComplete()).thenReturn(Optional.of(false)); + + // Gets the message handler. + Consumer messageHandler = invocation.getArgument(0); + // Sends the message to the message handler. + messageHandler.accept(textMessage); + + // Simulates server sending the final "turn complete" message. + LiveServerMessage completeMessage = mock(LiveServerMessage.class); + LiveServerContent completeServerContent = mock(LiveServerContent.class); + + when(completeMessage.serverContent()) + .thenReturn(Optional.of(completeServerContent)); + when(completeServerContent.modelTurn()).thenReturn(Optional.empty()); + // The turn is complete. + when(completeServerContent.turnComplete()).thenReturn(Optional.of(true)); + messageHandler.accept(completeMessage); + return null; + }) + .when(mockedSession) + .receive(any()); + + String response = + LiveGroundRagEngineWithTxt.generateContent(GEMINI_FLASH_LIVE_PREVIEW, "test-rag-corpus"); + + assertThat(response).contains("The newest model is Gemini"); + verify(mockedSession).close(); + } + } + + @Test + public void testLiveStructuredOutputWithTxt() throws GeneralSecurityException, IOException { + Optional response = + LiveStructuredOutputWithTxt.generateContent( + System.getenv("GOOGLE_CLOUD_PROJECT"), "us-central1", "openapi"); + assertThat(response).isPresent(); + assertThat(response.get().name).isNotEmpty(); + assertThat(response.get().date).isNotEmpty(); + assertThat(response.get().participants).isNotEmpty(); + String output = bout.toString(); + assertThat(output).contains("name=science fair date=Friday participants=[Alice, Bob]"); + } + + @Test + public void testLiveTranscribeWithAudio() { + String response = LiveTranscribeWithAudio.generateContent(GEMINI_FLASH_LIVE_2_5_GA); + assertThat(response).isNotEmpty(); + } + + @Test + public void testLiveTxtWithAudio() throws IOException { + String response = LiveTxtWithAudio.generateContent(GEMINI_FLASH_LIVE_PREVIEW); + assertThat(response).isNotEmpty(); + } + + @Test + public void testLiveWithTxt() { + String response = LiveWithTxt.generateContent(GEMINI_FLASH_LIVE_PREVIEW); + assertThat(response).isNotEmpty(); + } +} diff --git a/genai/snippets/src/test/java/genai/provisionedthroughput/ProvisionedThroughputIT.java b/genai/snippets/src/test/java/genai/provisionedthroughput/ProvisionedThroughputIT.java new file mode 100644 index 00000000000..620b0b562b4 --- /dev/null +++ b/genai/snippets/src/test/java/genai/provisionedthroughput/ProvisionedThroughputIT.java @@ -0,0 +1,66 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.provisionedthroughput; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ProvisionedThroughputIT { + + private static final String GEMINI_FLASH = "gemini-2.5-flash"; + private ByteArrayOutputStream bout; + + // Check if the required environment variables are set. + public static void requireEnvVar(String envVarName) { + assertWithMessage(String.format("Missing environment variable '%s' ", envVarName)) + .that(System.getenv(envVarName)) + .isNotEmpty(); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(bout)); + } + + @After + public void tearDown() { + System.setOut(null); + } + + @Test + public void testProvisionedThroughputWithTxt() { + String response = ProvisionedThroughputWithTxt.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + } + +} diff --git a/genai/snippets/src/test/java/genai/safety/SafetyIT.java b/genai/snippets/src/test/java/genai/safety/SafetyIT.java new file mode 100644 index 00000000000..b059f614bc1 --- /dev/null +++ b/genai/snippets/src/test/java/genai/safety/SafetyIT.java @@ -0,0 +1,73 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.safety; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import com.google.genai.types.GenerateContentResponse; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class SafetyIT { + + private static final String GEMINI_FLASH = "gemini-2.5-flash"; + private ByteArrayOutputStream bout; + + // Check if the required environment variables are set. + public static void requireEnvVar(String envVarName) { + assertWithMessage(String.format("Missing environment variable '%s' ", envVarName)) + .that(System.getenv(envVarName)) + .isNotEmpty(); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(bout)); + } + + @After + public void tearDown() { + System.setOut(null); + bout.reset(); + } + + @Test + public void testSafetyWithTxt() { + GenerateContentResponse response = SafetyWithTxt.generateContent(GEMINI_FLASH); + assertThat(response).isNotNull(); + assertThat(response.finishReason().toString()).isNotEmpty(); + assertThat(bout.toString()).contains("Category:"); + assertThat(bout.toString()).contains("Is Blocked:"); + assertThat(bout.toString()).contains("Probability:"); + assertThat(bout.toString()).contains("Severity:"); + assertThat(bout.toString()).contains("Severity Score:"); + } +} diff --git a/genai/snippets/src/test/java/genai/textgeneration/TextGenerationIT.java b/genai/snippets/src/test/java/genai/textgeneration/TextGenerationIT.java new file mode 100644 index 00000000000..773d4197695 --- /dev/null +++ b/genai/snippets/src/test/java/genai/textgeneration/TextGenerationIT.java @@ -0,0 +1,186 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.textgeneration; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TextGenerationIT { + + private static final String GEMINI_FLASH = "gemini-2.5-flash"; + private static final String LOCAL_IMG_1 = "resources/latte.jpg"; + private static final String LOCAL_IMG_2 = "resources/scones.jpg"; + private ByteArrayOutputStream bout; + private PrintStream out; + + // Check if the required environment variables are set. + public static void requireEnvVar(String envVarName) { + assertWithMessage(String.format("Missing environment variable '%s' ", envVarName)) + .that(System.getenv(envVarName)) + .isNotEmpty(); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() { + System.setOut(null); + } + + @Test + public void testTextGenerationAsyncWithText() { + String response = TextGenerationAsyncWithText.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + } + + @Test + public void testTextGenerationChatStreamWithText() { + String response = TextGenerationChatStreamWithText.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + } + + @Test + public void testTextGenerationChatWithText() { + String response = TextGenerationChatWithText.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + } + + @Test + public void testTextGenerationCodeWithPdf() { + String response = TextGenerationCodeWithPdf.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + } + + @Test + public void testTextGenerationConfigWithText() { + String response = TextGenerationConfigWithText.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + } + + @Test + public void testTextGenerationTranscriptWithGcsAudio() { + String response = TextGenerationTranscriptWithGcsAudio.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + } + + @Test + public void testTextGenerationWithGcsAudio() { + String response = TextGenerationWithGcsAudio.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + } + + @Test + public void testTextGenerationWithLocalVideo() throws IOException { + String response = TextGenerationWithLocalVideo.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + } + + @Test + public void testTextGenerationWithMultiImage() throws IOException { + String gcsFileImagePath = "gs://cloud-samples-data/generative-ai/image/scones.jpg"; + String response = + TextGenerationWithMultiImage.generateContent( + GEMINI_FLASH, gcsFileImagePath, LOCAL_IMG_1); + assertThat(response).isNotEmpty(); + } + + @Test + public void testTextGenerationWithMultiLocalImage() throws IOException { + String response = + TextGenerationWithMultiLocalImage.generateContent( + GEMINI_FLASH, LOCAL_IMG_1, LOCAL_IMG_2); + assertThat(response).isNotEmpty(); + } + + @Test + public void testTextGenerationWithMuteVideo() { + String response = TextGenerationWithMuteVideo.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + } + + @Test + public void testTextGenerationWithPdf() { + String response = TextGenerationWithPdf.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + } + + @Test + public void testTextGenerationWithSystemInstruction() { + String response = TextGenerationWithSystemInstruction.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + } + + @Test + public void testTextGenerationWithText() { + String response = TextGenerationWithText.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + } + + @Test + public void testTextGenerationWithTextAndImage() { + String response = TextGenerationWithTextAndImage.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + } + + @Test + public void testTextGenerationWithTextStream() { + String prompt = "Why is the sky blue?"; + String response = TextGenerationWithTextStream.generateContent(GEMINI_FLASH, prompt); + assertThat(response).isNotEmpty(); + } + + @Test + public void testTextGenerationWithVideo() { + String prompt = + " Analyze the provided video file, including its audio.\n" + + " Summarize the main points of the video concisely.\n" + + " Create a chapter breakdown with timestamps for key sections or topics discussed."; + + String response = TextGenerationWithVideo.generateContent(GEMINI_FLASH, prompt); + assertThat(response).isNotEmpty(); + assertThat(response).ignoringCase().contains("Tokyo"); + assertThat(response).ignoringCase().contains("Pixel"); + } + + @Test + public void testTextGenerationWithYoutubeVideo() { + String response = TextGenerationWithYoutubeVideo.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + } + +} \ No newline at end of file diff --git a/genai/snippets/src/test/java/genai/thinking/ThinkingIT.java b/genai/snippets/src/test/java/genai/thinking/ThinkingIT.java new file mode 100644 index 00000000000..bb04042a460 --- /dev/null +++ b/genai/snippets/src/test/java/genai/thinking/ThinkingIT.java @@ -0,0 +1,82 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.thinking; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ThinkingIT { + + private static final String GEMINI_FLASH = "gemini-2.5-flash"; + private ByteArrayOutputStream bout; + private PrintStream out; + + // Check if the required environment variables are set. + public static void requireEnvVar(String envVarName) { + assertWithMessage(String.format("Missing environment variable '%s' ", envVarName)) + .that(System.getenv(envVarName)) + .isNotEmpty(); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() { + System.setOut(null); + bout.reset(); + } + + @Test + public void testThinkingWithText() { + String response = ThinkingWithTxt.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + } + + @Test + public void testThinkingBudgetWithText() { + String response = ThinkingBudgetWithTxt.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + assertThat(bout.toString()).contains("Token count for thinking: "); + assertThat(bout.toString()).contains("Total token count: "); + } + + @Test + public void testThinkingIncludeThoughtsWithText() { + String response = ThinkingIncludeThoughtsWithTxt.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + } +} diff --git a/genai/snippets/src/test/java/genai/tools/ToolsIT.java b/genai/snippets/src/test/java/genai/tools/ToolsIT.java new file mode 100644 index 00000000000..5effb424560 --- /dev/null +++ b/genai/snippets/src/test/java/genai/tools/ToolsIT.java @@ -0,0 +1,190 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.tools; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.RETURNS_SELF; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.genai.Client; +import com.google.genai.Models; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.lang.reflect.Field; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.MockedStatic; + +@RunWith(JUnit4.class) +public class ToolsIT { + + private static final String GEMINI_FLASH = "gemini-2.5-flash"; + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private ByteArrayOutputStream bout; + private PrintStream out; + + // Check if the required environment variables are set. + public static void requireEnvVar(String envVarName) { + assertWithMessage(String.format("Missing environment variable '%s' ", envVarName)) + .that(System.getenv(envVarName)) + .isNotEmpty(); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() { + System.setOut(null); + bout.reset(); + } + + @Test + public void testGenerateContentWithFunctionDescription() { + String prompt = "What is the weather like in Boston?"; + + String response = ToolFunctionDescriptionWithText.generateContent(GEMINI_FLASH, prompt); + + assertThat(response).isNotEmpty(); + assertThat(response).contains("get_current_weather"); + assertThat(response).contains("location=Boston"); + } + + @Test + public void testGenerateContentWithFunctionCallingConfig() { + String response = ToolFunctionCallingConfigWithText.generateContent(GEMINI_FLASH); + + assertThat(response).isNotEmpty(); + assertThat(response).contains("get_album_sales"); + assertThat(response).contains("copies_sold=350000"); + assertThat(response).contains("album_name=Echoes of the Night"); + } + + @Test + public void testToolsCodeExecWithText() { + String response = ToolsCodeExecWithText.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + assertThat(bout.toString()).contains("Code:"); + assertThat(bout.toString()).contains("Outcome:"); + } + + @Test + public void testToolsCodeExecWithTextLocalImage() throws IOException { + String response = ToolsCodeExecWithTextLocalImage.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + assertThat(bout.toString()).contains("Code:"); + assertThat(bout.toString()).contains("Outcome:"); + } + + @Test + public void testToolsGoogleMapsCoordinatesWithTxt() { + String response = ToolsGoogleMapsCoordinatesWithTxt.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + assertThat(bout.toString()).contains(response); + } + + @Test + public void testToolsGoogleSearchAndUrlContextWithTxt() { + String url = "https://www.google.com/search?q=events+in+New+York"; + String response = ToolsGoogleSearchAndUrlContextWithTxt.generateContent(GEMINI_FLASH, url); + assertThat(response).isNotEmpty(); + assertThat(bout.toString()).contains(response); + } + + @Test + public void testToolsGoogleSearchWithText() { + String response = ToolsGoogleSearchWithText.generateContent(GEMINI_FLASH); + assertThat(response).isNotEmpty(); + } + + @Test + public void testToolsUrlContextWithTxt() { + String url1 = "https://cloud.google.com/vertex-ai/generative-ai/docs"; + String url2 = "https://cloud.google.com/docs/overview"; + String response = ToolsUrlContextWithTxt.generateContent(GEMINI_FLASH, url1, url2); + assertThat(response).isNotEmpty(); + String output = bout.toString(); + assertThat(output).contains("UrlContextMetadata"); + assertThat(output).contains("urlRetrievalStatus"); + assertThat(output).contains("URL_RETRIEVAL_STATUS_SUCCESS"); + assertThat(output).contains(url1); + assertThat(output).contains(url2); + } + + @Test + public void testToolsVaisWithText() throws NoSuchFieldException, IllegalAccessException { + String response = + "The process for making an appointment to renew your driver's license" + + " varies depending on your location."; + + String datastore = + String.format( + "projects/%s/locations/global/collections/default_collection/" + + "dataStores/grounding-test-datastore", + PROJECT_ID); + + Client.Builder mockedBuilder = mock(Client.Builder.class, RETURNS_SELF); + Client mockedClient = mock(Client.class); + Models mockedModels = mock(Models.class); + GenerateContentResponse mockedResponse = mock(GenerateContentResponse.class); + + try (MockedStatic mockedStatic = mockStatic(Client.class)) { + mockedStatic.when(Client::builder).thenReturn(mockedBuilder); + when(mockedBuilder.build()).thenReturn(mockedClient); + + // Using reflection because 'models' is a final field and cannot be mockable directly + Field field = Client.class.getDeclaredField("models"); + field.setAccessible(true); + field.set(mockedClient, mockedModels); + + when(mockedClient.models.generateContent( + anyString(), anyString(), any(GenerateContentConfig.class))) + .thenReturn(mockedResponse); + when(mockedResponse.text()).thenReturn(response); + + String generatedResponse = ToolsVaisWithText.generateContent(GEMINI_FLASH, datastore); + + verify(mockedClient.models, times(1)) + .generateContent(anyString(), anyString(), any(GenerateContentConfig.class)); + assertThat(generatedResponse).isNotEmpty(); + assertThat(response).isEqualTo(generatedResponse); + } + } +} diff --git a/genai/snippets/src/test/java/genai/tuning/TuningIT.java b/genai/snippets/src/test/java/genai/tuning/TuningIT.java new file mode 100644 index 00000000000..795b7f370c8 --- /dev/null +++ b/genai/snippets/src/test/java/genai/tuning/TuningIT.java @@ -0,0 +1,191 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.tuning; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.RETURNS_SELF; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.genai.Client; +import com.google.genai.Models; +import com.google.genai.Pager; +import com.google.genai.Tunings; +import com.google.genai.types.CreateTuningJobConfig; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.GetTuningJobConfig; +import com.google.genai.types.JobState; +import com.google.genai.types.ListTuningJobsConfig; +import com.google.genai.types.TunedModel; +import com.google.genai.types.TuningDataset; +import com.google.genai.types.TuningJob; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.lang.reflect.Field; +import java.util.Iterator; +import java.util.Optional; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.MockedStatic; + +@RunWith(JUnit4.class) +public class TuningIT { + + private static final String GEMINI_FLASH = "gemini-2.5-flash"; + private ByteArrayOutputStream bout; + private PrintStream out; + private Client.Builder mockedBuilder; + private Client mockedClient; + private Tunings mockedTunings; + private TuningJob mockedResponse; + private MockedStatic mockedStatic; + + // Check if the required environment variables are set. + public static void requireEnvVar(String envVarName) { + assertWithMessage(String.format("Missing environment variable '%s' ", envVarName)) + .that(System.getenv(envVarName)) + .isNotEmpty(); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() throws NoSuchFieldException, IllegalAccessException { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + mockedBuilder = mock(Client.Builder.class, RETURNS_SELF); + mockedClient = mock(Client.class); + mockedTunings = mock(Tunings.class); + mockedResponse = mock(TuningJob.class); + mockedStatic = mockStatic(Client.class); + mockedStatic.when(Client::builder).thenReturn(mockedBuilder); + when(mockedBuilder.build()).thenReturn(mockedClient); + // Using reflection because 'tunings' is a final field and cannot be mockable directly + Field field = Client.class.getDeclaredField("tunings"); + field.setAccessible(true); + field.set(mockedClient, mockedTunings); + } + + @After + public void tearDown() { + System.setOut(null); + bout.reset(); + mockedStatic.close(); + } + + @Test + public void testTuningJobCreate() throws InterruptedException { + + String expectedResponse = "test-tuning-job"; + + when(mockedClient.tunings.tune( + anyString(), any(TuningDataset.class), any(CreateTuningJobConfig.class))) + .thenReturn(mockedResponse); + + TunedModel tunedModel = + TunedModel.builder().model("test-model").endpoint("test-endpoint").build(); + when(mockedResponse.name()).thenReturn(Optional.of("test-tuning-job")); + when(mockedResponse.experiment()).thenReturn(Optional.of("test-experiment")); + when(mockedResponse.tunedModel()).thenReturn(Optional.of(tunedModel)); + when(mockedResponse.state()) + .thenReturn(Optional.of(new JobState(JobState.Known.JOB_STATE_SUCCEEDED))); + + String response = TuningJobCreate.createTuningJob(GEMINI_FLASH); + + verify(mockedClient.tunings, times(1)) + .tune(anyString(), any(TuningDataset.class), any(CreateTuningJobConfig.class)); + assertThat(response).isNotEmpty(); + assertThat(response).isEqualTo(expectedResponse); + } + + @Test + public void testTuningJobGet() { + when(mockedClient.tunings.get(anyString(), any(GetTuningJobConfig.class))) + .thenReturn(mockedResponse); + when(mockedResponse.name()).thenReturn(Optional.of("test-tuning-job")); + + Optional response = TuningJobGet.getTuningJob(GEMINI_FLASH); + verify(mockedClient.tunings, times(1)).get(anyString(), any(GetTuningJobConfig.class)); + assertThat(response).isPresent(); + assertThat(response.get()).isEqualTo("test-tuning-job"); + } + + @Test + public void testTuningJobList() { + Pager mockPagerResponse = mock(Pager.class); + Iterator mockIterator = mock(Iterator.class); + + TuningJob tuningJob1 = TuningJob.builder().name("test-tuning-job1").build(); + TuningJob tuningJob2 = TuningJob.builder().name("test-tuning-job2").build(); + + when(mockedClient.tunings.list(any(ListTuningJobsConfig.class))).thenReturn(mockPagerResponse); + when(mockPagerResponse.size()).thenReturn(2); + when(mockPagerResponse.iterator()).thenReturn(mockIterator); + when(mockIterator.hasNext()).thenReturn(true, true, false); + when(mockIterator.next()).thenReturn(tuningJob1, tuningJob2); + + Pager tuningJobs = TuningJobList.listTuningJob(); + verify(mockedClient.tunings, times(1)).list(any(ListTuningJobsConfig.class)); + assertThat(tuningJobs.size()).isEqualTo(2); + assertThat(bout.toString()).isNotEmpty(); + assertThat(bout.toString()).contains("test-tuning-job1"); + assertThat(bout.toString()).contains("test-tuning-job2"); + } + + @Test + public void testTuningTextGenWithTxt() throws NoSuchFieldException, IllegalAccessException { + Models mockedModels = mock(Models.class); + // Using reflection because 'models' is a final field and cannot be mockable directly + Field field = Client.class.getDeclaredField("models"); + field.setAccessible(true); + field.set(mockedClient, mockedModels); + + when(mockedClient.tunings.get(anyString(), any(GetTuningJobConfig.class))) + .thenReturn(mockedResponse); + TunedModel tunedModel = TunedModel.builder().endpoint("test-endpoint").build(); + when(mockedResponse.tunedModel()).thenReturn(Optional.of(tunedModel)); + + GenerateContentResponse mockedGeneratedResponse = mock(GenerateContentResponse.class); + + when(mockedClient.models.generateContent( + anyString(), anyString(), any(GenerateContentConfig.class))) + .thenReturn(mockedGeneratedResponse); + when(mockedGeneratedResponse.text()).thenReturn("Example response"); + + String response = TuningTextGenWithTxt.predictWithTunedEndpoint("test-tuning-job"); + + verify(mockedClient.tunings, times(1)).get(anyString(), any(GetTuningJobConfig.class)); + verify(mockedClient.models, times(1)) + .generateContent(anyString(), anyString(), any(GenerateContentConfig.class)); + assertThat(response).isNotEmpty(); + } +} diff --git a/genai/snippets/src/test/java/genai/videogeneration/VideoGenerationIT.java b/genai/snippets/src/test/java/genai/videogeneration/VideoGenerationIT.java new file mode 100644 index 00000000000..159e529c6e5 --- /dev/null +++ b/genai/snippets/src/test/java/genai/videogeneration/VideoGenerationIT.java @@ -0,0 +1,101 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package genai.videogeneration; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import com.google.api.gax.paging.Page; +import com.google.cloud.storage.Blob; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.UUID; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class VideoGenerationIT { + + private static final String VIDEO_GEN_MODEL = "veo-3.1-generate-001"; + private static final String BUCKET_NAME = + System.getenv().getOrDefault("BUCKET_NAME", "java-docs-samples-testing") + .replaceFirst("^gs://", ""); + private static final String PREFIX = "genai-video-generation-" + UUID.randomUUID(); + private static final String OUTPUT_GCS_URI = String.format("gs://%s/%s", BUCKET_NAME, PREFIX); + private ByteArrayOutputStream bout; + private PrintStream out; + + // Check if the required environment variables are set. + public static void requireEnvVar(String envVarName) { + assertWithMessage(String.format("Missing environment variable '%s' ", envVarName)) + .that(System.getenv(envVarName)) + .isNotEmpty(); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @AfterClass + public static void cleanup() { + try { + Storage storage = StorageOptions.getDefaultInstance().getService(); + Page blobs = storage.list(BUCKET_NAME, Storage.BlobListOption.prefix(PREFIX)); + + for (Blob blob : blobs.iterateAll()) { + storage.delete(blob.getBlobId()); + } + } catch (Exception e) { + System.err.println("Failed to cleanup bucket " + BUCKET_NAME + ": " + e.getMessage()); + } + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() { + System.setOut(null); + bout.reset(); + } + + @Test + public void testVideoGenWithImg() throws InterruptedException { + String response = VideoGenWithImg.generateContent(VIDEO_GEN_MODEL, OUTPUT_GCS_URI); + assertThat(response).isNotEmpty(); + assertThat(bout.toString()).contains(OUTPUT_GCS_URI); + } + + @Test + public void testVideoGenWithTxt() throws InterruptedException { + String response = VideoGenWithTxt.generateContent(VIDEO_GEN_MODEL, OUTPUT_GCS_URI); + assertThat(response).isNotEmpty(); + assertThat(bout.toString()).contains(OUTPUT_GCS_URI); + } +} diff --git a/healthcare/v1/pom.xml b/healthcare/v1/pom.xml index 54f5884582a..e1dd45daf58 100644 --- a/healthcare/v1/pom.xml +++ b/healthcare/v1/pom.xml @@ -53,9 +53,7 @@ 11 - - + @@ -69,19 +67,20 @@ - - com.google.http-client - google-http-client-jackson2 - com.google.apis google-api-services-healthcare v1-rev20240130-2.0.0 + + + com.google.http-client + google-http-client-jackson2 + com.google.api-client google-api-client - + com.google.auth google-auth-library-oauth2-http @@ -91,8 +90,6 @@ google-cloud-core compile - - com.google.truth @@ -111,7 +108,7 @@ httpmime 4.5.14 - + - + diff --git a/iam/api-client/pom.xml b/iam/api-client/pom.xml index c0bb6c7466a..a7d2d496457 100644 --- a/iam/api-client/pom.xml +++ b/iam/api-client/pom.xml @@ -78,12 +78,12 @@ v1-rev20211203-2.0.0 - + com.google.cloud google-cloud-policy-troubleshooter - + diff --git a/iam/snippets/src/main/java/AddBinding.java b/iam/snippets/src/main/java/AddBinding.java index ade1e167801..2cfc6f93517 100644 --- a/iam/snippets/src/main/java/AddBinding.java +++ b/iam/snippets/src/main/java/AddBinding.java @@ -27,13 +27,14 @@ public static void main(String[] args) { Policy policy = Policy.newBuilder().build(); // TODO: Replace with your role. String role = "roles/role-to-add"; - // TODO: Replace with your members. - List members = Collections.singletonList("user:member-to-add@example.com"); + // TODO: Replace with your principals. + // For examples, see https://cloud.google.com/iam/docs/principal-identifiers + List members = Collections.singletonList("principal-id"); addBinding(policy, role, members); } - // Adds a member to a role. + // Adds a principals to a role. public static Policy addBinding(Policy policy, String role, List members) { Binding binding = Binding.newBuilder() .setRole(role) diff --git a/iam/snippets/src/main/java/AddMember.java b/iam/snippets/src/main/java/AddMember.java index 81e2f52ee9a..b50fada1eab 100644 --- a/iam/snippets/src/main/java/AddMember.java +++ b/iam/snippets/src/main/java/AddMember.java @@ -26,13 +26,14 @@ public static void main(String[] args) { Policy policy = Policy.newBuilder().build(); // TODO: Replace with your role. String role = "roles/existing-role"; - // TODO: Replace with your member. - String member = "user:member-to-add@example.com"; + // TODO: Replace with your principal. + // For examples, see https://cloud.google.com/iam/docs/principal-identifiers + String member = "principal-id"; addMember(policy, role, member); } - // Adds a member to a pre-existing role. + // Adds a principal to a pre-existing role. public static Policy addMember(Policy policy, String role, String member) { List newBindingsList = new ArrayList<>(); @@ -44,13 +45,13 @@ public static Policy addMember(Policy policy, String role, String member) { } } - // Update the policy to add the member. + // Update the policy to add the principal. Policy updatedPolicy = policy.toBuilder() .clearBindings() .addAllBindings(newBindingsList) .build(); - System.out.println("Added member: " + updatedPolicy.getBindingsList()); + System.out.println("Added principal: " + updatedPolicy.getBindingsList()); return updatedPolicy; } diff --git a/iam/snippets/src/main/java/Quickstart.java b/iam/snippets/src/main/java/Quickstart.java index 2dd987029f1..c36ae434247 100644 --- a/iam/snippets/src/main/java/Quickstart.java +++ b/iam/snippets/src/main/java/Quickstart.java @@ -34,8 +34,9 @@ public static void main(String[] args) throws IOException { String projectId = "your-project"; // TODO: Replace with your service account name. String serviceAccount = "your-service-account"; - // TODO: Replace with the ID of your member in the form "user:member@example.com" - String member = "your-member"; + // TODO: Replace with the ID of your principal. + // For examples, see https://cloud.google.com/iam/docs/principal-identifiers + String member = "your-principal"; // The role to be granted. String role = "roles/logging.logWriter"; @@ -56,10 +57,10 @@ public static void quickstart(String projectId, String serviceAccount, // This client only needs to be created once, and can be reused for multiple requests. try (IAMClient iamClient = IAMClient.create()) { - // Grants your member the "Log writer" role for your project. + // Grants your principal the "Log writer" role for your project. addBinding(iamClient, projectId, serviceAccount, member, role); - // Get the project's policy and print all members with the "Log Writer" role + // Get the project's policy and print all principals with the "Log Writer" role Policy policy = getPolicy(iamClient, projectId, serviceAccount); Binding binding = null; @@ -73,14 +74,14 @@ public static void quickstart(String projectId, String serviceAccount, } System.out.println("Role: " + binding.getRole()); - System.out.print("Members: "); + System.out.print("Principals: "); for (String m : binding.getMembersList()) { System.out.print("[" + m + "] "); } System.out.println(); - // Removes member from the "Log writer" role. + // Removes principal from the "Log writer" role. removeMember(iamClient, projectId, serviceAccount, member, role); } } @@ -107,7 +108,7 @@ public static void addBinding(IAMClient iamClient, String projectId, String serv } if (binding != null) { - // If binding already exists, adds member to binding. + // If binding already exists, adds principal to binding. binding.getMembersList().add(member); } else { // If binding does not exist, adds binding to policy. @@ -127,7 +128,7 @@ public static void removeMember(IAMClient iamClient, String projectId, String se // Gets the project's policy. Policy.Builder policy = getPolicy(iamClient, projectId, serviceAccount).toBuilder(); - // Removes the member from the role. + // Removes the principal from the role. Binding binding = null; for (Binding b : policy.getBindingsList()) { if (b.getRole().equals(role)) { diff --git a/iam/snippets/src/main/java/RemoveMember.java b/iam/snippets/src/main/java/RemoveMember.java index e249e7fb40e..568f531177e 100644 --- a/iam/snippets/src/main/java/RemoveMember.java +++ b/iam/snippets/src/main/java/RemoveMember.java @@ -27,13 +27,14 @@ public static void main(String[] args) throws IOException { Policy policy = Policy.newBuilder().build(); // TODO: Replace with your role. String role = "roles/existing-role"; - // TODO: Replace with your member. - String member = "user:member-to-add@example.com"; + // TODO: Replace with your principal. + // For examples, see https://cloud.google.com/iam/docs/principal-identifiers + String member = "principal-id"; removeMember(policy, role, member); } - // Removes member from a role; removes binding if binding contains no members. + // Removes principal from a role; removes binding if binding contains no members. public static Policy removeMember(Policy policy, String role, String member) { // Creating new builder with all values copied from origin policy Policy.Builder policyBuilder = policy.toBuilder(); @@ -49,12 +50,12 @@ public static Policy removeMember(Policy policy, String role, String member) { if (binding != null && binding.getMembersList().contains(member)) { List newMemberList = new ArrayList<>(binding.getMembersList()); - // Removing member from a role + // Removing principal from the role newMemberList.remove(member); System.out.println("Member " + member + " removed from " + role); - // Adding all remaining members to create new binding + // Adding all remaining principals to create new binding Binding newBinding = binding.toBuilder() .clearMembers() .addAllMembers(newMemberList) @@ -70,14 +71,14 @@ public static Policy removeMember(Policy policy, String role, String member) { newBindingList.add(newBinding); } - // Update the policy to remove the member. + // Update the policy to remove the principal. policyBuilder.clearBindings() .addAllBindings(newBindingList); } Policy updatedPolicy = policyBuilder.build(); - System.out.println("Exising members: " + updatedPolicy.getBindingsList()); + System.out.println("Exising principals: " + updatedPolicy.getBindingsList()); return updatedPolicy; } diff --git a/jobs/v3/src/main/java/com/google/samples/JobServiceQuickstart.java b/jobs/v3/src/main/java/com/google/samples/JobServiceQuickstart.java index 19259cc1dfb..32b46130577 100644 --- a/jobs/v3/src/main/java/com/google/samples/JobServiceQuickstart.java +++ b/jobs/v3/src/main/java/com/google/samples/JobServiceQuickstart.java @@ -33,7 +33,6 @@ public class JobServiceQuickstart { // [START job_search_quick_start] - // [START quickstart] private static final JsonFactory JSON_FACTORY = new GsonFactory(); private static final NetHttpTransport NET_HTTP_TRANSPORT = new NetHttpTransport(); @@ -92,6 +91,5 @@ public static void main(String... args) throws Exception { } } - // [END quickstart] // [END job_search_quick_start] } diff --git a/jobs/v4/src/main/java/com/example/jobs/HistogramSearchJobs.java b/jobs/v4/src/main/java/com/example/jobs/HistogramSearchJobs.java index 57c34123392..86f71b39e00 100644 --- a/jobs/v4/src/main/java/com/example/jobs/HistogramSearchJobs.java +++ b/jobs/v4/src/main/java/com/example/jobs/HistogramSearchJobs.java @@ -16,7 +16,7 @@ package com.example.jobs; -// [START job_search_histogram_search] +// [START job_search_histogram_search_beta] import com.google.cloud.talent.v4beta1.HistogramQuery; import com.google.cloud.talent.v4beta1.Job; @@ -75,4 +75,4 @@ public static void searchJobs(String projectId, String tenantId, String query) } } } -// [END job_search_histogram_search] +// [END job_search_histogram_search_beta] diff --git a/kms/pom.xml b/kms/pom.xml index 2eece6e9747..805d7192cc1 100644 --- a/kms/pom.xml +++ b/kms/pom.xml @@ -31,7 +31,7 @@ com.google.cloud libraries-bom - 26.32.0 + 26.50.0 pom import @@ -42,16 +42,27 @@ com.google.cloud google-cloud-kms + 2.88.0 + + + com.google.api.grpc + proto-google-cloud-kms-v1 + 0.179.0 com.google.crypto.tink tink 1.12.0 - + + com.google.protobuf + protobuf-java + 4.33.2 + com.google.protobuf protobuf-java-util + 4.33.2 junit @@ -68,7 +79,7 @@ com.nimbusds nimbus-jose-jwt - 9.37.3 + 10.0.2 org.bouncycastle @@ -77,5 +88,16 @@ + + + + org.jacoco + jacoco-maven-plugin + + true + + + + diff --git a/kms/src/main/java/kms/DeleteKey.java b/kms/src/main/java/kms/DeleteKey.java new file mode 100644 index 00000000000..64fa74668a3 --- /dev/null +++ b/kms/src/main/java/kms/DeleteKey.java @@ -0,0 +1,59 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kms; + +// [START kms_delete_key] +import com.google.cloud.kms.v1.CryptoKeyName; +import com.google.cloud.kms.v1.DeleteCryptoKeyMetadata; +import com.google.cloud.kms.v1.KeyManagementServiceClient; +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +public class DeleteKey { + + public void deleteKey() throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String locationId = "us-east1"; + String keyRingId = "my-key-ring"; + String keyId = "my-key"; + deleteKey(projectId, locationId, keyRingId, keyId); + } + + // deleteKey deletes a crypto key. This action is permanent and cannot be undone. Once the key + // is deleted, it will no longer exist. + public void deleteKey(String projectId, String locationId, String keyRingId, String keyId) + throws IOException { + // Initialize client that will be used to send requests. This client only + // needs to be created once, and can be reused for multiple requests. After + // completing all of your requests, call the "close" method on the client to + // safely clean up any remaining background resources. + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + // Build the key name from the project, location, key ring, and key. + CryptoKeyName keyName = CryptoKeyName.of(projectId, locationId, keyRingId, keyId); + + // Delete the key. + // Warning: This operation is permanent and cannot be undone. + // Wait for the operation to complete. + client.deleteCryptoKeyAsync(keyName).get(); + System.out.printf("Deleted key: %s%n", keyName.toString()); + } catch (Exception e) { + System.err.printf("Failed to delete key: %s%n", e.getMessage()); + } + } +} +// [END kms_delete_key] diff --git a/kms/src/main/java/kms/DeleteKeyVersion.java b/kms/src/main/java/kms/DeleteKeyVersion.java new file mode 100644 index 00000000000..1af4d80915c --- /dev/null +++ b/kms/src/main/java/kms/DeleteKeyVersion.java @@ -0,0 +1,61 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kms; + +// [START kms_delete_key_version] +import com.google.cloud.kms.v1.CryptoKeyVersionName; +import com.google.cloud.kms.v1.KeyManagementServiceClient; +import java.io.IOException; + +public class DeleteKeyVersion { + + public void deleteKeyVersion() throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String locationId = "us-east1"; + String keyRingId = "my-key-ring"; + String keyId = "my-key"; + String keyVersionId = "123"; + deleteKeyVersion(projectId, locationId, keyRingId, keyId, keyVersionId); + } + + // deleteKeyVersion deletes a key version. This action is permanent and cannot be undone. Once the + // key version is deleted, it will no longer exist. + public void deleteKeyVersion( + String projectId, String locationId, String keyRingId, String keyId, String keyVersionId) + throws IOException { + // Initialize client that will be used to send requests. This client only + // needs to be created once, and can be reused for multiple requests. After + // completing all of your requests, call the "close" method on the client to + // safely clean up any remaining background resources. + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + // Build the key version name from the project, location, key ring, key, + // and key version. + CryptoKeyVersionName keyVersionName = + CryptoKeyVersionName.of(projectId, locationId, keyRingId, keyId, keyVersionId); + + // Delete the key version. + // Warning: This operation is permanent and cannot be undone. + // Wait for the operation to complete. + client.deleteCryptoKeyVersionAsync(keyVersionName).get(); + System.out.printf("Deleted key version: %s%n", keyVersionName.toString()); + } catch (Exception e) { + System.err.printf("Failed to delete key version: %s%n", e.getMessage()); + } + } +} +// [END kms_delete_key_version] diff --git a/kms/src/main/java/kms/GetRetiredResource.java b/kms/src/main/java/kms/GetRetiredResource.java new file mode 100644 index 00000000000..f273feda3c2 --- /dev/null +++ b/kms/src/main/java/kms/GetRetiredResource.java @@ -0,0 +1,53 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kms; + +// [START kms_get_retired_resource] +import com.google.cloud.kms.v1.KeyManagementServiceClient; +import com.google.cloud.kms.v1.RetiredResource; +import com.google.cloud.kms.v1.RetiredResourceName; +import java.io.IOException; + +public class GetRetiredResource { + + public void getRetiredResource() throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String locationId = "us-east1"; + String retiredResourceId = "my-retired-resource-id"; + getRetiredResource(projectId, locationId, retiredResourceId); + } + + // Get the retired resource. + public void getRetiredResource( + String projectId, String locationId, String retiredResourceId) + throws IOException { + // Initialize client that will be used to send requests. This client only + // needs to be created once, and can be reused for multiple requests. After + // completing all of your requests, call the "close" method on the client to + // safely clean up any remaining background resources. + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + // Build the retired resource name from the project, location, and retired resource id. + RetiredResourceName name = RetiredResourceName.of(projectId, locationId, retiredResourceId); + + // Get the retired resource. + RetiredResource response = client.getRetiredResource(name); + System.out.printf("Retired resource: %s%n", response.getName()); + } + } +} +// [END kms_get_retired_resource] diff --git a/kms/src/main/java/kms/IamRemoveMember.java b/kms/src/main/java/kms/IamRemoveMember.java index 448746134f6..af1833908d5 100644 --- a/kms/src/main/java/kms/IamRemoveMember.java +++ b/kms/src/main/java/kms/IamRemoveMember.java @@ -56,13 +56,29 @@ public void iamRemoveMember( // Search through the bindings and remove matches. String roleToFind = "roles/cloudkms.cryptoKeyEncrypterDecrypter"; + // Create a new list of bindings, removing the member from the role. + java.util.List newBindings = new java.util.ArrayList<>(); for (Binding binding : policy.getBindingsList()) { if (binding.getRole().equals(roleToFind) && binding.getMembersList().contains(member)) { - binding.getMembersList().remove(member); + Binding.Builder bindingBuilder = binding.toBuilder(); + // Remove the member. + // Note: ProtocolStringList is immutable, so we need to rebuild the members list. + java.util.List validMembers = new java.util.ArrayList<>(binding.getMembersList()); + validMembers.remove(member); + + bindingBuilder.clearMembers().addAllMembers(validMembers); + if (!validMembers.isEmpty()) { + newBindings.add(bindingBuilder.build()); + } + // If no members left, we can just omit the binding (effective removal). + } else { + newBindings.add(binding); } } - client.setIamPolicy(resourceName, policy); + Policy newPolicy = policy.toBuilder().clearBindings().addAllBindings(newBindings).build(); + + client.setIamPolicy(resourceName, newPolicy); System.out.printf("Updated IAM policy for %s%n", resourceName.toString()); } } diff --git a/kms/src/main/java/kms/ListRetiredResources.java b/kms/src/main/java/kms/ListRetiredResources.java new file mode 100644 index 00000000000..8e729a6eadb --- /dev/null +++ b/kms/src/main/java/kms/ListRetiredResources.java @@ -0,0 +1,52 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kms; + +// [START kms_list_retired_resources] +import com.google.cloud.kms.v1.KeyManagementServiceClient; +import com.google.cloud.kms.v1.LocationName; +import com.google.cloud.kms.v1.RetiredResource; +import java.io.IOException; + +public class ListRetiredResources { + + public void listRetiredResources() throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String locationId = "us-east1"; + listRetiredResources(projectId, locationId); + } + + // List retired resources in a specific project and location. + public void listRetiredResources(String projectId, String locationId) + throws IOException { + // Initialize client that will be used to send requests. This client only + // needs to be created once, and can be reused for multiple requests. After + // completing all of your requests, call the "close" method on the client to + // safely clean up any remaining background resources. + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + // Build the location name from the project and location. + LocationName locationName = LocationName.of(projectId, locationId); + + // List the retired resources. + for (RetiredResource resource : client.listRetiredResources(locationName).iterateAll()) { + System.out.printf("Retired resource: %s%n", resource.getName()); + } + } + } +} +// [END kms_list_retired_resources] diff --git a/kms/src/main/java/kms/VerifyAsymmetricEc.java b/kms/src/main/java/kms/VerifyAsymmetricEc.java index e86964509d0..844c275d1ee 100644 --- a/kms/src/main/java/kms/VerifyAsymmetricEc.java +++ b/kms/src/main/java/kms/VerifyAsymmetricEc.java @@ -45,6 +45,7 @@ public void verifyAsymmetricEc() throws IOException, GeneralSecurityException { verifyAsymmetricEc(projectId, locationId, keyRingId, keyId, keyVersionId, message, signature); } + // CPD-OFF // Verify the signature of a message signed with an RSA key. public void verifyAsymmetricEc( String projectId, diff --git a/kms/src/test/java/kms/SnippetsIT.java b/kms/src/test/java/kms/SnippetsIT.java index 49f18e61a1c..1de8fec89c3 100644 --- a/kms/src/test/java/kms/SnippetsIT.java +++ b/kms/src/test/java/kms/SnippetsIT.java @@ -18,6 +18,8 @@ import static com.google.common.truth.Truth.assertThat; +import com.google.api.gax.rpc.NotFoundException; +import com.google.cloud.kms.v1.CreateCryptoKeyRequest; import com.google.cloud.kms.v1.CryptoKey; import com.google.cloud.kms.v1.CryptoKey.CryptoKeyPurpose; import com.google.cloud.kms.v1.CryptoKeyName; @@ -54,6 +56,7 @@ import java.security.spec.X509EncodedKeySpec; import java.util.Base64; import java.util.UUID; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; import javax.crypto.Cipher; @@ -64,6 +67,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -81,44 +85,55 @@ public class SnippetsIT { private static String MAC_KEY_ID; private static String SYMMETRIC_KEY_ID; - private ByteArrayOutputStream stdOut; + @BeforeClass public static void beforeAll() throws IOException { Assert.assertFalse("missing GOOGLE_CLOUD_PROJECT", Strings.isNullOrEmpty(PROJECT_ID)); - KEY_RING_ID = getRandomId(); - createKeyRing(KEY_RING_ID); + KEY_RING_ID = "kms-test-keyring-java"; + try { + createKeyRing(KEY_RING_ID); + } catch (Exception e) { + // Ignore. + } - ASYMMETRIC_DECRYPT_KEY_ID = getRandomId(); - createAsymmetricDecryptKey(ASYMMETRIC_DECRYPT_KEY_ID); + ASYMMETRIC_DECRYPT_KEY_ID = "kms-test-asymmetric-decrypt-v3"; + createKeyOrIgnore(ASYMMETRIC_DECRYPT_KEY_ID, SnippetsIT::createAsymmetricDecryptKey); - ASYMMETRIC_SIGN_EC_KEY_ID = getRandomId(); - createAsymmetricSignEcKey(ASYMMETRIC_SIGN_EC_KEY_ID); + ASYMMETRIC_SIGN_EC_KEY_ID = "kms-test-asymmetric-sign-ec-v3"; + createKeyOrIgnore(ASYMMETRIC_SIGN_EC_KEY_ID, SnippetsIT::createAsymmetricSignEcKey); - ASYMMETRIC_SIGN_RSA_KEY_ID = getRandomId(); - createAsymmetricSignRsaKey(ASYMMETRIC_SIGN_RSA_KEY_ID); + ASYMMETRIC_SIGN_RSA_KEY_ID = "kms-test-asymmetric-sign-rsa-v3"; + createKeyOrIgnore(ASYMMETRIC_SIGN_RSA_KEY_ID, SnippetsIT::createAsymmetricSignRsaKey); - HSM_KEY_ID = getRandomId(); - createHsmKey(HSM_KEY_ID); + HSM_KEY_ID = "kms-test-hsm-v3"; + createKeyOrIgnore(HSM_KEY_ID, SnippetsIT::createHsmKey); - MAC_KEY_ID = getRandomId(); - createMacKey(MAC_KEY_ID); + MAC_KEY_ID = "kms-test-mac-v3"; + createKeyOrIgnore(MAC_KEY_ID, SnippetsIT::createMacKey); - SYMMETRIC_KEY_ID = getRandomId(); - createSymmetricKey(SYMMETRIC_KEY_ID); + SYMMETRIC_KEY_ID = "kms-test-symmetric-v3"; + createKeyOrIgnore(SYMMETRIC_KEY_ID, SnippetsIT::createSymmetricKey); } + private ByteArrayOutputStream stdOut; + private ByteArrayOutputStream stdErr; + @Before public void beforeEach() { stdOut = new ByteArrayOutputStream(); + stdErr = new ByteArrayOutputStream(); System.setOut(new PrintStream(stdOut)); + System.setErr(new PrintStream(stdErr)); } @After public void afterEach() { stdOut = null; + stdErr = null; System.setOut(null); + System.setErr(null); } @AfterClass @@ -134,6 +149,7 @@ public static void afterAll() throws IOException { client.updateCryptoKey(keyWithoutRotation, fieldMask); } + /* ListCryptoKeyVersionsRequest listVersionsRequest = ListCryptoKeyVersionsRequest.newBuilder() .setParent(key.getName()) @@ -143,6 +159,7 @@ public static void afterAll() throws IOException { client.listCryptoKeyVersions(listVersionsRequest).iterateAll()) { client.destroyCryptoKeyVersion(version.getName()); } + */ } } } @@ -162,9 +179,36 @@ private static String getRandomId() { private static KeyRing createKeyRing(String keyRingId) throws IOException { try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { - KeyRing keyRing = KeyRing.newBuilder().build(); - KeyRing createdKeyRing = client.createKeyRing(getLocationName(), keyRingId, keyRing); - return createdKeyRing; + KeyRingName keyRingName = KeyRingName.of(PROJECT_ID, LOCATION_ID, keyRingId); + try { + return client.getKeyRing(keyRingName); + } catch (com.google.api.gax.rpc.NotFoundException e) { + // KeyRing doesn't exist, create it. + KeyRing keyRing = KeyRing.newBuilder().build(); + return client.createKeyRing(getLocationName(), keyRingId, keyRing); + } + } + } + + interface KeyCreator { + void create(String id) throws IOException; + } + + private static void createKeyOrIgnore(String id, KeyCreator creator) throws IOException { + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + CryptoKeyName keyName = CryptoKeyName.of(PROJECT_ID, LOCATION_ID, KEY_RING_ID, id); + try { + client.getCryptoKey(keyName); + return; // Exists + } catch (com.google.api.gax.rpc.NotFoundException e) { + // Doesn't exist, try creating. + } + } + + try { + creator.create(id); + } catch (com.google.api.gax.rpc.AlreadyExistsException e) { + // Ignore } } @@ -272,6 +316,30 @@ private static CryptoKey createSymmetricKey(String keyId) throws IOException { } } + private static CryptoKey createSymmetricKeyWithNoInitialVersion(String keyId) throws IOException { + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + CryptoKey key = + CryptoKey.newBuilder() + .setPurpose(CryptoKeyPurpose.ENCRYPT_DECRYPT) + .setVersionTemplate( + CryptoKeyVersionTemplate.newBuilder() + .setAlgorithm(CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION) + .build()) + .putLabels("foo", "bar") + .putLabels("zip", "zap") + .build(); + + CreateCryptoKeyRequest request = + CreateCryptoKeyRequest.newBuilder() + .setParent(getKeyRingName().toString()) + .setCryptoKeyId(keyId) + .setCryptoKey(key) + .setSkipInitialVersionCreation(true) + .build(); + return client.createCryptoKey(request); + } + } + private static CryptoKeyVersion createKeyVersion(String keyId) throws IOException, InterruptedException, TimeoutException { try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { @@ -430,6 +498,71 @@ public void testDestroyRestoreKeyVersion() assertThat(stdOut.toString()).contains("Restored key version"); } + @Test + public void testDeleteKey() throws IOException { + String deleteKeyId = getRandomId(); + createSymmetricKeyWithNoInitialVersion(deleteKeyId); + + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + CryptoKeyName keyName = CryptoKeyName.of(PROJECT_ID, LOCATION_ID, KEY_RING_ID, deleteKeyId); + + // Delete the key. + new DeleteKey().deleteKey(PROJECT_ID, LOCATION_ID, KEY_RING_ID, deleteKeyId); + String output = stdOut.toString() + stdErr.toString(); + assertThat(output).contains("Deleted key"); + } + } + + @Test + public void testGetRetiredResource() + throws IOException, InterruptedException, ExecutionException { + String deleteKeyId = getRandomId(); + createSymmetricKeyWithNoInitialVersion(deleteKeyId); + + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + CryptoKeyName keyName = + CryptoKeyName.of(PROJECT_ID, LOCATION_ID, KEY_RING_ID, deleteKeyId); + client.deleteCryptoKeyAsync(keyName).get(); + } + + // List retired resources to find the one we just deleted. + String retiredResourceName = null; + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + for (com.google.cloud.kms.v1.RetiredResource resource : + client.listRetiredResources(getLocationName()).iterateAll()) { + if (resource.toString().contains(deleteKeyId)) { + retiredResourceName = resource.getName(); + break; + } + } + } + + // If not found, asserting null will fail. + assertThat(retiredResourceName).isNotNull(); + + // The name of the retired resource is required for Get. + String retiredResourceId = + retiredResourceName.substring(retiredResourceName.lastIndexOf('/') + 1); + new GetRetiredResource().getRetiredResource(PROJECT_ID, LOCATION_ID, retiredResourceId); + assertThat(stdOut.toString()).contains("Retired resource"); + } + + @Test + public void testListRetiredResources() + throws IOException, InterruptedException, ExecutionException { + String deleteKeyId = getRandomId(); + createSymmetricKeyWithNoInitialVersion(deleteKeyId); + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + CryptoKeyName keyName = + CryptoKeyName.of(PROJECT_ID, LOCATION_ID, KEY_RING_ID, deleteKeyId); + client.deleteCryptoKeyAsync(keyName).get(); + } + + new ListRetiredResources().listRetiredResources(PROJECT_ID, LOCATION_ID); + // Since we ran DeleteKey above, there should be at least one retired resource. + assertThat(stdOut.toString()).contains("Retired resource"); + } + @Test public void testDisableEnableKeyVersion() throws Exception { CryptoKeyVersion keyVersion = createKeyVersion(ASYMMETRIC_DECRYPT_KEY_ID); @@ -477,7 +610,9 @@ public void testGetKeyVersionAttestation() throws IOException { @Test public void testGetKeyLabels() throws IOException { - new GetKeyLabels().getKeyLabels(PROJECT_ID, LOCATION_ID, KEY_RING_ID, SYMMETRIC_KEY_ID); + String keyId = getRandomId(); + createSymmetricKey(keyId); + new GetKeyLabels().getKeyLabels(PROJECT_ID, LOCATION_ID, KEY_RING_ID, keyId); assertThat(stdOut.toString()).contains("foo=bar"); } @@ -541,8 +676,10 @@ public void testUpdateKeyAddRotation() throws IOException { @Test public void testUpdateKeyRemoveLabels() throws IOException { + String keyId = getRandomId(); + createSymmetricKeyWithNoInitialVersion(keyId); new UpdateKeyRemoveLabels() - .updateKeyRemoveLabels(PROJECT_ID, LOCATION_ID, KEY_RING_ID, SYMMETRIC_KEY_ID); + .updateKeyRemoveLabels(PROJECT_ID, LOCATION_ID, KEY_RING_ID, keyId); assertThat(stdOut.toString()).contains("Updated key"); } @@ -562,8 +699,10 @@ public void testUpdateKeySetPrimary() throws IOException { @Test public void testUpdateKeyUpdateLabels() throws IOException { + String keyId = getRandomId(); + createSymmetricKeyWithNoInitialVersion(keyId); new UpdateKeyUpdateLabels() - .updateKeyUpdateLabels(PROJECT_ID, LOCATION_ID, KEY_RING_ID, SYMMETRIC_KEY_ID); + .updateKeyUpdateLabels(PROJECT_ID, LOCATION_ID, KEY_RING_ID, keyId); assertThat(stdOut.toString()).contains("Updated key"); } diff --git a/managedkafka/examples/pom.xml b/managedkafka/examples/pom.xml index 7f1343971b3..217ef96ba08 100644 --- a/managedkafka/examples/pom.xml +++ b/managedkafka/examples/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.example.managedkafka managedkafka-snippets - pom + jar Google Cloud Managed Kafka Snippets https://github.com/GoogleCloudPlatform/java-docs-samples/tree/main/managedkafka @@ -29,7 +29,7 @@ com.google.cloud libraries-bom - 26.50.0 + 26.64.0 pom import diff --git a/managedkafka/examples/src/main/java/examples/CreateBigQuerySinkConnector.java b/managedkafka/examples/src/main/java/examples/CreateBigQuerySinkConnector.java new file mode 100644 index 00000000000..144af6b2a65 --- /dev/null +++ b/managedkafka/examples/src/main/java/examples/CreateBigQuerySinkConnector.java @@ -0,0 +1,112 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package examples; + +// [START managedkafka_create_bigquery_sink_connector] + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.managedkafka.v1.ConnectClusterName; +import com.google.cloud.managedkafka.v1.Connector; +import com.google.cloud.managedkafka.v1.ConnectorName; +import com.google.cloud.managedkafka.v1.CreateConnectorRequest; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectClient; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class CreateBigQuerySinkConnector { + + public static void main(String[] args) throws Exception { + // TODO(developer): Replace these variables before running the example. + String projectId = "my-project-id"; + String region = "my-region"; // e.g. us-east1 + String connectClusterId = "my-connect-cluster"; + String connectorId = "my-bigquery-sink-connector"; + String bigqueryProjectId = "my-bigquery-project-id"; + String datasetName = "my-dataset"; + String kafkaTopicName = "kafka-topic"; + String maxTasks = "3"; + String connectorClass = "com.wepay.kafka.connect.bigquery.BigQuerySinkConnector"; + String keyConverter = "org.apache.kafka.connect.storage.StringConverter"; + String valueConverter = "org.apache.kafka.connect.json.JsonConverter"; + String valueSchemasEnable = "false"; + createBigQuerySinkConnector( + projectId, + region, + connectClusterId, + connectorId, + bigqueryProjectId, + datasetName, + kafkaTopicName, + maxTasks, + connectorClass, + keyConverter, + valueConverter, + valueSchemasEnable); + } + + public static void createBigQuerySinkConnector( + String projectId, + String region, + String connectClusterId, + String connectorId, + String bigqueryProjectId, + String datasetName, + String kafkaTopicName, + String maxTasks, + String connectorClass, + String keyConverter, + String valueConverter, + String valueSchemasEnable) + throws Exception { + + // Build the connector configuration + Map configMap = new HashMap<>(); + configMap.put("name", connectorId); + configMap.put("project", bigqueryProjectId); + configMap.put("topics", kafkaTopicName); + configMap.put("tasks.max", maxTasks); + configMap.put("connector.class", connectorClass); + configMap.put("key.converter", keyConverter); + configMap.put("value.converter", valueConverter); + configMap.put("value.converter.schemas.enable", valueSchemasEnable); + configMap.put("defaultDataset", datasetName); + + Connector connector = + Connector.newBuilder() + .setName(ConnectorName.of(projectId, region, connectClusterId, connectorId).toString()) + .putAllConfigs(configMap) + .build(); + + try (ManagedKafkaConnectClient managedKafkaConnectClient = ManagedKafkaConnectClient.create()) { + CreateConnectorRequest request = + CreateConnectorRequest.newBuilder() + .setParent(ConnectClusterName.of(projectId, region, connectClusterId).toString()) + .setConnectorId(connectorId) + .setConnector(connector) + .build(); + + // This operation is being handled synchronously. + Connector response = managedKafkaConnectClient.createConnector(request); + System.out.printf("Created BigQuery Sink connector: %s\n", response.getName()); + } catch (IOException | ApiException e) { + System.err.printf("managedKafkaConnectClient.createConnector got err: %s\n", e.getMessage()); + } + } +} + +// [END managedkafka_create_bigquery_sink_connector] diff --git a/managedkafka/examples/src/main/java/examples/CreateCloudStorageSinkConnector.java b/managedkafka/examples/src/main/java/examples/CreateCloudStorageSinkConnector.java new file mode 100644 index 00000000000..be14c0e4a47 --- /dev/null +++ b/managedkafka/examples/src/main/java/examples/CreateCloudStorageSinkConnector.java @@ -0,0 +1,115 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package examples; + +// [START managedkafka_create_cloud_storage_sink_connector] + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.managedkafka.v1.ConnectClusterName; +import com.google.cloud.managedkafka.v1.Connector; +import com.google.cloud.managedkafka.v1.ConnectorName; +import com.google.cloud.managedkafka.v1.CreateConnectorRequest; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectClient; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class CreateCloudStorageSinkConnector { + + public static void main(String[] args) throws Exception { + // TODO(developer): Replace these variables before running the example. + String projectId = "my-project-id"; + String region = "my-region"; // e.g. us-east1 + String connectClusterId = "my-connect-cluster"; + String connectorId = "my-gcs-sink-connector"; + String bucketName = "my-gcs-bucket"; + String kafkaTopicName = "kafka-topic"; + String connectorClass = "io.aiven.kafka.connect.gcs.GcsSinkConnector"; + String maxTasks = "3"; + String gcsCredentialsDefault = "true"; + String formatOutputType = "json"; + String valueConverter = "org.apache.kafka.connect.json.JsonConverter"; + String valueSchemasEnable = "false"; + String keyConverter = "org.apache.kafka.connect.storage.StringConverter"; + createCloudStorageSinkConnector( + projectId, + region, + connectClusterId, + connectorId, + bucketName, + kafkaTopicName, + connectorClass, + maxTasks, + gcsCredentialsDefault, + formatOutputType, + valueConverter, + valueSchemasEnable, + keyConverter); + } + + public static void createCloudStorageSinkConnector( + String projectId, + String region, + String connectClusterId, + String connectorId, + String bucketName, + String kafkaTopicName, + String connectorClass, + String maxTasks, + String gcsCredentialsDefault, + String formatOutputType, + String valueConverter, + String valueSchemasEnable, + String keyConverter) + throws Exception { + + // Build the connector configuration + Map configMap = new HashMap<>(); + configMap.put("connector.class", connectorClass); + configMap.put("tasks.max", maxTasks); + configMap.put("topics", kafkaTopicName); + configMap.put("gcs.bucket.name", bucketName); + configMap.put("gcs.credentials.default", gcsCredentialsDefault); + configMap.put("format.output.type", formatOutputType); + configMap.put("name", connectorId); + configMap.put("value.converter", valueConverter); + configMap.put("value.converter.schemas.enable", valueSchemasEnable); + configMap.put("key.converter", keyConverter); + + Connector connector = Connector.newBuilder() + .setName( + ConnectorName.of(projectId, region, connectClusterId, connectorId).toString()) + .putAllConfigs(configMap) + .build(); + + try (ManagedKafkaConnectClient managedKafkaConnectClient = ManagedKafkaConnectClient.create()) { + CreateConnectorRequest request = CreateConnectorRequest.newBuilder() + .setParent(ConnectClusterName.of(projectId, region, connectClusterId).toString()) + .setConnectorId(connectorId) + .setConnector(connector) + .build(); + + // This operation is being handled synchronously. + Connector response = managedKafkaConnectClient.createConnector(request); + System.out.printf("Created Cloud Storage Sink connector: %s\n", response.getName()); + } catch (IOException | ApiException e) { + System.err.printf("managedKafkaConnectClient.createConnector got err: %s\n", e.getMessage()); + } + } +} + +// [END managedkafka_create_cloud_storage_sink_connector] diff --git a/managedkafka/examples/src/main/java/examples/CreateConnectCluster.java b/managedkafka/examples/src/main/java/examples/CreateConnectCluster.java new file mode 100644 index 00000000000..1f48eecb44e --- /dev/null +++ b/managedkafka/examples/src/main/java/examples/CreateConnectCluster.java @@ -0,0 +1,129 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package examples; + +// [START managedkafka_create_connect_cluster] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.api.gax.longrunning.OperationSnapshot; +import com.google.api.gax.longrunning.OperationTimedPollAlgorithm; +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.retrying.RetryingFuture; +import com.google.api.gax.retrying.TimedRetryAlgorithm; +import com.google.cloud.managedkafka.v1.CapacityConfig; +import com.google.cloud.managedkafka.v1.ConnectAccessConfig; +import com.google.cloud.managedkafka.v1.ConnectCluster; +import com.google.cloud.managedkafka.v1.ConnectGcpConfig; +import com.google.cloud.managedkafka.v1.ConnectNetworkConfig; +import com.google.cloud.managedkafka.v1.CreateConnectClusterRequest; +import com.google.cloud.managedkafka.v1.LocationName; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectClient; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectSettings; +import com.google.cloud.managedkafka.v1.OperationMetadata; +import java.time.Duration; +import java.util.concurrent.ExecutionException; + +public class CreateConnectCluster { + + public static void main(String[] args) throws Exception { + // TODO(developer): Replace these variables before running the example. + String projectId = "my-project-id"; + String region = "my-region"; // e.g. us-east1 + String clusterId = "my-connect-cluster"; + String subnet = "my-subnet"; // e.g. projects/my-project/regions/my-region/subnetworks/my-subnet + String kafkaCluster = "my-kafka-cluster"; // The Kafka cluster to connect to + int cpu = 12; + long memoryBytes = 12884901888L; // 12 GiB + createConnectCluster(projectId, region, clusterId, subnet, kafkaCluster, cpu, memoryBytes); + } + + public static void createConnectCluster( + String projectId, + String region, + String clusterId, + String subnet, + String kafkaCluster, + int cpu, + long memoryBytes) + throws Exception { + CapacityConfig capacityConfig = CapacityConfig.newBuilder().setVcpuCount(cpu) + .setMemoryBytes(memoryBytes).build(); + ConnectNetworkConfig networkConfig = ConnectNetworkConfig.newBuilder() + .setPrimarySubnet(subnet) + .build(); + // Optionally, you can also specify additional accessible subnets and resolvable + // DNS domains as part of your network configuration. For example: + // .addAllAdditionalSubnets(List.of("subnet-1", "subnet-2")) + // .addAllDnsDomainNames(List.of("dns-1", "dns-2")) + ConnectGcpConfig gcpConfig = ConnectGcpConfig.newBuilder() + .setAccessConfig(ConnectAccessConfig.newBuilder().addNetworkConfigs(networkConfig).build()) + .build(); + ConnectCluster connectCluster = ConnectCluster.newBuilder() + .setCapacityConfig(capacityConfig) + .setGcpConfig(gcpConfig) + .setKafkaCluster(kafkaCluster) + .build(); + + // Create the settings to configure the timeout for polling operations + ManagedKafkaConnectSettings.Builder settingsBuilder = ManagedKafkaConnectSettings.newBuilder(); + TimedRetryAlgorithm timedRetryAlgorithm = OperationTimedPollAlgorithm.create( + RetrySettings.newBuilder() + .setTotalTimeoutDuration(Duration.ofHours(1L)) + .build()); + settingsBuilder.createConnectClusterOperationSettings() + .setPollingAlgorithm(timedRetryAlgorithm); + + try (ManagedKafkaConnectClient managedKafkaConnectClient = ManagedKafkaConnectClient + .create(settingsBuilder.build())) { + CreateConnectClusterRequest request = CreateConnectClusterRequest.newBuilder() + .setParent(LocationName.of(projectId, region).toString()) + .setConnectClusterId(clusterId) + .setConnectCluster(connectCluster) + .build(); + + // The duration of this operation can vary considerably, typically taking + // between 10-30 minutes. + OperationFuture future = managedKafkaConnectClient + .createConnectClusterOperationCallable().futureCall(request); + + // Get the initial LRO and print details. + OperationSnapshot operation = future.getInitialFuture().get(); + System.out.printf( + "Connect cluster creation started. Operation name: %s\nDone: %s\nMetadata: %s\n", + operation.getName(), operation.isDone(), future.getMetadata().get().toString()); + + while (!future.isDone()) { + // The pollingFuture gives us the most recent status of the operation + RetryingFuture pollingFuture = future.getPollingFuture(); + OperationSnapshot currentOp = pollingFuture.getAttemptResult().get(); + System.out.printf("Polling Operation:\nName: %s\n Done: %s\n", + currentOp.getName(), + currentOp.isDone()); + } + + // NOTE: future.get() blocks completion until the operation is complete (isDone + // = True) + ConnectCluster response = future.get(); + System.out.printf("Created connect cluster: %s\n", response.getName()); + } catch (ExecutionException e) { + System.err.printf("managedKafkaConnectClient.createConnectCluster got err: %s\n", + e.getMessage()); + throw e; + } + } +} +// [END managedkafka_create_connect_cluster] diff --git a/managedkafka/examples/src/main/java/examples/CreateMirrorMaker2SourceConnector.java b/managedkafka/examples/src/main/java/examples/CreateMirrorMaker2SourceConnector.java new file mode 100644 index 00000000000..238e9994f72 --- /dev/null +++ b/managedkafka/examples/src/main/java/examples/CreateMirrorMaker2SourceConnector.java @@ -0,0 +1,113 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package examples; + +// [START managedkafka_create_mirrormaker2_connector] + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.managedkafka.v1.ConnectClusterName; +import com.google.cloud.managedkafka.v1.Connector; +import com.google.cloud.managedkafka.v1.ConnectorName; +import com.google.cloud.managedkafka.v1.CreateConnectorRequest; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectClient; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class CreateMirrorMaker2SourceConnector { + + public static void main(String[] args) throws Exception { + // TODO(developer): Replace these variables before running the example. + String projectId = "my-project-id"; + String region = "my-region"; // e.g. us-east1 + String maxTasks = "3"; + String connectClusterId = "my-connect-cluster"; + String connectorId = "my-mirrormaker2-connector"; + String sourceClusterBootstrapServers = "my-source-cluster:9092"; + String targetClusterBootstrapServers = "my-target-cluster:9092"; + String sourceClusterAlias = "source"; + String targetClusterAlias = "target"; // This is usually the primary cluster. + String connectorClass = "org.apache.kafka.connect.mirror.MirrorSourceConnector"; + String topics = ".*"; + // You can define an exclusion policy for topics as follows: + // To exclude internal MirrorMaker 2 topics, internal topics and replicated topics. + String topicsExclude = "mm2.*.internal,.*.replica,__.*"; + createMirrorMaker2SourceConnector( + projectId, + region, + maxTasks, + connectClusterId, + connectorId, + sourceClusterBootstrapServers, + targetClusterBootstrapServers, + sourceClusterAlias, + targetClusterAlias, + connectorClass, + topics, + topicsExclude); + } + + public static void createMirrorMaker2SourceConnector( + String projectId, + String region, + String maxTasks, + String connectClusterId, + String connectorId, + String sourceClusterBootstrapServers, + String targetClusterBootstrapServers, + String sourceClusterAlias, + String targetClusterAlias, + String connectorClass, + String topics, + String topicsExclude) + throws Exception { + + // Build the connector configuration + Map configMap = new HashMap<>(); + configMap.put("tasks.max", maxTasks); + configMap.put("connector.class", connectorClass); + configMap.put("name", connectorId); + configMap.put("source.cluster.alias", sourceClusterAlias); + configMap.put("target.cluster.alias", targetClusterAlias); + configMap.put("topics", topics); + configMap.put("topics.exclude", topicsExclude); + configMap.put("source.cluster.bootstrap.servers", sourceClusterBootstrapServers); + configMap.put("target.cluster.bootstrap.servers", targetClusterBootstrapServers); + + Connector connector = Connector.newBuilder() + .setName( + ConnectorName.of(projectId, region, connectClusterId, connectorId).toString()) + .putAllConfigs(configMap) + .build(); + + try (ManagedKafkaConnectClient managedKafkaConnectClient = ManagedKafkaConnectClient.create()) { + CreateConnectorRequest request = CreateConnectorRequest.newBuilder() + .setParent(ConnectClusterName.of(projectId, region, connectClusterId).toString()) + .setConnectorId(connectorId) + .setConnector(connector) + .build(); + + // This operation is being handled synchronously. + Connector response = managedKafkaConnectClient.createConnector(request); + System.out.printf("Created MirrorMaker2 Source connector: %s\n", response.getName()); + } catch (IOException | ApiException e) { + System.err.printf("managedKafkaConnectClient.createConnector got err: %s\n", e.getMessage()); + } + } +} + +// [END managedkafka_create_mirrormaker2_connector] \ No newline at end of file diff --git a/managedkafka/examples/src/main/java/examples/CreatePubSubSinkConnector.java b/managedkafka/examples/src/main/java/examples/CreatePubSubSinkConnector.java new file mode 100644 index 00000000000..2492a5c8833 --- /dev/null +++ b/managedkafka/examples/src/main/java/examples/CreatePubSubSinkConnector.java @@ -0,0 +1,107 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package examples; + +// [START managedkafka_create_pubsub_sink_connector] + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.managedkafka.v1.ConnectClusterName; +import com.google.cloud.managedkafka.v1.Connector; +import com.google.cloud.managedkafka.v1.ConnectorName; +import com.google.cloud.managedkafka.v1.CreateConnectorRequest; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectClient; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class CreatePubSubSinkConnector { + + public static void main(String[] args) throws Exception { + // TODO(developer): Replace these variables before running the example. + String projectId = "my-project-id"; + String region = "my-region"; // e.g. us-east1 + String connectClusterId = "my-connect-cluster"; + String connectorId = "my-pubsub-sink-connector"; + String pubsubProjectId = "my-pubsub-project-id"; + String pubsubTopicName = "my-pubsub-topic"; + String kafkaTopicName = "kafka-topic"; + String connectorClass = "com.google.pubsub.kafka.sink.CloudPubSubSinkConnector"; + String maxTasks = "3"; + String valueConverter = "org.apache.kafka.connect.storage.StringConverter"; + String keyConverter = "org.apache.kafka.connect.storage.StringConverter"; + createPubSubSinkConnector( + projectId, + region, + connectClusterId, + connectorId, + pubsubProjectId, + pubsubTopicName, + kafkaTopicName, + connectorClass, + maxTasks, + valueConverter, + keyConverter); + } + + public static void createPubSubSinkConnector( + String projectId, + String region, + String connectClusterId, + String connectorId, + String pubsubProjectId, + String pubsubTopicName, + String kafkaTopicName, + String connectorClass, + String maxTasks, + String valueConverter, + String keyConverter) + throws Exception { + + // Build the connector configuration + Map configMap = new HashMap<>(); + configMap.put("connector.class", connectorClass); + configMap.put("name", connectorId); + configMap.put("tasks.max", maxTasks); + configMap.put("topics", kafkaTopicName); + configMap.put("value.converter", valueConverter); + configMap.put("key.converter", keyConverter); + configMap.put("cps.topic", pubsubTopicName); + configMap.put("cps.project", pubsubProjectId); + + Connector connector = Connector.newBuilder() + .setName( + ConnectorName.of(projectId, region, connectClusterId, connectorId).toString()) + .putAllConfigs(configMap) + .build(); + + try (ManagedKafkaConnectClient managedKafkaConnectClient = ManagedKafkaConnectClient.create()) { + CreateConnectorRequest request = CreateConnectorRequest.newBuilder() + .setParent(ConnectClusterName.of(projectId, region, connectClusterId).toString()) + .setConnectorId(connectorId) + .setConnector(connector) + .build(); + + // This operation is being handled synchronously. + Connector response = managedKafkaConnectClient.createConnector(request); + System.out.printf("Created Pub/Sub Sink connector: %s\n", response.getName()); + } catch (IOException | ApiException e) { + System.err.printf("managedKafkaConnectClient.createConnector got err: %s\n", e.getMessage()); + } + } +} + +// [END managedkafka_create_pubsub_sink_connector] diff --git a/managedkafka/examples/src/main/java/examples/CreatePubSubSourceConnector.java b/managedkafka/examples/src/main/java/examples/CreatePubSubSourceConnector.java new file mode 100644 index 00000000000..c43537b152b --- /dev/null +++ b/managedkafka/examples/src/main/java/examples/CreatePubSubSourceConnector.java @@ -0,0 +1,107 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package examples; + +// [START managedkafka_create_pubsub_source_connector] + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.managedkafka.v1.ConnectClusterName; +import com.google.cloud.managedkafka.v1.Connector; +import com.google.cloud.managedkafka.v1.ConnectorName; +import com.google.cloud.managedkafka.v1.CreateConnectorRequest; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectClient; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class CreatePubSubSourceConnector { + + public static void main(String[] args) throws Exception { + // TODO(developer): Replace these variables before running the example. + String projectId = "my-project-id"; + String region = "my-region"; // e.g. us-east1 + String connectClusterId = "my-connect-cluster"; + String connectorId = "my-pubsub-source-connector"; + String pubsubProjectId = "my-pubsub-project-id"; + String subscriptionName = "my-subscription"; + String kafkaTopicName = "pubsub-topic"; + String connectorClass = "com.google.pubsub.kafka.source.CloudPubSubSourceConnector"; + String maxTasks = "3"; + String valueConverter = "org.apache.kafka.connect.converters.ByteArrayConverter"; + String keyConverter = "org.apache.kafka.connect.storage.StringConverter"; + createPubSubSourceConnector( + projectId, + region, + connectClusterId, + connectorId, + pubsubProjectId, + subscriptionName, + kafkaTopicName, + connectorClass, + maxTasks, + valueConverter, + keyConverter); + } + + public static void createPubSubSourceConnector( + String projectId, + String region, + String connectClusterId, + String connectorId, + String pubsubProjectId, + String subscriptionName, + String kafkaTopicName, + String connectorClass, + String maxTasks, + String valueConverter, + String keyConverter) + throws Exception { + + // Build the connector configuration + Map configMap = new HashMap<>(); + configMap.put("connector.class", connectorClass); + configMap.put("name", connectorId); + configMap.put("tasks.max", maxTasks); + configMap.put("kafka.topic", kafkaTopicName); + configMap.put("cps.subscription", subscriptionName); + configMap.put("cps.project", pubsubProjectId); + configMap.put("value.converter", valueConverter); + configMap.put("key.converter", keyConverter); + + Connector connector = Connector.newBuilder() + .setName( + ConnectorName.of(projectId, region, connectClusterId, connectorId).toString()) + .putAllConfigs(configMap) + .build(); + + try (ManagedKafkaConnectClient managedKafkaConnectClient = ManagedKafkaConnectClient.create()) { + CreateConnectorRequest request = CreateConnectorRequest.newBuilder() + .setParent(ConnectClusterName.of(projectId, region, connectClusterId).toString()) + .setConnectorId(connectorId) + .setConnector(connector) + .build(); + + // This operation is being handled synchronously. + Connector response = managedKafkaConnectClient.createConnector(request); + System.out.printf("Created Pub/Sub Source connector: %s\n", response.getName()); + } catch (IOException | ApiException e) { + System.err.printf("managedKafkaConnectClient.createConnector got err: %s\n", e.getMessage()); + } + } +} + +// [END managedkafka_create_pubsub_source_connector] diff --git a/managedkafka/examples/src/main/java/examples/DeleteConnectCluster.java b/managedkafka/examples/src/main/java/examples/DeleteConnectCluster.java new file mode 100644 index 00000000000..18196c36b2b --- /dev/null +++ b/managedkafka/examples/src/main/java/examples/DeleteConnectCluster.java @@ -0,0 +1,84 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package examples; + +// [START managedkafka_delete_connect_cluster] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.api.gax.longrunning.OperationSnapshot; +import com.google.api.gax.longrunning.OperationTimedPollAlgorithm; +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.retrying.TimedRetryAlgorithm; +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.managedkafka.v1.ConnectClusterName; +import com.google.cloud.managedkafka.v1.DeleteConnectClusterRequest; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectClient; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectSettings; +import com.google.cloud.managedkafka.v1.OperationMetadata; +import com.google.protobuf.Empty; +import java.io.IOException; +import java.time.Duration; + +public class DeleteConnectCluster { + + public static void main(String[] args) throws Exception { + // TODO(developer): Replace these variables before running the example. + String projectId = "my-project-id"; + String region = "my-region"; // e.g. us-east1 + String clusterId = "my-connect-cluster"; + deleteConnectCluster(projectId, region, clusterId); + } + + public static void deleteConnectCluster(String projectId, String region, String clusterId) + throws Exception { + + // Create the settings to configure the timeout for polling operations + ManagedKafkaConnectSettings.Builder settingsBuilder = ManagedKafkaConnectSettings.newBuilder(); + TimedRetryAlgorithm timedRetryAlgorithm = OperationTimedPollAlgorithm.create( + RetrySettings.newBuilder() + .setTotalTimeoutDuration(Duration.ofHours(1L)) + .build()); + settingsBuilder.deleteConnectClusterOperationSettings() + .setPollingAlgorithm(timedRetryAlgorithm); + + try (ManagedKafkaConnectClient managedKafkaConnectClient = ManagedKafkaConnectClient.create( + settingsBuilder.build())) { + DeleteConnectClusterRequest request = DeleteConnectClusterRequest.newBuilder() + .setName(ConnectClusterName.of(projectId, region, clusterId).toString()) + .build(); + OperationFuture future = managedKafkaConnectClient + .deleteConnectClusterOperationCallable().futureCall(request); + + // Get the initial LRO and print details. CreateConnectCluster contains sample + // code for polling logs. + OperationSnapshot operation = future.getInitialFuture().get(); + System.out.printf( + "Connect cluster deletion started. Operation name: %s\nDone: %s\nMetadata: %s\n", + operation.getName(), + operation.isDone(), + future.getMetadata().get().toString()); + + future.get(); + System.out.println("Deleted connect cluster"); + } catch (IOException | ApiException e) { + System.err.printf("managedKafkaConnectClient.deleteConnectCluster got err: %s\n", + e.getMessage()); + } + } +} + +// [END managedkafka_delete_connect_cluster] diff --git a/managedkafka/examples/src/main/java/examples/DeleteConnector.java b/managedkafka/examples/src/main/java/examples/DeleteConnector.java new file mode 100644 index 00000000000..96a09f79522 --- /dev/null +++ b/managedkafka/examples/src/main/java/examples/DeleteConnector.java @@ -0,0 +1,48 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package examples; + +// [START managedkafka_delete_connector] +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.managedkafka.v1.ConnectorName; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectClient; +import java.io.IOException; + +public class DeleteConnector { + + public static void main(String[] args) throws Exception { + // TODO(developer): Replace these variables before running the example. + String projectId = "my-project-id"; + String region = "my-region"; // e.g. us-east1 + String clusterId = "my-connect-cluster"; + String connectorId = "my-connector"; + deleteConnector(projectId, region, clusterId, connectorId); + } + + public static void deleteConnector( + String projectId, String region, String clusterId, String connectorId) throws IOException { + try (ManagedKafkaConnectClient managedKafkaConnectClient = ManagedKafkaConnectClient.create()) { + ConnectorName name = ConnectorName.of(projectId, region, clusterId, connectorId); + // This operation is handled synchronously. + managedKafkaConnectClient.deleteConnector(name); + System.out.printf("Deleted connector: %s\n", name); + } catch (IOException | ApiException e) { + System.err.printf("managedKafkaConnectClient.deleteConnector got err: %s\n", e.getMessage()); + } + } +} +// [END managedkafka_delete_connector] diff --git a/managedkafka/examples/src/main/java/examples/GetConnectCluster.java b/managedkafka/examples/src/main/java/examples/GetConnectCluster.java new file mode 100644 index 00000000000..e588896e6f1 --- /dev/null +++ b/managedkafka/examples/src/main/java/examples/GetConnectCluster.java @@ -0,0 +1,50 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package examples; + +// [START managedkafka_get_connect_cluster] +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.managedkafka.v1.ConnectCluster; +import com.google.cloud.managedkafka.v1.ConnectClusterName; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectClient; +import java.io.IOException; + +public class GetConnectCluster { + + public static void main(String[] args) throws Exception { + // TODO(developer): Replace these variables before running the example. + String projectId = "my-project-id"; + String region = "my-region"; // e.g. us-east1 + String clusterId = "my-connect-cluster"; + getConnectCluster(projectId, region, clusterId); + } + + public static void getConnectCluster(String projectId, String region, String clusterId) + throws Exception { + try (ManagedKafkaConnectClient managedKafkaConnectClient = ManagedKafkaConnectClient.create()) { + // This operation is being handled synchronously. + ConnectCluster connectCluster = managedKafkaConnectClient + .getConnectCluster(ConnectClusterName.of(projectId, region, clusterId)); + System.out.println(connectCluster.getAllFields()); + } catch (IOException | ApiException e) { + System.err.printf("managedKafkaConnectClient.getConnectCluster got err: %s\n", + e.getMessage()); + } + } +} + +// [END managedkafka_get_connect_cluster] diff --git a/managedkafka/examples/src/main/java/examples/GetConnector.java b/managedkafka/examples/src/main/java/examples/GetConnector.java new file mode 100644 index 00000000000..b5be2672e19 --- /dev/null +++ b/managedkafka/examples/src/main/java/examples/GetConnector.java @@ -0,0 +1,49 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package examples; + +// [START managedkafka_get_connector] +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.managedkafka.v1.Connector; +import com.google.cloud.managedkafka.v1.ConnectorName; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectClient; +import java.io.IOException; + +public class GetConnector { + + public static void main(String[] args) throws Exception { + // TODO(developer): Replace these variables before running the example. + String projectId = "my-project-id"; + String region = "my-region"; // e.g. us-east1 + String clusterId = "my-connect-cluster"; + String connectorId = "my-connector"; + getConnector(projectId, region, clusterId, connectorId); + } + + public static void getConnector( + String projectId, String region, String clusterId, String connectorId) throws IOException { + try (ManagedKafkaConnectClient managedKafkaConnectClient = ManagedKafkaConnectClient.create()) { + ConnectorName name = ConnectorName.of(projectId, region, clusterId, connectorId); + // This operation is handled synchronously. + Connector connector = managedKafkaConnectClient.getConnector(name); + System.out.println(connector.getAllFields()); + } catch (IOException | ApiException e) { + System.err.printf("managedKafkaConnectClient.getConnector got err: %s\n", e.getMessage()); + } + } +} +// [END managedkafka_get_connector] diff --git a/managedkafka/examples/src/main/java/examples/ListConnectClusters.java b/managedkafka/examples/src/main/java/examples/ListConnectClusters.java new file mode 100644 index 00000000000..2dcdbd55b03 --- /dev/null +++ b/managedkafka/examples/src/main/java/examples/ListConnectClusters.java @@ -0,0 +1,51 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package examples; + +// [START managedkafka_list_connect_clusters] +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.managedkafka.v1.ConnectCluster; +import com.google.cloud.managedkafka.v1.LocationName; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectClient; +import java.io.IOException; + +public class ListConnectClusters { + + public static void main(String[] args) throws Exception { + // TODO(developer): Replace these variables before running the example. + String projectId = "my-project-id"; + String region = "my-region"; // e.g. us-east1 + listConnectClusters(projectId, region); + } + + public static void listConnectClusters(String projectId, String region) throws Exception { + try (ManagedKafkaConnectClient managedKafkaConnectClient = + ManagedKafkaConnectClient.create()) { + LocationName locationName = LocationName.of(projectId, region); + // This operation is being handled synchronously. + for (ConnectCluster connectCluster : managedKafkaConnectClient + .listConnectClusters(locationName).iterateAll()) { + System.out.println(connectCluster.getAllFields()); + } + } catch (IOException | ApiException e) { + System.err.printf("managedKafkaConnectClient.listConnectClusters got err: %s\n", + e.getMessage()); + } + } +} + +// [END managedkafka_list_connect_clusters] diff --git a/managedkafka/examples/src/main/java/examples/ListConnectors.java b/managedkafka/examples/src/main/java/examples/ListConnectors.java new file mode 100644 index 00000000000..41d8ea9610b --- /dev/null +++ b/managedkafka/examples/src/main/java/examples/ListConnectors.java @@ -0,0 +1,49 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package examples; + +// [START managedkafka_list_connectors] +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.managedkafka.v1.ConnectClusterName; +import com.google.cloud.managedkafka.v1.Connector; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectClient; +import java.io.IOException; + +public class ListConnectors { + + public static void main(String[] args) throws Exception { + // TODO(developer): Replace these variables before running the example. + String projectId = "my-project-id"; + String region = "my-region"; // e.g. us-east1 + String clusterId = "my-connect-cluster"; + listConnectors(projectId, region, clusterId); + } + + public static void listConnectors(String projectId, String region, String clusterId) + throws IOException { + try (ManagedKafkaConnectClient managedKafkaConnectClient = ManagedKafkaConnectClient.create()) { + ConnectClusterName parent = ConnectClusterName.of(projectId, region, clusterId); + // This operation is handled synchronously. + for (Connector connector : managedKafkaConnectClient.listConnectors(parent).iterateAll()) { + System.out.println(connector.getAllFields()); + } + } catch (IOException | ApiException e) { + System.err.printf("managedKafkaConnectClient.listConnectors got err: %s\n", e.getMessage()); + } + } +} +// [END managedkafka_list_connectors] diff --git a/managedkafka/examples/src/main/java/examples/PauseConnector.java b/managedkafka/examples/src/main/java/examples/PauseConnector.java new file mode 100644 index 00000000000..36c26ee1ae1 --- /dev/null +++ b/managedkafka/examples/src/main/java/examples/PauseConnector.java @@ -0,0 +1,57 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package examples; + +// [START managedkafka_pause_connector] +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.managedkafka.v1.ConnectorName; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectClient; +import com.google.cloud.managedkafka.v1.PauseConnectorRequest; +import java.io.IOException; + +public class PauseConnector { + + public static void main(String[] args) throws Exception { + // TODO(developer): Replace these variables before running the example. + String projectId = "my-project-id"; + String region = "my-region"; // e.g. us-east1 + String connectClusterId = "my-connect-cluster"; + String connectorId = "my-connector"; + pauseConnector(projectId, region, connectClusterId, connectorId); + } + + public static void pauseConnector( + String projectId, String region, String connectClusterId, String connectorId) + throws Exception { + try (ManagedKafkaConnectClient managedKafkaConnectClient = + ManagedKafkaConnectClient.create()) { + ConnectorName connectorName = ConnectorName.of(projectId, region, connectClusterId, + connectorId); + PauseConnectorRequest request = PauseConnectorRequest.newBuilder() + .setName(connectorName.toString()).build(); + + // This operation is being handled synchronously. + managedKafkaConnectClient.pauseConnector(request); + System.out.printf("Connector %s paused successfully.\n", connectorId); + } catch (IOException | ApiException e) { + System.err.printf("managedKafkaConnectClient.pauseConnector got err: %s\n", + e.getMessage()); + } + } +} + +// [END managedkafka_pause_connector] diff --git a/managedkafka/examples/src/main/java/examples/RestartConnector.java b/managedkafka/examples/src/main/java/examples/RestartConnector.java new file mode 100644 index 00000000000..78ef135313c --- /dev/null +++ b/managedkafka/examples/src/main/java/examples/RestartConnector.java @@ -0,0 +1,57 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package examples; + +// [START managedkafka_restart_connector] +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.managedkafka.v1.ConnectorName; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectClient; +import com.google.cloud.managedkafka.v1.RestartConnectorRequest; +import java.io.IOException; + +public class RestartConnector { + + public static void main(String[] args) throws Exception { + // TODO(developer): Replace these variables before running the example. + String projectId = "my-project-id"; + String region = "my-region"; // e.g. us-east1 + String connectClusterId = "my-connect-cluster"; + String connectorId = "my-connector"; + restartConnector(projectId, region, connectClusterId, connectorId); + } + + public static void restartConnector( + String projectId, String region, String connectClusterId, String connectorId) + throws Exception { + try (ManagedKafkaConnectClient managedKafkaConnectClient = + ManagedKafkaConnectClient.create()) { + ConnectorName connectorName = ConnectorName.of(projectId, region, connectClusterId, + connectorId); + RestartConnectorRequest request = RestartConnectorRequest.newBuilder() + .setName(connectorName.toString()).build(); + + // This operation is being handled synchronously. + managedKafkaConnectClient.restartConnector(request); + System.out.printf("Connector %s restarted successfully.\n", connectorId); + } catch (IOException | ApiException e) { + System.err.printf("managedKafkaConnectClient.restartConnector got err: %s\n", + e.getMessage()); + } + } +} + +// [END managedkafka_restart_connector] diff --git a/managedkafka/examples/src/main/java/examples/ResumeConnector.java b/managedkafka/examples/src/main/java/examples/ResumeConnector.java new file mode 100644 index 00000000000..b3aa808d0f3 --- /dev/null +++ b/managedkafka/examples/src/main/java/examples/ResumeConnector.java @@ -0,0 +1,57 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package examples; + +// [START managedkafka_resume_connector] +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.managedkafka.v1.ConnectorName; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectClient; +import com.google.cloud.managedkafka.v1.ResumeConnectorRequest; +import java.io.IOException; + +public class ResumeConnector { + + public static void main(String[] args) throws Exception { + // TODO(developer): Replace these variables before running the example. + String projectId = "my-project-id"; + String region = "my-region"; // e.g. us-east1 + String connectClusterId = "my-connect-cluster"; + String connectorId = "my-connector"; + resumeConnector(projectId, region, connectClusterId, connectorId); + } + + public static void resumeConnector( + String projectId, String region, String connectClusterId, String connectorId) + throws Exception { + try (ManagedKafkaConnectClient managedKafkaConnectClient = + ManagedKafkaConnectClient.create()) { + ConnectorName connectorName = ConnectorName.of(projectId, region, connectClusterId, + connectorId); + ResumeConnectorRequest request = ResumeConnectorRequest.newBuilder() + .setName(connectorName.toString()).build(); + + // This operation is being handled synchronously. + managedKafkaConnectClient.resumeConnector(request); + System.out.printf("Connector %s resumed successfully.\n", connectorId); + } catch (IOException | ApiException e) { + System.err.printf("managedKafkaConnectClient.resumeConnector got err: %s\n", + e.getMessage()); + } + } +} + +// [END managedkafka_resume_connector] diff --git a/managedkafka/examples/src/main/java/examples/StopConnector.java b/managedkafka/examples/src/main/java/examples/StopConnector.java new file mode 100644 index 00000000000..e5bcd7ccd76 --- /dev/null +++ b/managedkafka/examples/src/main/java/examples/StopConnector.java @@ -0,0 +1,56 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package examples; + +// [START managedkafka_stop_connector] +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.managedkafka.v1.ConnectorName; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectClient; +import com.google.cloud.managedkafka.v1.StopConnectorRequest; +import java.io.IOException; + +public class StopConnector { + + public static void main(String[] args) throws Exception { + // TODO(developer): Replace these variables before running the example. + String projectId = "my-project-id"; + String region = "my-region"; // e.g. us-east1 + String connectClusterId = "my-connect-cluster"; + String connectorId = "my-connector"; + stopConnector(projectId, region, connectClusterId, connectorId); + } + + public static void stopConnector( + String projectId, String region, String connectClusterId, String connectorId) + throws Exception { + try (ManagedKafkaConnectClient managedKafkaConnectClient = + ManagedKafkaConnectClient.create()) { + ConnectorName connectorName = ConnectorName.of(projectId, region, connectClusterId, + connectorId); + StopConnectorRequest request = StopConnectorRequest.newBuilder() + .setName(connectorName.toString()).build(); + + // This operation is being handled synchronously. + managedKafkaConnectClient.stopConnector(request); + System.out.printf("Connector %s stopped successfully.\n", connectorId); + } catch (IOException | ApiException e) { + System.err.printf("managedKafkaConnectClient.stopConnector got err: %s\n", e.getMessage()); + } + } +} + +// [END managedkafka_stop_connector] diff --git a/managedkafka/examples/src/main/java/examples/UpdateCluster.java b/managedkafka/examples/src/main/java/examples/UpdateCluster.java index feeff950ffd..2fb19916ba8 100644 --- a/managedkafka/examples/src/main/java/examples/UpdateCluster.java +++ b/managedkafka/examples/src/main/java/examples/UpdateCluster.java @@ -41,7 +41,7 @@ public static void main(String[] args) throws Exception { String projectId = "my-project-id"; String region = "my-region"; // e.g. us-east1 String clusterId = "my-cluster"; - long memoryBytes = 4221225472L; // 4 GiB + long memoryBytes = 25769803776L; // 24 GiB updateCluster(projectId, region, clusterId, memoryBytes); } diff --git a/managedkafka/examples/src/main/java/examples/UpdateConnectCluster.java b/managedkafka/examples/src/main/java/examples/UpdateConnectCluster.java new file mode 100644 index 00000000000..7d22efedcab --- /dev/null +++ b/managedkafka/examples/src/main/java/examples/UpdateConnectCluster.java @@ -0,0 +1,92 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package examples; + +// [START managedkafka_update_connect_cluster] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.api.gax.longrunning.OperationSnapshot; +import com.google.api.gax.longrunning.OperationTimedPollAlgorithm; +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.retrying.TimedRetryAlgorithm; +import com.google.cloud.managedkafka.v1.CapacityConfig; +import com.google.cloud.managedkafka.v1.ConnectCluster; +import com.google.cloud.managedkafka.v1.ConnectClusterName; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectClient; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectSettings; +import com.google.cloud.managedkafka.v1.OperationMetadata; +import com.google.cloud.managedkafka.v1.UpdateConnectClusterRequest; +import com.google.protobuf.FieldMask; +import java.time.Duration; +import java.util.concurrent.ExecutionException; + +public class UpdateConnectCluster { + + public static void main(String[] args) throws Exception { + // TODO(developer): Replace these variables before running the example. + String projectId = "my-project-id"; + String region = "my-region"; // e.g. us-east1 + String clusterId = "my-connect-cluster"; + long memoryBytes = 25769803776L; // 24 GiB + updateConnectCluster(projectId, region, clusterId, memoryBytes); + } + + public static void updateConnectCluster( + String projectId, String region, String clusterId, long memoryBytes) throws Exception { + CapacityConfig capacityConfig = CapacityConfig.newBuilder().setMemoryBytes(memoryBytes).build(); + ConnectCluster connectCluster = ConnectCluster.newBuilder() + .setName(ConnectClusterName.of(projectId, region, clusterId).toString()) + .setCapacityConfig(capacityConfig) + .build(); + FieldMask updateMask = FieldMask.newBuilder().addPaths("capacity_config.memory_bytes").build(); + + // Create the settings to configure the timeout for polling operations + ManagedKafkaConnectSettings.Builder settingsBuilder = ManagedKafkaConnectSettings.newBuilder(); + TimedRetryAlgorithm timedRetryAlgorithm = OperationTimedPollAlgorithm.create( + RetrySettings.newBuilder() + .setTotalTimeoutDuration(Duration.ofHours(1L)) + .build()); + settingsBuilder.updateConnectClusterOperationSettings() + .setPollingAlgorithm(timedRetryAlgorithm); + + try (ManagedKafkaConnectClient managedKafkaConnectClient = ManagedKafkaConnectClient.create( + settingsBuilder.build())) { + UpdateConnectClusterRequest request = UpdateConnectClusterRequest.newBuilder() + .setUpdateMask(updateMask) + .setConnectCluster(connectCluster).build(); + OperationFuture future = managedKafkaConnectClient + .updateConnectClusterOperationCallable().futureCall(request); + + // Get the initial LRO and print details. CreateConnectCluster contains sample + // code for polling logs. + OperationSnapshot operation = future.getInitialFuture().get(); + System.out.printf( + "Connect cluster update started. Operation name: %s\nDone: %s\nMetadata: %s\n", + operation.getName(), + operation.isDone(), + future.getMetadata().get().toString()); + + ConnectCluster response = future.get(); + System.out.printf("Updated connect cluster: %s\n", response.getName()); + } catch (ExecutionException e) { + System.err.printf("managedKafkaConnectClient.updateConnectCluster got err: %s\n", + e.getMessage()); + } + } +} + +// [END managedkafka_update_connect_cluster] diff --git a/managedkafka/examples/src/main/java/examples/UpdateConnector.java b/managedkafka/examples/src/main/java/examples/UpdateConnector.java new file mode 100644 index 00000000000..37186b31de9 --- /dev/null +++ b/managedkafka/examples/src/main/java/examples/UpdateConnector.java @@ -0,0 +1,68 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package examples; + +// [START managedkafka_update_connector] +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.managedkafka.v1.Connector; +import com.google.cloud.managedkafka.v1.ConnectorName; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectClient; +import com.google.protobuf.FieldMask; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class UpdateConnector { + + public static void main(String[] args) throws Exception { + // TODO(developer): Replace these variables before running the example. + String projectId = "my-project-id"; + String region = "my-region"; // e.g. us-east1 + String clusterId = "my-connect-cluster"; + String connectorId = "my-connector"; + // The new value for the 'tasks.max' configuration. + String maxTasks = "5"; + updateConnector(projectId, region, clusterId, connectorId, maxTasks); + } + + public static void updateConnector( + String projectId, String region, String clusterId, String connectorId, String maxTasks) + throws IOException { + try (ManagedKafkaConnectClient managedKafkaConnectClient = ManagedKafkaConnectClient.create()) { + Map configMap = new HashMap<>(); + configMap.put("tasks.max", maxTasks); + + Connector connector = + Connector.newBuilder() + .setName(ConnectorName.of(projectId, region, clusterId, connectorId).toString()) + .putAllConfigs(configMap) + .build(); + + // The field mask specifies which fields to update. Here, we update the 'config' field. + FieldMask updateMask = FieldMask.newBuilder().addPaths("config").build(); + + // This operation is handled synchronously. + Connector updatedConnector = managedKafkaConnectClient.updateConnector(connector, updateMask); + System.out.printf("Updated connector: %s\n", updatedConnector.getName()); + System.out.println(updatedConnector.getAllFields()); + + } catch (IOException | ApiException e) { + System.err.printf("managedKafkaConnectClient.updateConnector got err: %s\n", e.getMessage()); + } + } +} +// [END managedkafka_update_connector] diff --git a/managedkafka/examples/src/test/java/examples/ConnectClustersTest.java b/managedkafka/examples/src/test/java/examples/ConnectClustersTest.java new file mode 100644 index 00000000000..78c3533fb30 --- /dev/null +++ b/managedkafka/examples/src/test/java/examples/ConnectClustersTest.java @@ -0,0 +1,433 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package examples; + +import static com.google.cloud.managedkafka.v1.ManagedKafkaConnectClient.create; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.api.core.ApiFuture; +import com.google.api.gax.longrunning.OperationFuture; +import com.google.api.gax.longrunning.OperationSnapshot; +import com.google.api.gax.retrying.RetryingFuture; +import com.google.api.gax.rpc.OperationCallable; +import com.google.cloud.managedkafka.v1.ConnectCluster; +import com.google.cloud.managedkafka.v1.ConnectClusterName; +import com.google.cloud.managedkafka.v1.Connector; +import com.google.cloud.managedkafka.v1.ConnectorName; +import com.google.cloud.managedkafka.v1.CreateConnectClusterRequest; +import com.google.cloud.managedkafka.v1.DeleteConnectClusterRequest; +import com.google.cloud.managedkafka.v1.LocationName; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectClient; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectSettings; +import com.google.cloud.managedkafka.v1.OperationMetadata; +import com.google.cloud.managedkafka.v1.PauseConnectorRequest; +import com.google.cloud.managedkafka.v1.RestartConnectorRequest; +import com.google.cloud.managedkafka.v1.ResumeConnectorRequest; +import com.google.cloud.managedkafka.v1.StopConnectorRequest; +import com.google.cloud.managedkafka.v1.UpdateConnectClusterRequest; +import com.google.protobuf.Empty; +import com.google.protobuf.FieldMask; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +@RunWith(JUnit4.class) +public class ConnectClustersTest { + protected static final String projectId = "test-project"; + protected static final String region = "us-central1"; + protected static final String clusterId = "test-connect-cluster"; + protected static final String kafkaCluster = "test-kafka-cluster"; + protected static final String connectClusterName = + "projects/test-project/locations/us-central1/connectClusters/test-connect-cluster"; + protected static final String connectorId = "test-connector"; + protected static final String connectorName = + "projects/test-project/locations/us-central1/connectClusters/test-connect-cluster" + + "/connectors/test-connector"; + private ByteArrayOutputStream bout; + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(bout)); + } + + @Test + public void createConnectClusterTest() throws Exception { + ManagedKafkaConnectClient managedKafkaConnectClient = mock(ManagedKafkaConnectClient.class); + OperationCallable + operationCallable = mock(OperationCallable.class); + OperationFuture operationFuture = + mock(OperationFuture.class); + + try (MockedStatic mockedStatic = + Mockito.mockStatic(ManagedKafkaConnectClient.class)) { + + // client creation + mockedStatic + .when(() -> create(any(ManagedKafkaConnectSettings.class))) + .thenReturn(managedKafkaConnectClient); + + // operation callable + when(managedKafkaConnectClient.createConnectClusterOperationCallable()) + .thenReturn(operationCallable); + when(operationCallable.futureCall(any(CreateConnectClusterRequest.class))) + .thenReturn(operationFuture); + + // initial future + ApiFuture initialFuture = mock(ApiFuture.class); + when(operationFuture.getInitialFuture()).thenReturn(initialFuture); + + // Metadata + ApiFuture metadataFuture = mock(ApiFuture.class); + OperationMetadata metadata = mock(OperationMetadata.class); + when(operationFuture.getMetadata()).thenReturn(metadataFuture); + when(metadataFuture.get()).thenReturn(metadata); + + // operation snapshot + OperationSnapshot operationSnapshot = mock(OperationSnapshot.class); + when(operationFuture.getInitialFuture().get()).thenReturn(operationSnapshot); + when(operationSnapshot.getName()) + .thenReturn("projects/test-project/locations/test-location/operations/test-operation"); + when(operationSnapshot.isDone()).thenReturn(false, false, true); + + // polling future + RetryingFuture pollingFuture = mock(RetryingFuture.class); + when(operationFuture.getPollingFuture()).thenReturn(pollingFuture); + when(operationFuture.isDone()).thenReturn(false, false, true); + ApiFuture attemptResult = mock(ApiFuture.class); + when(pollingFuture.getAttemptResult()).thenReturn(attemptResult); + when(attemptResult.get()).thenReturn(operationSnapshot); + + // Setup final result + ConnectCluster resultCluster = mock(ConnectCluster.class); + when(operationFuture.get()).thenReturn(resultCluster); + when(resultCluster.getName()).thenReturn(connectClusterName); + + String subnet = "test-subnet"; + int vcpu = 12; + long memory = 12884901888L; // 12 GiB + CreateConnectCluster.createConnectCluster( + projectId, region, clusterId, subnet, kafkaCluster, vcpu, memory); + String output = bout.toString(); + assertThat(output).contains("Created connect cluster"); + assertThat(output).contains(connectClusterName); + verify(managedKafkaConnectClient, times(1)).createConnectClusterOperationCallable(); + verify(operationCallable, times(1)).futureCall(any(CreateConnectClusterRequest.class)); + verify(operationFuture, times(2)).getPollingFuture(); // Verify 2 polling attempts + verify(pollingFuture, times(2)).getAttemptResult(); // Verify 2 attempt results + verify(operationSnapshot, times(3)).isDone(); // 2 polls + 1 initial check + } + } + + @Test + public void getConnectClusterTest() throws Exception { + ManagedKafkaConnectClient managedKafkaConnectClient = mock(ManagedKafkaConnectClient.class); + try (MockedStatic mockedStatic = + Mockito.mockStatic(ManagedKafkaConnectClient.class)) { + mockedStatic.when(() -> create()).thenReturn(managedKafkaConnectClient); + ConnectCluster connectCluster = + ConnectCluster.newBuilder() + .setName(ConnectClusterName.of(projectId, region, clusterId).toString()) + .build(); + when(managedKafkaConnectClient.getConnectCluster(any(ConnectClusterName.class))) + .thenReturn(connectCluster); + GetConnectCluster.getConnectCluster(projectId, region, clusterId); + String output = bout.toString(); + assertThat(output).contains(connectClusterName); + verify(managedKafkaConnectClient, times(1)).getConnectCluster(any(ConnectClusterName.class)); + } + } + + @Test + public void listConnectClustersTest() throws Exception { + ManagedKafkaConnectClient managedKafkaConnectClient = mock(ManagedKafkaConnectClient.class); + ManagedKafkaConnectClient.ListConnectClustersPagedResponse response = + mock(ManagedKafkaConnectClient.ListConnectClustersPagedResponse.class); + try (MockedStatic mockedStatic = + Mockito.mockStatic(ManagedKafkaConnectClient.class)) { + mockedStatic.when(() -> create()).thenReturn(managedKafkaConnectClient); + Iterable iterable = + () -> { + List connectClusters = new ArrayList<>(); + connectClusters.add( + ConnectCluster.newBuilder() + .setName(ConnectClusterName.of(projectId, region, clusterId).toString()) + .build()); + return connectClusters.iterator(); + }; + when(response.iterateAll()).thenReturn(iterable); + when(managedKafkaConnectClient.listConnectClusters(any(LocationName.class))) + .thenReturn(response); + ListConnectClusters.listConnectClusters(projectId, region); + String output = bout.toString(); + assertThat(output).contains(connectClusterName); + verify(managedKafkaConnectClient, times(1)).listConnectClusters(any(LocationName.class)); + } + } + + @Test + public void updateConnectClusterTest() throws Exception { + ManagedKafkaConnectClient managedKafkaConnectClient = mock(ManagedKafkaConnectClient.class); + OperationCallable + operationCallable = mock(OperationCallable.class); + OperationFuture operationFuture = + mock(OperationFuture.class); + + try (MockedStatic mockedStatic = + Mockito.mockStatic(ManagedKafkaConnectClient.class)) { + + // client creation + mockedStatic + .when(() -> create(any(ManagedKafkaConnectSettings.class))) + .thenReturn(managedKafkaConnectClient); + + // operation callable + when(managedKafkaConnectClient.updateConnectClusterOperationCallable()) + .thenReturn(operationCallable); + when(operationCallable.futureCall(any(UpdateConnectClusterRequest.class))) + .thenReturn(operationFuture); + + // initial future + ApiFuture initialFuture = mock(ApiFuture.class); + when(operationFuture.getInitialFuture()).thenReturn(initialFuture); + + // Metadata + ApiFuture metadataFuture = mock(ApiFuture.class); + OperationMetadata metadata = mock(OperationMetadata.class); + when(operationFuture.getMetadata()).thenReturn(metadataFuture); + when(metadataFuture.get()).thenReturn(metadata); + + // operation snapshot + OperationSnapshot operationSnapshot = mock(OperationSnapshot.class); + when(operationFuture.getInitialFuture().get()).thenReturn(operationSnapshot); + when(operationSnapshot.getName()) + .thenReturn("projects/test-project/locations/test-location/operations/test-operation"); + when(operationSnapshot.isDone()).thenReturn(true); + + // Setup final result + ConnectCluster resultCluster = mock(ConnectCluster.class); + when(operationFuture.get()).thenReturn(resultCluster); + when(resultCluster.getName()).thenReturn(connectClusterName); + + long memory = 38654705664L; // 36 GiB + UpdateConnectCluster.updateConnectCluster(projectId, region, clusterId, memory); + String output = bout.toString(); + assertThat(output).contains("Updated connect cluster"); + assertThat(output).contains(connectClusterName); + verify(managedKafkaConnectClient, times(1)).updateConnectClusterOperationCallable(); + verify(operationCallable, times(1)).futureCall(any(UpdateConnectClusterRequest.class)); + } + } + + @Test + public void deleteConnectClusterTest() throws Exception { + ManagedKafkaConnectClient managedKafkaConnectClient = mock(ManagedKafkaConnectClient.class); + OperationCallable operationCallable = + mock(OperationCallable.class); + OperationFuture operationFuture = mock(OperationFuture.class); + try (MockedStatic mockedStatic = + Mockito.mockStatic(ManagedKafkaConnectClient.class)) { + + // client creation + mockedStatic + .when(() -> create(any(ManagedKafkaConnectSettings.class))) + .thenReturn(managedKafkaConnectClient); + + // operation callable + when(managedKafkaConnectClient.deleteConnectClusterOperationCallable()) + .thenReturn(operationCallable); + when(operationCallable.futureCall(any(DeleteConnectClusterRequest.class))) + .thenReturn(operationFuture); + + // initial future + ApiFuture initialFuture = mock(ApiFuture.class); + when(operationFuture.getInitialFuture()).thenReturn(initialFuture); + + // Metadata + ApiFuture metadataFuture = mock(ApiFuture.class); + OperationMetadata metadata = mock(OperationMetadata.class); + when(operationFuture.getMetadata()).thenReturn(metadataFuture); + when(metadataFuture.get()).thenReturn(metadata); + + // operation snapshot + OperationSnapshot operationSnapshot = mock(OperationSnapshot.class); + when(operationFuture.getInitialFuture().get()).thenReturn(operationSnapshot); + when(operationSnapshot.getName()) + .thenReturn("projects/test-project/locations/test-location/operations/test-operation"); + when(operationSnapshot.isDone()).thenReturn(true); + + // Setup final result + Empty resultEmpty = mock(Empty.class); + when(operationFuture.get()).thenReturn(resultEmpty); + + DeleteConnectCluster.deleteConnectCluster(projectId, region, clusterId); + String output = bout.toString(); + assertThat(output).contains("Deleted connect cluster"); + verify(managedKafkaConnectClient, times(1)).deleteConnectClusterOperationCallable(); + verify(operationCallable, times(1)).futureCall(any(DeleteConnectClusterRequest.class)); + } + } + + @Test + public void pauseConnectorTest() throws Exception { + ManagedKafkaConnectClient managedKafkaConnectClient = mock(ManagedKafkaConnectClient.class); + try (MockedStatic mockedStatic = + Mockito.mockStatic(ManagedKafkaConnectClient.class)) { + mockedStatic.when(() -> create()).thenReturn(managedKafkaConnectClient); + PauseConnector.pauseConnector(projectId, region, clusterId, connectorId); + String output = bout.toString(); + assertThat(output).contains("Connector " + connectorId + " paused successfully."); + verify(managedKafkaConnectClient, times(1)).pauseConnector(any(PauseConnectorRequest.class)); + } + } + + @Test + public void listConnectorsTest() throws Exception { + ManagedKafkaConnectClient managedKafkaConnectClient = mock(ManagedKafkaConnectClient.class); + ManagedKafkaConnectClient.ListConnectorsPagedResponse response = + mock(ManagedKafkaConnectClient.ListConnectorsPagedResponse.class); + + try (MockedStatic mockedStatic = + Mockito.mockStatic(ManagedKafkaConnectClient.class)) { + mockedStatic.when(() -> create()).thenReturn(managedKafkaConnectClient); + + List connectors = new ArrayList<>(); + connectors.add(Connector.newBuilder().setName(connectorName).build()); + Iterable iterable = () -> connectors.iterator(); + + when(response.iterateAll()).thenReturn(iterable); + when(managedKafkaConnectClient.listConnectors(any(ConnectClusterName.class))) + .thenReturn(response); + + ListConnectors.listConnectors(projectId, region, clusterId); + + String output = bout.toString(); + assertThat(output).contains(connectorName); + verify(managedKafkaConnectClient, times(1)).listConnectors(any(ConnectClusterName.class)); + } + } + + @Test + public void getConnectorTest() throws Exception { + ManagedKafkaConnectClient managedKafkaConnectClient = mock(ManagedKafkaConnectClient.class); + try (MockedStatic mockedStatic = + Mockito.mockStatic(ManagedKafkaConnectClient.class)) { + mockedStatic.when(() -> create()).thenReturn(managedKafkaConnectClient); + + Connector connector = Connector.newBuilder().setName(connectorName).build(); + when(managedKafkaConnectClient.getConnector(any(ConnectorName.class))).thenReturn(connector); + + GetConnector.getConnector(projectId, region, clusterId, connectorId); + String output = bout.toString(); + + assertThat(output).contains(connectorName); + verify(managedKafkaConnectClient, times(1)).getConnector(any(ConnectorName.class)); + } + } + + @Test + public void deleteConnectorTest() throws Exception { + ManagedKafkaConnectClient managedKafkaConnectClient = mock(ManagedKafkaConnectClient.class); + try (MockedStatic mockedStatic = + Mockito.mockStatic(ManagedKafkaConnectClient.class)) { + mockedStatic.when(() -> create()).thenReturn(managedKafkaConnectClient); + + DeleteConnector.deleteConnector(projectId, region, clusterId, connectorId); + + String output = bout.toString(); + assertThat(output).contains("Deleted connector: " + connectorName); + verify(managedKafkaConnectClient, times(1)).deleteConnector(any(ConnectorName.class)); + } + } + + @Test + public void updateConnectorTest() throws Exception { + ManagedKafkaConnectClient managedKafkaConnectClient = mock(ManagedKafkaConnectClient.class); + try (MockedStatic mockedStatic = + Mockito.mockStatic(ManagedKafkaConnectClient.class)) { + mockedStatic.when(() -> create()).thenReturn(managedKafkaConnectClient); + + Connector updatedConnector = + Connector.newBuilder().setName(connectorName).putConfigs("tasks.max", "5").build(); + + when(managedKafkaConnectClient.updateConnector(any(Connector.class), any(FieldMask.class))) + .thenReturn(updatedConnector); + + UpdateConnector.updateConnector(projectId, region, clusterId, connectorId, "5"); + + String output = bout.toString(); + assertThat(output).contains("Updated connector: " + connectorName); + assertThat(output).contains("tasks.max"); + assertThat(output).contains("5"); + verify(managedKafkaConnectClient, times(1)) + .updateConnector(any(Connector.class), any(FieldMask.class)); + } + } + + @Test + public void resumeConnectorTest() throws Exception { + ManagedKafkaConnectClient managedKafkaConnectClient = mock(ManagedKafkaConnectClient.class); + try (MockedStatic mockedStatic = + Mockito.mockStatic(ManagedKafkaConnectClient.class)) { + mockedStatic.when(() -> create()).thenReturn(managedKafkaConnectClient); + ResumeConnector.resumeConnector(projectId, region, clusterId, connectorId); + String output = bout.toString(); + assertThat(output).contains("Connector " + connectorId + " resumed successfully."); + verify(managedKafkaConnectClient, times(1)) + .resumeConnector(any(ResumeConnectorRequest.class)); + } + } + + @Test + public void restartConnectorTest() throws Exception { + ManagedKafkaConnectClient managedKafkaConnectClient = mock(ManagedKafkaConnectClient.class); + try (MockedStatic mockedStatic = + Mockito.mockStatic(ManagedKafkaConnectClient.class)) { + mockedStatic.when(() -> create()).thenReturn(managedKafkaConnectClient); + RestartConnector.restartConnector(projectId, region, clusterId, connectorId); + String output = bout.toString(); + assertThat(output).contains("Connector " + connectorId + " restarted successfully."); + verify(managedKafkaConnectClient, times(1)) + .restartConnector(any(RestartConnectorRequest.class)); + } + } + + @Test + public void stopConnectorTest() throws Exception { + ManagedKafkaConnectClient managedKafkaConnectClient = mock(ManagedKafkaConnectClient.class); + try (MockedStatic mockedStatic = + Mockito.mockStatic(ManagedKafkaConnectClient.class)) { + mockedStatic.when(() -> create()).thenReturn(managedKafkaConnectClient); + StopConnector.stopConnector(projectId, region, clusterId, connectorId); + String output = bout.toString(); + assertThat(output).contains("Connector " + connectorId + " stopped successfully."); + verify(managedKafkaConnectClient, times(1)).stopConnector(any(StopConnectorRequest.class)); + } + } +} diff --git a/managedkafka/examples/src/test/java/examples/ConnectorsTest.java b/managedkafka/examples/src/test/java/examples/ConnectorsTest.java new file mode 100644 index 00000000000..4aa69524760 --- /dev/null +++ b/managedkafka/examples/src/test/java/examples/ConnectorsTest.java @@ -0,0 +1,302 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package examples; + +import static com.google.cloud.managedkafka.v1.ManagedKafkaConnectClient.create; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.cloud.managedkafka.v1.Connector; +import com.google.cloud.managedkafka.v1.ConnectorName; +import com.google.cloud.managedkafka.v1.CreateConnectorRequest; +import com.google.cloud.managedkafka.v1.ManagedKafkaConnectClient; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +@RunWith(JUnit4.class) +public class ConnectorsTest { + + protected static final String projectId = "test-project"; + protected static final String region = "us-central1"; + protected static final String connectClusterId = "test-connect-cluster"; + protected static final String mirrorMaker2ConnectorId = "test-mirrormaker2-source-connector"; + protected static final String pubsubSourceConnectorId = "test-pubsub-source-connector"; + protected static final String pubsubSinkConnectorId = "test-pubsub-sink-connector"; + protected static final String gcsConnectorId = "test-gcs-sink-connector"; + protected static final String bigqueryConnectorId = "test-bigquery-sink-connector"; + + protected static final String mirrorMaker2SourceConnectorName = + "projects/test-project/locations/us-central1/connectClusters/" + + "test-connect-cluster/connectors/test-mirrormaker2-source-connector"; + protected static final String pubsubSourceConnectorName = + "projects/test-project/locations/us-central1/connectClusters/" + + "test-connect-cluster/connectors/test-pubsub-source-connector"; + protected static final String pubsubSinkConnectorName = + "projects/test-project/locations/us-central1/connectClusters/" + + "test-connect-cluster/connectors/test-pubsub-sink-connector"; + protected static final String gcsConnectorName = + "projects/test-project/locations/us-central1/connectClusters/" + + "test-connect-cluster/connectors/test-gcs-sink-connector"; + protected static final String bigqueryConnectorName = + "projects/test-project/locations/us-central1/connectClusters/" + + "test-connect-cluster/connectors/test-bigquery-sink-connector"; + + private ByteArrayOutputStream bout; + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(bout)); + } + + @Test + public void createMirrorMaker2SourceConnectorTest() throws Exception { + ManagedKafkaConnectClient managedKafkaConnectClient = mock(ManagedKafkaConnectClient.class); + try (MockedStatic mockedStatic = + Mockito.mockStatic(ManagedKafkaConnectClient.class)) { + mockedStatic.when(() -> create()).thenReturn(managedKafkaConnectClient); + Connector connector = + Connector.newBuilder() + .setName( + ConnectorName.of(projectId, region, connectClusterId, mirrorMaker2ConnectorId) + .toString()) + .build(); + when(managedKafkaConnectClient.createConnector(any(CreateConnectorRequest.class))) + .thenReturn(connector); + + String sourceClusterBootstrapServers = "source-cluster:9092"; + String targetClusterBootstrapServers = "target-cluster:9092"; + String maxTasks = "3"; + String sourceClusterAlias = "source"; + String targetClusterAlias = "target"; + String connectorClass = "org.apache.kafka.connect.mirror.MirrorSourceConnector"; + String topics = ".*"; + String topicsExclude = "mm2.*.internal,.*.replica,__.*"; + + CreateMirrorMaker2SourceConnector.createMirrorMaker2SourceConnector( + projectId, + region, + maxTasks, + connectClusterId, + mirrorMaker2ConnectorId, + sourceClusterBootstrapServers, + targetClusterBootstrapServers, + sourceClusterAlias, + targetClusterAlias, + connectorClass, + topics, + topicsExclude); + + String output = bout.toString(); + assertThat(output).contains("Created MirrorMaker2 Source connector"); + assertThat(output).contains(mirrorMaker2SourceConnectorName); + verify(managedKafkaConnectClient, times(1)) + .createConnector(any(CreateConnectorRequest.class)); + } + } + + @Test + public void createPubSubSourceConnectorTest() throws Exception { + ManagedKafkaConnectClient managedKafkaConnectClient = mock(ManagedKafkaConnectClient.class); + try (MockedStatic mockedStatic = + Mockito.mockStatic(ManagedKafkaConnectClient.class)) { + mockedStatic.when(() -> create()).thenReturn(managedKafkaConnectClient); + Connector connector = + Connector.newBuilder() + .setName( + ConnectorName.of(projectId, region, connectClusterId, pubsubSourceConnectorId) + .toString()) + .build(); + when(managedKafkaConnectClient.createConnector(any(CreateConnectorRequest.class))) + .thenReturn(connector); + + String pubsubProjectId = "test-pubsub-project"; + String subscriptionName = "test-subscription"; + String kafkaTopicName = "test-kafka-topic"; + String connectorClass = "com.google.pubsub.kafka.source.CloudPubSubSourceConnector"; + String maxTasks = "3"; + String valueConverter = "org.apache.kafka.connect.converters.ByteArrayConverter"; + String keyConverter = "org.apache.kafka.connect.storage.StringConverter"; + + CreatePubSubSourceConnector.createPubSubSourceConnector( + projectId, + region, + connectClusterId, + pubsubSourceConnectorId, + pubsubProjectId, + subscriptionName, + kafkaTopicName, + connectorClass, + maxTasks, + valueConverter, + keyConverter); + + String output = bout.toString(); + assertThat(output).contains("Created Pub/Sub Source connector"); + assertThat(output).contains(pubsubSourceConnectorName); + verify(managedKafkaConnectClient, times(1)) + .createConnector(any(CreateConnectorRequest.class)); + } + } + + @Test + public void createPubSubSinkConnectorTest() throws Exception { + ManagedKafkaConnectClient managedKafkaConnectClient = mock(ManagedKafkaConnectClient.class); + try (MockedStatic mockedStatic = + Mockito.mockStatic(ManagedKafkaConnectClient.class)) { + mockedStatic.when(() -> create()).thenReturn(managedKafkaConnectClient); + Connector connector = + Connector.newBuilder() + .setName( + ConnectorName.of(projectId, region, connectClusterId, pubsubSinkConnectorId) + .toString()) + .build(); + when(managedKafkaConnectClient.createConnector(any(CreateConnectorRequest.class))) + .thenReturn(connector); + + String pubsubProjectId = "test-pubsub-project"; + String pubsubTopicName = "test-pubsub-topic"; + String kafkaTopicName = "test-kafka-topic"; + String connectorClass = "com.google.pubsub.kafka.sink.CloudPubSubSinkConnector"; + String maxTasks = "3"; + String valueConverter = "org.apache.kafka.connect.storage.StringConverter"; + String keyConverter = "org.apache.kafka.connect.storage.StringConverter"; + + CreatePubSubSinkConnector.createPubSubSinkConnector( + projectId, + region, + connectClusterId, + pubsubSinkConnectorId, + pubsubProjectId, + pubsubTopicName, + kafkaTopicName, + connectorClass, + maxTasks, + valueConverter, + keyConverter); + + String output = bout.toString(); + assertThat(output).contains("Created Pub/Sub Sink connector"); + assertThat(output).contains(pubsubSinkConnectorName); + verify(managedKafkaConnectClient, times(1)) + .createConnector(any(CreateConnectorRequest.class)); + } + } + + @Test + public void createCloudStorageSinkConnectorTest() throws Exception { + ManagedKafkaConnectClient managedKafkaConnectClient = mock(ManagedKafkaConnectClient.class); + try (MockedStatic mockedStatic = + Mockito.mockStatic(ManagedKafkaConnectClient.class)) { + mockedStatic.when(() -> create()).thenReturn(managedKafkaConnectClient); + Connector connector = + Connector.newBuilder() + .setName( + ConnectorName.of(projectId, region, connectClusterId, gcsConnectorId).toString()) + .build(); + when(managedKafkaConnectClient.createConnector(any(CreateConnectorRequest.class))) + .thenReturn(connector); + + String bucketName = "test-gcs-bucket"; + String kafkaTopicName = "test-kafka-topic"; + String connectorClass = "io.aiven.kafka.connect.gcs.GcsSinkConnector"; + String maxTasks = "3"; + String gcsCredentialsDefault = "true"; + String formatOutputType = "json"; + String valueConverter = "org.apache.kafka.connect.json.JsonConverter"; + String valueSchemasEnable = "false"; + String keyConverter = "org.apache.kafka.connect.storage.StringConverter"; + + CreateCloudStorageSinkConnector.createCloudStorageSinkConnector( + projectId, + region, + connectClusterId, + gcsConnectorId, + bucketName, + kafkaTopicName, + connectorClass, + maxTasks, + gcsCredentialsDefault, + formatOutputType, + valueConverter, + valueSchemasEnable, + keyConverter); + + String output = bout.toString(); + assertThat(output).contains("Created Cloud Storage Sink connector"); + assertThat(output).contains(gcsConnectorName); + verify(managedKafkaConnectClient, times(1)) + .createConnector(any(CreateConnectorRequest.class)); + } + } + + @Test + public void createBigQuerySinkConnectorTest() throws Exception { + ManagedKafkaConnectClient managedKafkaConnectClient = mock(ManagedKafkaConnectClient.class); + try (MockedStatic mockedStatic = + Mockito.mockStatic(ManagedKafkaConnectClient.class)) { + mockedStatic.when(() -> create()).thenReturn(managedKafkaConnectClient); + Connector connector = + Connector.newBuilder() + .setName( + ConnectorName.of(projectId, region, connectClusterId, bigqueryConnectorId) + .toString()) + .build(); + when(managedKafkaConnectClient.createConnector(any(CreateConnectorRequest.class))) + .thenReturn(connector); + + String bigqueryProjectId = "test-bigquery-project"; + String datasetName = "test_dataset"; + String kafkaTopicName = "test-kafka-topic"; + String maxTasks = "3"; + String connectorClass = "com.wepay.kafka.connect.bigquery.BigQuerySinkConnector"; + String keyConverter = "org.apache.kafka.connect.storage.StringConverter"; + String valueConverter = "org.apache.kafka.connect.json.JsonConverter"; + String valueSchemasEnable = "false"; + + CreateBigQuerySinkConnector.createBigQuerySinkConnector( + projectId, + region, + connectClusterId, + bigqueryConnectorId, + bigqueryProjectId, + datasetName, + kafkaTopicName, + maxTasks, + connectorClass, + keyConverter, + valueConverter, + valueSchemasEnable); + + String output = bout.toString(); + assertThat(output).contains("Created BigQuery Sink connector"); + assertThat(output).contains(bigqueryConnectorName); + verify(managedKafkaConnectClient, times(1)) + .createConnector(any(CreateConnectorRequest.class)); + } + } +} diff --git a/media/transcoder/pom.xml b/media/transcoder/pom.xml index 3bda913bcc5..6c6d5a38412 100644 --- a/media/transcoder/pom.xml +++ b/media/transcoder/pom.xml @@ -35,7 +35,6 @@ 11 11 - @@ -54,7 +53,6 @@ com.google.cloud google-cloud-video-transcoder - junit diff --git a/memorystore/redis/client_side_metrics/pom.xml b/memorystore/redis/client_side_metrics/pom.xml new file mode 100644 index 00000000000..876aa034f65 --- /dev/null +++ b/memorystore/redis/client_side_metrics/pom.xml @@ -0,0 +1,100 @@ + + + + 4.0.0 + + com.example.memorystore + redis-client-side-metrics + 1.0-SNAPSHOT + + + 1.8 + 1.8 + UTF-8 + + + + + + redis.clients + jedis + 5.1.0 + + + io.opentelemetry + opentelemetry-api + 1.36.0 + + + io.opentelemetry + opentelemetry-sdk + 1.36.0 + + + com.google.cloud.opentelemetry + exporter-trace + 0.28.0 + + + com.google.cloud.opentelemetry + exporter-metrics + 0.28.0 + + + + + junit + junit + 4.13.2 + test + + + org.mockito + mockito-core + 4.11.0 + test + + + org.slf4j + slf4j-simple + 1.7.36 + test + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + + + \ No newline at end of file diff --git a/memorystore/redis/client_side_metrics/src/main/java/com/example/memorystore/redis/RedisTelemetryApp.java b/memorystore/redis/client_side_metrics/src/main/java/com/example/memorystore/redis/RedisTelemetryApp.java new file mode 100644 index 00000000000..b9de30e53ac --- /dev/null +++ b/memorystore/redis/client_side_metrics/src/main/java/com/example/memorystore/redis/RedisTelemetryApp.java @@ -0,0 +1,290 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.memorystore.redis; + +// [START memorystore_redis_client_side_metrics] +import com.google.cloud.opentelemetry.metric.GoogleCloudMetricExporter; +import com.google.cloud.opentelemetry.trace.TraceExporter; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.DoubleHistogram; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.metrics.export.MetricExporter; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; +import redis.clients.jedis.exceptions.JedisConnectionException; + +import java.time.Duration; +import java.util.function.Function; + +/** + * Sample application demonstrating client-side metrics and tracing for + * Google Cloud Memorystore for Redis. + */ +public final class RedisTelemetryApp { + /** Attribute key for Redis operation names. */ + private static final AttributeKey ATTR_OPERATION = + AttributeKey.stringKey("operation"); + + /** Maximum number of Redis reconnection attempts. */ + private static final int MAX_RETRIES = 3; + + /** Maximum total connections for the Jedis pool. */ + private static final int POOL_MAX_TOTAL = 20; + + /** Interval in seconds for exporting metrics to Google Cloud. */ + private static final long METRIC_INTERVAL_SECONDS = 10L; + + /** Base multiplier for exponential backoff sleep (in milliseconds). */ + private static final long RETRY_BACKOFF_BASE_MS = 100L; + + /** Conversion factor from Nanoseconds to Milliseconds. */ + private static final double NANO_TO_MS = 1_000_000.0; + + /** Default Redis port. */ + private static final int DEFAULT_REDIS_PORT = 6379; + + /** OpenTelemetry Tracer instance for recording trace spans. */ + private static Tracer tracer; + + /** OpenTelemetry Histogram for Redis round-trip time. */ + private static DoubleHistogram rttHist; + + /** OpenTelemetry Histogram for pool blocking latency. */ + private static DoubleHistogram clientBlockHist; + + /** OpenTelemetry Histogram for application logic blocking latency. */ + private static DoubleHistogram appBlockHist; + + /** OpenTelemetry Counter for Redis reconnection retry events. */ + private static LongCounter retryCounter; + + /** OpenTelemetry Counter for Redis connectivity errors. */ + private static LongCounter connErrorCounter; + + /** Shared Jedis connection pool. */ + private static JedisPool jedisPool; + + /** + * Private constructor to prevent instantiation of this utility class. + */ + private RedisTelemetryApp() { + } + + /** + * Main entry point for running the sample application. + * + * @param args Command line arguments (not used). + */ + public static void main(final String[] args) { + setupTelemetry(); + + final String host = System.getenv() + .getOrDefault("REDISHOST", "localhost"); + final int port = Integer.parseInt(System.getenv() + .getOrDefault("REDISPORT", + String.valueOf(DEFAULT_REDIS_PORT))); + + final JedisPoolConfig poolConfig = new JedisPoolConfig(); + poolConfig.setMaxTotal(POOL_MAX_TOTAL); + poolConfig.setBlockWhenExhausted(true); + jedisPool = new JedisPool(poolConfig, host, port); + + try { + run(); + } finally { + if (jedisPool != null) { + jedisPool.close(); + } + } + } + + /** + * Executes the core business logic of reading and writing to Redis. + * + * @return The string retrieved from the Redis 'get' operation. + */ + static String run() { + final Span span = tracer.spanBuilder("process_user_span") + .startSpan(); + try { + smartRedisCall("set_user", jedis -> + jedis.set("user:123", "active")); + + final String result = smartRedisCall("get_user", jedis -> + jedis.get("user:123")); + System.out.println("Retrieved: " + result); + return result; + } catch (Exception e) { + span.recordException(e); + throw e; + } finally { + span.end(); + } + } + + /** + * Injects mocked or no-op telemetry and pool instances for unit testing. + * + * @param pool The mocked or test JedisPool instance. + * @param testOpenTelemetry The OpenTelemetry instance to use for testing. + */ + static void initForTest( + final JedisPool pool, + final OpenTelemetry testOpenTelemetry) { + jedisPool = pool; + tracer = testOpenTelemetry.getTracer("jedis.client"); + final Meter meter = testOpenTelemetry.getMeter("jedis.metrics"); + + rttHist = meter.histogramBuilder("redis_client_rtt") + .setUnit("ms").build(); + clientBlockHist = meter + .histogramBuilder("redis_client_blocking_latency") + .setUnit("ms").build(); + appBlockHist = meter + .histogramBuilder("redis_application_blocking_latency") + .setUnit("ms").build(); + retryCounter = meter.counterBuilder("redis_retry_count").build(); + connErrorCounter = meter + .counterBuilder("redis_connectivity_error_count") + .build(); + + retryCounter.add(0, Attributes.of(ATTR_OPERATION, "startup")); + connErrorCounter.add(0, Attributes.of(ATTR_OPERATION, "startup")); + } + + /** + * Configures the production OpenTelemetry SDK to export Traces and Metrics + * to Google Cloud Operations. + */ + private static void setupTelemetry() { + final SpanExporter traceExporter = + TraceExporter.createWithDefaultConfiguration(); + final SdkTracerProvider tracerProvider = + SdkTracerProvider.builder() + .addSpanProcessor( + BatchSpanProcessor.builder(traceExporter) + .build()) + .build(); + + final MetricExporter metricExporter = + GoogleCloudMetricExporter.createWithDefaultConfiguration(); + final SdkMeterProvider meterProvider = + SdkMeterProvider.builder() + .registerMetricReader( + PeriodicMetricReader.builder(metricExporter) + .setInterval(Duration.ofSeconds( + METRIC_INTERVAL_SECONDS)) + .build()) + .build(); + + final OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() + .setTracerProvider(tracerProvider) + .setMeterProvider(meterProvider) + .buildAndRegisterGlobal(); + + tracer = openTelemetry.getTracer("jedis.client"); + final Meter meter = openTelemetry.getMeter("jedis.metrics"); + + rttHist = meter.histogramBuilder("redis_client_rtt") + .setUnit("ms").build(); + clientBlockHist = meter + .histogramBuilder("redis_client_blocking_latency") + .setUnit("ms").build(); + appBlockHist = meter + .histogramBuilder("redis_application_blocking_latency") + .setUnit("ms").build(); + retryCounter = meter.counterBuilder("redis_retry_count").build(); + connErrorCounter = meter + .counterBuilder("redis_connectivity_error_count") + .build(); + + retryCounter.add(0, Attributes.of(ATTR_OPERATION, "startup")); + connErrorCounter.add(0, Attributes.of(ATTR_OPERATION, "startup")); + } + + /** + * Wraps a Redis operation with latency metrics, reconnection retry logic, + * and trace spans. + * + * @param The return type of the Redis operation. + * @param operationName The name of the operation for metric attributes. + * @param operation The Redis command lambda to execute safely. + * @return The return value from the Redis command. + */ + private static T smartRedisCall( + final String operationName, + final Function operation) { + int attempt = 0; + final Attributes attrs = Attributes.of(ATTR_OPERATION, + operationName); + + final Span span = tracer.spanBuilder(operationName).startSpan(); + + try { + while (attempt < MAX_RETRIES) { + final long poolStart = System.nanoTime(); + try (Jedis jedis = jedisPool.getResource()) { + clientBlockHist.record((System.nanoTime() - poolStart) + / NANO_TO_MS, attrs); + + final long reqStart = System.nanoTime(); + final T response = operation.apply(jedis); + rttHist.record((System.nanoTime() - reqStart) + / NANO_TO_MS, attrs); + + final long appStart = System.nanoTime(); + @SuppressWarnings("unused") + final String dummy = String.valueOf(response); + appBlockHist.record((System.nanoTime() - appStart) + / NANO_TO_MS, attrs); + + return response; + } catch (JedisConnectionException e) { + attempt++; + connErrorCounter.add(1, attrs); + retryCounter.add(1, attrs); + span.recordException(e); + if (attempt >= MAX_RETRIES) { + throw e; + } + try { + Thread.sleep((long) (Math.pow(2, attempt) + * RETRY_BACKOFF_BASE_MS)); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + } + } + return null; + } finally { + span.end(); + } + } +} +// [END memorystore_redis_client_side_metrics] diff --git a/memorystore/redis/client_side_metrics/src/main/java/com/example/memorystore/redis/package-info.java b/memorystore/redis/client_side_metrics/src/main/java/com/example/memorystore/redis/package-info.java new file mode 100644 index 00000000000..55ed524cf92 --- /dev/null +++ b/memorystore/redis/client_side_metrics/src/main/java/com/example/memorystore/redis/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Memorystore Redis Client-Side Metrics sample package. + */ +package com.example.memorystore.redis; diff --git a/memorystore/redis/client_side_metrics/src/test/java/com/example/memorystore/redis/RedisTelemetryAppTest.java b/memorystore/redis/client_side_metrics/src/test/java/com/example/memorystore/redis/RedisTelemetryAppTest.java new file mode 100644 index 00000000000..a066d9a946f --- /dev/null +++ b/memorystore/redis/client_side_metrics/src/test/java/com/example/memorystore/redis/RedisTelemetryAppTest.java @@ -0,0 +1,110 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.memorystore.redis; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import io.opentelemetry.api.OpenTelemetry; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.exceptions.JedisConnectionException; + +/** Unit tests for {@link RedisTelemetryApp} verifying Redis interaction and Retry logic. */ +@RunWith(JUnit4.class) +public class RedisTelemetryAppTest { + + private JedisPool mockJedisPool; + private Jedis mockJedis; + + @Before + public void setUp() { + mockJedisPool = mock(JedisPool.class); + mockJedis = mock(Jedis.class); + + // Inject the mocked pool and a NO-OP OpenTelemetry instance so tests + // run hermetically without contacting real GCP Trace/Metric APIs. + RedisTelemetryApp.initForTest(mockJedisPool, OpenTelemetry.noop()); + + // Configure the mocked pool to hand out our mocked Jedis connection + when(mockJedisPool.getResource()).thenReturn(mockJedis); + + // Configure expected mocked Redis responses + when(mockJedis.set(eq("user:123"), eq("active"))).thenReturn("OK"); + when(mockJedis.get(eq("user:123"))).thenReturn("active"); + } + + @Test + public void testRun_successfulExecution() { + // Execute the extracted sample logic + String result = RedisTelemetryApp.run(); + + // Verify the application correctly read the final state + assertNotNull("Result should not be null", result); + assertEquals("active", result); + + // Verify the Redis pool handed out a connection, and the sample called set & get + verify(mockJedisPool, atLeastOnce()).getResource(); + verify(mockJedis, times(1)).set("user:123", "active"); + verify(mockJedis, times(1)).get("user:123"); + verify(mockJedis, atLeastOnce()).close(); // try-with-resources closes connection + } + + @Test + public void testRun_retryMechanismOnConnectivityFailure() { + // Configure the mocked pool to throw a Connection Exception on the FIRST call, + // and return the valid mock connection on the SECOND call. + when(mockJedisPool.getResource()) + .thenThrow(new JedisConnectionException("Temporary network blip")) + .thenReturn(mockJedis); + + String result = RedisTelemetryApp.run(); + + // Verify it still succeeds due to the retry handler in smartRedisCall + assertEquals("active", result); + + // Verify getResource was called 3 times (1 failed + 1 successful for 'set', and 1 successful for 'get') + verify(mockJedisPool, times(3)).getResource(); + verify(mockJedis, times(1)).set("user:123", "active"); + verify(mockJedis, times(1)).get("user:123"); + } + + @Test + public void testRun_failureAfterMaxRetries() { + // Configure the mocked pool to fail CONSTANTLY + when(mockJedisPool.getResource()) + .thenThrow(new JedisConnectionException("Redis is down permanently")); + + // Verify it stops retrying and propagates the exception upward to alert CI + assertThrows(JedisConnectionException.class, RedisTelemetryApp::run); + + // Verify it attempted exactly 3 retries before quitting + verify(mockJedisPool, times(3)).getResource(); + } +} \ No newline at end of file diff --git a/memorystore/redis/gce_deployment/deploy.sh b/memorystore/redis/gce_deployment/deploy.sh index fcbf874617f..82f893de6e4 100755 --- a/memorystore/redis/gce_deployment/deploy.sh +++ b/memorystore/redis/gce_deployment/deploy.sh @@ -33,7 +33,7 @@ cd .. mvn clean package #Copy the WAR artifact to the GCS bucket location -gsutil cp -r target/${WAR} gs://"$GCS_BUCKET_NAME"/gce/ +gcloud storage cp --recursive target/${WAR} gs://"$GCS_BUCKET_NAME"/gce/ cd gce_deployment diff --git a/memorystore/redis/gce_deployment/startup-script.sh b/memorystore/redis/gce_deployment/startup-script.sh index fe7d00c1c2e..21d85de9763 100644 --- a/memorystore/redis/gce_deployment/startup-script.sh +++ b/memorystore/redis/gce_deployment/startup-script.sh @@ -21,7 +21,7 @@ PROJECTID=$(curl -s "http://metadata.google.internal/computeMetadata/v1/project/ GCS_BUCKET_NAME=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/gcs-bucket" -H "Metadata-Flavor: Google") WAR=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/app-war" -H "Metadata-Flavor: Google") -gsutil cp gs://"$GCS_BUCKET_NAME"/gce/"$WAR" . +gcloud storage cp gs://"$GCS_BUCKET_NAME"/gce/"$WAR" . # Install dependencies from apt apt-get update diff --git a/modelarmor/pom.xml b/modelarmor/pom.xml index ae557ad832b..00a7a27fa96 100644 --- a/modelarmor/pom.xml +++ b/modelarmor/pom.xml @@ -43,7 +43,7 @@ com.google.cloud libraries-bom - 26.60.0 + 26.64.0 pom import diff --git a/modelarmor/src/main/java/modelarmor/GetFolderFloorSetting.java b/modelarmor/src/main/java/modelarmor/GetFolderFloorSetting.java index ba44bf3ee6a..b5f3a10c363 100644 --- a/modelarmor/src/main/java/modelarmor/GetFolderFloorSetting.java +++ b/modelarmor/src/main/java/modelarmor/GetFolderFloorSetting.java @@ -38,7 +38,7 @@ public static FloorSetting getFolderFloorSetting(String folderId) throws IOExcep // Initialize client that will be used to send requests. This client only // needs to be created once, and can be reused for multiple requests. try (ModelArmorClient client = ModelArmorClient.create()) { - String name = FloorSettingName.of(folderId, "global").toString(); + String name = FloorSettingName.ofFolderLocationName(folderId, "global").toString(); GetFloorSettingRequest request = GetFloorSettingRequest.newBuilder().setName(name).build(); diff --git a/modelarmor/src/test/java/modelarmor/SnippetsIT.java b/modelarmor/src/test/java/modelarmor/SnippetsIT.java index ede9b6fa591..2b30d9a623f 100644 --- a/modelarmor/src/test/java/modelarmor/SnippetsIT.java +++ b/modelarmor/src/test/java/modelarmor/SnippetsIT.java @@ -28,6 +28,8 @@ import com.google.cloud.modelarmor.v1.FilterConfig; import com.google.cloud.modelarmor.v1.FilterMatchState; import com.google.cloud.modelarmor.v1.FilterResult; +import com.google.cloud.modelarmor.v1.FloorSetting; +import com.google.cloud.modelarmor.v1.FloorSettingName; import com.google.cloud.modelarmor.v1.LocationName; import com.google.cloud.modelarmor.v1.MaliciousUriFilterSettings; import com.google.cloud.modelarmor.v1.MaliciousUriFilterSettings.MaliciousUriFilterEnforcement; @@ -46,6 +48,7 @@ import com.google.cloud.modelarmor.v1.SdpFinding; import com.google.cloud.modelarmor.v1.Template; import com.google.cloud.modelarmor.v1.TemplateName; +import com.google.cloud.modelarmor.v1.UpdateFloorSettingRequest; import com.google.privacy.dlp.v2.CreateDeidentifyTemplateRequest; import com.google.privacy.dlp.v2.CreateInspectTemplateRequest; import com.google.privacy.dlp.v2.DeidentifyConfig; @@ -72,6 +75,7 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -80,6 +84,10 @@ public class SnippetsIT { private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String FOLDER_ID = System.getenv() + .getOrDefault("MA_FOLDER_ID", "global"); + private static final String ORGANIZATION_ID = System.getenv() + .getOrDefault("MA_ORG_ID", "global"); private static final String LOCATION_ID = System.getenv() .getOrDefault("GOOGLE_CLOUD_PROJECT_LOCATION", "us-central1"); private static final String MA_ENDPOINT = String.format("modelarmor.%s.rep.googleapis.com:443", @@ -99,7 +107,11 @@ public class SnippetsIT { private static String TEST_DEIDENTIFY_TEMPLATE_NAME; private ByteArrayOutputStream stdOut; private PrintStream originalOut; + private static String[] floorSettingNames; private static String[] templateToDelete; + private static String projectFloorSettingName; + private static String folderFloorSettingName; + private static String organizationFloorSettingName; // Check if the required environment variables are set. private static void requireEnvVar(String varName) { @@ -111,6 +123,14 @@ private static void requireEnvVar(String varName) { @BeforeClass public static void beforeAll() throws IOException { requireEnvVar("GOOGLE_CLOUD_PROJECT"); + requireEnvVar("MA_FOLDER_ID"); + requireEnvVar("MA_ORG_ID"); + + projectFloorSettingName = + FloorSettingName.ofProjectLocationName(PROJECT_ID, "global").toString(); + folderFloorSettingName = FloorSettingName.ofFolderLocationName(FOLDER_ID, "global").toString(); + organizationFloorSettingName = + FloorSettingName.ofOrganizationLocationName(ORGANIZATION_ID, "global").toString(); TEST_TEMPLATE_ID = randomId(); TEST_RAI_TEMPLATE_ID = randomId(); @@ -147,6 +167,10 @@ private static String randomId() { @AfterClass public static void afterAll() throws IOException { requireEnvVar("GOOGLE_CLOUD_PROJECT"); + requireEnvVar("MA_FOLDER_ID"); + requireEnvVar("MA_ORG_ID"); + + resetFloorSettings(); // Delete templates after running tests. templateToDelete = new String[] { @@ -380,6 +404,67 @@ private static void deleteTemplate(String templateId) throws IOException { } } + private static void resetFloorSettings() throws IOException { + floorSettingNames = new String[] { + projectFloorSettingName, folderFloorSettingName, organizationFloorSettingName + }; + + + try (ModelArmorClient client = ModelArmorClient.create()) { + for (String name : floorSettingNames) { + FloorSetting floorSetting = FloorSetting.newBuilder() + .setName(name) + .setFilterConfig(FilterConfig.newBuilder().build()) + .setEnableFloorSettingEnforcement(false) + .build(); + + UpdateFloorSettingRequest request = UpdateFloorSettingRequest.newBuilder() + .setFloorSetting(floorSetting) + .build(); + + client.updateFloorSetting(request); + } + } + } + + // Tests for Folder setting snippets. + @Test + public void testGetOrganizationFloorSetting() throws IOException { + GetOrganizationFloorSetting.getOrganizationFloorSetting(ORGANIZATION_ID); + assertThat(stdOut.toString()).contains("Fetched floor setting for organization:"); + } + + @Test + public void testGetFolderFloorSetting() throws IOException { + GetFolderFloorSetting.getFolderFloorSetting(FOLDER_ID); + assertThat(stdOut.toString()).contains("Fetched floor setting for folder:"); + } + + @Test + public void testGetProjectFloorSetting() throws IOException { + GetProjectFloorSetting.getProjectFloorSetting(PROJECT_ID); + assertThat(stdOut.toString()).contains("Fetched floor setting for project:"); + } + + @Test + public void testUpdateOrganizationFloorSetting() throws IOException { + UpdateOrganizationsFloorSetting.updateOrganizationFloorSetting(ORGANIZATION_ID); + assertThat(stdOut.toString()).contains("Updated floor setting for organization:"); + } + + @Test + public void testUpdateFolderFloorSetting() throws IOException { + UpdateFolderFloorSetting.updateFolderFloorSetting(FOLDER_ID); + assertThat(stdOut.toString()).contains("Updated floor setting for folder:"); + } + + + @Test + public void testUpdateProjectFloorSetting() throws IOException { + UpdateProjectFloorSetting.updateProjectFloorSetting(PROJECT_ID); + assertThat(stdOut.toString()).contains("Updated floor setting for project:"); + } + // Tests for Template CRUD snippets. @Test public void testUpdateModelArmorTemplate() throws IOException { diff --git a/parametermanager/pom.xml b/parametermanager/pom.xml index 17deb982a30..33cd5ffdc52 100644 --- a/parametermanager/pom.xml +++ b/parametermanager/pom.xml @@ -43,7 +43,7 @@ com.google.cloud libraries-bom - 26.54.0 + 26.60.0 pom import @@ -91,6 +91,11 @@ google-iam-policy test + + com.google.cloud + google-cloud-kms + test + diff --git a/parametermanager/src/main/java/parametermanager/CreateParamWithKmsKey.java b/parametermanager/src/main/java/parametermanager/CreateParamWithKmsKey.java new file mode 100644 index 00000000000..c15bf7df2d9 --- /dev/null +++ b/parametermanager/src/main/java/parametermanager/CreateParamWithKmsKey.java @@ -0,0 +1,66 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package parametermanager; + +// [START parametermanager_create_param_with_kms_key] + +import com.google.cloud.parametermanager.v1.LocationName; +import com.google.cloud.parametermanager.v1.Parameter; +import com.google.cloud.parametermanager.v1.ParameterManagerClient; +import java.io.IOException; + +/** + * Example class to create a new parameter with provided KMS key + * using the Parameter Manager SDK for GCP. + */ +public class CreateParamWithKmsKey { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String parameterId = "your-parameter-id"; + String kmsKeyName = "your-kms-key"; + + // Call the method to create a parameter with the specified kms key. + createParameterWithKmsKey(projectId, parameterId, kmsKeyName); + } + + // This is an example snippet for creating a new parameter with a specific format. + public static Parameter createParameterWithKmsKey( + String projectId, String parameterId, String kmsKeyName) throws IOException { + // Initialize the client that will be used to send requests. + try (ParameterManagerClient client = ParameterManagerClient.create()) { + String locationId = "global"; + + // Build the parent name from the project. + LocationName location = LocationName.of(projectId, locationId); + + // Build the parameter to create with the provided format. + Parameter parameter = Parameter.newBuilder().setKmsKey(kmsKeyName).build(); + + // Create the parameter. + Parameter createdParameter = + client.createParameter(location.toString(), parameter, parameterId); + System.out.printf( + "Created parameter %s with kms key %s\n", + createdParameter.getName(), createdParameter.getKmsKey()); + + return createdParameter; + } + } +} +// [END parametermanager_create_param_with_kms_key] diff --git a/parametermanager/src/main/java/parametermanager/RemoveParamKmsKey.java b/parametermanager/src/main/java/parametermanager/RemoveParamKmsKey.java new file mode 100644 index 00000000000..f6312503fc1 --- /dev/null +++ b/parametermanager/src/main/java/parametermanager/RemoveParamKmsKey.java @@ -0,0 +1,74 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package parametermanager; + +// [START parametermanager_remove_param_kms_key] + +import com.google.cloud.parametermanager.v1.Parameter; +import com.google.cloud.parametermanager.v1.ParameterManagerClient; +import com.google.cloud.parametermanager.v1.ParameterManagerSettings; +import com.google.cloud.parametermanager.v1.ParameterName; +import com.google.protobuf.FieldMask; +import com.google.protobuf.util.FieldMaskUtil; +import java.io.IOException; + +/** + * This class demonstrates how to change the kms key of a parameter + * using the Parameter Manager SDK for GCP. + */ +public class RemoveParamKmsKey { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String parameterId = "your-parameter-id"; + + // Call the method to remove kms key of a parameter. + removeParamKmsKey(projectId, parameterId); + } + + // This is an example snippet for updating the kms key of a parameter. + public static Parameter removeParamKmsKey( + String projectId, String parameterId) throws IOException { + // Initialize the client that will be used to send requests. This client only + // needs to be created once, and can be reused for multiple requests. + try (ParameterManagerClient client = ParameterManagerClient.create()) { + String locationId = "global"; + + // Build the parameter name. + ParameterName name = ParameterName.of(projectId, locationId, parameterId); + + // Remove kms key of a parameter . + Parameter parameter = Parameter.newBuilder() + .setName(name.toString()) + .clearKmsKey() + .build(); + + // Build the field mask for the kms_key field. + FieldMask fieldMask = FieldMaskUtil.fromString("kms_key"); + + // Update the parameter kms key. + Parameter updatedParameter = client.updateParameter(parameter, fieldMask); + System.out.printf( + "Removed kms key for parameter %s\n", + updatedParameter.getName()); + + return updatedParameter; + } + } +} +// [END parametermanager_remove_param_kms_key] diff --git a/parametermanager/src/main/java/parametermanager/RenderParamVersion.java b/parametermanager/src/main/java/parametermanager/RenderParamVersion.java index 1bf43e8a8e0..ecea22907d4 100644 --- a/parametermanager/src/main/java/parametermanager/RenderParamVersion.java +++ b/parametermanager/src/main/java/parametermanager/RenderParamVersion.java @@ -25,6 +25,7 @@ /** * This class demonstrates how to render a parameter version using the Parameter Manager SDK for * GCP. + * The version can be a version ID as a string (e.g. "parameter1") or an alias (e.g. "latest") */ public class RenderParamVersion { diff --git a/parametermanager/src/main/java/parametermanager/UpdateParamKmsKey.java b/parametermanager/src/main/java/parametermanager/UpdateParamKmsKey.java new file mode 100644 index 00000000000..1a906fb768f --- /dev/null +++ b/parametermanager/src/main/java/parametermanager/UpdateParamKmsKey.java @@ -0,0 +1,75 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package parametermanager; + +// [START parametermanager_update_param_kms_key] + +import com.google.cloud.parametermanager.v1.Parameter; +import com.google.cloud.parametermanager.v1.ParameterManagerClient; +import com.google.cloud.parametermanager.v1.ParameterManagerSettings; +import com.google.cloud.parametermanager.v1.ParameterName; +import com.google.protobuf.FieldMask; +import com.google.protobuf.util.FieldMaskUtil; +import java.io.IOException; + +/** + * This class demonstrates how to change the kms key of a parameter + * using theParameter Manager SDK for GCP. + */ +public class UpdateParamKmsKey { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String parameterId = "your-parameter-id"; + String kmsKeyName = "your-kms-key"; + + // Call the method to update kms key of a parameter. + updateParamKmsKey(projectId, parameterId, kmsKeyName); + } + + // This is an example snippet for updating the kms key of a parameter. + public static Parameter updateParamKmsKey( + String projectId, String parameterId, String kmsKeyName) throws IOException { + // Initialize the client that will be used to send requests. This client only + // needs to be created once, and can be reused for multiple requests. + try (ParameterManagerClient client = ParameterManagerClient.create()) { + String locationId = "global"; + + // Build the parameter name. + ParameterName name = ParameterName.of(projectId, locationId, parameterId); + + // Set the parameter kms key to update. + Parameter parameter = Parameter.newBuilder() + .setName(name.toString()) + .setKmsKey(kmsKeyName) + .build(); + + // Build the field mask for the kms_key field. + FieldMask fieldMask = FieldMaskUtil.fromString("kms_key"); + + // Update the parameter kms key. + Parameter updatedParameter = client.updateParameter(parameter, fieldMask); + System.out.printf( + "Updated parameter %s with kms key %s\n", + updatedParameter.getName(), updatedParameter.getKmsKey()); + + return updatedParameter; + } + } +} +// [END parametermanager_update_param_kms_key] diff --git a/parametermanager/src/main/java/parametermanager/regionalsamples/CreateRegionalParamWithKmsKey.java b/parametermanager/src/main/java/parametermanager/regionalsamples/CreateRegionalParamWithKmsKey.java new file mode 100644 index 00000000000..8eccd640d5a --- /dev/null +++ b/parametermanager/src/main/java/parametermanager/regionalsamples/CreateRegionalParamWithKmsKey.java @@ -0,0 +1,75 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package parametermanager.regionalsamples; + +// [START parametermanager_create_regional_param_with_kms_key] + +import com.google.cloud.parametermanager.v1.LocationName; +import com.google.cloud.parametermanager.v1.Parameter; +import com.google.cloud.parametermanager.v1.ParameterManagerClient; +import com.google.cloud.parametermanager.v1.ParameterManagerSettings; +import java.io.IOException; + +/** + * Example class to create a new regional parameter with provided KMS + * key using the Parameter Manager SDK for GCP. + */ +public class CreateRegionalParamWithKmsKey { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String locationId = "your-location-id"; + String parameterId = "your-parameter-id"; + String kmsKeyName = "your-kms-key"; + + // Call the method to create a regional parameter with the specified kms key. + createRegionalParameterWithKmsKey(projectId, locationId, parameterId, kmsKeyName); + } + + // This is an example snippet for creating a new parameter with a specific format. + public static Parameter createRegionalParameterWithKmsKey( + String projectId, String locationId, String parameterId, String kmsKeyName) + throws IOException { + + // Endpoint to call the regional parameter manager server + String apiEndpoint = String.format("parametermanager.%s.rep.googleapis.com:443", locationId); + ParameterManagerSettings parameterManagerSettings = + ParameterManagerSettings.newBuilder().setEndpoint(apiEndpoint).build(); + + // Initialize the client that will be used to send requests. This client only needs to be + // created once, and can be reused for multiple requests. + try (ParameterManagerClient client = ParameterManagerClient.create(parameterManagerSettings)) { + + // Build the parent name from the project. + LocationName location = LocationName.of(projectId, locationId); + + // Build the parameter to create with the provided format. + Parameter parameter = Parameter.newBuilder().setKmsKey(kmsKeyName).build(); + + // Create the parameter. + Parameter createdParameter = + client.createParameter(location.toString(), parameter, parameterId); + System.out.printf( + "Created regional parameter %s with kms key %s\n", + createdParameter.getName(), createdParameter.getKmsKey()); + + return createdParameter; + } + } +} +// [END parametermanager_create_regional_param_with_kms_key] diff --git a/parametermanager/src/main/java/parametermanager/regionalsamples/RemoveRegionalParamKmsKey.java b/parametermanager/src/main/java/parametermanager/regionalsamples/RemoveRegionalParamKmsKey.java new file mode 100644 index 00000000000..4614b5321c4 --- /dev/null +++ b/parametermanager/src/main/java/parametermanager/regionalsamples/RemoveRegionalParamKmsKey.java @@ -0,0 +1,80 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package parametermanager.regionalsamples; + +// [START parametermanager_remove_regional_param_kms_key] + +import com.google.cloud.parametermanager.v1.Parameter; +import com.google.cloud.parametermanager.v1.ParameterManagerClient; +import com.google.cloud.parametermanager.v1.ParameterManagerSettings; +import com.google.cloud.parametermanager.v1.ParameterName; +import com.google.protobuf.FieldMask; +import com.google.protobuf.util.FieldMaskUtil; +import java.io.IOException; + +/** + * This class demonstrates how to change the kms key of a parameter + * using the Parameter Manager SDK for GCP. + */ +public class RemoveRegionalParamKmsKey { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String locationId = "your-location-id"; + String parameterId = "your-parameter-id"; + + // Call the method to remove kms key of a parameter. + removeRegionalParamKmsKey(projectId, locationId, parameterId); + } + + // This is an example snippet for updating the kms key of a parameter. + public static Parameter removeRegionalParamKmsKey( + String projectId, String locationId, String parameterId) throws IOException { + + // Endpoint to call the regional parameter manager server + String apiEndpoint = String.format("parametermanager.%s.rep.googleapis.com:443", locationId); + ParameterManagerSettings parameterManagerSettings = + ParameterManagerSettings.newBuilder().setEndpoint(apiEndpoint).build(); + + // Initialize the client that will be used to send requests. This client only needs to be + // created once, and can be reused for multiple requests. + try (ParameterManagerClient client = ParameterManagerClient.create(parameterManagerSettings)) { + + // Build the parameter name. + ParameterName name = ParameterName.of(projectId, locationId, parameterId); + + // Remove kms key of a parameter . + Parameter parameter = Parameter.newBuilder() + .setName(name.toString()) + .clearKmsKey() + .build(); + + // Build the field mask for the kms_key field. + FieldMask fieldMask = FieldMaskUtil.fromString("kms_key"); + + // Update the parameter kms key. + Parameter updatedParameter = client.updateParameter(parameter, fieldMask); + System.out.printf( + "Removed kms key for regional parameter %s\n", + updatedParameter.getName()); + + return updatedParameter; + } + } +} +// [END parametermanager_remove_regional_param_kms_key] diff --git a/parametermanager/src/main/java/parametermanager/regionalsamples/UpdateRegionalParamKmsKey.java b/parametermanager/src/main/java/parametermanager/regionalsamples/UpdateRegionalParamKmsKey.java new file mode 100644 index 00000000000..eb55f344073 --- /dev/null +++ b/parametermanager/src/main/java/parametermanager/regionalsamples/UpdateRegionalParamKmsKey.java @@ -0,0 +1,82 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package parametermanager.regionalsamples; + +// [START parametermanager_update_regional_param_kms_key] + +import com.google.cloud.parametermanager.v1.Parameter; +import com.google.cloud.parametermanager.v1.ParameterManagerClient; +import com.google.cloud.parametermanager.v1.ParameterManagerSettings; +import com.google.cloud.parametermanager.v1.ParameterName; +import com.google.protobuf.FieldMask; +import com.google.protobuf.util.FieldMaskUtil; +import java.io.IOException; + +/** + * This class demonstrates how to change the kms key of a regional + * parameter using the Parameter Manager SDK for GCP. + */ +public class UpdateRegionalParamKmsKey { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String locationId = "your-location-id"; + String parameterId = "your-parameter-id"; + String kmsKeyName = "your-kms-key"; + + // Call the method to update kms key of a parameter. + updateRegionalParamKmsKey(projectId, locationId, parameterId, kmsKeyName); + } + + // This is an example snippet for updating the kms key of a parameter. + public static Parameter updateRegionalParamKmsKey( + String projectId, String locationId, String parameterId, String kmsKeyName) + throws IOException { + + // Endpoint to call the regional parameter manager server + String apiEndpoint = String.format("parametermanager.%s.rep.googleapis.com:443", locationId); + ParameterManagerSettings parameterManagerSettings = + ParameterManagerSettings.newBuilder().setEndpoint(apiEndpoint).build(); + + // Initialize the client that will be used to send requests. This client only needs to be + // created once, and can be reused for multiple requests. + try (ParameterManagerClient client = ParameterManagerClient.create(parameterManagerSettings)) { + + // Build the parameter name. + ParameterName name = ParameterName.of(projectId, locationId, parameterId); + + // Set the parameter kms key to update. + Parameter parameter = Parameter.newBuilder() + .setName(name.toString()) + .setKmsKey(kmsKeyName) + .build(); + + // Build the field mask for the kms_key field. + FieldMask fieldMask = FieldMaskUtil.fromString("kms_key"); + + // Update the parameter kms key. + Parameter updatedParameter = client.updateParameter(parameter, fieldMask); + System.out.printf( + "Updated regional parameter %s with kms key %s\n", + updatedParameter.getName(), updatedParameter.getKmsKey()); + + return updatedParameter; + } + } +} +// [END parametermanager_update_regional_param_kms_key] diff --git a/parametermanager/src/test/java/parametermanager/SnippetsIT.java b/parametermanager/src/test/java/parametermanager/SnippetsIT.java index bbeac0d74a1..1dddf7fb802 100644 --- a/parametermanager/src/test/java/parametermanager/SnippetsIT.java +++ b/parametermanager/src/test/java/parametermanager/SnippetsIT.java @@ -17,7 +17,18 @@ package parametermanager; import static com.google.common.truth.Truth.assertThat; - +import static org.junit.Assert.assertEquals; + +import com.google.api.gax.rpc.AlreadyExistsException; +import com.google.cloud.kms.v1.CryptoKey; +import com.google.cloud.kms.v1.CryptoKeyName; +import com.google.cloud.kms.v1.CryptoKeyVersion; +import com.google.cloud.kms.v1.CryptoKeyVersionTemplate; +import com.google.cloud.kms.v1.KeyManagementServiceClient; +import com.google.cloud.kms.v1.KeyRing; +import com.google.cloud.kms.v1.KeyRingName; +import com.google.cloud.kms.v1.ListCryptoKeyVersionsRequest; +import com.google.cloud.kms.v1.ProtectionLevel; import com.google.cloud.parametermanager.v1.LocationName; import com.google.cloud.parametermanager.v1.Parameter; import com.google.cloud.parametermanager.v1.ParameterFormat; @@ -39,6 +50,8 @@ import com.google.iam.v1.Policy; import com.google.iam.v1.SetIamPolicyRequest; import com.google.protobuf.ByteString; +import com.google.protobuf.FieldMask; +import com.google.protobuf.util.FieldMaskUtil; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; @@ -76,6 +89,12 @@ public class SnippetsIT { private static ParameterName TEST_PARAMETER_NAME_TO_RENDER; private static ParameterVersionName TEST_PARAMETER_VERSION_NAME_TO_RENDER; private static SecretName SECRET_NAME; + private static ParameterName TEST_PARAMETER_NAME_WITH_KMS; + private static String KEY_RING_ID; + private static String HSM_KEY_ID; + private static ParameterName TEST_PARAMETER_NAME_UPDATE_WITH_KMS; + private static String NEW_HSM_KEY_ID; + private static ParameterName TEST_PARAMETER_NAME_DELETE_WITH_KMS; private ByteArrayOutputStream stdOut; @BeforeClass @@ -164,6 +183,33 @@ public static void beforeAll() throws IOException { TEST_PARAMETER_VERSION_NAME_TO_RENDER.getParameter(), TEST_PARAMETER_VERSION_NAME_TO_RENDER.getParameterVersion(), payload); + + // test create parameter with kms key + TEST_PARAMETER_NAME_WITH_KMS = ParameterName.of(PROJECT_ID, "global", randomId()); + KEY_RING_ID = "test-parameter-manager-snippets"; + HSM_KEY_ID = randomId(); + createKeyRing(KEY_RING_ID); + createHsmKey(HSM_KEY_ID); + + // test update kms key of parameter + TEST_PARAMETER_NAME_UPDATE_WITH_KMS = ParameterName.of(PROJECT_ID, "global", randomId()); + KEY_RING_ID = "test-parameter-manager-snippets"; + HSM_KEY_ID = randomId(); + NEW_HSM_KEY_ID = randomId(); + createKeyRing(KEY_RING_ID); + createHsmKey(HSM_KEY_ID); + createHsmKey(NEW_HSM_KEY_ID); + String kmsKeyId = CryptoKeyName.of(PROJECT_ID, "global", KEY_RING_ID, HSM_KEY_ID).toString(); + createParameterWithKms(TEST_PARAMETER_NAME_UPDATE_WITH_KMS.getParameter(), kmsKeyId); + + // test delete kms key of parameter + TEST_PARAMETER_NAME_DELETE_WITH_KMS = ParameterName.of(PROJECT_ID, "global", randomId()); + KEY_RING_ID = "test-parameter-manager-snippets"; + HSM_KEY_ID = randomId(); + createKeyRing(KEY_RING_ID); + createHsmKey(HSM_KEY_ID); + kmsKeyId = CryptoKeyName.of(PROJECT_ID, "global", KEY_RING_ID, HSM_KEY_ID).toString(); + createParameterWithKms(TEST_PARAMETER_NAME_DELETE_WITH_KMS.getParameter(), kmsKeyId); } @AfterClass @@ -191,6 +237,33 @@ public static void afterAll() throws IOException { deleteParameterVersion(TEST_PARAMETER_VERSION_NAME_TO_GET.toString()); deleteParameterVersion(TEST_PARAMETER_VERSION_NAME_TO_GET_1.toString()); deleteParameter(TEST_PARAMETER_NAME_TO_GET.toString()); + + deleteParameter(TEST_PARAMETER_NAME_WITH_KMS.toString()); + + deleteParameter(TEST_PARAMETER_NAME_UPDATE_WITH_KMS.toString()); + + deleteParameter(TEST_PARAMETER_NAME_DELETE_WITH_KMS.toString()); + + // Iterate over each key ring's key's crypto key versions and destroy. + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + for (CryptoKey key : client.listCryptoKeys(getKeyRingName()).iterateAll()) { + if (key.hasRotationPeriod() || key.hasNextRotationTime()) { + CryptoKey keyWithoutRotation = CryptoKey.newBuilder().setName(key.getName()).build(); + FieldMask fieldMask = FieldMaskUtil.fromString("rotation_period,next_rotation_time"); + client.updateCryptoKey(keyWithoutRotation, fieldMask); + } + + ListCryptoKeyVersionsRequest listVersionsRequest = + ListCryptoKeyVersionsRequest.newBuilder() + .setParent(key.getName()) + .setFilter("state != DESTROYED AND state != DESTROY_SCHEDULED") + .build(); + for (CryptoKeyVersion version : + client.listCryptoKeyVersions(listVersionsRequest).iterateAll()) { + client.destroyCryptoKeyVersion(version.getName()); + } + } + } } private static String randomId() { @@ -198,6 +271,14 @@ private static String randomId() { return "java-" + random.nextLong(); } + private static KeyRingName getKeyRingName() { + return KeyRingName.of(PROJECT_ID, "global", KEY_RING_ID); + } + + private static com.google.cloud.kms.v1.LocationName getLocationName() { + return com.google.cloud.kms.v1.LocationName.of(PROJECT_ID, "global"); + } + private static Parameter createParameter(String parameterId, ParameterFormat format) throws IOException { LocationName parent = LocationName.of(PROJECT_ID, "global"); @@ -208,6 +289,48 @@ private static Parameter createParameter(String parameterId, ParameterFormat for } } + private static Parameter createParameterWithKms(String parameterId, String kmsKeyId) + throws IOException { + LocationName parent = LocationName.of(PROJECT_ID, "global"); + Parameter parameter = Parameter.newBuilder().setKmsKey(kmsKeyId).build(); + + try (ParameterManagerClient client = ParameterManagerClient.create()) { + return client.createParameter(parent.toString(), parameter, parameterId); + } + } + + private static KeyRing createKeyRing(String keyRingId) throws IOException { + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + KeyRing keyRing = KeyRing.newBuilder().build(); + KeyRing createdKeyRing = client.createKeyRing(getLocationName(), keyRingId, keyRing); + return createdKeyRing; + } catch (AlreadyExistsException e) { + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + return client.getKeyRing(KeyRingName.of(PROJECT_ID, "global", keyRingId)); + } + } + } + + private static CryptoKey createHsmKey(String keyId) throws IOException { + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + CryptoKey key = + CryptoKey.newBuilder() + .setPurpose(CryptoKey.CryptoKeyPurpose.ENCRYPT_DECRYPT) + .setVersionTemplate( + CryptoKeyVersionTemplate.newBuilder() + .setAlgorithm(CryptoKeyVersion + .CryptoKeyVersionAlgorithm + .GOOGLE_SYMMETRIC_ENCRYPTION) + .setProtectionLevel(ProtectionLevel.HSM) + .build()) + .putLabels("foo", "bar") + .putLabels("zip", "zap") + .build(); + CryptoKey createdKey = client.createCryptoKey(getKeyRingName(), keyId, key); + return createdKey; + } + } + private static void createParameterVersion(String parameterId, String versionId, String payload) throws IOException { ParameterName parameterName = ParameterName.of(PROJECT_ID, "global", parameterId); @@ -443,6 +566,49 @@ public void testCreateParamVersion() throws IOException { assertThat(stdOut.toString()).contains("Created parameter version"); } + @Test + public void testCreateParamWithKmsKey() throws IOException { + ParameterName parameterName = TEST_PARAMETER_NAME_WITH_KMS; + String cryptoKey = CryptoKeyName.of(PROJECT_ID, "global", KEY_RING_ID, HSM_KEY_ID).toString(); + CreateParamWithKmsKey.createParameterWithKmsKey( + parameterName.getProject(), parameterName.getParameter(), cryptoKey); + + String expected = String.format( + "Created parameter %s with kms key %s\n", + parameterName, cryptoKey); + assertThat(stdOut.toString()).contains(expected); + } + + @Test + public void testUpdateParamKmsKey() throws IOException { + ParameterName parameterName = TEST_PARAMETER_NAME_UPDATE_WITH_KMS; + String cryptoKey = CryptoKeyName + .of(PROJECT_ID, "global", KEY_RING_ID, NEW_HSM_KEY_ID) + .toString(); + Parameter updatedParameter = UpdateParamKmsKey + .updateParamKmsKey(parameterName.getProject(), parameterName.getParameter(), cryptoKey); + + String expected = String.format( + "Updated parameter %s with kms key %s\n", + parameterName, cryptoKey); + assertThat(stdOut.toString()).contains(expected); + assertThat(updatedParameter.getKmsKey()).contains(NEW_HSM_KEY_ID); + assertThat(updatedParameter.getKmsKey()).doesNotContain(HSM_KEY_ID); + } + + @Test + public void testRemoveParamKmsKey() throws IOException { + ParameterName parameterName = TEST_PARAMETER_NAME_DELETE_WITH_KMS; + Parameter updatedParameter = RemoveParamKmsKey + .removeParamKmsKey(parameterName.getProject(), parameterName.getParameter()); + + String expected = String.format( + "Removed kms key for parameter %s\n", + parameterName); + assertThat(stdOut.toString()).contains(expected); + assertEquals("", updatedParameter.getKmsKey()); + } + @Test public void testStructuredCreateParamVersion() throws IOException { ParameterVersionName parameterVersionName = TEST_PARAMETER_VERSION_NAME_WITH_FORMAT; diff --git a/parametermanager/src/test/java/parametermanager/regionalsamples/SnippetsIT.java b/parametermanager/src/test/java/parametermanager/regionalsamples/SnippetsIT.java index 696339ab394..baf346f1dae 100644 --- a/parametermanager/src/test/java/parametermanager/regionalsamples/SnippetsIT.java +++ b/parametermanager/src/test/java/parametermanager/regionalsamples/SnippetsIT.java @@ -17,7 +17,18 @@ package parametermanager.regionalsamples; import static com.google.common.truth.Truth.assertThat; - +import static org.junit.Assert.assertEquals; + +import com.google.api.gax.rpc.AlreadyExistsException; +import com.google.cloud.kms.v1.CryptoKey; +import com.google.cloud.kms.v1.CryptoKeyName; +import com.google.cloud.kms.v1.CryptoKeyVersion; +import com.google.cloud.kms.v1.CryptoKeyVersionTemplate; +import com.google.cloud.kms.v1.KeyManagementServiceClient; +import com.google.cloud.kms.v1.KeyRing; +import com.google.cloud.kms.v1.KeyRingName; +import com.google.cloud.kms.v1.ListCryptoKeyVersionsRequest; +import com.google.cloud.kms.v1.ProtectionLevel; import com.google.cloud.parametermanager.v1.LocationName; import com.google.cloud.parametermanager.v1.Parameter; import com.google.cloud.parametermanager.v1.ParameterFormat; @@ -39,6 +50,8 @@ import com.google.iam.v1.Policy; import com.google.iam.v1.SetIamPolicyRequest; import com.google.protobuf.ByteString; +import com.google.protobuf.FieldMask; +import com.google.protobuf.util.FieldMaskUtil; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; @@ -79,6 +92,12 @@ public class SnippetsIT { private static ParameterName TEST_PARAMETER_NAME_TO_RENDER; private static ParameterVersionName TEST_PARAMETER_VERSION_NAME_TO_RENDER; private static SecretName SECRET_NAME; + private static ParameterName TEST_PARAMETER_NAME_WITH_KMS; + private static String KEY_RING_ID; + private static String HSM_KEY_ID; + private static ParameterName TEST_PARAMETER_NAME_UPDATE_WITH_KMS; + private static String NEW_HSM_KEY_ID; + private static ParameterName TEST_PARAMETER_NAME_DELETE_WITH_KMS; private ByteArrayOutputStream stdOut; @BeforeClass @@ -162,7 +181,7 @@ public static void beforeAll() throws IOException { createParameter(TEST_PARAMETER_NAME_TO_RENDER.getParameter(), ParameterFormat.JSON); iamGrantAccess(SECRET_NAME, testParameter.getPolicyMember().getIamPolicyUidPrincipal()); try { - Thread.sleep(60000); + Thread.sleep(120000); } catch (InterruptedException e) { throw new RuntimeException(e); } @@ -178,6 +197,33 @@ public static void beforeAll() throws IOException { TEST_PARAMETER_VERSION_NAME_TO_RENDER.getParameter(), TEST_PARAMETER_VERSION_NAME_TO_RENDER.getParameterVersion(), payload); + + // test create parameter with kms key + TEST_PARAMETER_NAME_WITH_KMS = ParameterName.of(PROJECT_ID, LOCATION_ID, randomId()); + KEY_RING_ID = "test-regional-parameter-manager-snippets"; + HSM_KEY_ID = randomId(); + createKeyRing(KEY_RING_ID); + createHsmKey(HSM_KEY_ID); + + // test update kms key of parameter + TEST_PARAMETER_NAME_UPDATE_WITH_KMS = ParameterName.of(PROJECT_ID, LOCATION_ID, randomId()); + KEY_RING_ID = "test-regional-parameter-manager-snippets"; + HSM_KEY_ID = randomId(); + NEW_HSM_KEY_ID = randomId(); + createKeyRing(KEY_RING_ID); + createHsmKey(HSM_KEY_ID); + createHsmKey(NEW_HSM_KEY_ID); + String kmsKeyId = CryptoKeyName.of(PROJECT_ID, LOCATION_ID, KEY_RING_ID, HSM_KEY_ID).toString(); + createParameterWithKms(TEST_PARAMETER_NAME_UPDATE_WITH_KMS.getParameter(), kmsKeyId); + + // test delete kms key of parameter + TEST_PARAMETER_NAME_DELETE_WITH_KMS = ParameterName.of(PROJECT_ID, LOCATION_ID, randomId()); + KEY_RING_ID = "test-regional-parameter-manager-snippets"; + HSM_KEY_ID = randomId(); + createKeyRing(KEY_RING_ID); + createHsmKey(HSM_KEY_ID); + kmsKeyId = CryptoKeyName.of(PROJECT_ID, LOCATION_ID, KEY_RING_ID, HSM_KEY_ID).toString(); + createParameterWithKms(TEST_PARAMETER_NAME_DELETE_WITH_KMS.getParameter(), kmsKeyId); } @AfterClass @@ -208,6 +254,33 @@ public static void afterAll() throws IOException { deleteParameterVersion(TEST_PARAMETER_VERSION_NAME_TO_GET.toString()); deleteParameterVersion(TEST_PARAMETER_VERSION_NAME_TO_GET_1.toString()); deleteParameter(TEST_PARAMETER_NAME_TO_GET.toString()); + + deleteParameter(TEST_PARAMETER_NAME_WITH_KMS.toString()); + + deleteParameter(TEST_PARAMETER_NAME_UPDATE_WITH_KMS.toString()); + + deleteParameter(TEST_PARAMETER_NAME_DELETE_WITH_KMS.toString()); + + // Iterate over each key ring's key's crypto key versions and destroy. + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + for (CryptoKey key : client.listCryptoKeys(getKeyRingName()).iterateAll()) { + if (key.hasRotationPeriod() || key.hasNextRotationTime()) { + CryptoKey keyWithoutRotation = CryptoKey.newBuilder().setName(key.getName()).build(); + FieldMask fieldMask = FieldMaskUtil.fromString("rotation_period,next_rotation_time"); + client.updateCryptoKey(keyWithoutRotation, fieldMask); + } + + ListCryptoKeyVersionsRequest listVersionsRequest = + ListCryptoKeyVersionsRequest.newBuilder() + .setParent(key.getName()) + .setFilter("state != DESTROYED AND state != DESTROY_SCHEDULED") + .build(); + for (CryptoKeyVersion version : + client.listCryptoKeyVersions(listVersionsRequest).iterateAll()) { + client.destroyCryptoKeyVersion(version.getName()); + } + } + } } private static String randomId() { @@ -215,6 +288,14 @@ private static String randomId() { return "java-" + random.nextLong(); } + private static KeyRingName getKeyRingName() { + return KeyRingName.of(PROJECT_ID, LOCATION_ID, KEY_RING_ID); + } + + private static com.google.cloud.kms.v1.LocationName getLocationName() { + return com.google.cloud.kms.v1.LocationName.of(PROJECT_ID, LOCATION_ID); + } + private static Parameter createParameter(String parameterId, ParameterFormat format) throws IOException { // Endpoint to call the regional parameter manager server @@ -230,6 +311,52 @@ private static Parameter createParameter(String parameterId, ParameterFormat for } } + private static Parameter createParameterWithKms(String parameterId, String kmsKeyId) + throws IOException { + // Endpoint to call the regional parameter manager server + String apiEndpoint = String.format("parametermanager.%s.rep.googleapis.com:443", LOCATION_ID); + ParameterManagerSettings parameterManagerSettings = + ParameterManagerSettings.newBuilder().setEndpoint(apiEndpoint).build(); + + LocationName parent = LocationName.of(PROJECT_ID, LOCATION_ID); + Parameter parameter = Parameter.newBuilder().setKmsKey(kmsKeyId).build(); + + try (ParameterManagerClient client = ParameterManagerClient.create(parameterManagerSettings)) { + return client.createParameter(parent.toString(), parameter, parameterId); + } + } + + private static KeyRing createKeyRing(String keyRingId) throws IOException { + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + KeyRing keyRing = KeyRing.newBuilder().build(); + KeyRing createdKeyRing = client.createKeyRing(getLocationName(), keyRingId, keyRing); + return createdKeyRing; + } catch (AlreadyExistsException e) { + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + return client.getKeyRing(KeyRingName.of(PROJECT_ID, LOCATION_ID, keyRingId)); + } + } + } + + private static CryptoKey createHsmKey(String keyId) throws IOException { + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + CryptoKey key = + CryptoKey.newBuilder() + .setPurpose(CryptoKey.CryptoKeyPurpose.ENCRYPT_DECRYPT) + .setVersionTemplate( + CryptoKeyVersionTemplate.newBuilder() + .setAlgorithm(CryptoKeyVersion + .CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION) + .setProtectionLevel(ProtectionLevel.HSM) + .build()) + .putLabels("foo", "bar") + .putLabels("zip", "zap") + .build(); + CryptoKey createdKey = client.createCryptoKey(getKeyRingName(), keyId, key); + return createdKey; + } + } + private static void createParameterVersion(String parameterId, String versionId, String payload) throws IOException { // Endpoint to call the regional parameter manager server @@ -517,6 +644,59 @@ public void testEnableRegionalParameterVersion() throws IOException { assertThat(stdOut.toString()).contains("Enabled regional parameter version"); } + @Test + public void testCreateRegionalParamWithKmsKey() throws IOException { + ParameterName parameterName = TEST_PARAMETER_NAME_WITH_KMS; + String cryptoKey = CryptoKeyName.of(PROJECT_ID, LOCATION_ID, KEY_RING_ID, HSM_KEY_ID) + .toString(); + CreateRegionalParamWithKmsKey + .createRegionalParameterWithKmsKey( + parameterName.getProject(), + LOCATION_ID, + parameterName.getParameter(), + cryptoKey); + + String expected = String.format( + "Created regional parameter %s with kms key %s\n", + parameterName, cryptoKey); + assertThat(stdOut.toString()).contains(expected); + } + + @Test + public void testUpdateRegionalParamKmsKey() throws IOException { + ParameterName parameterName = TEST_PARAMETER_NAME_UPDATE_WITH_KMS; + String cryptoKey = CryptoKeyName.of(PROJECT_ID, LOCATION_ID, KEY_RING_ID, NEW_HSM_KEY_ID) + .toString(); + Parameter updatedParameter = UpdateRegionalParamKmsKey + .updateRegionalParamKmsKey( + parameterName.getProject(), + LOCATION_ID, + parameterName.getParameter(), + cryptoKey); + + String expected = String.format( + "Updated regional parameter %s with kms key %s\n", + parameterName, cryptoKey); + assertThat(stdOut.toString()).contains(expected); + assertThat(updatedParameter.getKmsKey()).contains(NEW_HSM_KEY_ID); + assertThat(updatedParameter.getKmsKey()).doesNotContain(HSM_KEY_ID); + } + + @Test + public void testRemoveRegionalParamKmsKey() throws IOException { + ParameterName parameterName = TEST_PARAMETER_NAME_DELETE_WITH_KMS; + Parameter updatedParameter = RemoveRegionalParamKmsKey + .removeRegionalParamKmsKey( + parameterName.getProject(), LOCATION_ID, parameterName.getParameter()); + + String expected = String.format( + "Removed kms key for regional parameter %s\n", + parameterName); + assertThat(stdOut.toString()).contains(expected); + assertEquals("", updatedParameter.getKmsKey()); + } + + @Test public void testDeleteRegionalParameterVersion() throws IOException { ParameterVersionName parameterVersionName = TEST_PARAMETER_VERSION_NAME_TO_DELETE; diff --git a/pubsub/streaming-analytics/README.md b/pubsub/streaming-analytics/README.md index 89c1476b9c5..761ce88e09b 100644 --- a/pubsub/streaming-analytics/README.md +++ b/pubsub/streaming-analytics/README.md @@ -51,7 +51,7 @@ Dataflow]. BUCKET_NAME=your-gcs-bucket PROJECT_NAME=$(gcloud config get-value project) - gsutil mb gs://$BUCKET_NAME + gcloud storage buckets create gs://$BUCKET_NAME ``` 1. Start a [Google Cloud Scheduler] job that publishes one message to a [Google @@ -155,7 +155,7 @@ in the [GCP Console Storage page]. You may need to wait a few minutes for the files to appear. ```bash -gsutil ls gs://$BUCKET_NAME/samples/ +gcloud storage ls gs://$BUCKET_NAME/samples/ ``` ## Cleanup @@ -185,10 +185,10 @@ gsutil ls gs://$BUCKET_NAME/samples/ ```bash # Delete only the files created by this sample. - gsutil -m rm -rf "gs://$BUCKET_NAME/samples/output*" + gcloud storage rm --recursive --continue-on-error "gs://$BUCKET_NAME/samples/output*" # [optional] Remove the Cloud Storage bucket. - gsutil rb gs://$BUCKET_NAME + gcloud storage buckets delete gs://$BUCKET_NAME ``` [Google Cloud Pub/Sub]: https://cloud.google.com/pubsub/docs/ diff --git a/pubsublite/streaming-analytics/README.md b/pubsublite/streaming-analytics/README.md index 86fadffbe25..391d4b78f73 100644 --- a/pubsublite/streaming-analytics/README.md +++ b/pubsublite/streaming-analytics/README.md @@ -55,7 +55,7 @@ Resources needed for this example: export PROJECT_ID=$(gcloud config get-value project) export BUCKET=your-gcs-bucket - gsutil mb gs://$BUCKET + gcloud storage buckets create gs://$BUCKET ``` 1. Create a Pub/Sub Lite topic and subscription. Set `LITE_LOCATION` to a @@ -130,7 +130,7 @@ mvn compile exec:java \ Storage bucket. ```sh -gsutil ls "gs://$BUCKET/samples/output*" +gcloud storage ls "gs://$BUCKET/samples/output*" ``` ## (Optional) Creating a custom Dataflow template @@ -199,7 +199,7 @@ input parameters. 1. Delete the Cloud Storage objects: ```sh - gsutil -m rm -rf "gs://$BUCKET/samples/output*" + gcloud storage rm --recursive --continue-on-error "gs://$BUCKET/samples/output*" ``` 1. Delete the template image in Cloud Registry and delete the Flex template if @@ -207,13 +207,13 @@ input parameters. ```sh gcloud container images delete $TEMPLATE_IMAGE - gsutil rm $TEMPLATE_PATH + gcloud storage rm $TEMPLATE_PATH ``` 1. Delete the Cloud Storage bucket: ```sh - gsutil rb "gs://$BUCKET" + gcloud storage buckets delete "gs://$BUCKET" ``` [Apache Beam]: https://beam.apache.org/ diff --git a/pubsublite/streaming-analytics/src/test/java/examples/PubsubliteToGcsIT.java b/pubsublite/streaming-analytics/src/test/java/examples/PubsubliteToGcsIT.java index d4d33f644a5..40f76db1c39 100644 --- a/pubsublite/streaming-analytics/src/test/java/examples/PubsubliteToGcsIT.java +++ b/pubsublite/streaming-analytics/src/test/java/examples/PubsubliteToGcsIT.java @@ -61,6 +61,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; @@ -200,6 +201,7 @@ public void tearDown() throws Exception { } @Test + @Ignore("https://cloud.google.com/pubsub/lite/docs Deprecated") public void testPubsubliteToGcs() throws InterruptedException, ExecutionException { // Run the pipeline on Dataflow as instructed in the README. PubsubliteToGcs.main( diff --git a/recaptcha_enterprise/demosite/pom.xml b/recaptcha_enterprise/demosite/pom.xml index b5dde77dd67..b9c2b32a45a 100644 --- a/recaptcha_enterprise/demosite/pom.xml +++ b/recaptcha_enterprise/demosite/pom.xml @@ -1,7 +1,6 @@ + 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"> 4.0.0 com.example.recaptchaenterprise demosite @@ -37,7 +36,7 @@ com.google.cloud import pom - 26.32.0 + 26.75.0 diff --git a/recaptcha_enterprise/snippets/src/main/java/recaptcha/passwordleak/CreatePasswordLeakAssessment.java b/recaptcha_enterprise/snippets/src/main/java/recaptcha/passwordleak/CreatePasswordLeakAssessment.java index 151db57d942..749f7901ae4 100644 --- a/recaptcha_enterprise/snippets/src/main/java/recaptcha/passwordleak/CreatePasswordLeakAssessment.java +++ b/recaptcha_enterprise/snippets/src/main/java/recaptcha/passwordleak/CreatePasswordLeakAssessment.java @@ -30,7 +30,6 @@ import java.util.List; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; -import org.bouncycastle.util.encoders.Base64; public class CreatePasswordLeakAssessment { @@ -93,9 +92,8 @@ public static void checkPasswordLeak( PasswordCheckVerification verification = passwordLeak.createVerification(username, password).get(); - byte[] lookupHashPrefix = Base64.encode(verification.getLookupHashPrefix()); - byte[] encryptedUserCredentialsHash = Base64.encode( - verification.getEncryptedUserCredentialsHash()); + byte[] lookupHashPrefix = verification.getLookupHashPrefix(); + byte[] encryptedUserCredentialsHash = verification.getEncryptedUserCredentialsHash(); // Pass the credentials to the createPasswordLeakAssessment() to get back // the matching database entry for the hash prefix. @@ -108,7 +106,7 @@ public static void checkPasswordLeak( // Convert to appropriate input format. List leakMatchPrefixes = credentials.getEncryptedLeakMatchPrefixesList().stream() - .map(x -> Base64.decode(x.toByteArray())) + .map(x -> x.toByteArray()) .collect(Collectors.toList()); // Verify if the encrypted credentials are present in the obtained match list. @@ -116,7 +114,7 @@ public static void checkPasswordLeak( passwordLeak .verify( verification, - Base64.decode(credentials.getReencryptedUserCredentialsHash().toByteArray()), + credentials.getReencryptedUserCredentialsHash().toByteArray(), leakMatchPrefixes) .get(); diff --git a/recaptcha_enterprise/snippets/src/pom.xml b/recaptcha_enterprise/snippets/src/pom.xml index 9ac37e23eef..6cd66fb3866 100644 --- a/recaptcha_enterprise/snippets/src/pom.xml +++ b/recaptcha_enterprise/snippets/src/pom.xml @@ -14,7 +14,7 @@ com.google.cloud.samples shared-configuration - 1.2.0 + 1.2.2 diff --git a/retail/interactive-tutorials/src/main/java/events/ImportUserEventsBigQuery.java b/retail/interactive-tutorials/src/main/java/events/ImportUserEventsBigQuery.java index 4f66b047409..4de4aed2ce7 100644 --- a/retail/interactive-tutorials/src/main/java/events/ImportUserEventsBigQuery.java +++ b/retail/interactive-tutorials/src/main/java/events/ImportUserEventsBigQuery.java @@ -20,6 +20,8 @@ package events; +// [START retail_import_user_events_from_big_query] + import com.google.api.gax.rpc.NotFoundException; import com.google.cloud.ServiceOptions; import com.google.cloud.bigquery.BigQueryException; @@ -122,3 +124,4 @@ public static void importUserEventsFromBigQuery( } } } +// [END retail_import_user_events_from_big_query] diff --git a/retail/interactive-tutorials/src/main/java/events/ImportUserEventsGcs.java b/retail/interactive-tutorials/src/main/java/events/ImportUserEventsGcs.java index 3128f9ba064..001b079e57c 100644 --- a/retail/interactive-tutorials/src/main/java/events/ImportUserEventsGcs.java +++ b/retail/interactive-tutorials/src/main/java/events/ImportUserEventsGcs.java @@ -20,6 +20,8 @@ package events; +// [START retail_import_user_events_from_gcs] + import com.google.api.gax.rpc.InvalidArgumentException; import com.google.api.gax.rpc.PermissionDeniedException; import com.google.cloud.ServiceOptions; @@ -128,3 +130,5 @@ public static void importUserEventsFromGcs( } } } + +// [END retail_import_user_events_from_gcs] diff --git a/retail/interactive-tutorials/src/main/java/events/ImportUserEventsInline.java b/retail/interactive-tutorials/src/main/java/events/ImportUserEventsInline.java index d7e882292de..83de634ad45 100644 --- a/retail/interactive-tutorials/src/main/java/events/ImportUserEventsInline.java +++ b/retail/interactive-tutorials/src/main/java/events/ImportUserEventsInline.java @@ -20,6 +20,8 @@ package events; +// [START retail_import_user_events_from_inline_source] + import com.google.api.gax.longrunning.OperationFuture; import com.google.cloud.ServiceOptions; import com.google.cloud.bigquery.BigQueryException; @@ -124,3 +126,5 @@ public static void importUserEventsFromInlineSource(String defaultCatalog) } } } + +// [END retail_import_user_events_from_inline_source] diff --git a/retail/interactive-tutorials/src/main/java/events/PurgeUserEvent.java b/retail/interactive-tutorials/src/main/java/events/PurgeUserEvent.java index c101110437f..b8019126295 100644 --- a/retail/interactive-tutorials/src/main/java/events/PurgeUserEvent.java +++ b/retail/interactive-tutorials/src/main/java/events/PurgeUserEvent.java @@ -20,6 +20,8 @@ package events; +// [START retail_purge_user_events] + import static setup.SetupCleanup.writeUserEvent; import com.google.api.gax.longrunning.OperationFuture; @@ -73,3 +75,5 @@ public static void callPurgeUserEvents(String defaultCatalog, String visitorId) } } } + +// [END retail_purge_user_events] diff --git a/retail/interactive-tutorials/src/main/java/events/RejoinUserEvent.java b/retail/interactive-tutorials/src/main/java/events/RejoinUserEvent.java index ef624d3cb4e..1f9c5df551e 100644 --- a/retail/interactive-tutorials/src/main/java/events/RejoinUserEvent.java +++ b/retail/interactive-tutorials/src/main/java/events/RejoinUserEvent.java @@ -20,6 +20,8 @@ package events; +// [START retail_rejoin_user_events] + import static setup.SetupCleanup.purgeUserEvent; import static setup.SetupCleanup.writeUserEvent; @@ -73,3 +75,5 @@ public static void callRejoinUserEvents(String defaultCatalog, String visitorId) purgeUserEvent(visitorId); } } + +// [END retail_rejoin_user_events] diff --git a/retail/interactive-tutorials/src/main/java/events/WriteUserEvent.java b/retail/interactive-tutorials/src/main/java/events/WriteUserEvent.java index b95dd710e8e..c7572457ac5 100644 --- a/retail/interactive-tutorials/src/main/java/events/WriteUserEvent.java +++ b/retail/interactive-tutorials/src/main/java/events/WriteUserEvent.java @@ -20,6 +20,7 @@ package events; +// [START retail_write_user_event] import static setup.SetupCleanup.purgeUserEvent; import com.google.cloud.ServiceOptions; @@ -78,3 +79,4 @@ public static void writeUserEvent(String defaultCatalog, String visitorId) purgeUserEvent(visitorId); } } +// [END retail_write_user_event] diff --git a/retail/interactive-tutorials/src/main/java/product/AddFulfillmentPlaces.java b/retail/interactive-tutorials/src/main/java/product/AddFulfillmentPlaces.java index e6ffff10094..1ab6e0f9d52 100644 --- a/retail/interactive-tutorials/src/main/java/product/AddFulfillmentPlaces.java +++ b/retail/interactive-tutorials/src/main/java/product/AddFulfillmentPlaces.java @@ -14,10 +14,10 @@ * limitations under the License. */ -// [START retail_add_fulfillment_places] - package product; +// [START retail_add_fulfillment_places] + import static setup.SetupCleanup.createProduct; import static setup.SetupCleanup.deleteProduct; import static setup.SetupCleanup.getProduct; @@ -83,4 +83,5 @@ public static void addFulfillmentPlaces(String productName, String placeId) } } } + // [END retail_add_fulfillment_places] diff --git a/retail/interactive-tutorials/src/main/java/product/CreateProduct.java b/retail/interactive-tutorials/src/main/java/product/CreateProduct.java index 9060a987965..3a685dd9c20 100644 --- a/retail/interactive-tutorials/src/main/java/product/CreateProduct.java +++ b/retail/interactive-tutorials/src/main/java/product/CreateProduct.java @@ -20,6 +20,8 @@ package product; +// [START retail_create_product] + import static setup.SetupCleanup.deleteProduct; import com.google.cloud.ServiceOptions; @@ -86,3 +88,5 @@ public static Product createProduct(String productId, String branchName) throws } } } + +// [END retail_create_product] diff --git a/retail/interactive-tutorials/src/main/java/product/DeleteProduct.java b/retail/interactive-tutorials/src/main/java/product/DeleteProduct.java index 04055af1978..7eb0dd29cb2 100644 --- a/retail/interactive-tutorials/src/main/java/product/DeleteProduct.java +++ b/retail/interactive-tutorials/src/main/java/product/DeleteProduct.java @@ -20,6 +20,7 @@ package product; +// [START retail_delete_product] import static setup.SetupCleanup.createProduct; import com.google.cloud.retail.v2.DeleteProductRequest; @@ -52,3 +53,4 @@ public static void deleteProduct(String productName) throws IOException { } } } +// [END retail_delete_product] diff --git a/retail/interactive-tutorials/src/main/java/product/GetProduct.java b/retail/interactive-tutorials/src/main/java/product/GetProduct.java index 25a88c718d2..b065a7970d7 100644 --- a/retail/interactive-tutorials/src/main/java/product/GetProduct.java +++ b/retail/interactive-tutorials/src/main/java/product/GetProduct.java @@ -20,6 +20,7 @@ package product; +// [START retail_get_product] import static setup.SetupCleanup.createProduct; import static setup.SetupCleanup.deleteProduct; @@ -61,3 +62,4 @@ public static Product getProduct(String productName) throws IOException { } } } +// [END retail_get_product] diff --git a/retail/interactive-tutorials/src/main/java/product/ImportProductsInlineSource.java b/retail/interactive-tutorials/src/main/java/product/ImportProductsInlineSource.java index c16fc821ffe..e1cfe79b289 100644 --- a/retail/interactive-tutorials/src/main/java/product/ImportProductsInlineSource.java +++ b/retail/interactive-tutorials/src/main/java/product/ImportProductsInlineSource.java @@ -20,6 +20,7 @@ package product; +// [START retail_import_products_from_inline_source] import com.google.api.gax.rpc.InvalidArgumentException; import com.google.cloud.ServiceOptions; import com.google.cloud.retail.v2.ColorInfo; @@ -204,7 +205,7 @@ public static List getProducts() { products.add(product1); products.add(product2); - return products; } } +// [END retail_import_products_from_inline_source] diff --git a/retail/interactive-tutorials/src/main/java/product/RemoveFulfillmentPlaces.java b/retail/interactive-tutorials/src/main/java/product/RemoveFulfillmentPlaces.java index 6a532dcacf7..114f26d6020 100644 --- a/retail/interactive-tutorials/src/main/java/product/RemoveFulfillmentPlaces.java +++ b/retail/interactive-tutorials/src/main/java/product/RemoveFulfillmentPlaces.java @@ -16,6 +16,8 @@ package product; +// [START retail_remove_fulfillment_places] + import static setup.SetupCleanup.createProduct; import static setup.SetupCleanup.deleteProduct; import static setup.SetupCleanup.getProduct; @@ -82,3 +84,5 @@ public static void removeFulfillmentPlaces(String productName, String storeId) } } } + +// [END retail_remove_fulfillment_places] diff --git a/retail/interactive-tutorials/src/main/java/product/SetInventory.java b/retail/interactive-tutorials/src/main/java/product/SetInventory.java index 5a5f1441037..5146b745adf 100644 --- a/retail/interactive-tutorials/src/main/java/product/SetInventory.java +++ b/retail/interactive-tutorials/src/main/java/product/SetInventory.java @@ -16,6 +16,8 @@ package product; +// [START retail_set_inventory] + import static setup.SetupCleanup.createProduct; import static setup.SetupCleanup.deleteProduct; import static setup.SetupCleanup.getProduct; @@ -93,6 +95,8 @@ public static void setInventory(String productName) throws IOException, Interrup .build(); System.out.printf("Set inventory request: %s%n", setInventoryRequest); + // [END retail_set_inventory] + // To send an out-of-order request assign the invalid SetTime here: // Instant instant = LocalDateTime.now().minusDays(1).toInstant(ZoneOffset.UTC); // Timestamp previousDay = Timestamp.newBuilder() diff --git a/retail/interactive-tutorials/src/main/java/product/UpdateProduct.java b/retail/interactive-tutorials/src/main/java/product/UpdateProduct.java index 4d37ad2220a..542330901eb 100644 --- a/retail/interactive-tutorials/src/main/java/product/UpdateProduct.java +++ b/retail/interactive-tutorials/src/main/java/product/UpdateProduct.java @@ -20,6 +20,7 @@ package product; +// [START retail_update_product] import static setup.SetupCleanup.createProduct; import static setup.SetupCleanup.deleteProduct; @@ -91,3 +92,4 @@ public static void updateProduct(Product originalProduct, String defaultBranchNa } } } +// [END retail_update_product] diff --git a/retail/interactive-tutorials/src/main/java/search/SearchWithBoostSpec.java b/retail/interactive-tutorials/src/main/java/search/SearchWithBoostSpec.java index caf5df7dba0..f0d6f9cf00e 100644 --- a/retail/interactive-tutorials/src/main/java/search/SearchWithBoostSpec.java +++ b/retail/interactive-tutorials/src/main/java/search/SearchWithBoostSpec.java @@ -21,6 +21,8 @@ package search; +// [START retail_search_product_with_boost_spec] + import com.google.cloud.ServiceOptions; import com.google.cloud.retail.v2.SearchRequest; import com.google.cloud.retail.v2.SearchRequest.BoostSpec; @@ -79,3 +81,4 @@ public static void searchResponse(String defaultSearchPlacementName) throws IOEx } } } +// [END retail_search_product_with_boost_spec] diff --git a/retail/interactive-tutorials/src/main/java/search/SearchWithFiltering.java b/retail/interactive-tutorials/src/main/java/search/SearchWithFiltering.java index 1c46b16454d..c1f2697810a 100644 --- a/retail/interactive-tutorials/src/main/java/search/SearchWithFiltering.java +++ b/retail/interactive-tutorials/src/main/java/search/SearchWithFiltering.java @@ -21,6 +21,8 @@ package search; +// [START retail_search_for_products_with_filtering] + import com.google.cloud.ServiceOptions; import com.google.cloud.retail.v2.SearchRequest; import com.google.cloud.retail.v2.SearchResponse; @@ -71,3 +73,5 @@ public static void searchResponse(String defaultSearchPlacementName) throws IOEx } } } + +// [END retail_search_for_products_with_filtering] diff --git a/retail/interactive-tutorials/src/main/java/search/SearchWithOrdering.java b/retail/interactive-tutorials/src/main/java/search/SearchWithOrdering.java index d3139a080e9..f1df89d25ff 100644 --- a/retail/interactive-tutorials/src/main/java/search/SearchWithOrdering.java +++ b/retail/interactive-tutorials/src/main/java/search/SearchWithOrdering.java @@ -21,6 +21,8 @@ package search; +// [START retail_search_for_products_with_ordering] + import com.google.cloud.ServiceOptions; import com.google.cloud.retail.v2.SearchRequest; import com.google.cloud.retail.v2.SearchResponse; @@ -70,3 +72,5 @@ public static void searchResponse(String defaultSearchPlacementName) throws IOEx } } } + +// [END retail_search_for_products_with_ordering] diff --git a/retail/interactive-tutorials/src/main/java/search/SearchWithPagination.java b/retail/interactive-tutorials/src/main/java/search/SearchWithPagination.java index c3d80c1ac62..3bbd8c11d7f 100644 --- a/retail/interactive-tutorials/src/main/java/search/SearchWithPagination.java +++ b/retail/interactive-tutorials/src/main/java/search/SearchWithPagination.java @@ -22,6 +22,8 @@ package search; +// [START retail_search_for_products_with_pagination] + import com.google.cloud.ServiceOptions; import com.google.cloud.retail.v2.SearchRequest; import com.google.cloud.retail.v2.SearchResponse; @@ -77,3 +79,5 @@ public static void searchResponse(String defaultSearchPlacementName) throws IOEx } } } + +// [END retail_search_for_products_with_pagination] diff --git a/retail/interactive-tutorials/src/main/java/search/SearchWithQueryExpansionSpec.java b/retail/interactive-tutorials/src/main/java/search/SearchWithQueryExpansionSpec.java index 0576de17e2b..8c218465663 100644 --- a/retail/interactive-tutorials/src/main/java/search/SearchWithQueryExpansionSpec.java +++ b/retail/interactive-tutorials/src/main/java/search/SearchWithQueryExpansionSpec.java @@ -22,6 +22,8 @@ package search; +// [START retail_search_for_products_with_query_expansion_specification] + import com.google.cloud.ServiceOptions; import com.google.cloud.retail.v2.SearchRequest; import com.google.cloud.retail.v2.SearchRequest.QueryExpansionSpec; @@ -76,3 +78,5 @@ public static void searchResponse(String defaultSearchPlacementName) throws IOEx } } } + +// [END retail_search_for_products_with_query_expansion_specification] diff --git a/retail/interactive-tutorials/src/test/java/search/SearchWithBoostSpecTest.java b/retail/interactive-tutorials/src/test/java/search/SearchWithBoostSpecTest.java index 8d6bb221070..12f2ca841c6 100644 --- a/retail/interactive-tutorials/src/test/java/search/SearchWithBoostSpecTest.java +++ b/retail/interactive-tutorials/src/test/java/search/SearchWithBoostSpecTest.java @@ -26,6 +26,7 @@ import java.util.concurrent.ExecutionException; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -50,6 +51,7 @@ public void setUp() throws IOException, InterruptedException, ExecutionException } @Test + @Ignore("https://github.com/GoogleCloudPlatform/java-docs-samples/issues/10133") public void testOutput() { String outputResult = bout.toString(); diff --git a/retail/interactive-tutorials/src/test/java/search/SearchWithFacetSpecTest.java b/retail/interactive-tutorials/src/test/java/search/SearchWithFacetSpecTest.java index 2f5e14112fc..975bef2bc07 100644 --- a/retail/interactive-tutorials/src/test/java/search/SearchWithFacetSpecTest.java +++ b/retail/interactive-tutorials/src/test/java/search/SearchWithFacetSpecTest.java @@ -26,6 +26,7 @@ import java.util.concurrent.ExecutionException; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -50,6 +51,7 @@ public void setUp() throws IOException, InterruptedException, ExecutionException } @Test + @Ignore("https://github.com/GoogleCloudPlatform/java-docs-samples/issues/10133") public void testOutput() { String outputResult = bout.toString(); diff --git a/retail/interactive-tutorials/src/test/java/search/SearchWithFilteringTest.java b/retail/interactive-tutorials/src/test/java/search/SearchWithFilteringTest.java index 0f91119dc56..10fbe615178 100644 --- a/retail/interactive-tutorials/src/test/java/search/SearchWithFilteringTest.java +++ b/retail/interactive-tutorials/src/test/java/search/SearchWithFilteringTest.java @@ -26,6 +26,7 @@ import java.util.concurrent.ExecutionException; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -51,6 +52,7 @@ public void setUp() throws IOException, InterruptedException, ExecutionException } @Test + @Ignore("https://github.com/GoogleCloudPlatform/java-docs-samples/issues/10133") public void testOutput() { String outputResult = bout.toString(); diff --git a/retail/snippets/README.md b/retail/snippets/README.md new file mode 100644 index 00000000000..9c44aecbfae --- /dev/null +++ b/retail/snippets/README.md @@ -0,0 +1,26 @@ +# Vertex AI Search for commerce Samples + +This directory contains Java samples for [Vertex AI Search for commerce](https://cloud.google.com/retail/docs/search-basic#search). + +## Prerequisites + +To run these samples, you must have: + +1. **A Google Cloud Project** with the [Vertex AI Search for commerce API](https://console.cloud.google.com/apis/library/retail.googleapis.com) enabled. +2. **Vertex AI Search for commerce** set up with a valid catalog and serving configuration (placement). +3. **Authentication**: These samples use [Application Default Credentials (ADC)](https://cloud.google.com/docs/authentication/provide-credentials-adc). + - If running locally, you can set up ADC by running: + ```bash + gcloud auth application-default login + ``` +4. **IAM Roles**: The service account or user running the samples needs the `roles/retail.viewer` (Retail Viewer) role or higher. + +## Samples + +- **[Search.java](src/main/java/com/example/search/Search.java)**: Basic search request showing both text search and browse search (using categories). +- **[SearchPagination.java](src/main/java/com/example/search/SearchPagination.java)**: Shows how to use `next_page_token` to paginate through search results. +- **[SearchOffset.java](src/main/java/com/example/search/SearchOffset.java)**: Shows how to use `offset` to skip a specified number of results. + +## Documentation + +For more information, see the [Vertex AI Search for commerce documentation](https://docs.cloud.google.com/retail/docs/search-basic#search). \ No newline at end of file diff --git a/retail/snippets/pom.xml b/retail/snippets/pom.xml new file mode 100644 index 00000000000..0066e78fb56 --- /dev/null +++ b/retail/snippets/pom.xml @@ -0,0 +1,72 @@ + + + + 4.0.0 + com.example.retail + retail-samples + 1.0-SNAPSHOT + + + + shared-configuration + com.google.cloud.samples + 1.2.2 + + + + 21 + 21 + + + + + + libraries-bom + com.google.cloud + import + pom + 26.80.0 + + + + + + + com.google.cloud + google-cloud-retail + + + + + truth + com.google.truth + test + 1.4.5 + + + org.junit.jupiter + junit-jupiter + 5.14.3 + test + + + diff --git a/retail/snippets/src/main/java/com/example/search/Search.java b/retail/snippets/src/main/java/com/example/search/Search.java new file mode 100644 index 00000000000..42305cb1000 --- /dev/null +++ b/retail/snippets/src/main/java/com/example/search/Search.java @@ -0,0 +1,85 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.search; + +// [START retail_v2_search_request] + +import com.google.cloud.retail.v2.BranchName; +import com.google.cloud.retail.v2.Product; +import com.google.cloud.retail.v2.SearchRequest; +import com.google.cloud.retail.v2.SearchResponse; +import com.google.cloud.retail.v2.SearchResponse.SearchResult; +import com.google.cloud.retail.v2.SearchServiceClient; +import com.google.cloud.retail.v2.SearchServiceClient.SearchPagedResponse; +import com.google.cloud.retail.v2.ServingConfigName; +import java.io.IOException; +import java.util.List; + +public class Search { + public static void main(String[] args) throws IOException { + String projectId = "my-project-id"; + String placementId = "default_search"; + String visitorId = "my-visitor-id"; + String query = "my search query"; + List categories = List.of("category"); + + search(projectId, placementId, visitorId, query, categories); + } + + /** + * Search for products using Vertex AI Search for commerce. + * + * Performs a search request for a specific placement. Handles both text search (using query) + * and browse search (using page_categories). + * + * @param projectId The Google Cloud project ID. + * @param placementId The placement name for the search. + * @param visitorId A unique identifier for the user. + * @param query The search term for text search. + * @param categories The categories for browse search. + */ + public static void search( + String projectId, String placementId, String visitorId, String query, List categories) + throws IOException { + try (SearchServiceClient searchServiceClient = SearchServiceClient.create()) { + ServingConfigName placementName = + ServingConfigName.of(projectId, "global", "default_catalog", placementId); + BranchName branchName = + BranchName.of(projectId, "global", "default_catalog", "default_branch"); + SearchRequest searchRequest = + SearchRequest.newBuilder() + .setPlacement(placementName.toString()) + .setBranch(branchName.toString()) + .setVisitorId(visitorId) + .setQuery(query) + .addAllPageCategories(categories) + .setPageSize(10) + .build(); + SearchPagedResponse response = searchServiceClient.search(searchRequest); + + SearchResponse searchResponse = response.getPage().getResponse(); + + System.out.println("Found " + searchResponse.getResultsCount() + " results in current page"); + for (SearchResult searchResult : searchResponse.getResultsList()) { + Product product = searchResult.getProduct(); + System.out.println("---- Search Result ----"); + System.out.println("Product Name: " + product.getName()); + } + } + } +} +// [END retail_v2_search_request] diff --git a/retail/snippets/src/main/java/com/example/search/SearchOffset.java b/retail/snippets/src/main/java/com/example/search/SearchOffset.java new file mode 100644 index 00000000000..df9cbff4ecb --- /dev/null +++ b/retail/snippets/src/main/java/com/example/search/SearchOffset.java @@ -0,0 +1,83 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.search; + +// [START retail_v2_search_offset] + +import com.google.cloud.retail.v2.BranchName; +import com.google.cloud.retail.v2.Product; +import com.google.cloud.retail.v2.SearchRequest; +import com.google.cloud.retail.v2.SearchResponse; +import com.google.cloud.retail.v2.SearchResponse.SearchResult; +import com.google.cloud.retail.v2.SearchServiceClient; +import com.google.cloud.retail.v2.SearchServiceClient.SearchPagedResponse; +import com.google.cloud.retail.v2.ServingConfigName; +import java.io.IOException; + +public class SearchOffset { + public static void main(String[] args) throws IOException { + String projectId = "my-project-id"; + String placementId = "default_search"; + String visitorId = "my-visitor-id"; + String query = "my search query"; + int offset = 10; + + searchWithOffset(projectId, placementId, visitorId, query, offset); + } + + /** + * Search for products with an offset using Vertex AI Search for commerce. + * + * Performs a search request starting from a specified position. + * + * @param projectId The Google Cloud project ID. + * @param placementId The placement name for the search. + * @param visitorId A unique identifier for the user. + * @param query The search term for text search. + * @param offset The number of results to skip. + */ + public static void searchWithOffset( + String projectId, String placementId, String visitorId, String query, int offset) + throws IOException { + try (SearchServiceClient searchServiceClient = SearchServiceClient.create()) { + ServingConfigName placementName = + ServingConfigName.of(projectId, "global", "default_catalog", placementId); + BranchName branchName = + BranchName.of(projectId, "global", "default_catalog", "default_branch"); + SearchRequest searchRequest = + SearchRequest.newBuilder() + .setPlacement(placementName.toString()) + .setBranch(branchName.toString()) + .setVisitorId(visitorId) + .setQuery(query) + .setPageSize(10) + .setOffset(offset) + .build(); + SearchPagedResponse response = searchServiceClient.search(searchRequest); + + SearchResponse searchResponse = response.getPage().getResponse(); + + System.out.println("Found " + searchResponse.getResultsCount() + " results in current page"); + for (SearchResult searchResult : searchResponse.getResultsList()) { + Product product = searchResult.getProduct(); + System.out.println("---- Search Result ----"); + System.out.println("Product Name: " + product.getName()); + } + } + } +} +// [END retail_v2_search_offset] diff --git a/retail/snippets/src/main/java/com/example/search/SearchPagination.java b/retail/snippets/src/main/java/com/example/search/SearchPagination.java new file mode 100644 index 00000000000..1ef5534bf2d --- /dev/null +++ b/retail/snippets/src/main/java/com/example/search/SearchPagination.java @@ -0,0 +1,94 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.search; + +// [START retail_v2_search_pagination] + +import com.google.cloud.retail.v2.BranchName; +import com.google.cloud.retail.v2.Product; +import com.google.cloud.retail.v2.SearchRequest; +import com.google.cloud.retail.v2.SearchResponse.SearchResult; +import com.google.cloud.retail.v2.SearchServiceClient; +import com.google.cloud.retail.v2.SearchServiceClient.SearchPage; +import com.google.cloud.retail.v2.SearchServiceClient.SearchPagedResponse; +import com.google.cloud.retail.v2.ServingConfigName; +import java.io.IOException; + +public class SearchPagination { + public static void main(String[] args) throws IOException { + String projectId = "my-project-id"; + String placementId = "default_search"; + String visitorId = "my-visitor-id"; + String query = "my search query"; + int pageSize = 10; + + searchWithPagination(projectId, placementId, visitorId, query, pageSize); + } + + /** + * Search for products with pagination using Vertex AI Search for commerce. + * + * Performs a search request, then uses the next_page_token to get the next page. + * + * @param projectId The Google Cloud project ID. + * @param placementId The placement name for the search. + * @param visitorId A unique identifier for the user. + * @param query The search term for text search. + * @param pageSize The amount of results per page. + */ + public static void searchWithPagination( + String projectId, String placementId, String visitorId, String query, int pageSize) + throws IOException { + try (SearchServiceClient searchServiceClient = SearchServiceClient.create()) { + ServingConfigName placementName = + ServingConfigName.of(projectId, "global", "default_catalog", placementId); + BranchName branchName = + BranchName.of(projectId, "global", "default_catalog", "default_branch"); + SearchRequest request = + SearchRequest.newBuilder() + .setPlacement(placementName.toString()) + .setBranch(branchName.toString()) + .setVisitorId(visitorId) + .setQuery(query) + .setPageSize(pageSize) + .build(); + int currentPage = 0; + while (true) { + SearchPagedResponse response = searchServiceClient.search(request); + + SearchPage page = response.getPage(); + currentPage++; + System.out.println("\nResults of page number " + currentPage + ":"); + System.out.println( + "Found " + page.getResponse().getResultsCount() + " results in current page"); + for (SearchResult searchResult : page.getResponse().getResultsList()) { + Product product = searchResult.getProduct(); + System.out.println("---- Search Result ----"); + System.out.println("Product Name: " + product.getName()); + } + + if (page.hasNextPage()) { + request = request.toBuilder().setPageToken(page.getNextPageToken()).build(); + } else { + System.out.println("\nNo more available pages."); + break; + } + } + } + } +} +// [END retail_v2_search_pagination] diff --git a/retail/snippets/src/test/java/com/example/search/SearchIT.java b/retail/snippets/src/test/java/com/example/search/SearchIT.java new file mode 100644 index 00000000000..5333b9ed34f --- /dev/null +++ b/retail/snippets/src/test/java/com/example/search/SearchIT.java @@ -0,0 +1,91 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.search; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.google.cloud.retail.v2.Product; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.List; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class SearchIT { + private static PrintStream origPrintStream; + private static ByteArrayOutputStream bout; + + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String PLACEMENT_ID = "default_search"; + private static final String VISITOR_ID = "test_visitor"; + private static final int NUM_PRODUCTS_TO_TEST = 2; + private static final String PRODUCT_TITLE = "Hot Java Testing"; + private static final List CATEGORIES = List.of("Beverages"); + private static List productsToTest; + + private static void requireEnvVar(String varName) { + assertNotNull( + "Environment variable " + varName + " is required to perform these tests.", + System.getenv(varName)); + } + + @BeforeAll + public static void setUp() throws InterruptedException, IOException { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + + // Create products to be searched + productsToTest = Utils.createProductsToTest(NUM_PRODUCTS_TO_TEST, PROJECT_ID, PRODUCT_TITLE); + Utils.waitForProductsToBeReadyToTest(PROJECT_ID, productsToTest, PRODUCT_TITLE); + + origPrintStream = System.out; + bout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(bout)); + } + + @AfterAll + public static void tearDown() throws IOException { + // Clean up products created for testing + Utils.deleteProducts(productsToTest); + + System.setOut(origPrintStream); + } + + @Test + public void testSearch() throws Exception { + Search.search(PROJECT_ID, PLACEMENT_ID, VISITOR_ID, PRODUCT_TITLE, CATEGORIES); + String output = bout.toString(); + assertThat(output).contains("Found " + NUM_PRODUCTS_TO_TEST + " results in current page"); + assertThat(output).contains("Product Name:"); + } + + @Test + public void testSearchWithoutQueryString() throws Exception { + Search.search(PROJECT_ID, PLACEMENT_ID, VISITOR_ID, "", CATEGORIES); + String output = bout.toString(); + assertThat(output).contains("Product Name:"); + } + + @Test + public void testSearch_productsNotFound() throws Exception { + Search.search(PROJECT_ID, PLACEMENT_ID, VISITOR_ID, "INVALID_QUERY_VALUE_FOR_TEST", CATEGORIES); + String output = bout.toString(); + assertThat(output).contains("Found " + 0 + " results in current page"); + } +} diff --git a/retail/snippets/src/test/java/com/example/search/SearchOffsetIT.java b/retail/snippets/src/test/java/com/example/search/SearchOffsetIT.java new file mode 100644 index 00000000000..3c5a5fb6484 --- /dev/null +++ b/retail/snippets/src/test/java/com/example/search/SearchOffsetIT.java @@ -0,0 +1,108 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.search; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.google.api.gax.rpc.InvalidArgumentException; +import com.google.cloud.retail.v2.Product; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.List; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class SearchOffsetIT { + private static PrintStream origPrintStream; + private static ByteArrayOutputStream bout; + + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String PLACEMENT_ID = "default_search"; + private static final String VISITOR_ID = "test_visitor"; + private static final int NUM_PRODUCTS_TO_TEST = 2; + private static final String PRODUCT_TITLE = "Hot Java Testing"; + private static List productsToTest; + + private static void requireEnvVar(String varName) { + assertNotNull( + "Environment variable " + varName + " is required to perform these tests.", + System.getenv(varName)); + } + + @BeforeAll + public static void setUp() throws InterruptedException, IOException { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + + // Create products to be searched + productsToTest = Utils.createProductsToTest(NUM_PRODUCTS_TO_TEST, PROJECT_ID, PRODUCT_TITLE); + Utils.waitForProductsToBeReadyToTest(PROJECT_ID, productsToTest, PRODUCT_TITLE); + + origPrintStream = System.out; + bout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(bout)); + } + + @AfterAll + public static void tearDown() throws IOException { + // Clean up products created for testing + Utils.deleteProducts(productsToTest); + + System.setOut(origPrintStream); + } + + @Test + public void testSearchWithOffset() throws Exception { + int offset = 1; + SearchOffset.searchWithOffset(PROJECT_ID, PLACEMENT_ID, VISITOR_ID, PRODUCT_TITLE, offset); + String output = bout.toString(); + assertThat(output) + .contains("Found " + (NUM_PRODUCTS_TO_TEST - offset) + " results in current page"); + assertThat(output).contains("Product Name:"); + } + + @Test + public void testSearchWithOffset_offsetEqualsZero() throws Exception { + int offset = 0; + SearchOffset.searchWithOffset(PROJECT_ID, PLACEMENT_ID, VISITOR_ID, PRODUCT_TITLE, offset); + String output = bout.toString(); + assertThat(output).contains("Found " + NUM_PRODUCTS_TO_TEST + " results in current page"); + assertThat(output).contains("Product Name:"); + } + + @Test + public void testSearchWithOffset_offsetTooLarge_foundZeroResults() throws Exception { + int offset = 5; + SearchOffset.searchWithOffset(PROJECT_ID, PLACEMENT_ID, VISITOR_ID, PRODUCT_TITLE, offset); + String output = bout.toString(); + assertThat(output).contains("Found 0 results in current page"); + } + + @Test + public void testSearchWithOffset_negativeOffset_throwsInvalidArgumentException() + throws Exception { + int offset = -1; + assertThrows( + InvalidArgumentException.class, + () -> + SearchOffset.searchWithOffset( + PROJECT_ID, PLACEMENT_ID, VISITOR_ID, PRODUCT_TITLE, offset)); + } +} diff --git a/retail/snippets/src/test/java/com/example/search/SearchPaginationIT.java b/retail/snippets/src/test/java/com/example/search/SearchPaginationIT.java new file mode 100644 index 00000000000..cc75b200836 --- /dev/null +++ b/retail/snippets/src/test/java/com/example/search/SearchPaginationIT.java @@ -0,0 +1,111 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.search; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.google.api.gax.rpc.InvalidArgumentException; +import com.google.cloud.retail.v2.Product; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.List; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class SearchPaginationIT { + private static PrintStream origPrintStream; + private static ByteArrayOutputStream bout; + + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String PLACEMENT_ID = "default_search"; + private static final String VISITOR_ID = "test_visitor"; + private static final int NUM_PRODUCTS_TO_TEST = 2; + private static final String PRODUCT_TITLE = "Hot Java Testing"; + private static List productsToTest; + + private static void requireEnvVar(String varName) { + assertNotNull( + "Environment variable " + varName + " is required to perform these tests.", + System.getenv(varName)); + } + + @BeforeAll + public static void setUp() throws InterruptedException, IOException { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + + // Create products to be searched + productsToTest = Utils.createProductsToTest(NUM_PRODUCTS_TO_TEST, PROJECT_ID, PRODUCT_TITLE); + Utils.waitForProductsToBeReadyToTest(PROJECT_ID, productsToTest, PRODUCT_TITLE); + + origPrintStream = System.out; + bout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(bout)); + } + + @AfterAll + public static void tearDown() throws IOException { + // Clean up products created for testing + Utils.deleteProducts(productsToTest); + + System.setOut(origPrintStream); + } + + @Test + public void testSearchWithPaginationAndMultiplePages() throws Exception { + SearchPagination.searchWithPagination(PROJECT_ID, PLACEMENT_ID, VISITOR_ID, PRODUCT_TITLE, 1); + String output = bout.toString(); + assertThat(output).contains("Results of page number 1:"); + assertThat(output).contains("Found 1 results in current page"); + assertThat(output).contains("Results of page number 2:"); + assertThat(output).contains("Found 1 results in current page"); + assertThat(output).contains("No more available pages."); + } + + @Test + public void testSearchWithPaginationAndSinglePage() throws Exception { + SearchPagination.searchWithPagination(PROJECT_ID, PLACEMENT_ID, VISITOR_ID, PRODUCT_TITLE, 10); + String output = bout.toString(); + assertThat(output).contains("Results of page number 1:"); + assertThat(output).contains("Found " + NUM_PRODUCTS_TO_TEST + " results in current page"); + assertThat(output).contains("No more available pages."); + } + + @Test + public void testSearchWithPaginationAndNoResults() throws Exception { + SearchPagination.searchWithPagination( + PROJECT_ID, PLACEMENT_ID, VISITOR_ID, "INVALID_QUERY_VALUE_FOR_TEST", 10); + String output = bout.toString(); + assertThat(output).contains("Results of page number 1:"); + assertThat(output).contains("Found 0 results in current page"); + assertThat(output).contains("No more available pages."); + } + + @Test + public void testSearchWithPagination_negativePageSize_throwsInvalidArgumentException() + throws Exception { + int pageSize = -1; + assertThrows( + InvalidArgumentException.class, + () -> + SearchPagination.searchWithPagination( + PROJECT_ID, PLACEMENT_ID, VISITOR_ID, PRODUCT_TITLE, pageSize)); + } +} diff --git a/retail/snippets/src/test/java/com/example/search/Utils.java b/retail/snippets/src/test/java/com/example/search/Utils.java new file mode 100644 index 00000000000..3d53ae60a89 --- /dev/null +++ b/retail/snippets/src/test/java/com/example/search/Utils.java @@ -0,0 +1,124 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.search; + +import com.google.cloud.retail.v2.BranchName; +import com.google.cloud.retail.v2.CreateProductRequest; +import com.google.cloud.retail.v2.DeleteProductRequest; +import com.google.cloud.retail.v2.PriceInfo; +import com.google.cloud.retail.v2.Product; +import com.google.cloud.retail.v2.Product.Availability; +import com.google.cloud.retail.v2.Product.Type; +import com.google.cloud.retail.v2.ProductServiceClient; +import com.google.cloud.retail.v2.SearchRequest; +import com.google.cloud.retail.v2.SearchServiceClient; +import com.google.cloud.retail.v2.SearchServiceClient.SearchPagedResponse; +import com.google.cloud.retail.v2.ServingConfigName; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class Utils { + + public static List createProductsToTest( + int amount, String projectId, String productTitle) throws IOException { + List productIds = new ArrayList<>(); + for (int i = 0; i < amount; i++) { + productIds.add(UUID.randomUUID().toString()); + } + return createProducts(projectId, productIds, productTitle); + } + + public static List createProducts( + String projectId, List productIds, String productTitle) throws IOException { + BranchName branchName = BranchName.of(projectId, "global", "default_catalog", "default_branch"); + + float price = 8.0f; + float originalPrice = 12.0f; + + PriceInfo priceInfo = + PriceInfo.newBuilder() + .setPrice(price) + .setOriginalPrice(originalPrice) + .setCurrencyCode("USD") + .build(); + + Product generatedProduct = + Product.newBuilder() + .setTitle(productTitle) + .setType(Type.PRIMARY) + .addCategories("Beverages") + .addBrands("Google") + .setPriceInfo(priceInfo) + .setAvailability(Availability.IN_STOCK) + .build(); + List createdProducts = new ArrayList<>(); + try (ProductServiceClient serviceClient = ProductServiceClient.create()) { + for (String productId : productIds) { + CreateProductRequest createProductRequest = + CreateProductRequest.newBuilder() + .setProduct(generatedProduct) + .setProductId(productId) + .setParent(branchName.toString()) + .build(); + + createdProducts.add(serviceClient.createProduct(createProductRequest)); + } + } + return createdProducts; + } + + public static void deleteProducts(List products) throws IOException { + try (ProductServiceClient serviceClient = ProductServiceClient.create()) { + for (Product product : products) { + DeleteProductRequest deleteProductRequest = + DeleteProductRequest.newBuilder().setName(product.getName()).build(); + + serviceClient.deleteProduct(deleteProductRequest); + } + } + } + + public static void waitForProductsToBeReadyToTest( + String projectId, List products, String productTitle) + throws InterruptedException, IOException { + try (SearchServiceClient searchServiceClient = SearchServiceClient.create()) { + ServingConfigName servingConfigName = + ServingConfigName.of(projectId, "global", "default_catalog", "default_search"); + BranchName branchName = + BranchName.of(projectId, "global", "default_catalog", "default_branch"); + SearchRequest searchRequest = + SearchRequest.newBuilder() + .setPlacement(servingConfigName.toString()) + .setBranch(branchName.toString()) + .setVisitorId("test_visitor") + .setQuery(productTitle) + .setPageSize(products.size()) + .build(); + + for (int i = 0; i < 3; i++) { + Thread.sleep(10000); + + SearchPagedResponse response = searchServiceClient.search(searchRequest); + if (response.getPage().getResponse().getResultsCount() == products.size()) { + break; + } + } + } + } +} diff --git a/run/helloworld/Dockerfile b/run/helloworld/Dockerfile index 63a3b54449c..1396b214fd3 100644 --- a/run/helloworld/Dockerfile +++ b/run/helloworld/Dockerfile @@ -15,7 +15,7 @@ # [START cloudrun_helloworld_dockerfile] # Use the official maven image to create a build artifact. # https://hub.docker.com/_/maven -FROM maven:3-eclipse-temurin-17-alpine as builder +FROM maven:3-eclipse-temurin-25-alpine as builder # Copy local code to the container image. WORKDIR /app @@ -27,7 +27,7 @@ RUN mvn package -DskipTests # Use Eclipse Temurin for base image. # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds -FROM eclipse-temurin:17.0.15_6-jre-alpine +FROM eclipse-temurin:25-jre-alpine # Copy the jar to the production image from the builder stage. COPY --from=builder /app/target/helloworld-*.jar /helloworld.jar diff --git a/run/helloworld/pom.xml b/run/helloworld/pom.xml index 70e213d033f..0242b3f715e 100644 --- a/run/helloworld/pom.xml +++ b/run/helloworld/pom.xml @@ -24,7 +24,7 @@ limitations under the License. com.google.cloud.samples shared-configuration - 1.2.0 + 1.2.2 @@ -41,9 +41,10 @@ limitations under the License. UTF-8 UTF-8 - 17 - 17 - 3.2.2 + + 21 + 21 + 3.5.0 @@ -68,6 +69,11 @@ limitations under the License. + + org.jacoco + jacoco-maven-plugin + 0.8.14 + org.springframework.boot spring-boot-maven-plugin @@ -84,8 +90,11 @@ limitations under the License. com.google.cloud.tools jib-maven-plugin - 3.4.0 + 3.4.6 + + eclipse-temurin:25-jre-alpine + gcr.io/PROJECT_ID/helloworld diff --git a/run/helloworld/src/test/java/com/example/helloworld/HelloworldApplicationTests.java b/run/helloworld/src/test/java/com/example/helloworld/HelloworldApplicationTests.java index c35e651b6bc..ede94413142 100644 --- a/run/helloworld/src/test/java/com/example/helloworld/HelloworldApplicationTests.java +++ b/run/helloworld/src/test/java/com/example/helloworld/HelloworldApplicationTests.java @@ -20,15 +20,12 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; -@RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class HelloworldApplicationTests { diff --git a/run/image-processing/Dockerfile b/run/image-processing/Dockerfile index fc39fccf880..0a4c99f7557 100644 --- a/run/image-processing/Dockerfile +++ b/run/image-processing/Dockerfile @@ -17,7 +17,7 @@ # It's important to use JDK 8u191 or above that has container support enabled. # https://hub.docker.com/_/eclipse-temurin/ # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds -FROM eclipse-temurin:17.0.15_6-jre +FROM eclipse-temurin:17.0.18_8-jre # Install Imagemagick into the container image. # For more on system packages review the system packages tutorial. diff --git a/run/logging-manual/output_structured_log.md b/run/logging-manual/output_structured_log.md new file mode 100644 index 00000000000..12b67b15de7 --- /dev/null +++ b/run/logging-manual/output_structured_log.md @@ -0,0 +1,3 @@ + +{"severity":"INFO","time":"YYYY-MM-DDTHH:MM:SS.SSSZ","logging.googleapis.com/labels":{"component":"arbitrary-property","levelName":"INFO","loggerName":"com.example.cloudrun.App","levelValue":"20000"},"logging.googleapis.com/trace_sampled":false,"message":"This is the default display field."} + diff --git a/run/logging-manual/pom.xml b/run/logging-manual/pom.xml index 913a536b45c..a200ec5c39b 100644 --- a/run/logging-manual/pom.xml +++ b/run/logging-manual/pom.xml @@ -28,6 +28,18 @@ limitations under the License. 17 + + + + com.google.cloud + libraries-bom + 26.32.0 + pom + import + + + + com.sparkjava @@ -40,14 +52,18 @@ limitations under the License. 2.0.12 - net.logstash.logback - logstash-logback-encoder - 7.4 + com.google.cloud + google-cloud-logging-logback + + + ch.qos.logback + logback-core + 1.5.34 ch.qos.logback logback-classic - 1.4.14 + 1.5.34 com.squareup.okhttp3 diff --git a/run/logging-manual/src/main/java/com/example/cloudrun/App.java b/run/logging-manual/src/main/java/com/example/cloudrun/App.java index df04d15df67..3f5372f2150 100644 --- a/run/logging-manual/src/main/java/com/example/cloudrun/App.java +++ b/run/logging-manual/src/main/java/com/example/cloudrun/App.java @@ -16,7 +16,6 @@ package com.example.cloudrun; -import static net.logstash.logback.argument.StructuredArguments.kv; import static spark.Spark.get; import static spark.Spark.port; @@ -27,6 +26,7 @@ import okhttp3.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.MDC; public class App { @@ -41,30 +41,27 @@ public static void main(String[] args) { "/", (req, res) -> { // [START cloudrun_manual_logging] - // Build structured log messages as an object. - Object globalLogFields = null; - // Add log correlation to nest all log messages beneath request log in Log Viewer. // TODO(developer): delete this code if you're creating a Cloud // Function and it is *NOT* triggered by HTTP. String traceHeader = req.headers("x-cloud-trace-context"); if (traceHeader != null && project != null) { String trace = traceHeader.split("/")[0]; - globalLogFields = - kv( - "logging.googleapis.com/trace", - String.format("projects/%s/traces/%s", project, trace)); + MDC.put( + "logging.googleapis.com/trace", + String.format("projects/%s/traces/%s", project, trace)); } // -- End log correlation code -- - // Create a structured log entry using key value pairs. + // Create a structured log entry using MDC (Mapped Diagnostic Context) keys. // For instantiating the "logger" variable, see // https://cloud.google.com/run/docs/logging#run_manual_logging-java - logger.error( - "This is the default display field.", - kv("component", "arbitrary-property"), - kv("severity", "NOTICE"), - globalLogFields); + MDC.put("component", "arbitrary-property"); + + logger.info("This is the default display field."); + + // Clear MDC at the end of the request to avoid resource leaks + MDC.clear(); // [END cloudrun_manual_logging] res.status(200); return "Hello Logger!"; diff --git a/run/logging-manual/src/main/resources/logback.xml b/run/logging-manual/src/main/resources/logback.xml index e7dcb0430ec..bd9218f3439 100644 --- a/run/logging-manual/src/main/resources/logback.xml +++ b/run/logging-manual/src/main/resources/logback.xml @@ -1,21 +1,20 @@ - - - - - [ignore] - [ignore] - [ignore] - [ignore] - [ignore] - [ignore] - - + + + + INFO + + application.log + gae_app + com.example.logging.logback.enhancers.ExampleEnhancer + WARN + + true - + - + \ No newline at end of file diff --git a/run/logging-manual/src/test/java/com/example/cloudrun/AppTest.java b/run/logging-manual/src/test/java/com/example/cloudrun/AppTest.java index e56027b7184..c4fc70a66fa 100644 --- a/run/logging-manual/src/test/java/com/example/cloudrun/AppTest.java +++ b/run/logging-manual/src/test/java/com/example/cloudrun/AppTest.java @@ -34,11 +34,17 @@ public class AppTest { - private ByteArrayOutputStream bout; - private PrintStream out; + private static ByteArrayOutputStream bout; + private static PrintStream out; + private static PrintStream originalOut; @BeforeClass public static void beforeClass() { + originalOut = System.out; + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + App app = new App(); app.main(new String[] {}); awaitInitialization(); @@ -46,14 +52,13 @@ public static void beforeClass() { @AfterClass public static void afterClass() { + System.setOut(originalOut); stop(); } @Before public void setUp() { - bout = new ByteArrayOutputStream(); - out = new PrintStream(bout); - System.setOut(out); + bout.reset(); } @Test @@ -63,7 +68,7 @@ public void shouldSucceed() throws IOException { assertEquals("Hello Logger!", response.body); String output = bout.toString(); assertTrue(output.toString().contains("This is the default display field.")); - assertTrue(output.toString().contains("NOTICE")); + assertTrue(output.toString().contains("INFO")); assertTrue(output.toString().contains("arbitrary-property")); } diff --git a/run/service-auth/pom.xml b/run/service-auth/pom.xml new file mode 100644 index 00000000000..8bdb845ee88 --- /dev/null +++ b/run/service-auth/pom.xml @@ -0,0 +1,110 @@ + + + + 4.0.0 + com.example.run + service-auth + 0.0.1-SNAPSHOT + jar + + + + + com.google.cloud.samples + shared-configuration + 1.2.2 + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + UTF-8 + UTF-8 + 17 + 17 + 3.2.2 + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + test + + + junit + junit + test + + + com.google.api-client + google-api-client + 2.7.2 + + + com.google.http-client + google-http-client + 1.47.0 + + + com.google.auth + google-auth-library-oauth2-http + 1.35.0 + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + repackage + + + + + + com.google.cloud.tools + jib-maven-plugin + 3.4.0 + + + gcr.io/PROJECT_ID/service-auth + + + + + + diff --git a/run/service-auth/src/main/java/com/example/serviceauth/Authentication.java b/run/service-auth/src/main/java/com/example/serviceauth/Authentication.java new file mode 100644 index 00000000000..f92ff6437a9 --- /dev/null +++ b/run/service-auth/src/main/java/com/example/serviceauth/Authentication.java @@ -0,0 +1,131 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.serviceauth; + +// [START cloudrun_service_to_service_receive] + +import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken; +import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier; +import com.google.api.client.http.apache.v2.ApacheHttpTransport; +import com.google.api.client.json.gson.GsonFactory; +import java.util.Arrays; +import java.util.Collection; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; + +@SpringBootApplication +public class Authentication { + @RestController + class AuthenticationController { + + private final AuthenticationService authService = new AuthenticationService(); + + @GetMapping("/") + public ResponseEntity getEmailFromAuthHeader( + @RequestHeader(value = "Authorization", required = false) String authHeader) { + String responseBody; + if (authHeader == null) { + responseBody = "Error verifying ID token: missing Authorization header"; + return new ResponseEntity<>(responseBody, HttpStatus.UNAUTHORIZED); + } + + String email = authService.parseAuthHeader(authHeader); + if (email == null) { + responseBody = "Unauthorized request. Please supply a valid bearer token."; + HttpHeaders headers = new HttpHeaders(); + headers.add("WWW-Authenticate", "Bearer"); + return new ResponseEntity<>(responseBody, headers, HttpStatus.UNAUTHORIZED); + } + + responseBody = "Hello, " + email; + return new ResponseEntity<>(responseBody, HttpStatus.OK); + } + } + + public class AuthenticationService { + /* + * Parse the authorization header, validate and decode the Bearer token. + * + * Args: + * authHeader: String of HTTP header with a Bearer token. + * + * Returns: + * A string containing the email from the token. + * null if the token is invalid or the email can't be retrieved. + */ + public String parseAuthHeader(String authHeader) { + // Split the auth type and value from the header. + String[] authHeaderStrings = authHeader.split(" "); + if (authHeaderStrings.length != 2) { + System.out.println("Malformed Authorization header"); + return null; + } + String authType = authHeaderStrings[0]; + String tokenValue = authHeaderStrings[1]; + // Validate and decode the ID token in the header. + if (!"bearer".equals(authType.toLowerCase())) { + System.out.println("Unhandled header format: " + authType); + return null; + } + + // Get the service URL from the environment variable + // set at the time of deployment. + String serviceUrl = System.getenv("SERVICE_URL"); + // Define the expected audience as the Service Base URL. + Collection audience = Arrays.asList(serviceUrl); + + try { + // Find more information about the verification process in: + // https://developers.google.com/identity/sign-in/web/backend-auth#java + // https://cloud.google.com/java/docs/reference/google-api-client/latest/com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier + GoogleIdTokenVerifier verifier = + new GoogleIdTokenVerifier.Builder(new ApacheHttpTransport(), new GsonFactory()) + .setAudience(audience) + .build(); + GoogleIdToken googleIdToken = verifier.verify(tokenValue); + + // More info about the structure for the decoded ID Token here: + // https://cloud.google.com/docs/authentication/token-types#id + // https://cloud.google.com/java/docs/reference/google-api-client/latest/com.google.api.client.googleapis.auth.oauth2.GoogleIdToken + // https://cloud.google.com/java/docs/reference/google-api-client/latest/com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload + GoogleIdToken.Payload payload = googleIdToken.getPayload(); + if (!payload.getEmailVerified()) { + System.out.println("Invalid token. Email wasn't verified."); + return null; + } + System.out.println("Email verified: " + payload.getEmail()); + return payload.getEmail(); + + } catch (Exception exception) { + System.out.println("Ivalid token: " + exception); + } + return null; + } + } + + public static void main(String[] args) { + SpringApplication.run(Authentication.class, args); + } + + // [END cloudrun_service_to_service_receive] +} diff --git a/run/service-auth/src/test/java/com/example/serviceauth/AuthenticationTests.java b/run/service-auth/src/test/java/com/example/serviceauth/AuthenticationTests.java new file mode 100644 index 00000000000..215cfac5663 --- /dev/null +++ b/run/service-auth/src/test/java/com/example/serviceauth/AuthenticationTests.java @@ -0,0 +1,228 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.serviceauth; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.google.api.client.http.HttpStatusCodes; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.auth.oauth2.IdTokenCredentials; +import com.google.auth.oauth2.IdTokenProvider; +import com.google.auth.oauth2.IdTokenProvider.Option; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.net.http.HttpTimeoutException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class AuthenticationTests { + + private static String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static String REGION = "us-central1"; + private static String PROJECT_NUMBER; + private static String SERVICE_URL; + private static String SERVICE_NAME; + private static HttpClient HTTP_CLIENT; + + @BeforeAll + public static void setUp() throws InterruptedException, IOException { + PROJECT_NUMBER = getProjectNumber(); + SERVICE_NAME = generateServiceName(); + SERVICE_URL = generateServiceUrl(); + deployService(); + + HTTP_CLIENT = HttpClient.newHttpClient(); + waitForService(); + } + + @AfterAll + public static void tearDown() throws InterruptedException, IOException { + deleteService(); + } + + private static String getProjectNumber() throws InterruptedException, IOException { + return getOutputFromCommand( + List.of("gcloud", "projects", "describe", PROJECT_ID, "--format=value(projectNumber)")); + } + + private static String generateServiceName() { + return String.format("receive-java-%s", UUID.randomUUID().toString().substring(0, 8)); + } + + private static String generateServiceUrl() { + return String.format("https://%s-%s.%s.run.app", SERVICE_NAME, PROJECT_NUMBER, REGION); + } + + private static String deployService() throws InterruptedException, IOException { + return getOutputFromCommand( + List.of( + "gcloud", + "run", + "deploy", + SERVICE_NAME, + "--project", + PROJECT_ID, + "--source", + ".", + "--region=" + REGION, + "--allow-unauthenticated", + "--set-env-vars=SERVICE_URL=" + SERVICE_URL, + "--quiet")); + } + + private static String deleteService() throws InterruptedException, IOException { + return getOutputFromCommand( + List.of( + "gcloud", + "run", + "services", + "delete", + SERVICE_NAME, + "--project", + PROJECT_ID, + "--async", + "--region=" + REGION, + "--quiet")); + } + + private static String getOutputFromCommand(List command) + throws InterruptedException, IOException { + + ProcessBuilder processBuilder = new ProcessBuilder(command); + + Process process = processBuilder.start(); + String output = + new String(process.getInputStream().readAllBytes(), StandardCharsets.UTF_8).strip(); + + process.waitFor(); + + return output; + } + + private static void waitForService() { + HttpResponse response = null; + int waitingTimeInSeconds = 1; + int retryTimeLimitInSeconds = 32; + while (waitingTimeInSeconds <= retryTimeLimitInSeconds) { + response = executeRequest(buildRequest(null, null)); + if (response != null) { + break; + } + waitingTimeInSeconds *= 2; + try { + Thread.sleep(waitingTimeInSeconds * 1000); + } catch (Exception e) { + Thread.currentThread().interrupt(); + } + } + } + + private static HttpRequest buildRequest(String headerName, String headerValue) { + HttpRequest.Builder requestBuilder = + HttpRequest.newBuilder().uri(URI.create(SERVICE_URL)).GET(); + if (headerName != null) { + requestBuilder = requestBuilder.header(headerName, headerValue); + } + return requestBuilder.build(); + } + + private static HttpResponse executeRequest(HttpRequest request) { + HttpResponse response = null; + int retryDelay = 3000; + int retryLimit = 5; + + for (int attempt = 0; attempt < retryLimit; attempt++) { + try { + response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == HttpStatusCodes.STATUS_CODE_OK + || response.statusCode() == HttpStatusCodes.STATUS_CODE_UNAUTHORIZED) { + return response; + } + } catch (HttpTimeoutException exception) { + System.out.println(String.format("TimeoutException: %s", exception)); + System.out.println("Retrying..."); + } catch (IOException | InterruptedException exception) { + System.out.println(String.format("Exception: %s", exception)); + System.out.println("Retrying..."); + } + + try { + Thread.sleep(retryDelay); + } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); + } + } + + return null; + } + + private String getGoogleIdToken() throws IOException { + GoogleCredentials googleCredentials = GoogleCredentials.getApplicationDefault(); + + IdTokenCredentials idTokenCredentials = + IdTokenCredentials.newBuilder() + .setIdTokenProvider((IdTokenProvider) googleCredentials) + .setTargetAudience(SERVICE_URL) + .setOptions(Arrays.asList(Option.FORMAT_FULL, Option.LICENSES_TRUE)) + .build(); + + return idTokenCredentials.refreshAccessToken().getTokenValue(); + } + + @Test + public void testValidToken() throws Exception { + String token = getGoogleIdToken(); + HttpRequest request = buildRequest("Authorization", "bearer " + token); + HttpResponse response = executeRequest(request); + + assertTrue(response != null); + assertTrue(response.statusCode() == HttpStatusCodes.STATUS_CODE_OK); + assertTrue(response.body().contains("Hello,")); + assertTrue(response.body().contains("@")); + } + + @Test + public void testInvalidToken() throws Exception { + String token = "invalid_token"; + HttpRequest request = buildRequest("Authorization", "bearer " + token); + HttpResponse response = executeRequest(request); + + assertTrue(response != null); + assertTrue(response.statusCode() == HttpStatusCodes.STATUS_CODE_UNAUTHORIZED); + assertTrue(response.body().contains("Please supply a valid bearer token.")); + } + + @Test + public void testAnonymousRequest() throws Exception { + HttpRequest request = buildRequest(null, null); + HttpResponse response = executeRequest(request); + + assertTrue(response != null); + assertTrue(response.statusCode() == HttpStatusCodes.STATUS_CODE_UNAUTHORIZED); + assertTrue(response.body().contains("missing Authorization header")); + } +} diff --git a/run/system-package/Dockerfile b/run/system-package/Dockerfile index 5bd7cdaef73..b4a0e9f93ff 100644 --- a/run/system-package/Dockerfile +++ b/run/system-package/Dockerfile @@ -15,7 +15,7 @@ # [START cloudrun_system_package_dockerfile] # Use the Official eclipse-temurin image for a lean production stage of our multi-stage build. # https://hub.docker.com/_/eclipse-temurin/ -FROM eclipse-temurin:17.0.15_6-jre +FROM eclipse-temurin:17.0.18_8-jre RUN apt-get update -y && apt-get install -y \ graphviz \ diff --git a/secretmanager/pom.xml b/secretmanager/pom.xml index 69ce69523af..ade777ecb4e 100644 --- a/secretmanager/pom.xml +++ b/secretmanager/pom.xml @@ -45,7 +45,7 @@ com.google.cloud import pom - 26.42.0 + 26.62.0 @@ -54,8 +54,17 @@ com.google.cloud google-cloud-secretmanager + 2.66.0 + + + com.google.api.grpc + proto-google-cloud-secretmanager-v1 + 2.66.0 + + + com.google.cloud + google-cloud-resourcemanager - com.google.protobuf protobuf-java-util diff --git a/secretmanager/src/main/java/secretmanager/CreateSecretWithDelayedDestroy.java b/secretmanager/src/main/java/secretmanager/CreateSecretWithDelayedDestroy.java new file mode 100644 index 00000000000..a75199e005f --- /dev/null +++ b/secretmanager/src/main/java/secretmanager/CreateSecretWithDelayedDestroy.java @@ -0,0 +1,67 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package secretmanager; + +// [START secretmanager_create_secret_with_delayed_destroy] + +import com.google.cloud.secretmanager.v1.ProjectName; +import com.google.cloud.secretmanager.v1.Replication; +import com.google.cloud.secretmanager.v1.Secret; +import com.google.cloud.secretmanager.v1.SecretManagerServiceClient; +import com.google.protobuf.Duration; +import java.io.IOException; + +public class CreateSecretWithDelayedDestroy { + + public static void createSecretWithDelayedDestroy() throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String secretId = "your-secret-id"; + Integer versionDestroyTtl = 86400; + createSecretWithDelayedDestroy(projectId, secretId, versionDestroyTtl); + } + + // Create secret with version destroy TTL. + public static Secret createSecretWithDelayedDestroy( + String projectId, + String secretId, + Integer versionDestroyTtl) + throws IOException { + // Initialize the client that will be used to send requests. + try (SecretManagerServiceClient client = SecretManagerServiceClient.create()) { + // Build the parent name from the project. + ProjectName projectName = ProjectName.of(projectId); + + // Build the secret to create. + Secret secret = + Secret.newBuilder() + .setReplication( + Replication.newBuilder() + .setAutomatic(Replication.Automatic.newBuilder().build()) + .build()) + .setVersionDestroyTtl(Duration.newBuilder().setSeconds(versionDestroyTtl)) + .build(); + + // Create the secret. + Secret createdSecret = client.createSecret(projectName, secretId, secret); + System.out.printf("Created secret with version destroy ttl %s\n", createdSecret.getName()); + + return createdSecret; + } + } +} +// [END secretmanager_create_secret_with_delayed_destroy] diff --git a/secretmanager/src/main/java/secretmanager/CreateSecretWithTags.java b/secretmanager/src/main/java/secretmanager/CreateSecretWithTags.java new file mode 100644 index 00000000000..e2e9f731583 --- /dev/null +++ b/secretmanager/src/main/java/secretmanager/CreateSecretWithTags.java @@ -0,0 +1,69 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package secretmanager; + +// [START secretmanager_create_secret_with_tags] +import com.google.cloud.secretmanager.v1.ProjectName; +import com.google.cloud.secretmanager.v1.Replication; +import com.google.cloud.secretmanager.v1.Secret; +import com.google.cloud.secretmanager.v1.SecretManagerServiceClient; +import java.io.IOException; + +public class CreateSecretWithTags { + + public static void createSecretWithTags() throws IOException { + // TODO(developer): Replace these variables before running the sample. + + // This is the id of the GCP project + String projectId = "your-project-id"; + // This is the id of the secret to act on + String secretId = "your-secret-id"; + // This is the key of the tag to be added + String tagKey = "your-tag-key"; + // This is the value of the tag to be added + String tagValue = "your-tag-value"; + createSecretWithTags(projectId, secretId, tagKey, tagValue); + } + + // Create a secret with tags. + public static Secret createSecretWithTags( + String projectId, String secretId, String tagKey, String tagValue) throws IOException { + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (SecretManagerServiceClient client = SecretManagerServiceClient.create()) { + + // Build the name. + ProjectName projectName = ProjectName.of(projectId); + + // Build the secret to create with tags. + Secret secret = + Secret.newBuilder() + .setReplication( + Replication.newBuilder() + .setAutomatic(Replication.Automatic.newBuilder().build()) + .build()) + .putTags(tagKey, tagValue) + .build(); + + // Create the secret. + Secret createdSecret = client.createSecret(projectName, secretId, secret); + System.out.printf("Created secret with Tags %s\n", createdSecret.getName()); + return createdSecret; + } + } +} +// [END secretmanager_create_secret_with_tags] diff --git a/secretmanager/src/main/java/secretmanager/DisableSecretDelayedDestroy.java b/secretmanager/src/main/java/secretmanager/DisableSecretDelayedDestroy.java new file mode 100644 index 00000000000..d558f1c1de5 --- /dev/null +++ b/secretmanager/src/main/java/secretmanager/DisableSecretDelayedDestroy.java @@ -0,0 +1,64 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package secretmanager; + +// [START secretmanager_disable_secret_delayed_destroy] + +import com.google.cloud.secretmanager.v1.Secret; +import com.google.cloud.secretmanager.v1.SecretManagerServiceClient; +import com.google.cloud.secretmanager.v1.SecretName; +import com.google.protobuf.FieldMask; +import com.google.protobuf.util.FieldMaskUtil; +import java.io.IOException; + +public class DisableSecretDelayedDestroy { + + public static void disableSecretDelayedDestroy() throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String secretId = "your-secret-id"; + disableSecretDelayedDestroy(projectId, secretId); + } + + // Disables delayed destroy for a secret. + public static Secret disableSecretDelayedDestroy( + String projectId, + String secretId) + throws IOException { + // Initialize the client that will be used to send requests. + try (SecretManagerServiceClient client = SecretManagerServiceClient.create()) { + // Build the parent name from the project and secret. + SecretName secretName = SecretName.of(projectId, secretId); + + // Build the secret to update. + Secret secret = + Secret.newBuilder() + .setName(secretName.toString()) + .build(); + + // Build the field mask. + FieldMask fieldMask = FieldMaskUtil.fromString("version_destroy_ttl"); + + // Update the secret. + Secret updatedSecret = client.updateSecret(secret, fieldMask); + System.out.printf("Updated secret %s\n", updatedSecret.getName()); + + return updatedSecret; + } + } +} +// [END secretmanager_disable_secret_delayed_destroy] diff --git a/secretmanager/src/main/java/secretmanager/UpdateSecretWithDelayedDestroy.java b/secretmanager/src/main/java/secretmanager/UpdateSecretWithDelayedDestroy.java new file mode 100644 index 00000000000..21d061f9dbe --- /dev/null +++ b/secretmanager/src/main/java/secretmanager/UpdateSecretWithDelayedDestroy.java @@ -0,0 +1,68 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package secretmanager; + +// [START secretmanager_update_secret_with_delayed_destroy] + +import com.google.cloud.secretmanager.v1.Secret; +import com.google.cloud.secretmanager.v1.SecretManagerServiceClient; +import com.google.cloud.secretmanager.v1.SecretName; +import com.google.protobuf.Duration; +import com.google.protobuf.FieldMask; +import com.google.protobuf.util.FieldMaskUtil; +import java.io.IOException; + +public class UpdateSecretWithDelayedDestroy { + + public static void updateSecretWithDelayedDestroy() throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String secretId = "your-secret-id"; + Integer versionDestroyTtl = 86400; + updateSecretWithDelayedDestroy(projectId, secretId, versionDestroyTtl); + } + + // Update secret with version destroy TTL. + public static Secret updateSecretWithDelayedDestroy( + String projectId, + String secretId, + Integer versionDestroyTtl) + throws IOException { + // Initialize the client that will be used to send requests. + try (SecretManagerServiceClient client = SecretManagerServiceClient.create()) { + // Build the parent name from the project and secret. + SecretName secretName = SecretName.of(projectId, secretId); + + // Build the secret to update. + Secret secret = + Secret.newBuilder() + .setName(secretName.toString()) + .setVersionDestroyTtl(Duration.newBuilder().setSeconds(versionDestroyTtl)) + .build(); + + // Build the field mask. + FieldMask fieldMask = FieldMaskUtil.fromString("version_destroy_ttl"); + + // Update the secret. + Secret updatedSecret = client.updateSecret(secret, fieldMask); + System.out.printf("Updated secret %s\n", updatedSecret.getName()); + + return updatedSecret; + } + } +} +// [END secretmanager_update_secret_with_delayed_destroy] diff --git a/secretmanager/src/main/java/secretmanager/regionalsamples/CreateRegionalSecretWithDelayedDestroy.java b/secretmanager/src/main/java/secretmanager/regionalsamples/CreateRegionalSecretWithDelayedDestroy.java new file mode 100644 index 00000000000..2866dfaea57 --- /dev/null +++ b/secretmanager/src/main/java/secretmanager/regionalsamples/CreateRegionalSecretWithDelayedDestroy.java @@ -0,0 +1,71 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package secretmanager.regionalsamples; + +// [START secretmanager_create_regional_secret_with_delayed_destroy] + +import com.google.cloud.secretmanager.v1.LocationName; +import com.google.cloud.secretmanager.v1.Secret; +import com.google.cloud.secretmanager.v1.SecretManagerServiceClient; +import com.google.cloud.secretmanager.v1.SecretManagerServiceSettings; +import com.google.protobuf.Duration; +import java.io.IOException; + +public class CreateRegionalSecretWithDelayedDestroy { + + public static void createRegionalSecretWithDelayedDestroy() throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String locationId = "your-location-id"; + String secretId = "your-secret-id"; + Integer versionDestroyTtl = 86400; + createRegionalSecretWithDelayedDestroy(projectId, locationId, secretId, versionDestroyTtl); + } + + // Create secret with version destroy TTL. + public static Secret createRegionalSecretWithDelayedDestroy( + String projectId, + String locationId, + String secretId, + Integer versionDestroyTtl) + throws IOException { + // Endpoint to call the regional secret manager sever + String apiEndpoint = String.format("secretmanager.%s.rep.googleapis.com:443", locationId); + SecretManagerServiceSettings secretManagerServiceSettings = + SecretManagerServiceSettings.newBuilder().setEndpoint(apiEndpoint).build(); + + // Initialize the client that will be used to send requests. + try (SecretManagerServiceClient client = + SecretManagerServiceClient.create(secretManagerServiceSettings)) { + // Build the parent name from the project. + LocationName locationName = LocationName.of(projectId, locationId); + + // Build the secret to create. + Secret secret = + Secret.newBuilder() + .setVersionDestroyTtl(Duration.newBuilder().setSeconds(versionDestroyTtl)) + .build(); + + // Create the secret. + Secret createdSecret = client.createSecret(locationName, secretId, secret); + System.out.printf("Created secret with version destroy ttl %s\n", createdSecret.getName()); + + return createdSecret; + } + } +} +// [END secretmanager_create_regional_secret_with_delayed_destroy] diff --git a/secretmanager/src/main/java/secretmanager/regionalsamples/CreateRegionalSecretWithTags.java b/secretmanager/src/main/java/secretmanager/regionalsamples/CreateRegionalSecretWithTags.java new file mode 100644 index 00000000000..f3933adc3c4 --- /dev/null +++ b/secretmanager/src/main/java/secretmanager/regionalsamples/CreateRegionalSecretWithTags.java @@ -0,0 +1,79 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package secretmanager.regionalsamples; + +// [START secretmanager_create_regional_secret_with_tags] +import com.google.cloud.secretmanager.v1.LocationName; +import com.google.cloud.secretmanager.v1.Secret; +import com.google.cloud.secretmanager.v1.SecretManagerServiceClient; +import com.google.cloud.secretmanager.v1.SecretManagerServiceSettings; +import java.io.IOException; + +public class CreateRegionalSecretWithTags { + + public static void createRegionalSecretWithTags() throws IOException { + // TODO(developer): Replace these variables before running the sample. + + // This is the id of the GCP project + String projectId = "your-project-id"; + // Location of the secret. + String locationId = "your-location-id"; + // This is the id of the secret to act on + String secretId = "your-secret-id"; + // This is the key of the tag to be added + String tagKey = "your-tag-key"; + // This is the value of the tag to be added + String tagValue = "your-tag-value"; + createRegionalSecretWithTags(projectId, locationId, secretId, tagKey, tagValue); + } + + // Create a secret with tags. + public static Secret createRegionalSecretWithTags( + String projectId, + String locationId, + String secretId, + String tagKey, + String tagValue) + throws IOException { + + // Endpoint to call the regional secret manager sever + String apiEndpoint = String.format("secretmanager.%s.rep.googleapis.com:443", locationId); + SecretManagerServiceSettings secretManagerServiceSettings = + SecretManagerServiceSettings.newBuilder().setEndpoint(apiEndpoint).build(); + + // Initialize client that will be used to send requests. This client only needs to be created + // once, and can be reused for multiple requests. + try (SecretManagerServiceClient client = + SecretManagerServiceClient.create(secretManagerServiceSettings)) { + + // Build the parent name from the project. + LocationName location = LocationName.of(projectId, locationId); + + // Build the secret to create with tags. + Secret secret = + Secret.newBuilder() + .putTags(tagKey, tagValue) + .build(); + + // Create the secret. + Secret createdSecret = client.createSecret(location.toString(), secretId, secret); + System.out.printf("Created secret with Tags%s\n", createdSecret.getName()); + return createdSecret; + } + } +} +// [END secretmanager_create_regional_secret_with_tags] diff --git a/secretmanager/src/main/java/secretmanager/regionalsamples/DisableRegionalSecretDelayedDestroy.java b/secretmanager/src/main/java/secretmanager/regionalsamples/DisableRegionalSecretDelayedDestroy.java new file mode 100644 index 00000000000..03753d09d77 --- /dev/null +++ b/secretmanager/src/main/java/secretmanager/regionalsamples/DisableRegionalSecretDelayedDestroy.java @@ -0,0 +1,74 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package secretmanager.regionalsamples; + +// [START secretmanager_disable_regional_secret_delayed_destroy] + +import com.google.cloud.secretmanager.v1.Secret; +import com.google.cloud.secretmanager.v1.SecretManagerServiceClient; +import com.google.cloud.secretmanager.v1.SecretManagerServiceSettings; +import com.google.cloud.secretmanager.v1.SecretName; +import com.google.protobuf.FieldMask; +import com.google.protobuf.util.FieldMaskUtil; +import java.io.IOException; + +public class DisableRegionalSecretDelayedDestroy { + + public static void disableRegionalSecretDelayedDestroy() throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String locationId = "your-location-id"; + String secretId = "your-secret-id"; + disableRegionalSecretDelayedDestroy(projectId, locationId, secretId); + } + + // Disables the secret's delayed destroy. + public static Secret disableRegionalSecretDelayedDestroy( + String projectId, + String locationId, + String secretId) + throws IOException { + // Endpoint to call the regional secret manager sever + String apiEndpoint = String.format("secretmanager.%s.rep.googleapis.com:443", locationId); + SecretManagerServiceSettings secretManagerServiceSettings = + SecretManagerServiceSettings.newBuilder().setEndpoint(apiEndpoint).build(); + + // Initialize the client that will be used to send requests. + try (SecretManagerServiceClient client = + SecretManagerServiceClient.create(secretManagerServiceSettings)) { + // Build the parent name from the project and secret. + SecretName secretName = + SecretName.ofProjectLocationSecretName(projectId, locationId, secretId); + + // Build the secret to update. + Secret secret = + Secret.newBuilder() + .setName(secretName.toString()) + .build(); + + // Build the field mask. + FieldMask fieldMask = FieldMaskUtil.fromString("version_destroy_ttl"); + + // Update the secret. + Secret updatedSecret = client.updateSecret(secret, fieldMask); + System.out.printf("Updated secret %s\n", updatedSecret.getName()); + + return updatedSecret; + } + } +} +// [END secretmanager_disable_regional_secret_delayed_destroy] diff --git a/secretmanager/src/main/java/secretmanager/regionalsamples/UpdateRegionalSecretWithDelayedDestroy.java b/secretmanager/src/main/java/secretmanager/regionalsamples/UpdateRegionalSecretWithDelayedDestroy.java new file mode 100644 index 00000000000..2d233846d97 --- /dev/null +++ b/secretmanager/src/main/java/secretmanager/regionalsamples/UpdateRegionalSecretWithDelayedDestroy.java @@ -0,0 +1,78 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package secretmanager.regionalsamples; + +// [START secretmanager_update_regional_secret_with_delayed_destroy] + +import com.google.cloud.secretmanager.v1.Secret; +import com.google.cloud.secretmanager.v1.SecretManagerServiceClient; +import com.google.cloud.secretmanager.v1.SecretManagerServiceSettings; +import com.google.cloud.secretmanager.v1.SecretName; +import com.google.protobuf.Duration; +import com.google.protobuf.FieldMask; +import com.google.protobuf.util.FieldMaskUtil; +import java.io.IOException; + +public class UpdateRegionalSecretWithDelayedDestroy { + + public static void updateRegionalSecretWithDelayedDestroy() throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String locationId = "your-location-id"; + String secretId = "your-secret-id"; + Integer versionDestroyTtl = 86400; + updateRegionalSecretWithDelayedDestroy(projectId, locationId, secretId, versionDestroyTtl); + } + + // Update secret with version destroy TTL. + public static Secret updateRegionalSecretWithDelayedDestroy( + String projectId, + String locationId, + String secretId, + Integer versionDestroyTtl) + throws IOException { + // Endpoint to call the regional secret manager sever + String apiEndpoint = String.format("secretmanager.%s.rep.googleapis.com:443", locationId); + SecretManagerServiceSettings secretManagerServiceSettings = + SecretManagerServiceSettings.newBuilder().setEndpoint(apiEndpoint).build(); + + // Initialize the client that will be used to send requests. + try (SecretManagerServiceClient client = + SecretManagerServiceClient.create(secretManagerServiceSettings)) { + // Build the parent name from the project and secret. + SecretName secretName = + SecretName.ofProjectLocationSecretName(projectId, locationId, secretId); + + // Build the secret to update. + Secret secret = + Secret.newBuilder() + .setName(secretName.toString()) + .setVersionDestroyTtl(Duration.newBuilder().setSeconds(versionDestroyTtl)) + .build(); + + // Build the field mask. + FieldMask fieldMask = FieldMaskUtil.fromString("version_destroy_ttl"); + + // Update the secret. + Secret updatedSecret = client.updateSecret(secret, fieldMask); + System.out.printf("Updated secret %s\n", updatedSecret.getName()); + + return updatedSecret; + } + } +} +// [END secretmanager_update_regional_secret_with_delayed_destroy] diff --git a/secretmanager/src/test/java/secretmanager/SnippetsIT.java b/secretmanager/src/test/java/secretmanager/SnippetsIT.java index 99a94319a45..41f88a1e6d9 100644 --- a/secretmanager/src/test/java/secretmanager/SnippetsIT.java +++ b/secretmanager/src/test/java/secretmanager/SnippetsIT.java @@ -19,6 +19,19 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertFalse; +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.resourcemanager.v3.CreateTagKeyMetadata; +import com.google.cloud.resourcemanager.v3.CreateTagKeyRequest; +import com.google.cloud.resourcemanager.v3.CreateTagValueMetadata; +import com.google.cloud.resourcemanager.v3.CreateTagValueRequest; +import com.google.cloud.resourcemanager.v3.DeleteTagKeyMetadata; +import com.google.cloud.resourcemanager.v3.DeleteTagKeyRequest; +import com.google.cloud.resourcemanager.v3.DeleteTagValueMetadata; +import com.google.cloud.resourcemanager.v3.DeleteTagValueRequest; +import com.google.cloud.resourcemanager.v3.TagKey; +import com.google.cloud.resourcemanager.v3.TagKeysClient; +import com.google.cloud.resourcemanager.v3.TagValue; +import com.google.cloud.resourcemanager.v3.TagValuesClient; import com.google.cloud.secretmanager.v1.AddSecretVersionRequest; import com.google.cloud.secretmanager.v1.CreateSecretRequest; import com.google.cloud.secretmanager.v1.DeleteSecretRequest; @@ -36,6 +49,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; +import java.lang.Exception; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Base64; @@ -75,9 +89,12 @@ public class SnippetsIT { private static Secret TEST_SECRET; private static Secret TEST_SECRET_TO_DELETE; private static Secret TEST_SECRET_TO_DELETE_WITH_ETAG; + private static Secret TEST_SECRET_TO_DELAYED_DESTROY; private static Secret TEST_SECRET_WITH_VERSIONS; + private static SecretName TEST_SECRET_WITH_DELAYED_DESTROY; private static SecretName TEST_SECRET_TO_CREATE_NAME; private static SecretName TEST_SECRET_WITH_LABEL_TO_CREATE_NAME; + private static SecretName TEST_SECRET_WITH_TAGS_TO_CREATE_NAME; private static SecretName TEST_SECRET_WITH_ANNOTATION_TO_CREATE_NAME; private static SecretName TEST_UMMR_SECRET_TO_CREATE_NAME; private static SecretVersion TEST_SECRET_VERSION; @@ -88,18 +105,24 @@ public class SnippetsIT { private static SecretVersion TEST_SECRET_VERSION_TO_ENABLE; private static SecretVersion TEST_SECRET_VERSION_TO_ENABLE_WITH_ETAG; + private static TagKey TAG_KEY; + private static TagValue TAG_VALUE; + private ByteArrayOutputStream stdOut; @BeforeClass - public static void beforeAll() throws IOException { + public static void beforeAll() throws Exception { Assert.assertFalse("missing GOOGLE_CLOUD_PROJECT", Strings.isNullOrEmpty(PROJECT_ID)); TEST_SECRET = createSecret(true); TEST_SECRET_TO_DELETE = createSecret(false); TEST_SECRET_TO_DELETE_WITH_ETAG = createSecret(false); TEST_SECRET_WITH_VERSIONS = createSecret(false); + TEST_SECRET_TO_DELAYED_DESTROY = createSecret(false); + TEST_SECRET_WITH_DELAYED_DESTROY = SecretName.of(PROJECT_ID, randomSecretId()); TEST_SECRET_TO_CREATE_NAME = SecretName.of(PROJECT_ID, randomSecretId()); TEST_UMMR_SECRET_TO_CREATE_NAME = SecretName.of(PROJECT_ID, randomSecretId()); + TEST_SECRET_WITH_TAGS_TO_CREATE_NAME = SecretName.of(PROJECT_ID, randomSecretId()); TEST_SECRET_WITH_LABEL_TO_CREATE_NAME = SecretName.of(PROJECT_ID, randomSecretId()); TEST_SECRET_WITH_ANNOTATION_TO_CREATE_NAME = SecretName.of(PROJECT_ID, randomSecretId()); @@ -113,6 +136,7 @@ public static void beforeAll() throws IOException { disableSecretVersion(TEST_SECRET_VERSION_TO_ENABLE); TEST_SECRET_VERSION_TO_ENABLE_WITH_ETAG = disableSecretVersion( TEST_SECRET_VERSION_TO_ENABLE_WITH_ETAG); + createTags(); } @Before @@ -128,17 +152,21 @@ public void afterEach() { } @AfterClass - public static void afterAll() throws IOException { + public static void afterAll() throws Exception { Assert.assertFalse("missing GOOGLE_CLOUD_PROJECT", Strings.isNullOrEmpty(PROJECT_ID)); deleteSecret(TEST_SECRET.getName()); deleteSecret(TEST_SECRET_TO_CREATE_NAME.toString()); + deleteSecret(TEST_SECRET_WITH_TAGS_TO_CREATE_NAME.toString()); deleteSecret(TEST_SECRET_WITH_LABEL_TO_CREATE_NAME.toString()); deleteSecret(TEST_SECRET_WITH_ANNOTATION_TO_CREATE_NAME.toString()); deleteSecret(TEST_UMMR_SECRET_TO_CREATE_NAME.toString()); deleteSecret(TEST_SECRET_TO_DELETE.getName()); deleteSecret(TEST_SECRET_TO_DELETE_WITH_ETAG.getName()); deleteSecret(TEST_SECRET_WITH_VERSIONS.getName()); + deleteSecret(TEST_SECRET_WITH_DELAYED_DESTROY.toString()); + deleteSecret(TEST_SECRET_TO_DELAYED_DESTROY.getName()); + deleteTags(); } private static String randomSecretId() { @@ -146,6 +174,66 @@ private static String randomSecretId() { return "java-" + random.nextLong(); } + private static void createTags() throws Exception { + try (TagKeysClient tagKeysClient = TagKeysClient.create()) { + Random random = new Random(); + ProjectName parent = ProjectName.of(PROJECT_ID); + CreateTagKeyRequest request = + CreateTagKeyRequest.newBuilder() + .setTagKey( + TagKey + .newBuilder() + .setParent(parent.toString()) + .setShortName("java-" + random.nextLong()) + .build()) + .build(); + OperationFuture future = + tagKeysClient.createTagKeyOperationCallable().futureCall(request); + TagKey response = future.get(); + TAG_KEY = response; + } + + try (TagValuesClient tagValuesClient = TagValuesClient.create()) { + Random random = new Random(); + CreateTagValueRequest request = + CreateTagValueRequest.newBuilder() + .setTagValue( + TagValue + .newBuilder() + .setParent(TAG_KEY.getName()) + .setShortName("java-" + random.nextLong()) + .build()) + .build(); + OperationFuture future = + tagValuesClient.createTagValueOperationCallable().futureCall(request); + TagValue response = future.get(); + TAG_VALUE = response; + } + } + + private static void deleteTags() throws Exception { + Thread.sleep(60000); + try (TagValuesClient tagValuesClient = TagValuesClient.create()) { + DeleteTagValueRequest request = + DeleteTagValueRequest.newBuilder() + .setName(TAG_VALUE.getName()) + .build(); + OperationFuture future = + tagValuesClient.deleteTagValueOperationCallable().futureCall(request); + TagValue response = future.get(); + } + + try (TagKeysClient tagKeysClient = TagKeysClient.create()) { + DeleteTagKeyRequest request = + DeleteTagKeyRequest.newBuilder() + .setName(TAG_KEY.getName()) + .build(); + OperationFuture future = + tagKeysClient.deleteTagKeyOperationCallable().futureCall(request); + TagKey response = future.get(); + } + } + private static Secret createSecret(boolean addAnnotation) throws IOException { ProjectName parent = ProjectName.of(PROJECT_ID); @@ -257,6 +345,19 @@ public void testCreateSecretWithLabel() throws IOException { assertThat(secret.getLabelsMap()).containsEntry(LABEL_KEY, LABEL_VALUE); } + @Test + public void testCreateSecretWithTag() throws IOException { + SecretName name = TEST_SECRET_WITH_TAGS_TO_CREATE_NAME; + Secret secret = CreateSecretWithTags.createSecretWithTags( + name.getProject(), + name.getSecret(), + TAG_KEY.getName(), + TAG_VALUE.getName() + ); + + assertThat(stdOut.toString()).contains("Created secret with Tags"); + } + @Test public void testCreateSecretWithAnnotations() throws IOException { SecretName name = TEST_SECRET_WITH_ANNOTATION_TO_CREATE_NAME; @@ -488,6 +589,36 @@ public void testUpdateSecretWithAlias() throws IOException { assertThat(stdOut.toString()).contains("test"); } + @Test + public void testCreateSecretWithDelayedDestroy() throws IOException { + SecretName name = TEST_SECRET_WITH_DELAYED_DESTROY; + Secret secret = CreateSecretWithDelayedDestroy.createSecretWithDelayedDestroy( + name.getProject(), name.getSecret(), 86400); + + assertThat(stdOut.toString()).contains("Created secret with version destroy ttl"); + assertThat(secret.getVersionDestroyTtl().getSeconds()).isEqualTo(86400); + } + + @Test + public void testUpdateSecretWithDelayedDestroy() throws IOException { + SecretName name = SecretName.parse(TEST_SECRET_TO_DELAYED_DESTROY.getName()); + Secret secret = UpdateSecretWithDelayedDestroy.updateSecretWithDelayedDestroy( + name.getProject(), name.getSecret(), 86520); + + assertThat(stdOut.toString()).contains("Updated secret"); + assertThat(secret.getVersionDestroyTtl().getSeconds()).isEqualTo(86520); + } + + @Test + public void testDisableSecretDelayedDestroy() throws IOException { + SecretName name = SecretName.parse(TEST_SECRET_TO_DELAYED_DESTROY.getName()); + Secret secret = DisableSecretDelayedDestroy.disableSecretDelayedDestroy( + name.getProject(), name.getSecret()); + + assertThat(stdOut.toString()).contains("Updated secret"); + assertThat(secret.getVersionDestroyTtl().getSeconds()).isEqualTo(0); + } + @Test public void testConsumeEventNotification() { String message = "hello!"; @@ -504,5 +635,4 @@ public void testConsumeEventNotification() { assertThat(log).isEqualTo( "Received SECRET_UPDATE for projects/p/secrets/s. New metadata: hello!"); } - } diff --git a/secretmanager/src/test/java/secretmanager/regionalsamples/SnippetsIT.java b/secretmanager/src/test/java/secretmanager/regionalsamples/SnippetsIT.java index bfe060b9bb6..11ca876dc30 100644 --- a/secretmanager/src/test/java/secretmanager/regionalsamples/SnippetsIT.java +++ b/secretmanager/src/test/java/secretmanager/regionalsamples/SnippetsIT.java @@ -22,12 +22,26 @@ import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import com.google.api.gax.longrunning.OperationFuture; import com.google.api.gax.rpc.NotFoundException; +import com.google.cloud.resourcemanager.v3.CreateTagKeyMetadata; +import com.google.cloud.resourcemanager.v3.CreateTagKeyRequest; +import com.google.cloud.resourcemanager.v3.CreateTagValueMetadata; +import com.google.cloud.resourcemanager.v3.CreateTagValueRequest; +import com.google.cloud.resourcemanager.v3.DeleteTagKeyMetadata; +import com.google.cloud.resourcemanager.v3.DeleteTagKeyRequest; +import com.google.cloud.resourcemanager.v3.DeleteTagValueMetadata; +import com.google.cloud.resourcemanager.v3.DeleteTagValueRequest; +import com.google.cloud.resourcemanager.v3.TagKey; +import com.google.cloud.resourcemanager.v3.TagKeysClient; +import com.google.cloud.resourcemanager.v3.TagValue; +import com.google.cloud.resourcemanager.v3.TagValuesClient; import com.google.cloud.secretmanager.v1.AddSecretVersionRequest; import com.google.cloud.secretmanager.v1.CreateSecretRequest; import com.google.cloud.secretmanager.v1.DeleteSecretRequest; import com.google.cloud.secretmanager.v1.DisableSecretVersionRequest; import com.google.cloud.secretmanager.v1.LocationName; +import com.google.cloud.secretmanager.v1.ProjectName; import com.google.cloud.secretmanager.v1.Secret; import com.google.cloud.secretmanager.v1.SecretManagerServiceClient; import com.google.cloud.secretmanager.v1.SecretManagerServiceClient.ListSecretVersionsPage; @@ -47,6 +61,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; +import java.lang.Exception; import java.util.Map; import java.util.Random; import org.junit.After; @@ -84,8 +99,11 @@ public class SnippetsIT { private static Secret TEST_REGIONAL_SECRET_TO_DELETE; private static Secret TEST_REGIONAL_SECRET_TO_DELETE_WITH_ETAG; private static Secret TEST_REGIONAL_SECRET_WITH_VERSIONS; + private static Secret TEST_REGIONAL_SECRET_TO_DELAYED_DESTROY; + private static SecretName TEST_REGIONAL_SECRET_WITH_DELAYED_DESTROY; private static SecretName TEST_REGIONAL_SECRET_TO_CREATE_NAME; private static SecretName TEST_REGIONAL_SECRET_WITH_LABEL_TO_CREATE_NAME; + private static SecretName TEST_REGIONAL_SECRET_WITH_TAGS_TO_CREATE_NAME; private static SecretName TEST_REGIONAL_SECRET_WITH_ANNOTATION_TO_CREATE_NAME; private static SecretVersion TEST_REGIONAL_SECRET_VERSION; private static SecretVersion TEST_REGIONAL_SECRET_VERSION_TO_DESTROY; @@ -95,10 +113,13 @@ public class SnippetsIT { private static SecretVersion TEST_REGIONAL_SECRET_VERSION_TO_ENABLE; private static SecretVersion TEST_REGIONAL_SECRET_VERSION_TO_ENABLE_WITH_ETAG; + private static TagKey TAG_KEY; + private static TagValue TAG_VALUE; + private ByteArrayOutputStream stdOut; @BeforeClass - public static void beforeAll() throws IOException { + public static void beforeAll() throws Exception { Assert.assertFalse("missing GOOGLE_CLOUD_PROJECT", Strings.isNullOrEmpty(PROJECT_ID)); Assert.assertFalse("missing GOOGLE_CLOUD_PROJECT_LOCATION", Strings.isNullOrEmpty(LOCATION_ID)); @@ -107,6 +128,9 @@ public static void beforeAll() throws IOException { TEST_REGIONAL_SECRET_TO_DELETE = createRegionalSecret(); TEST_REGIONAL_SECRET_TO_DELETE_WITH_ETAG = createRegionalSecret(); TEST_REGIONAL_SECRET_WITH_VERSIONS = createRegionalSecret(); + TEST_REGIONAL_SECRET_TO_DELAYED_DESTROY = createRegionalSecret(); + TEST_REGIONAL_SECRET_WITH_DELAYED_DESTROY = + SecretName.ofProjectLocationSecretName(PROJECT_ID, LOCATION_ID, randomSecretId()); TEST_REGIONAL_SECRET_TO_CREATE_NAME = SecretName.ofProjectLocationSecretName(PROJECT_ID, LOCATION_ID, randomSecretId()); TEST_REGIONAL_SECRET_WITH_ANNOTATION_TO_CREATE_NAME = @@ -114,6 +138,8 @@ public static void beforeAll() throws IOException { TEST_REGIONAL_SECRET_WITH_LABEL_TO_CREATE_NAME = SecretName.ofProjectLocationSecretName(PROJECT_ID, LOCATION_ID, randomSecretId()); + TEST_REGIONAL_SECRET_WITH_TAGS_TO_CREATE_NAME = + SecretName.ofProjectLocationSecretName(PROJECT_ID, LOCATION_ID, randomSecretId()); TEST_REGIONAL_SECRET_VERSION = addRegionalSecretVersion(TEST_REGIONAL_SECRET_WITH_VERSIONS); TEST_REGIONAL_SECRET_VERSION_TO_DESTROY = addRegionalSecretVersion(TEST_REGIONAL_SECRET_WITH_VERSIONS); @@ -130,6 +156,7 @@ public static void beforeAll() throws IOException { disableRegionalSecretVersion(TEST_REGIONAL_SECRET_VERSION_TO_ENABLE); TEST_REGIONAL_SECRET_VERSION_TO_ENABLE_WITH_ETAG = disableRegionalSecretVersion( TEST_REGIONAL_SECRET_VERSION_TO_ENABLE_WITH_ETAG); + createTags(); } @Before @@ -145,16 +172,20 @@ public void afterEach() { } @AfterClass - public static void afterAll() throws IOException { + public static void afterAll() throws Exception { Assert.assertFalse("missing GOOGLE_CLOUD_PROJECT", Strings.isNullOrEmpty(PROJECT_ID)); deleteRegionalSecret(TEST_REGIONAL_SECRET.getName()); deleteRegionalSecret(TEST_REGIONAL_SECRET_TO_CREATE_NAME.toString()); deleteRegionalSecret(TEST_REGIONAL_SECRET_WITH_LABEL_TO_CREATE_NAME.toString()); + deleteRegionalSecret(TEST_REGIONAL_SECRET_WITH_TAGS_TO_CREATE_NAME.toString()); deleteRegionalSecret(TEST_REGIONAL_SECRET_WITH_ANNOTATION_TO_CREATE_NAME.toString()); deleteRegionalSecret(TEST_REGIONAL_SECRET_TO_DELETE.getName()); deleteRegionalSecret(TEST_REGIONAL_SECRET_TO_DELETE_WITH_ETAG.getName()); deleteRegionalSecret(TEST_REGIONAL_SECRET_WITH_VERSIONS.getName()); + deleteRegionalSecret(TEST_REGIONAL_SECRET_WITH_DELAYED_DESTROY.toString()); + deleteRegionalSecret(TEST_REGIONAL_SECRET_TO_DELAYED_DESTROY.getName()); + deleteTags(); } private static String randomSecretId() { @@ -162,6 +193,65 @@ private static String randomSecretId() { return "test-drz-" + random.nextLong(); } + private static void createTags() throws Exception { + try (TagKeysClient tagKeysClient = TagKeysClient.create()) { + ProjectName parent = ProjectName.of(PROJECT_ID); + Random random = new Random(); + CreateTagKeyRequest request = + CreateTagKeyRequest.newBuilder() + .setTagKey( + TagKey.newBuilder() + .setParent(parent.toString()) + .setShortName("java-" + random.nextLong()) + .build()) + .build(); + OperationFuture future = + tagKeysClient.createTagKeyOperationCallable().futureCall(request); + TagKey response = future.get(); + TAG_KEY = response; + } + + try (TagValuesClient tagValuesClient = TagValuesClient.create()) { + Random random = new Random(); + CreateTagValueRequest request = + CreateTagValueRequest.newBuilder() + .setTagValue( + TagValue.newBuilder() + .setParent(TAG_KEY.getName()) + .setShortName("java-" + random.nextLong()) + .build()) + .build(); + OperationFuture future = + tagValuesClient.createTagValueOperationCallable().futureCall(request); + TagValue response = future.get(); + TAG_VALUE = response; + } + + } + + private static void deleteTags() throws Exception { + Thread.sleep(60000); + try (TagValuesClient tagValuesClient = TagValuesClient.create()) { + DeleteTagValueRequest request = + DeleteTagValueRequest.newBuilder() + .setName(TAG_VALUE.getName()) + .build(); + OperationFuture future = + tagValuesClient.deleteTagValueOperationCallable().futureCall(request); + TagValue response = future.get(); + } + + try (TagKeysClient tagKeysClient = TagKeysClient.create()) { + DeleteTagKeyRequest request = + DeleteTagKeyRequest.newBuilder() + .setName(TAG_KEY.getName()) + .build(); + OperationFuture future = + tagKeysClient.deleteTagKeyOperationCallable().futureCall(request); + TagKey response = future.get(); + } + } + private static Secret createRegionalSecret() throws IOException { LocationName parent = LocationName.of(PROJECT_ID, LOCATION_ID); @@ -253,6 +343,20 @@ public void testCreateRegionalSecretWithLabel() throws IOException { assertThat(secret.getLabelsMap()).containsEntry(LABEL_KEY, LABEL_VALUE); } + @Test + public void testCreateRegionalSecretWithTags() throws IOException { + SecretName name = TEST_REGIONAL_SECRET_WITH_TAGS_TO_CREATE_NAME; + Secret secret = CreateRegionalSecretWithTags.createRegionalSecretWithTags( + name.getProject(), + name.getLocation(), + name.getSecret(), + TAG_KEY.getName(), + TAG_VALUE.getName() + ); + + assertThat(stdOut.toString()).contains("Created secret with Tags"); + } + @Test public void testAddRegionalSecretVersion() throws IOException { SecretName name = SecretName.parse(TEST_REGIONAL_SECRET_WITH_VERSIONS.getName()); @@ -577,5 +681,35 @@ public void testEditSecretAnnotations() throws IOException { assertThat(updatedSecret.getAnnotationsMap()).containsEntry( UPDATED_ANNOTATION_KEY, UPDATED_ANNOTATION_VALUE); } + + @Test + public void testCreateRegionalSecretWithDelayedDestroy() throws IOException { + SecretName name = TEST_REGIONAL_SECRET_WITH_DELAYED_DESTROY; + Secret secret = CreateRegionalSecretWithDelayedDestroy.createRegionalSecretWithDelayedDestroy( + name.getProject(), name.getLocation(), name.getSecret(), 86400); + + assertThat(stdOut.toString()).contains("Created secret with version destroy ttl"); + assertThat(secret.getVersionDestroyTtl().getSeconds()).isEqualTo(86400); + } + + @Test + public void testUpdateRegionalSecretWithDelayedDestroy() throws IOException { + SecretName name = SecretName.parse(TEST_REGIONAL_SECRET_TO_DELAYED_DESTROY.getName()); + Secret secret = UpdateRegionalSecretWithDelayedDestroy.updateRegionalSecretWithDelayedDestroy( + name.getProject(), name.getLocation(), name.getSecret(), 86520); + + assertThat(stdOut.toString()).contains("Updated secret"); + assertThat(secret.getVersionDestroyTtl().getSeconds()).isEqualTo(86520); + } + + @Test + public void testDisableRegionalSecretDelayedDestroy() throws IOException { + SecretName name = SecretName.parse(TEST_REGIONAL_SECRET_TO_DELAYED_DESTROY.getName()); + Secret secret = DisableRegionalSecretDelayedDestroy.disableRegionalSecretDelayedDestroy( + name.getProject(), name.getLocation(), name.getSecret()); + + assertThat(stdOut.toString()).contains("Updated secret"); + assertThat(secret.getVersionDestroyTtl().getSeconds()).isEqualTo(0); + } } - \ No newline at end of file + diff --git a/security-command-center/snippets/src/main/java/com/example/securitycenter/FindingSnippets.java b/security-command-center/snippets/src/main/java/com/example/securitycenter/FindingSnippets.java new file mode 100644 index 00000000000..3a5349b892f --- /dev/null +++ b/security-command-center/snippets/src/main/java/com/example/securitycenter/FindingSnippets.java @@ -0,0 +1,565 @@ +/* + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.securitycenter; + +import com.google.cloud.securitycenter.v1.Finding; +import com.google.cloud.securitycenter.v1.Finding.State; +import com.google.cloud.securitycenter.v1.FindingName; +import com.google.cloud.securitycenter.v1.GroupFindingsRequest; +import com.google.cloud.securitycenter.v1.GroupResult; +import com.google.cloud.securitycenter.v1.ListFindingsRequest; +import com.google.cloud.securitycenter.v1.ListFindingsResponse.ListFindingsResult; +import com.google.cloud.securitycenter.v1.OrganizationName; +import com.google.cloud.securitycenter.v1.SecurityCenterClient; +import com.google.cloud.securitycenter.v1.SecurityCenterClient.GroupFindingsPagedResponse; +import com.google.cloud.securitycenter.v1.SecurityCenterClient.ListFindingsPagedResponse; +import com.google.cloud.securitycenter.v1.SourceName; +import com.google.cloud.securitycenter.v1.UpdateFindingRequest; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.iam.v1.TestIamPermissionsResponse; +import com.google.protobuf.FieldMask; +import com.google.protobuf.Timestamp; +import com.google.protobuf.Value; +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; +import java.util.List; + +/** + * Snippets for how to work with Findings in Security Command Center. + */ +public class FindingSnippets { + + private FindingSnippets() { + } + + /** + * Create a finding under a source. + * + * @param sourceName The source for the finding. + */ + // [START securitycenter_create_finding] + static Finding createFinding(SourceName sourceName, String findingId) { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + // SourceName sourceName = SourceName.of(/*organization=*/"123234324",/*source=*/ + // "423432321"); + // String findingId = "samplefindingid"; + + // Use the current time as the finding "event time". + Instant eventTime = Instant.now(); + + // The resource this finding applies to. The CSCC UI can link + // the findings for a resource to the corresponding Asset of a resource + // if there are matches. + String resourceName = "//cloudresourcemanager.googleapis.com/organizations/11232"; + + // Start setting up a request to create a finding in a source. + Finding finding = + Finding.newBuilder() + .setParent(sourceName.toString()) + .setState(State.ACTIVE) + .setResourceName(resourceName) + .setEventTime( + Timestamp.newBuilder() + .setSeconds(eventTime.getEpochSecond()) + .setNanos(eventTime.getNano())) + .setCategory("MEDIUM_RISK_ONE") + .build(); + + // Call the API. + Finding response = client.createFinding(sourceName, findingId, finding); + + System.out.println("Created Finding: " + response); + return response; + } catch (IOException e) { + throw new RuntimeException("Couldn't create client.", e); + } + } + // [END securitycenter_create_finding] + + /** + * Create a finding with source properties under a source. + * + * @param sourceName The source for the finding. + */ + // [START securitycenter_create_finding_with_source_properties] + static Finding createFindingWithSourceProperties(SourceName sourceName) { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + // SourceName sourceName = SourceName.of(/*organization=*/"123234324",/*source=*/ + // "423432321"); + + // Use the current time as the finding "event time". + Instant eventTime = Instant.now(); + + // Controlled by caller. + String findingId = "samplefindingid2"; + + // The resource this finding applies to. The CSCC UI can link + // the findings for a resource to the corresponding Asset of a resource + // if there are matches. + String resourceName = "//cloudresourcemanager.googleapis.com/organizations/11232"; + + // Define source properties values as protobuf "Value" objects. + Value stringValue = Value.newBuilder().setStringValue("stringExample").build(); + Value numValue = Value.newBuilder().setNumberValue(1234).build(); + ImmutableMap sourceProperties = + ImmutableMap.of("stringKey", stringValue, "numKey", numValue); + + // Start setting up a request to create a finding in a source. + Finding finding = + Finding.newBuilder() + .setParent(sourceName.toString()) + .setState(State.ACTIVE) + .setResourceName(resourceName) + .setEventTime( + Timestamp.newBuilder() + .setSeconds(eventTime.getEpochSecond()) + .setNanos(eventTime.getNano())) + .putAllSourceProperties(sourceProperties) + .build(); + + // Call the API. + Finding response = client.createFinding(sourceName, findingId, finding); + + System.out.println("Created Finding with Source Properties: " + response); + return response; + } catch (IOException e) { + throw new RuntimeException("Couldn't create client.", e); + } + } + // [END securitycenter_create_finding_with_source_properties] + + /** + * Update a finding's source properties. + * + * @param findingName The finding to update. + */ + // [START securitycenter_update_finding_source_properties] + static Finding updateFinding(FindingName findingName) { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + // FindingName findingName = FindingName.of(/*organization=*/"123234324", + // /*source=*/"423432321", /*findingId=*/"samplefindingid2"); + + // Use the current time as the finding "event time". + Instant eventTime = Instant.now(); + + // Define source properties values as protobuf "Value" objects. + Value stringValue = Value.newBuilder().setStringValue("value").build(); + + FieldMask updateMask = + FieldMask.newBuilder() + .addPaths("event_time") + .addPaths("source_properties.stringKey") + .build(); + + Finding finding = + Finding.newBuilder() + .setName(findingName.toString()) + .setEventTime( + Timestamp.newBuilder() + .setSeconds(eventTime.getEpochSecond()) + .setNanos(eventTime.getNano())) + .putSourceProperties("stringKey", stringValue) + .build(); + + UpdateFindingRequest.Builder request = + UpdateFindingRequest.newBuilder().setFinding(finding).setUpdateMask(updateMask); + + // Call the API. + Finding response = client.updateFinding(request.build()); + + System.out.println("Updated Finding: " + response); + return response; + } catch (IOException e) { + throw new RuntimeException("Couldn't create client.", e); + } + } + // [END securitycenter_update_finding_source_properties] + + /** + * Updates a finding's state to INACTIVE. + * + * @param findingName The finding to update. + */ + // [START securitycenter_update_finding_state] + static Finding setFindingState(FindingName findingName) { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + // FindingName findingName = FindingName.of(/*organization=*/"123234324", + // /*source=*/"423432321", /*findingId=*/"samplefindingid2"); + + // Use the current time as the finding "event time". + Instant eventTime = Instant.now(); + + Finding response = + client.setFindingState( + findingName, + State.INACTIVE, + Timestamp.newBuilder() + .setSeconds(eventTime.getEpochSecond()) + .setNanos(eventTime.getNano()) + .build()); + + System.out.println("Updated Finding: " + response); + return response; + } catch (IOException e) { + throw new RuntimeException("Couldn't create client.", e); + } + } + // [END securitycenter_update_finding_state] + + /** + * List all findings under an organization. + * + * @param organizationName The source to list all findings for. + */ + // [START securitycenter_list_all_findings] + static ImmutableList listAllFindings(OrganizationName organizationName) { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + // Input parameters for SourceName must be in one of the following formats: + // * OrganizationName organizationName = OrganizationName.of("organization-id"); + // organizationName.getOrganization(); + // * ProjectName projectName = ProjectName.of("project-id"); + // projectName.getProject(); + // * FolderName folderName = FolderName.of("folder-id"); + // folderName.getFolder(); + // + // "-" Indicates listing across all sources. + SourceName sourceName = SourceName.of(organizationName.getOrganization(), "-"); + + ListFindingsRequest.Builder request = + ListFindingsRequest.newBuilder().setParent(sourceName.toString()); + + // Call the API. + ListFindingsPagedResponse response = client.listFindings(request.build()); + + // This creates one list for all findings. If your organization has a large number of + // findings this can cause out of memory issues. You can process them in incrementally + // by returning the Iterable returned response.iterateAll() directly. + ImmutableList results = ImmutableList.copyOf(response.iterateAll()); + System.out.println("Findings:"); + System.out.println(results); + return results; + } catch (IOException e) { + throw new RuntimeException("Couldn't create client.", e); + } + } + // [END securitycenter_list_all_findings] + + /** + * List filtered findings under a source. + * + * @param sourceName The source to list filtered findings for. + */ + // [START securitycenter_list_filtered_findings] + static ImmutableList listFilteredFindings(SourceName sourceName) { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + // parentId: must be one of the following: + // "organization-id" + // "project-id" + // "folder-id" + // SourceName sourceName = SourceName.of(parentId, sourceId); + + // Create filter to category of MEDIUM_RISK_ONE + String filter = "category=\"MEDIUM_RISK_ONE\""; + + ListFindingsRequest.Builder request = + ListFindingsRequest.newBuilder().setParent(sourceName.toString()).setFilter(filter); + + // Call the API. + ListFindingsPagedResponse response = client.listFindings(request.build()); + + // This creates one list for all findings. If your organization has a large number of + // findings this can cause out of memory issues. You can process them in incrementally + // by returning the Iterable returned response.iterateAll() directly. + ImmutableList results = ImmutableList.copyOf(response.iterateAll()); + System.out.println("Findings:"); + System.out.println(results); + return results; + } catch (IOException e) { + throw new RuntimeException("Couldn't create client.", e); + } + } + // [END securitycenter_list_filtered_findings] + + /** + * List findings at a specific time under a source. + * + * @param sourceName The source to list findings at a specific time for. + */ + // [START securitycenter_list_findings_within_time_range] + static ImmutableList listFindingsAtTime(SourceName sourceName) { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + // parentId: must be one of the following: + // "organization-id" + // "project-id" + // "folder-id" + // SourceName sourceName = SourceName.of(parentId, sourceId); + + // 5 days ago + Instant fiveDaysAgo = Instant.now().minus(Duration.ofDays(5)); + + // Create filter to only list findings that occurred before five days ago. + String filter = String.format("event_time < \"%s\"", fiveDaysAgo.toString()); + + ListFindingsRequest.Builder request = + ListFindingsRequest.newBuilder().setParent(sourceName.toString()).setFilter(filter); + + // Call the API. + ListFindingsPagedResponse response = client.listFindings(request.build()); + + // This creates one list for all findings. If your organization has a large number of + // findings this can cause out of memory issues. You can process them in incrementally + // by returning the Iterable returned response.iterateAll() directly. + ImmutableList results = ImmutableList.copyOf(response.iterateAll()); + System.out.println("Findings:"); + System.out.println(results); + return results; + } catch (IOException e) { + throw new RuntimeException("Couldn't create client.", e); + } + } + // [END securitycenter_list_findings_within_time_range] + + /** + * Demonstrate calling testIamPermissions to determin if the service account has the correct + * permissions. + * + * @param sourceName The source to create a finding for. + */ + // [START securitycenter_test_iam] + static TestIamPermissionsResponse testIamPermissions(SourceName sourceName) { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + // SourceName sourceName = SourceName.of(/*organizationId=*/"123234324", + // /*sourceId=*/"423432321"); + + // Iam permission to test. + List permissionsToTest = + ImmutableList.of("securitycenter.findings.update"); + + // Call the API. + TestIamPermissionsResponse response = + client.testIamPermissions(sourceName.toString(), permissionsToTest); + System.out.println("IAM Permission:"); + System.out.println(response); + + return response; + } catch (IOException e) { + throw new RuntimeException("Couldn't create client.", e); + } + } + // [END securitycenter_test_iam] + + /** + * Group all findings under an organization across all sources by their specified properties (e.g. + * category). + * + * @param organizationName The organization to group all findings for. + */ + // [START securitycenter_group_all_findings] + static ImmutableList groupFindings(OrganizationName organizationName) { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + // Input parameters for 'SourceName' must be in one of the following formats: + // * OrganizationName organizationName = OrganizationName.of("organization-id"); + // organizationName.getOrganization(); + // * ProjectName projectName = ProjectName.of("project-id"); + // projectName.getProject(); + // * FolderName folderName = FolderName.of("folder-id"); + // folderName.getFolder(); + SourceName sourceName = SourceName.of(organizationName.getOrganization(), "-"); + + GroupFindingsRequest.Builder request = + GroupFindingsRequest.newBuilder().setParent(sourceName.toString()).setGroupBy("category"); + + // Call the API. + GroupFindingsPagedResponse response = client.groupFindings(request.build()); + + // This creates one list for all findings. If your organization has a large number of + // findings + // this can cause out of memory issues. You can process them batches by returning + // the Iterable returned response.iterateAll() directly. + ImmutableList results = ImmutableList.copyOf(response.iterateAll()); + System.out.println("Findings:"); + System.out.println(results); + return results; + } catch (IOException e) { + throw new RuntimeException("Couldn't create client.", e); + } + } + // [END securitycenter_group_all_findings] + + /** + * Group findings under an organization and a source by their specified properties (e.g. + * category). + * + * @param sourceName The source to limit the findings to. + */ + // [START securitycenter_group_findings_with_source] + static ImmutableList groupFindingsWithSource(SourceName sourceName) { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + // parentId: must be one of the following: + // "organization-id" + // "project-id" + // "folder-id" + // SourceName sourceName = SourceName.of(parentId, sourceId); + + GroupFindingsRequest.Builder request = + GroupFindingsRequest.newBuilder().setParent(sourceName.toString()).setGroupBy("category"); + + // Call the API. + GroupFindingsPagedResponse response = client.groupFindings(request.build()); + + // This creates one list for all findings. If your organization has a large number of + // findings + // this can cause out of memory issues. You can process them batches by returning + // the Iterable returned response.iterateAll() directly. + ImmutableList results = ImmutableList.copyOf(response.iterateAll()); + System.out.println("Findings:"); + System.out.println(results); + return results; + } catch (IOException e) { + throw new RuntimeException("Couldn't create client.", e); + } + } + // [END securitycenter_group_findings_with_source] + + /** + * Group active findings under an organization and a source by their specified properties (e.g. + * category). + * + * @param sourceName The source to limit the findings to. + */ + // [START securitycenter_group_active_findings_with_source] + static ImmutableList groupActiveFindingsWithSource(SourceName sourceName) { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + // parentId: must be one of the following: + // "organization-id" + // "project-id" + // "folder-id" + // SourceName sourceName = SourceName.of(parentId, sourceId); + + GroupFindingsRequest.Builder request = + GroupFindingsRequest.newBuilder() + .setParent(sourceName.toString()) + .setGroupBy("category") + .setFilter("state=\"ACTIVE\""); + + // Call the API. + GroupFindingsPagedResponse response = client.groupFindings(request.build()); + + // This creates one list for all findings. If your organization has a large number of + // findings + // this can cause out of memory issues. You can process them batches by returning + // the Iterable returned response.iterateAll() directly. + ImmutableList results = ImmutableList.copyOf(response.iterateAll()); + System.out.println("Findings:"); + System.out.println(results); + return results; + } catch (IOException e) { + throw new RuntimeException("Couldn't create client.", e); + } + } + // [END securitycenter_group_active_findings_with_source] + + /** + * Group active findings under an organization and a source by their specified properties (e.g. + * category) at a specified time. + * + * @param sourceName The source to limit the findings to. + */ + // [START securitycenter_group_active_findings_with_source_at_time] + static ImmutableList groupActiveFindingsWithSourceAtTime(SourceName sourceName) { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + // parentId: must be one of the following: + // "organization-id" + // "project-id" + // "folder-id" + // SourceName sourceName = SourceName.of(parentId, sourceId); + + // 1 day ago + Instant oneDayAgo = Instant.now().minusSeconds(60 * 60 * 24); + + GroupFindingsRequest.Builder request = + GroupFindingsRequest.newBuilder() + .setParent(sourceName.toString()) + .setGroupBy("category") + .setFilter("state=\"ACTIVE\"") + .setReadTime( + Timestamp.newBuilder() + .setSeconds(oneDayAgo.getEpochSecond()) + .setNanos(oneDayAgo.getNano())); + + // Call the API. + GroupFindingsPagedResponse response = client.groupFindings(request.build()); + + // This creates one list for all findings. If your organization has a large number of + // findings + // this can cause out of memory issues. You can process them batches by returning + // the Iterable returned response.iterateAll() directly. + ImmutableList results = ImmutableList.copyOf(response.iterateAll()); + System.out.println("Findings:"); + System.out.println(results); + return results; + } catch (IOException e) { + throw new RuntimeException("Couldn't create client.", e); + } + } + // [END securitycenter_group_active_findings_with_source_at_time] + + /** + * Group active findings under an organization and a source by their state_changes + * (ADDED/CHANGED/UNCHANGED) during a period. + * + * @param sourceName The source to limit the findings to. + */ + // [START securitycenter_group_active_findings_with_source_and_compare_duration] + static ImmutableList groupActiveFindingsWithSourceAndCompareDuration( + SourceName sourceName, Duration duration) { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + // parentId: must be one of the following: + // "organization-id" + // "project-id" + // "folder-id" + // SourceName sourceName = SourceName.of(parentId, sourceId); + + GroupFindingsRequest.Builder request = + GroupFindingsRequest.newBuilder() + .setParent(sourceName.toString()) + .setGroupBy("state_change") + .setFilter("state=\"ACTIVE\""); + request + .getCompareDurationBuilder() + .setSeconds(duration.getSeconds()) + .setNanos(duration.getNano()); + + // Call the API. + GroupFindingsPagedResponse response = client.groupFindings(request.build()); + + // This creates one list for all findings. If your organization has a large number of + // findings + // this can cause out of memory issues. You can process them batches by returning + // the Iterable returned response.iterateAll() directly. + ImmutableList results = ImmutableList.copyOf(response.iterateAll()); + System.out.println("Findings:"); + System.out.println(results); + return results; + } catch (IOException e) { + throw new RuntimeException("Couldn't create client.", e); + } + } + // [END securitycenter_group_active_findings_with_source_and_compare_duration] +} diff --git a/security-command-center/snippets/src/main/java/com/example/securitycenter/SourceSnippets.java b/security-command-center/snippets/src/main/java/com/example/securitycenter/SourceSnippets.java new file mode 100644 index 00000000000..5224e49016c --- /dev/null +++ b/security-command-center/snippets/src/main/java/com/example/securitycenter/SourceSnippets.java @@ -0,0 +1,223 @@ +/* + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.securitycenter; + +import com.google.cloud.securitycenter.v1.CreateSourceRequest; +import com.google.cloud.securitycenter.v1.GetSourceRequest; +import com.google.cloud.securitycenter.v1.ListSourcesRequest; +import com.google.cloud.securitycenter.v1.OrganizationName; +import com.google.cloud.securitycenter.v1.SecurityCenterClient; +import com.google.cloud.securitycenter.v1.SecurityCenterClient.ListSourcesPagedResponse; +import com.google.cloud.securitycenter.v1.Source; +import com.google.cloud.securitycenter.v1.SourceName; +import com.google.cloud.securitycenter.v1.UpdateSourceRequest; +import com.google.common.collect.ImmutableList; +import com.google.iam.v1.Binding; +import com.google.iam.v1.GetIamPolicyRequest; +import com.google.iam.v1.Policy; +import com.google.iam.v1.SetIamPolicyRequest; +import com.google.protobuf.FieldMask; +import java.io.IOException; + +/** + * Snippets for how to work with Sources in Security Command Center. + */ +public class SourceSnippets { + + private SourceSnippets() { + } + + /** + * Create a source under an organization. + * + * @param organizationName The organization for the source. + */ + // [START securitycenter_create_source] + static Source createSource(OrganizationName organizationName) { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + // Start setting up a request to create a source in an organization. + // OrganizationName organizationName = OrganizationName.of(/*organizationId=*/"123234324"); + Source source = + Source.newBuilder() + .setDisplayName("Customized Display Name") + .setDescription("A new custom source that does X") + .build(); + + CreateSourceRequest.Builder request = + CreateSourceRequest.newBuilder().setParent(organizationName.toString()).setSource(source); + + // Call the API. + Source response = client.createSource(request.build()); + + System.out.println("Created Source: " + response); + return response; + } catch (IOException e) { + throw new RuntimeException("Couldn't create client.", e); + } + } + // [END securitycenter_create_source] + + /** + * List sources under an organization. + * + * @param organizationName The organization for the source. + */ + // [START securitycenter_list_sources] + static ImmutableList listSources(OrganizationName organizationName) { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + // Start setting up a request to list sources in an organization, project, or folder. + // Parent must be in one of the following formats: + // OrganizationName organizationName = OrganizationName.of("organization-id"); + // ProjectName projectName = ProjectName.of("project-id"); + // FolderName folderName = FolderName.of("folder-id"); + ListSourcesRequest.Builder request = + ListSourcesRequest.newBuilder().setParent(organizationName.toString()); + + // Call the API. + ListSourcesPagedResponse response = client.listSources(request.build()); + + // This creates one list for all sources. If your organization has a large number of sources + // this can cause out of memory issues. You can process them batches by returning + // the Iterable returned response.iterateAll() directly. + ImmutableList results = ImmutableList.copyOf(response.iterateAll()); + System.out.println("Sources:"); + System.out.println(results); + return results; + } catch (IOException e) { + throw new RuntimeException("Couldn't create client.", e); + } + } + // [END securitycenter_list_sources] + + /** + * Update a source under an organization. + * + * @param sourceName The source to update. + */ + // [START securitycenter_update_source] + static Source updateSource(SourceName sourceName) { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + // Start setting up a request to update a source. + // SourceName sourceName = SourceName.of(/*organization=*/"123234324",/*source=*/ + // "423432321"); + Source source = + Source.newBuilder() + .setDisplayName("Updated Display Name") + .setName(sourceName.toString()) + .build(); + FieldMask updateMask = FieldMask.newBuilder().addPaths("display_name").build(); + + UpdateSourceRequest.Builder request = + UpdateSourceRequest.newBuilder().setSource(source).setUpdateMask(updateMask); + + // Call the API. + Source response = client.updateSource(request.build()); + + System.out.println("Updated Source: " + response); + return response; + } catch (IOException e) { + throw new RuntimeException("Couldn't create client.", e); + } + } + // [END securitycenter_update_source] + + /** + * Get a source under an organization. + * + * @param sourceName The source to get. + */ + // [START securitycenter_get_source] + static Source getSource(SourceName sourceName) { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + // Start setting up a request to get a source. + // SourceName sourceName = SourceName.of(/*organization=*/"123234324",/*source=*/ + // "423432321"); + GetSourceRequest.Builder request = + GetSourceRequest.newBuilder().setName(sourceName.toString()); + + // Call the API. + Source response = client.getSource(request.build()); + + System.out.println("Source: " + response); + return response; + } catch (IOException e) { + throw new RuntimeException("Couldn't create client.", e); + } + } + // [END securitycenter_get_source] + + /** + * Set IAM policy for a source. + * + * @param sourceName The source to set IAM Policy for. + */ + // [START securitycenter_set_source_iam] + static Policy setIamPolicySource(SourceName sourceName, String userEmail) { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + // userEmail = "someuser@domain.com" + // Set up IAM Policy for the user userMail to use the role findingsEditor. + // The user must be a valid google account. + Policy oldPolicy = client.getIamPolicy(sourceName.toString()); + Binding bindings = + Binding.newBuilder() + .setRole("roles/securitycenter.findingsEditor") + .addMembers("user:" + userEmail) + .build(); + Policy policy = oldPolicy.toBuilder().addBindings(bindings).build(); + + // Start setting up a request to set IAM policy for a source. + // SourceName sourceName = SourceName.of("123234324", "423432321"); + SetIamPolicyRequest.Builder request = + SetIamPolicyRequest.newBuilder().setPolicy(policy).setResource(sourceName.toString()); + + // Call the API. + Policy response = client.setIamPolicy(request.build()); + + System.out.println("Policy: " + response); + return response; + } catch (IOException e) { + throw new RuntimeException("Couldn't create client.", e); + } + } + // [END securitycenter_set_source_iam] + + /** + * Get IAM policy for a source. + * + * @param sourceName The source to set IAM Policy for. + */ + // [START securitycenter_get_source_iam] + static Policy getIamPolicySource(SourceName sourceName) { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + // Start setting up a request to get IAM policy for a source. + // SourceName sourceName = SourceName.of(/*organization=*/"123234324",/*source=*/ + // "423432321"); + GetIamPolicyRequest request = + GetIamPolicyRequest.newBuilder().setResource(sourceName.toString()).build(); + + // Call the API. + Policy response = client.getIamPolicy(request); + + System.out.println("Policy: " + response); + return response; + } catch (IOException e) { + throw new RuntimeException("Couldn't create client.", e); + } + } + // [END securitycenter_get_source_iam] + +} diff --git a/security-command-center/snippets/src/main/java/management/api/ListDescendantEventThreatDetectionCustomModules.java b/security-command-center/snippets/src/main/java/management/api/ListDescendantEventThreatDetectionCustomModules.java index 1aeccbd4582..758aac2829b 100644 --- a/security-command-center/snippets/src/main/java/management/api/ListDescendantEventThreatDetectionCustomModules.java +++ b/security-command-center/snippets/src/main/java/management/api/ListDescendantEventThreatDetectionCustomModules.java @@ -17,9 +17,10 @@ package management.api; // [START securitycenter_list_descendant_event_threat_detection_custom_module] +import com.google.cloud.securitycentermanagement.v1.EventThreatDetectionCustomModule; import com.google.cloud.securitycentermanagement.v1.ListDescendantEventThreatDetectionCustomModulesRequest; import com.google.cloud.securitycentermanagement.v1.SecurityCenterManagementClient; -import com.google.cloud.securitycentermanagement.v1.SecurityCenterManagementClient.ListDescendantEventThreatDetectionCustomModulesPagedResponse; +import com.google.common.collect.ImmutableList; import java.io.IOException; public class ListDescendantEventThreatDetectionCustomModules { @@ -31,7 +32,7 @@ public static void main(String[] args) throws IOException { listDescendantEventThreatDetectionCustomModules(projectId); } - public static ListDescendantEventThreatDetectionCustomModulesPagedResponse + public static ImmutableList listDescendantEventThreatDetectionCustomModules(String projectId) throws IOException { // Initialize client that will be used to send requests. This client only needs @@ -46,10 +47,8 @@ public static void main(String[] args) throws IOException { .setParent(parent) .build(); - ListDescendantEventThreatDetectionCustomModulesPagedResponse response = - client.listDescendantEventThreatDetectionCustomModules(request); - - return response; + return ImmutableList.copyOf( + client.listDescendantEventThreatDetectionCustomModules(request).iterateAll()); } } } diff --git a/security-command-center/snippets/src/main/java/management/api/ListDescendantSecurityHealthAnalyticsCustomModules.java b/security-command-center/snippets/src/main/java/management/api/ListDescendantSecurityHealthAnalyticsCustomModules.java index ae39a37deb5..90209d83b03 100644 --- a/security-command-center/snippets/src/main/java/management/api/ListDescendantSecurityHealthAnalyticsCustomModules.java +++ b/security-command-center/snippets/src/main/java/management/api/ListDescendantSecurityHealthAnalyticsCustomModules.java @@ -19,7 +19,8 @@ // [START securitycenter_list_descendant_security_health_analytics_custom_module] import com.google.cloud.securitycentermanagement.v1.ListDescendantSecurityHealthAnalyticsCustomModulesRequest; import com.google.cloud.securitycentermanagement.v1.SecurityCenterManagementClient; -import com.google.cloud.securitycentermanagement.v1.SecurityCenterManagementClient.ListDescendantSecurityHealthAnalyticsCustomModulesPagedResponse; +import com.google.cloud.securitycentermanagement.v1.SecurityHealthAnalyticsCustomModule; +import com.google.common.collect.ImmutableList; import java.io.IOException; public class ListDescendantSecurityHealthAnalyticsCustomModules { @@ -32,7 +33,7 @@ public static void main(String[] args) throws IOException { listDescendantSecurityHealthAnalyticsCustomModules(projectId); } - public static ListDescendantSecurityHealthAnalyticsCustomModulesPagedResponse + public static ImmutableList listDescendantSecurityHealthAnalyticsCustomModules(String projectId) throws IOException { // Initialize client that will be used to send requests. This client only needs @@ -45,10 +46,8 @@ public static void main(String[] args) throws IOException { .setParent(String.format("projects/%s/locations/global", projectId)) .build(); - ListDescendantSecurityHealthAnalyticsCustomModulesPagedResponse response = - client.listDescendantSecurityHealthAnalyticsCustomModules(request); - - return response; + return ImmutableList.copyOf( + client.listDescendantSecurityHealthAnalyticsCustomModules(request).iterateAll()); } } } diff --git a/security-command-center/snippets/src/main/java/management/api/ListEffectiveEventThreatDetectionCustomModules.java b/security-command-center/snippets/src/main/java/management/api/ListEffectiveEventThreatDetectionCustomModules.java index e44490bc436..e291aad5a3c 100644 --- a/security-command-center/snippets/src/main/java/management/api/ListEffectiveEventThreatDetectionCustomModules.java +++ b/security-command-center/snippets/src/main/java/management/api/ListEffectiveEventThreatDetectionCustomModules.java @@ -17,9 +17,10 @@ package management.api; // [START securitycenter_list_effective_event_threat_detection_custom_module] +import com.google.cloud.securitycentermanagement.v1.EffectiveEventThreatDetectionCustomModule; import com.google.cloud.securitycentermanagement.v1.ListEffectiveEventThreatDetectionCustomModulesRequest; import com.google.cloud.securitycentermanagement.v1.SecurityCenterManagementClient; -import com.google.cloud.securitycentermanagement.v1.SecurityCenterManagementClient.ListEffectiveEventThreatDetectionCustomModulesPagedResponse; +import com.google.common.collect.ImmutableList; import java.io.IOException; public class ListEffectiveEventThreatDetectionCustomModules { @@ -31,7 +32,7 @@ public static void main(String[] args) throws IOException { listEffectiveEventThreatDetectionCustomModules(projectId); } - public static ListEffectiveEventThreatDetectionCustomModulesPagedResponse + public static ImmutableList listEffectiveEventThreatDetectionCustomModules(String projectId) throws IOException { // Initialize client that will be used to send requests. This client only needs @@ -46,10 +47,8 @@ public static void main(String[] args) throws IOException { .setParent(parent) .build(); - ListEffectiveEventThreatDetectionCustomModulesPagedResponse response = - client.listEffectiveEventThreatDetectionCustomModules(request); - - return response; + return ImmutableList.copyOf( + client.listEffectiveEventThreatDetectionCustomModules(request).iterateAll()); } } } diff --git a/security-command-center/snippets/src/main/java/management/api/ListEffectiveSecurityHealthAnalyticsCustomModules.java b/security-command-center/snippets/src/main/java/management/api/ListEffectiveSecurityHealthAnalyticsCustomModules.java index 8e4da2917d9..4e57d9f70ae 100644 --- a/security-command-center/snippets/src/main/java/management/api/ListEffectiveSecurityHealthAnalyticsCustomModules.java +++ b/security-command-center/snippets/src/main/java/management/api/ListEffectiveSecurityHealthAnalyticsCustomModules.java @@ -17,9 +17,10 @@ package management.api; // [START securitycenter_list_effective_security_health_analytics_custom_module] +import com.google.cloud.securitycentermanagement.v1.EffectiveSecurityHealthAnalyticsCustomModule; import com.google.cloud.securitycentermanagement.v1.ListEffectiveSecurityHealthAnalyticsCustomModulesRequest; import com.google.cloud.securitycentermanagement.v1.SecurityCenterManagementClient; -import com.google.cloud.securitycentermanagement.v1.SecurityCenterManagementClient.ListEffectiveSecurityHealthAnalyticsCustomModulesPagedResponse; +import com.google.common.collect.ImmutableList; import java.io.IOException; public class ListEffectiveSecurityHealthAnalyticsCustomModules { @@ -32,7 +33,7 @@ public static void main(String[] args) throws IOException { listEffectiveSecurityHealthAnalyticsCustomModules(projectId); } - public static ListEffectiveSecurityHealthAnalyticsCustomModulesPagedResponse + public static ImmutableList listEffectiveSecurityHealthAnalyticsCustomModules(String projectId) throws IOException { // Initialize client that will be used to send requests. This client only needs @@ -45,10 +46,8 @@ public static void main(String[] args) throws IOException { .setParent(String.format("projects/%s/locations/global", projectId)) .build(); - ListEffectiveSecurityHealthAnalyticsCustomModulesPagedResponse response = - client.listEffectiveSecurityHealthAnalyticsCustomModules(request); - - return response; + return ImmutableList.copyOf( + client.listEffectiveSecurityHealthAnalyticsCustomModules(request).iterateAll()); } } } diff --git a/security-command-center/snippets/src/main/java/management/api/ListEventThreatDetectionCustomModules.java b/security-command-center/snippets/src/main/java/management/api/ListEventThreatDetectionCustomModules.java index 4e4b0340a1a..f7f287f0350 100644 --- a/security-command-center/snippets/src/main/java/management/api/ListEventThreatDetectionCustomModules.java +++ b/security-command-center/snippets/src/main/java/management/api/ListEventThreatDetectionCustomModules.java @@ -17,9 +17,10 @@ package management.api; // [START securitycenter_list_event_threat_detection_custom_module] +import com.google.cloud.securitycentermanagement.v1.EventThreatDetectionCustomModule; import com.google.cloud.securitycentermanagement.v1.ListEventThreatDetectionCustomModulesRequest; import com.google.cloud.securitycentermanagement.v1.SecurityCenterManagementClient; -import com.google.cloud.securitycentermanagement.v1.SecurityCenterManagementClient.ListEventThreatDetectionCustomModulesPagedResponse; +import com.google.common.collect.ImmutableList; import java.io.IOException; public class ListEventThreatDetectionCustomModules { @@ -32,7 +33,7 @@ public static void main(String[] args) throws IOException { listEventThreatDetectionCustomModules(projectId); } - public static ListEventThreatDetectionCustomModulesPagedResponse + public static ImmutableList listEventThreatDetectionCustomModules(String projectId) throws IOException { // Initialize client that will be used to send requests. This client only needs @@ -45,10 +46,8 @@ public static void main(String[] args) throws IOException { ListEventThreatDetectionCustomModulesRequest request = ListEventThreatDetectionCustomModulesRequest.newBuilder().setParent(parent).build(); - ListEventThreatDetectionCustomModulesPagedResponse response = - client.listEventThreatDetectionCustomModules(request); - - return response; + return ImmutableList.copyOf( + client.listEventThreatDetectionCustomModules(request).iterateAll()); } } } diff --git a/security-command-center/snippets/src/main/java/management/api/ListSecurityCenterServices.java b/security-command-center/snippets/src/main/java/management/api/ListSecurityCenterServices.java index 95978804ecd..775f2a7cdab 100644 --- a/security-command-center/snippets/src/main/java/management/api/ListSecurityCenterServices.java +++ b/security-command-center/snippets/src/main/java/management/api/ListSecurityCenterServices.java @@ -19,7 +19,8 @@ // [START securitycenter_list_security_center_service] import com.google.cloud.securitycentermanagement.v1.ListSecurityCenterServicesRequest; import com.google.cloud.securitycentermanagement.v1.SecurityCenterManagementClient; -import com.google.cloud.securitycentermanagement.v1.SecurityCenterManagementClient.ListSecurityCenterServicesPagedResponse; +import com.google.cloud.securitycentermanagement.v1.SecurityCenterService; +import com.google.common.collect.ImmutableList; import java.io.IOException; public class ListSecurityCenterServices { @@ -32,7 +33,7 @@ public static void main(String[] args) throws IOException { listSecurityCenterServices(projectId); } - public static ListSecurityCenterServicesPagedResponse listSecurityCenterServices(String projectId) + public static ImmutableList listSecurityCenterServices(String projectId) throws IOException { // Initialize client that will be used to send requests. This client only needs // to be created @@ -42,8 +43,7 @@ public static ListSecurityCenterServicesPagedResponse listSecurityCenterServices ListSecurityCenterServicesRequest.newBuilder() .setParent(String.format("projects/%s/locations/global", projectId)) .build(); - ListSecurityCenterServicesPagedResponse response = client.listSecurityCenterServices(request); - return response; + return ImmutableList.copyOf(client.listSecurityCenterServices(request).iterateAll()); } } } diff --git a/security-command-center/snippets/src/main/java/management/api/ListSecurityHealthAnalyticsCustomModules.java b/security-command-center/snippets/src/main/java/management/api/ListSecurityHealthAnalyticsCustomModules.java index f3d994f9c60..a47807e9f58 100644 --- a/security-command-center/snippets/src/main/java/management/api/ListSecurityHealthAnalyticsCustomModules.java +++ b/security-command-center/snippets/src/main/java/management/api/ListSecurityHealthAnalyticsCustomModules.java @@ -19,7 +19,8 @@ // [START securitycenter_list_security_health_analytics_custom_module] import com.google.cloud.securitycentermanagement.v1.ListSecurityHealthAnalyticsCustomModulesRequest; import com.google.cloud.securitycentermanagement.v1.SecurityCenterManagementClient; -import com.google.cloud.securitycentermanagement.v1.SecurityCenterManagementClient.ListSecurityHealthAnalyticsCustomModulesPagedResponse; +import com.google.cloud.securitycentermanagement.v1.SecurityHealthAnalyticsCustomModule; +import com.google.common.collect.ImmutableList; import java.io.IOException; public class ListSecurityHealthAnalyticsCustomModules { @@ -32,7 +33,7 @@ public static void main(String[] args) throws IOException { listSecurityHealthAnalyticsCustomModules(projectId); } - public static ListSecurityHealthAnalyticsCustomModulesPagedResponse + public static ImmutableList listSecurityHealthAnalyticsCustomModules(String projectId) throws IOException { // Initialize client that will be used to send requests. This client only needs // to be created @@ -44,10 +45,8 @@ public static void main(String[] args) throws IOException { .setParent(String.format("projects/%s/locations/global", projectId)) .build(); - ListSecurityHealthAnalyticsCustomModulesPagedResponse response = - client.listSecurityHealthAnalyticsCustomModules(request); - - return response; + return ImmutableList.copyOf( + client.listSecurityHealthAnalyticsCustomModules(request).iterateAll()); } } } diff --git a/security-command-center/snippets/src/test/java/MuteFindingIT.java b/security-command-center/snippets/src/test/java/MuteFindingIT.java index 5b162332df1..9910ef150e8 100644 --- a/security-command-center/snippets/src/test/java/MuteFindingIT.java +++ b/security-command-center/snippets/src/test/java/MuteFindingIT.java @@ -25,6 +25,8 @@ import com.google.cloud.securitycenter.v1.Finding.State; import com.google.cloud.securitycenter.v1.ListFindingsRequest; import com.google.cloud.securitycenter.v1.ListFindingsResponse.ListFindingsResult; +import com.google.cloud.securitycenter.v1.ListMuteConfigsRequest; +import com.google.cloud.securitycenter.v1.MuteConfig; import com.google.cloud.securitycenter.v1.SecurityCenterClient; import com.google.cloud.securitycenter.v1.SecurityCenterClient.ListFindingsPagedResponse; import com.google.cloud.securitycenter.v1.Source; @@ -76,16 +78,41 @@ public static void requireEnvVar(String envVarName) { .isNotEmpty(); } + private static void cleanUpExistingMuteRules() { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + ListMuteConfigsRequest request = + ListMuteConfigsRequest.newBuilder() + .setParent(String.format("projects/%s", PROJECT_ID)) + .build(); + for (MuteConfig muteConfig : client.listMuteConfigs(request).iterateAll()) { + String name = muteConfig.getName(); + String muteConfigId = name.substring(name.lastIndexOf('/') + 1); + if (muteConfigId.startsWith("random-mute-id-")) { + try { + client.deleteMuteConfig(name); + } catch (Exception e) { + System.err.println("Failed to delete mute rule: " + name); + e.printStackTrace(); + } + } + } + } catch (Exception e) { + System.err.println("Warning: Pre-test cleanup of mute rules failed: " + e.getMessage()); + } + } + @BeforeClass public static void setUp() throws IOException { - final PrintStream out = System.out; - stdOut = new ByteArrayOutputStream(); - System.setOut(new PrintStream(stdOut)); - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); requireEnvVar("SCC_PROJECT_ID"); requireEnvVar("SCC_PROJECT_ORG_ID"); + cleanUpExistingMuteRules(); + + final PrintStream out = System.out; + stdOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdOut)); + // Create mute rules. CreateMuteRule.createMuteRule(String.format("projects/%s", PROJECT_ID), MUTE_RULE_CREATE); CreateMuteRule.createMuteRule(String.format("projects/%s", PROJECT_ID), MUTE_RULE_UPDATE); @@ -106,10 +133,19 @@ public static void cleanUp() { final PrintStream out = System.out; stdOut = new ByteArrayOutputStream(); System.setOut(new PrintStream(stdOut)); - DeleteMuteRule.deleteMuteRule(PROJECT_ID, MUTE_RULE_CREATE); - assertThat(stdOut.toString()).contains("Mute rule deleted successfully: " + MUTE_RULE_CREATE); - DeleteMuteRule.deleteMuteRule(PROJECT_ID, MUTE_RULE_UPDATE); - assertThat(stdOut.toString()).contains("Mute rule deleted successfully: " + MUTE_RULE_UPDATE); + try { + DeleteMuteRule.deleteMuteRule(PROJECT_ID, MUTE_RULE_CREATE); + } catch (Exception e) { + System.err.println("Failed to delete mute rule: " + MUTE_RULE_CREATE); + e.printStackTrace(); + } + try { + DeleteMuteRule.deleteMuteRule(PROJECT_ID, MUTE_RULE_UPDATE); + } catch (Exception e) { + System.err.println("Failed to delete mute rule: " + MUTE_RULE_UPDATE); + e.printStackTrace(); + } + cleanUpExistingMuteRules(); stdOut = null; System.setOut(out); } diff --git a/security-command-center/snippets/src/test/java/com/example/securitycenter/ITFindingSnippets.java b/security-command-center/snippets/src/test/java/com/example/securitycenter/ITFindingSnippets.java new file mode 100644 index 00000000000..2653285cef5 --- /dev/null +++ b/security-command-center/snippets/src/test/java/com/example/securitycenter/ITFindingSnippets.java @@ -0,0 +1,129 @@ +/* + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.securitycenter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import com.google.cloud.securitycenter.v1.Finding.State; +import com.google.cloud.securitycenter.v1.FindingName; +import com.google.cloud.securitycenter.v1.OrganizationName; +import com.google.cloud.securitycenter.v1.SourceName; +import com.google.protobuf.Value; +import java.io.IOException; +import java.time.Duration; +import org.junit.BeforeClass; +import org.junit.Test; + +/** Smoke tests for {@link com.google.cloud.examples.securitycenter.snippets.FindingSnippets} */ +public class ITFindingSnippets { + + private static SourceName SOURCE_NAME; + private static FindingName FINDING_NAME; + + @BeforeClass + public static void setUp() throws IOException { + org.junit.Assume.assumeTrue( + "Skipping tests: GCLOUD_ORGANIZATION env var is not set.", + System.getenv("GCLOUD_ORGANIZATION") != null); + SOURCE_NAME = SourceName.parse(SourceSnippets.createSource(getOrganizationId()).getName()); + FINDING_NAME = + FindingName.parse(FindingSnippets.createFinding(SOURCE_NAME, "testfindingid").getName()); + } + + @Test + public void testCreateFinding() throws IOException { + assertNotNull(FindingSnippets.createFinding(SOURCE_NAME, "samplefindingid")); + } + + @Test + public void testCreateFindingWithSourceProperties() throws IOException { + assertNotNull(FindingSnippets.createFindingWithSourceProperties(SOURCE_NAME)); + } + + @Test + public void testUpdateFinding() throws IOException { + Value stringValue = Value.newBuilder().setStringValue("value").build(); + assertEquals( + stringValue, + FindingSnippets.updateFinding(FINDING_NAME) + .getSourcePropertiesMap() + .get("stringKey")); + } + + @Test + public void testUpdateFindingState() throws IOException { + assertEquals(State.INACTIVE, FindingSnippets.setFindingState(FINDING_NAME).getState()); + } + + @Test + public void testListAllFindings() throws IOException { + assertTrue(FindingSnippets.listAllFindings(getOrganizationId()).size() > 1); + } + + @Test + public void testListFilteredFindings() throws IOException { + assertTrue(FindingSnippets.listFilteredFindings(SOURCE_NAME).size() > 0); + } + + @Test + public void testListFindingsAtTime() throws IOException { + assertEquals(0, FindingSnippets.listFindingsAtTime(SOURCE_NAME).size()); + } + + @Test + public void testTestIamPermissions() throws IOException { + assertTrue( + FindingSnippets.testIamPermissions(SOURCE_NAME) + .getPermissions(0) + .equals("securitycenter.findings.update")); + } + + @Test + public void testGroupFindings() throws IOException { + assertTrue(FindingSnippets.groupFindings(getOrganizationId()).size() > 0); + } + + @Test + public void testGroupFindingsWithSource() throws IOException { + assertTrue(FindingSnippets.groupFindingsWithSource(SOURCE_NAME).size() > 0); + } + + @Test + public void testGroupActiveFindingsWithSource() throws IOException { + assertTrue(FindingSnippets.groupActiveFindingsWithSource(SOURCE_NAME).size() > 0); + } + + @Test + public void testGroupActiveFindingsWithSourceAtTime() throws IOException { + assertEquals(0, FindingSnippets.groupActiveFindingsWithSourceAtTime(SOURCE_NAME).size()); + } + + @Test + public void testGroupActiveFindingsWithSourceAndCompareDuration() throws IOException { + assertTrue( + FindingSnippets.groupActiveFindingsWithSourceAndCompareDuration( + SOURCE_NAME, Duration.ofDays(1)) + .size() + > 0); + } + + private static OrganizationName getOrganizationId() { + return OrganizationName.of(System.getenv("GCLOUD_ORGANIZATION")); + } +} \ No newline at end of file diff --git a/security-command-center/snippets/src/test/java/com/example/securitycenter/ITSourceSnippets.java b/security-command-center/snippets/src/test/java/com/example/securitycenter/ITSourceSnippets.java new file mode 100644 index 00000000000..9348a2f45dd --- /dev/null +++ b/security-command-center/snippets/src/test/java/com/example/securitycenter/ITSourceSnippets.java @@ -0,0 +1,80 @@ +/* + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.securitycenter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import com.google.cloud.securitycenter.v1.OrganizationName; +import com.google.cloud.securitycenter.v1.SourceName; +import java.io.IOException; +import org.junit.BeforeClass; +import org.junit.Test; + +/** Smoke tests for {@link com.google.cloud.examples.securitycenter.snippets.SourceSnippets} */ +public class ITSourceSnippets { + + private static SourceName SOURCE_NAME; + + @BeforeClass + public static void setUp() throws IOException { + org.junit.Assume.assumeTrue( + "Skipping tests: GCLOUD_ORGANIZATION env var is not set.", + System.getenv("GCLOUD_ORGANIZATION") != null); + SOURCE_NAME = SourceName.parse(SourceSnippets.createSource(getOrganizationId()).getName()); + } + + @Test + public void testCreateSource() throws IOException { + assertNotNull(SourceSnippets.createSource(getOrganizationId())); + } + + @Test + public void testListSources() throws IOException { + assertTrue(SourceSnippets.listSources(getOrganizationId()).size() > 1); + } + + @Test + public void testUpdateSource() throws IOException { + assertEquals( + "Updated Display Name", SourceSnippets.updateSource(SOURCE_NAME).getDisplayName()); + } + + @Test + public void testGetSource() throws IOException { + assertTrue(SourceSnippets.getSource(SOURCE_NAME).getName().equals(SOURCE_NAME.toString())); + } + + @Test + public void testSetSourceIamPolicy() throws IOException { + assertTrue( + SourceSnippets.setIamPolicySource(SOURCE_NAME, "csccclienttest@gmail.com") + .getBindings(0) + .getRole() + .equals("roles/securitycenter.findingsEditor")); + } + + @Test + public void testGetSourceIamPolicy() throws IOException { + assertNotNull(SourceSnippets.getIamPolicySource(SOURCE_NAME)); + } + + private static OrganizationName getOrganizationId() { + return OrganizationName.of(System.getenv("GCLOUD_ORGANIZATION")); + } +} diff --git a/security-command-center/snippets/src/test/java/management/api/EventThreatDetectionCustomModuleTest.java b/security-command-center/snippets/src/test/java/management/api/EventThreatDetectionCustomModuleTest.java index 5c0743c5233..ab47e2a7aaa 100644 --- a/security-command-center/snippets/src/test/java/management/api/EventThreatDetectionCustomModuleTest.java +++ b/security-command-center/snippets/src/test/java/management/api/EventThreatDetectionCustomModuleTest.java @@ -24,19 +24,17 @@ import com.google.cloud.securitycentermanagement.v1.EffectiveEventThreatDetectionCustomModule; import com.google.cloud.securitycentermanagement.v1.EventThreatDetectionCustomModule; import com.google.cloud.securitycentermanagement.v1.EventThreatDetectionCustomModule.EnablementState; -import com.google.cloud.securitycentermanagement.v1.SecurityCenterManagementClient.ListDescendantEventThreatDetectionCustomModulesPagedResponse; -import com.google.cloud.securitycentermanagement.v1.SecurityCenterManagementClient.ListEffectiveEventThreatDetectionCustomModulesPagedResponse; -import com.google.cloud.securitycentermanagement.v1.SecurityCenterManagementClient.ListEventThreatDetectionCustomModulesPagedResponse; import com.google.cloud.securitycentermanagement.v1.ValidateEventThreatDetectionCustomModuleResponse; import com.google.cloud.testing.junit4.MultipleAttemptsRule; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.StreamSupport; +import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Rule; @@ -49,7 +47,7 @@ public class EventThreatDetectionCustomModuleTest { // TODO(Developer): Replace the below variable private static final String PROJECT_ID = System.getenv("SCC_PROJECT_ID"); private static final String CUSTOM_MODULE_DISPLAY_NAME = - "java_sample_etd_custom_module_test_" + UUID.randomUUID(); + "java_sample_etd_custom_module_test_" + UUID.randomUUID().toString().replace("-", "_"); private static final int MAX_ATTEMPT_COUNT = 3; private static final int INITIAL_BACKOFF_MILLIS = 120000; // 2 minutes private static List createdCustomModuleIds = new ArrayList<>(); @@ -69,12 +67,29 @@ public static void requireEnvVar(String envVarName) { public static void setUp() { requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); requireEnvVar("SCC_PROJECT_ID"); + cleanUpExistingCustomModules(); } - @AfterClass - // Perform cleanup of all the custom modules created by the current execution of the test, after - // running tests - public static void cleanUp() throws IOException { + private static void cleanUpExistingCustomModules() { + try { + ImmutableList response = + ListEventThreatDetectionCustomModules + .listEventThreatDetectionCustomModules(PROJECT_ID); + for (EventThreatDetectionCustomModule module : response) { + if (module.getDisplayName() != null + && module.getDisplayName().startsWith("java_sample_etd_custom_module_test_")) { + deleteCustomModule(PROJECT_ID, extractCustomModuleId(module.getName())); + } + } + } catch (Exception e) { + System.err.println( + "Warning: Pre-test cleanup of custom modules failed: " + e.getMessage()); + } + } + + @After + // Perform cleanup of custom modules created by the current test method execution + public void tearDown() throws IOException { for (String customModuleId : createdCustomModuleIds) { try { deleteCustomModule(PROJECT_ID, customModuleId); @@ -83,6 +98,13 @@ public static void cleanUp() throws IOException { e.printStackTrace(); } } + createdCustomModuleIds.clear(); + } + + @AfterClass + // Final cleanup sweep after all tests in class have run + public static void cleanUp() throws IOException { + cleanUpExistingCustomModules(); } // extractCustomModuleID extracts the custom module Id from the full name and below regex will @@ -123,9 +145,14 @@ public void testDeleteEventThreatDetectionCustomModule() throws IOException { CreateEventThreatDetectionCustomModule.createEventThreatDetectionCustomModule( PROJECT_ID, CUSTOM_MODULE_DISPLAY_NAME); String customModuleId = extractCustomModuleId(response.getName()); - assertTrue( + createdCustomModuleIds.add(customModuleId); + boolean isDeleted = DeleteEventThreatDetectionCustomModule.deleteEventThreatDetectionCustomModule( - PROJECT_ID, customModuleId)); + PROJECT_ID, customModuleId); + if (isDeleted) { + createdCustomModuleIds.remove(customModuleId); + } + assertTrue(isDeleted); } @Test @@ -134,10 +161,10 @@ public void testListEventThreatDetectionCustomModules() throws IOException { CreateEventThreatDetectionCustomModule.createEventThreatDetectionCustomModule( PROJECT_ID, CUSTOM_MODULE_DISPLAY_NAME); createdCustomModuleIds.add(extractCustomModuleId(createCustomModuleResponse.getName())); - ListEventThreatDetectionCustomModulesPagedResponse response = + ImmutableList response = ListEventThreatDetectionCustomModules.listEventThreatDetectionCustomModules(PROJECT_ID); assertTrue( - StreamSupport.stream(response.iterateAll().spliterator(), false) + response.stream() .anyMatch(module -> CUSTOM_MODULE_DISPLAY_NAME.equals(module.getDisplayName()))); } @@ -193,11 +220,11 @@ public void testListEffectiveEventThreatDetectionCustomModules() throws IOExcept CreateEventThreatDetectionCustomModule.createEventThreatDetectionCustomModule( PROJECT_ID, CUSTOM_MODULE_DISPLAY_NAME); createdCustomModuleIds.add(extractCustomModuleId(createCustomModuleResponse.getName())); - ListEffectiveEventThreatDetectionCustomModulesPagedResponse response = + ImmutableList response = ListEffectiveEventThreatDetectionCustomModules .listEffectiveEventThreatDetectionCustomModules(PROJECT_ID); assertTrue( - StreamSupport.stream(response.iterateAll().spliterator(), false) + response.stream() .anyMatch(module -> CUSTOM_MODULE_DISPLAY_NAME.equals(module.getDisplayName()))); } @@ -207,11 +234,11 @@ public void testListDescendantEventThreatDetectionCustomModules() throws IOExcep CreateEventThreatDetectionCustomModule.createEventThreatDetectionCustomModule( PROJECT_ID, CUSTOM_MODULE_DISPLAY_NAME); createdCustomModuleIds.add(extractCustomModuleId(createCustomModuleResponse.getName())); - ListDescendantEventThreatDetectionCustomModulesPagedResponse response = + ImmutableList response = ListDescendantEventThreatDetectionCustomModules .listDescendantEventThreatDetectionCustomModules(PROJECT_ID); assertTrue( - StreamSupport.stream(response.iterateAll().spliterator(), false) + response.stream() .anyMatch(module -> CUSTOM_MODULE_DISPLAY_NAME.equals(module.getDisplayName()))); } diff --git a/security-command-center/snippets/src/test/java/management/api/SecurityCenterServiceTest.java b/security-command-center/snippets/src/test/java/management/api/SecurityCenterServiceTest.java index fba741c7ad7..096a3292afc 100644 --- a/security-command-center/snippets/src/test/java/management/api/SecurityCenterServiceTest.java +++ b/security-command-center/snippets/src/test/java/management/api/SecurityCenterServiceTest.java @@ -21,11 +21,10 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import com.google.cloud.securitycentermanagement.v1.SecurityCenterManagementClient.ListSecurityCenterServicesPagedResponse; import com.google.cloud.securitycentermanagement.v1.SecurityCenterService; import com.google.cloud.securitycentermanagement.v1.SecurityCenterService.EnablementState; +import com.google.common.collect.ImmutableList; import java.io.IOException; -import java.util.stream.StreamSupport; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; @@ -63,12 +62,12 @@ public void testGetSecurityCenterService() throws IOException { @Test public void testListSecurityCenterServices() throws IOException { - ListSecurityCenterServicesPagedResponse response = + ImmutableList response = ListSecurityCenterServices.listSecurityCenterServices(PROJECT_ID); assertNotNull(response); // check whether the response contains the specified service assertTrue( - StreamSupport.stream(response.iterateAll().spliterator(), false) + response.stream() .anyMatch(service -> service.getName().contains(SERVICE))); } diff --git a/security-command-center/snippets/src/test/java/management/api/SecurityHealthAnalyticsCustomModuleTest.java b/security-command-center/snippets/src/test/java/management/api/SecurityHealthAnalyticsCustomModuleTest.java index 7e2bae5e109..d7972e85d76 100644 --- a/security-command-center/snippets/src/test/java/management/api/SecurityHealthAnalyticsCustomModuleTest.java +++ b/security-command-center/snippets/src/test/java/management/api/SecurityHealthAnalyticsCustomModuleTest.java @@ -22,20 +22,19 @@ import static org.junit.Assert.assertTrue; import com.google.cloud.securitycentermanagement.v1.EffectiveSecurityHealthAnalyticsCustomModule; -import com.google.cloud.securitycentermanagement.v1.SecurityCenterManagementClient.ListDescendantSecurityHealthAnalyticsCustomModulesPagedResponse; -import com.google.cloud.securitycentermanagement.v1.SecurityCenterManagementClient.ListEffectiveSecurityHealthAnalyticsCustomModulesPagedResponse; -import com.google.cloud.securitycentermanagement.v1.SecurityCenterManagementClient.ListSecurityHealthAnalyticsCustomModulesPagedResponse; import com.google.cloud.securitycentermanagement.v1.SecurityHealthAnalyticsCustomModule; import com.google.cloud.securitycentermanagement.v1.SecurityHealthAnalyticsCustomModule.EnablementState; import com.google.cloud.securitycentermanagement.v1.SimulateSecurityHealthAnalyticsCustomModuleResponse; import com.google.cloud.testing.junit4.MultipleAttemptsRule; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.StreamSupport; +import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Rule; @@ -47,7 +46,8 @@ public class SecurityHealthAnalyticsCustomModuleTest { // TODO(Developer): Replace the below variable private static final String PROJECT_ID = System.getenv("SCC_PROJECT_ID"); - private static final String CUSTOM_MODULE_DISPLAY_NAME = "java_sample_custom_module_test"; + private static final String CUSTOM_MODULE_DISPLAY_NAME = + "java_sample_custom_module_test_" + UUID.randomUUID().toString().replace("-", "_"); private static final int MAX_ATTEMPT_COUNT = 3; private static final int INITIAL_BACKOFF_MILLIS = 120000; // 2 minutes private static List createdCustomModuleIds = new ArrayList<>(); @@ -67,12 +67,29 @@ public static void requireEnvVar(String envVarName) { public static void setUp() throws InterruptedException { requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); requireEnvVar("SCC_PROJECT_ID"); + cleanUpExistingCustomModules(); } - @AfterClass - // Perform cleanup of all the custom modules created by the current execution of the test, after - // running tests - public static void cleanUp() throws IOException { + private static void cleanUpExistingCustomModules() { + try { + ImmutableList response = + ListSecurityHealthAnalyticsCustomModules + .listSecurityHealthAnalyticsCustomModules(PROJECT_ID); + for (SecurityHealthAnalyticsCustomModule module : response) { + if (module.getDisplayName() != null + && module.getDisplayName().startsWith("java_sample_custom_module_test")) { + deleteCustomModule(PROJECT_ID, extractCustomModuleId(module.getName())); + } + } + } catch (Exception e) { + System.err.println( + "Warning: Pre-test cleanup of custom modules failed: " + e.getMessage()); + } + } + + @After + // Perform cleanup of custom modules created by the current test method execution + public void tearDown() throws IOException { for (String customModuleId : createdCustomModuleIds) { try { deleteCustomModule(PROJECT_ID, customModuleId); @@ -81,6 +98,13 @@ public static void cleanUp() throws IOException { e.printStackTrace(); } } + createdCustomModuleIds.clear(); + } + + @AfterClass + // Final cleanup sweep after all tests in class have run + public static void cleanUp() throws IOException { + cleanUpExistingCustomModules(); } // extractCustomModuleID extracts the custom module Id from the full name and below regex will @@ -121,9 +145,14 @@ public void testDeleteSecurityHealthAnalyticsCustomModule() throws IOException { CreateSecurityHealthAnalyticsCustomModule.createSecurityHealthAnalyticsCustomModule( PROJECT_ID, CUSTOM_MODULE_DISPLAY_NAME); String customModuleId = extractCustomModuleId(response.getName()); - assertTrue( + createdCustomModuleIds.add(customModuleId); + boolean isDeleted = DeleteSecurityHealthAnalyticsCustomModule.deleteSecurityHealthAnalyticsCustomModule( - PROJECT_ID, customModuleId)); + PROJECT_ID, customModuleId); + if (isDeleted) { + createdCustomModuleIds.remove(customModuleId); + } + assertTrue(isDeleted); } @Test @@ -132,11 +161,11 @@ public void testListSecurityHealthAnalyticsCustomModules() throws IOException { CreateSecurityHealthAnalyticsCustomModule.createSecurityHealthAnalyticsCustomModule( PROJECT_ID, CUSTOM_MODULE_DISPLAY_NAME); createdCustomModuleIds.add(extractCustomModuleId(createCustomModuleResponse.getName())); - ListSecurityHealthAnalyticsCustomModulesPagedResponse response = + ImmutableList response = ListSecurityHealthAnalyticsCustomModules.listSecurityHealthAnalyticsCustomModules( PROJECT_ID); assertTrue( - StreamSupport.stream(response.iterateAll().spliterator(), false) + response.stream() .anyMatch(module -> CUSTOM_MODULE_DISPLAY_NAME.equals(module.getDisplayName()))); } @@ -192,11 +221,11 @@ public void testListEffectiveSecurityHealthAnalyticsCustomModules() throws IOExc CreateSecurityHealthAnalyticsCustomModule.createSecurityHealthAnalyticsCustomModule( PROJECT_ID, CUSTOM_MODULE_DISPLAY_NAME); createdCustomModuleIds.add(extractCustomModuleId(createCustomModuleResponse.getName())); - ListEffectiveSecurityHealthAnalyticsCustomModulesPagedResponse response = + ImmutableList response = ListEffectiveSecurityHealthAnalyticsCustomModules .listEffectiveSecurityHealthAnalyticsCustomModules(PROJECT_ID); assertTrue( - StreamSupport.stream(response.iterateAll().spliterator(), false) + response.stream() .anyMatch(module -> CUSTOM_MODULE_DISPLAY_NAME.equals(module.getDisplayName()))); } @@ -206,11 +235,11 @@ public void testListDescendantSecurityHealthAnalyticsCustomModules() throws IOEx CreateSecurityHealthAnalyticsCustomModule.createSecurityHealthAnalyticsCustomModule( PROJECT_ID, CUSTOM_MODULE_DISPLAY_NAME); createdCustomModuleIds.add(extractCustomModuleId(createCustomModuleResponse.getName())); - ListDescendantSecurityHealthAnalyticsCustomModulesPagedResponse response = + ImmutableList response = ListDescendantSecurityHealthAnalyticsCustomModules .listDescendantSecurityHealthAnalyticsCustomModules(PROJECT_ID); assertTrue( - StreamSupport.stream(response.iterateAll().spliterator(), false) + response.stream() .anyMatch(module -> CUSTOM_MODULE_DISPLAY_NAME.equals(module.getDisplayName()))); } diff --git a/security-command-center/snippets/src/test/java/vtwo/MuteFindingIT.java b/security-command-center/snippets/src/test/java/vtwo/MuteFindingIT.java index f5028bf406e..c7bdb2c55f2 100644 --- a/security-command-center/snippets/src/test/java/vtwo/MuteFindingIT.java +++ b/security-command-center/snippets/src/test/java/vtwo/MuteFindingIT.java @@ -23,6 +23,7 @@ import com.google.cloud.securitycenter.v2.Finding.Mute; import com.google.cloud.securitycenter.v2.ListFindingsRequest; import com.google.cloud.securitycenter.v2.ListFindingsResponse.ListFindingsResult; +import com.google.cloud.securitycenter.v2.ListMuteConfigsRequest; import com.google.cloud.securitycenter.v2.MuteConfig; import com.google.cloud.securitycenter.v2.SecurityCenterClient; import com.google.cloud.securitycenter.v2.SecurityCenterClient.ListFindingsPagedResponse; @@ -85,16 +86,41 @@ public static void requireEnvVar(String envVarName) { .isNotEmpty(); } + private static void cleanUpExistingMuteRules() { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + ListMuteConfigsRequest request = + ListMuteConfigsRequest.newBuilder() + .setParent(String.format("projects/%s/locations/%s", PROJECT_ID, LOCATION)) + .build(); + for (MuteConfig muteConfig : client.listMuteConfigs(request).iterateAll()) { + String name = muteConfig.getName(); + String muteConfigId = name.substring(name.lastIndexOf('/') + 1); + if (muteConfigId.startsWith("random-mute-id-")) { + try { + client.deleteMuteConfig(name); + } catch (Exception e) { + System.err.println("Failed to delete mute rule: " + name); + e.printStackTrace(); + } + } + } + } catch (Exception e) { + System.err.println("Warning: Pre-test cleanup of mute rules failed: " + e.getMessage()); + } + } + @BeforeClass public static void setUp() throws IOException, InterruptedException { - final PrintStream out = System.out; - stdOut = new ByteArrayOutputStream(); - System.setOut(new PrintStream(stdOut)); - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); requireEnvVar("SCC_PROJECT_ID"); requireEnvVar("SCC_PROJECT_ORG_ID"); + cleanUpExistingMuteRules(); + + final PrintStream out = System.out; + stdOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdOut)); + // Create mute rules. CreateMuteRule.createMuteRule(PROJECT_ID, LOCATION, MUTE_RULE_CREATE); CreateMuteRule.createMuteRule(PROJECT_ID, LOCATION, MUTE_RULE_UPDATE); @@ -131,10 +157,19 @@ public static void cleanUp() throws IOException { final PrintStream out = System.out; stdOut = new ByteArrayOutputStream(); System.setOut(new PrintStream(stdOut)); - DeleteMuteRule.deleteMuteRule(PROJECT_ID, LOCATION, MUTE_RULE_CREATE); - assertThat(stdOut.toString()).contains("Mute rule deleted successfully: " + MUTE_RULE_CREATE); - DeleteMuteRule.deleteMuteRule(PROJECT_ID, LOCATION, MUTE_RULE_UPDATE); - assertThat(stdOut.toString()).contains("Mute rule deleted successfully: " + MUTE_RULE_UPDATE); + try { + DeleteMuteRule.deleteMuteRule(PROJECT_ID, LOCATION, MUTE_RULE_CREATE); + } catch (Exception e) { + System.err.println("Failed to delete mute rule: " + MUTE_RULE_CREATE); + e.printStackTrace(); + } + try { + DeleteMuteRule.deleteMuteRule(PROJECT_ID, LOCATION, MUTE_RULE_UPDATE); + } catch (Exception e) { + System.err.println("Failed to delete mute rule: " + MUTE_RULE_UPDATE); + e.printStackTrace(); + } + cleanUpExistingMuteRules(); stdOut = null; System.setOut(out); } diff --git a/texttospeech/beta/src/main/java/com/example/texttospeech/SynthesizeText.java b/texttospeech/beta/src/main/java/com/example/texttospeech/SynthesizeText.java index 344900f0f00..211d8b0cb50 100644 --- a/texttospeech/beta/src/main/java/com/example/texttospeech/SynthesizeText.java +++ b/texttospeech/beta/src/main/java/com/example/texttospeech/SynthesizeText.java @@ -81,7 +81,6 @@ public static void synthesizeText(String text) throws Exception { } // [END tts_synthesize_text] - // [START tts_synthesize_text_audio_profile_beta] /** * Demonstrates using the Text to Speech client with audio profiles to synthesize text or ssml * @@ -125,7 +124,6 @@ public static void synthesizeTextWithAudioProfile(String text, String effectsPro } } } - // [END tts_synthesize_text_audio_profile_beta] // [START tts_synthesize_ssml] /** diff --git a/texttospeech/snippets/src/main/java/com/example/texttospeech/SynthesizeText.java b/texttospeech/snippets/src/main/java/com/example/texttospeech/SynthesizeText.java index 9e5f00484dc..5345c4b038a 100644 --- a/texttospeech/snippets/src/main/java/com/example/texttospeech/SynthesizeText.java +++ b/texttospeech/snippets/src/main/java/com/example/texttospeech/SynthesizeText.java @@ -75,9 +75,9 @@ public static ByteString synthesizeText(String text) throws Exception { } } } + // [END tts_synthesize_text] - // [START tts_synthesize_text_audio_profile] /** * Demonstrates using the Text to Speech client with audio profiles to synthesize text or ssml * @@ -122,7 +122,6 @@ public static ByteString synthesizeTextWithAudioProfile(String text, String effe } } } - // [END tts_synthesize_text_audio_profile] // [START tts_synthesize_ssml] /** diff --git a/vertexai/snippets/src/main/java/vertexai/gemini/ControlledGenerationSchema6.java b/vertexai/snippets/src/main/java/vertexai/gemini/ControlledGenerationSchema6.java deleted file mode 100644 index e1a23e58ecc..00000000000 --- a/vertexai/snippets/src/main/java/vertexai/gemini/ControlledGenerationSchema6.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package vertexai.gemini; - -// [START generativeaionvertexai_gemini_controlled_generation_response_schema_6] -import com.google.cloud.vertexai.VertexAI; -import com.google.cloud.vertexai.api.GenerateContentResponse; -import com.google.cloud.vertexai.api.GenerationConfig; -import com.google.cloud.vertexai.api.Schema; -import com.google.cloud.vertexai.api.Type; -import com.google.cloud.vertexai.generativeai.ContentMaker; -import com.google.cloud.vertexai.generativeai.GenerativeModel; -import com.google.cloud.vertexai.generativeai.PartMaker; -import com.google.cloud.vertexai.generativeai.ResponseHandler; -import java.io.IOException; - -public class ControlledGenerationSchema6 { - public static void main(String[] args) throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "genai-java-demos"; - String location = "us-central1"; - String modelName = "gemini-2.0-flash-001"; - - controlGenerationWithJsonSchema6(projectId, location, modelName); - } - - // Generate responses that are always valid JSON and comply with a JSON schema - public static String controlGenerationWithJsonSchema6( - String projectId, String location, String modelName) - throws IOException { - // Initialize client that will be used to send requests. This client only needs - // to be created once, and can be reused for multiple requests. - try (VertexAI vertexAI = new VertexAI(projectId, location)) { - GenerationConfig generationConfig = GenerationConfig.newBuilder() - .setResponseMimeType("application/json") - .setResponseSchema(Schema.newBuilder() - .setType(Type.ARRAY) - .setItems(Schema.newBuilder() - .setType(Type.OBJECT) - .putProperties("object", Schema.newBuilder().setType(Type.STRING).build()) - .build()) - .build()) - .build(); - - GenerativeModel model = new GenerativeModel(modelName, vertexAI) - .withGenerationConfig(generationConfig); - - // These images in Cloud Storage are viewable at - // https://storage.googleapis.com/cloud-samples-data/generative-ai/image/office-desk.jpeg - // https://storage.googleapis.com/cloud-samples-data/generative-ai/image/gardening-tools.jpeg - - GenerateContentResponse response = model.generateContent( - ContentMaker.fromMultiModalData( - PartMaker.fromMimeTypeAndData("image/jpeg", - "gs://cloud-samples-data/generative-ai/image/office-desk.jpeg"), - PartMaker.fromMimeTypeAndData("image/jpeg", - "gs://cloud-samples-data/generative-ai/image/gardening-tools.jpeg"), - "Generate a list of objects in the images." - ) - ); - - String output = ResponseHandler.getText(response); - System.out.println(output); - return output; - } - } -} -// [END generativeaionvertexai_gemini_controlled_generation_response_schema_6] \ No newline at end of file diff --git a/vertexai/snippets/src/main/java/vertexai/gemini/FunctionCalling.java b/vertexai/snippets/src/main/java/vertexai/gemini/FunctionCalling.java deleted file mode 100644 index dc7ce54db2b..00000000000 --- a/vertexai/snippets/src/main/java/vertexai/gemini/FunctionCalling.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package vertexai.gemini; - -// [START generativeaionvertexai_gemini_function_calling] -import com.google.cloud.vertexai.VertexAI; -import com.google.cloud.vertexai.api.Content; -import com.google.cloud.vertexai.api.FunctionDeclaration; -import com.google.cloud.vertexai.api.GenerateContentResponse; -import com.google.cloud.vertexai.api.Schema; -import com.google.cloud.vertexai.api.Tool; -import com.google.cloud.vertexai.api.Type; -import com.google.cloud.vertexai.generativeai.ChatSession; -import com.google.cloud.vertexai.generativeai.ContentMaker; -import com.google.cloud.vertexai.generativeai.GenerativeModel; -import com.google.cloud.vertexai.generativeai.PartMaker; -import com.google.cloud.vertexai.generativeai.ResponseHandler; -import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; - -public class FunctionCalling { - public static void main(String[] args) throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "your-google-cloud-project-id"; - String location = "us-central1"; - String modelName = "gemini-2.0-flash-001"; - - String promptText = "What's the weather like in Paris?"; - - whatsTheWeatherLike(projectId, location, modelName, promptText); - } - - // A request involving the interaction with an external tool - public static String whatsTheWeatherLike(String projectId, String location, - String modelName, String promptText) - throws IOException { - // Initialize client that will be used to send requests. - // This client only needs to be created once, and can be reused for multiple requests. - try (VertexAI vertexAI = new VertexAI(projectId, location)) { - - FunctionDeclaration functionDeclaration = FunctionDeclaration.newBuilder() - .setName("getCurrentWeather") - .setDescription("Get the current weather in a given location") - .setParameters( - Schema.newBuilder() - .setType(Type.OBJECT) - .putProperties("location", Schema.newBuilder() - .setType(Type.STRING) - .setDescription("location") - .build() - ) - .addRequired("location") - .build() - ) - .build(); - - System.out.println("Function declaration:"); - System.out.println(functionDeclaration); - - // Add the function to a "tool" - Tool tool = Tool.newBuilder() - .addFunctionDeclarations(functionDeclaration) - .build(); - - // Start a chat session from a model, with the use of the declared function. - GenerativeModel model = new GenerativeModel(modelName, vertexAI) - .withTools(Arrays.asList(tool)); - ChatSession chat = model.startChat(); - - System.out.println(String.format("Ask the question: %s", promptText)); - GenerateContentResponse response = chat.sendMessage(promptText); - - // The model will most likely return a function call to the declared - // function `getCurrentWeather` with "Paris" as the value for the - // argument `location`. - System.out.println("\nPrint response: "); - System.out.println(ResponseHandler.getContent(response)); - - // Provide an answer to the model so that it knows what the result - // of a "function call" is. - Content content = - ContentMaker.fromMultiModalData( - PartMaker.fromFunctionResponse( - "getCurrentWeather", - Collections.singletonMap("currentWeather", "sunny"))); - System.out.println("Provide the function response: "); - System.out.println(content); - response = chat.sendMessage(content); - - // See what the model replies now - System.out.println("Print response: "); - String finalAnswer = ResponseHandler.getText(response); - System.out.println(finalAnswer); - - return finalAnswer; - } - } -} -// [END generativeaionvertexai_gemini_function_calling] diff --git a/vertexai/snippets/src/main/java/vertexai/gemini/GetMediaTokenCount.java b/vertexai/snippets/src/main/java/vertexai/gemini/GetMediaTokenCount.java deleted file mode 100644 index 444ee2b34d4..00000000000 --- a/vertexai/snippets/src/main/java/vertexai/gemini/GetMediaTokenCount.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package vertexai.gemini; - -// [START generativeaionvertexai_gemini_token_count_advanced] -import com.google.cloud.vertexai.VertexAI; -import com.google.cloud.vertexai.api.Content; -import com.google.cloud.vertexai.api.CountTokensResponse; -import com.google.cloud.vertexai.generativeai.ContentMaker; -import com.google.cloud.vertexai.generativeai.GenerativeModel; -import com.google.cloud.vertexai.generativeai.PartMaker; -import java.io.IOException; - -public class GetMediaTokenCount { - public static void main(String[] args) throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "your-google-cloud-project-id"; - String location = "us-central1"; - String modelName = "gemini-2.0-flash-001"; - - getMediaTokenCount(projectId, location, modelName); - } - - // Gets the number of tokens for the prompt with text and video and the model's response. - public static int getMediaTokenCount(String projectId, String location, String modelName) - throws IOException { - // Initialize client that will be used to send requests. - // This client only needs to be created once, and can be reused for multiple requests. - try (VertexAI vertexAI = new VertexAI(projectId, location)) { - GenerativeModel model = new GenerativeModel(modelName, vertexAI); - - Content content = ContentMaker.fromMultiModalData( - "Provide a description of the video.", - PartMaker.fromMimeTypeAndData( - "video/mp4", "gs://cloud-samples-data/generative-ai/video/pixel8.mp4") - ); - - CountTokensResponse response = model.countTokens(content); - - int tokenCount = response.getTotalTokens(); - System.out.println("Token count: " + tokenCount); - - return tokenCount; - } - } -} -// [END generativeaionvertexai_gemini_token_count_advanced] diff --git a/vertexai/snippets/src/main/java/vertexai/gemini/GetTokenCount.java b/vertexai/snippets/src/main/java/vertexai/gemini/GetTokenCount.java deleted file mode 100644 index 61911d7e44f..00000000000 --- a/vertexai/snippets/src/main/java/vertexai/gemini/GetTokenCount.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package vertexai.gemini; - -// [START generativeaionvertexai_gemini_token_count] -import com.google.cloud.vertexai.VertexAI; -import com.google.cloud.vertexai.api.CountTokensResponse; -import com.google.cloud.vertexai.api.GenerateContentResponse; -import com.google.cloud.vertexai.generativeai.GenerativeModel; -import java.io.IOException; - -public class GetTokenCount { - public static void main(String[] args) throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "your-google-cloud-project-id"; - String location = "us-central1"; - String modelName = "gemini-2.0-flash-001"; - - getTokenCount(projectId, location, modelName); - } - - // Gets the number of tokens for the prompt and the model's response. - public static int getTokenCount(String projectId, String location, String modelName) - throws IOException { - // Initialize client that will be used to send requests. - // This client only needs to be created once, and can be reused for multiple requests. - try (VertexAI vertexAI = new VertexAI(projectId, location)) { - GenerativeModel model = new GenerativeModel(modelName, vertexAI); - - String textPrompt = "Why is the sky blue?"; - CountTokensResponse response = model.countTokens(textPrompt); - - int promptTokenCount = response.getTotalTokens(); - int promptCharCount = response.getTotalBillableCharacters(); - - System.out.println("Prompt token Count: " + promptTokenCount); - System.out.println("Prompt billable character count: " + promptCharCount); - - GenerateContentResponse contentResponse = model.generateContent(textPrompt); - - int tokenCount = contentResponse.getUsageMetadata().getPromptTokenCount(); - int candidateTokenCount = contentResponse.getUsageMetadata().getCandidatesTokenCount(); - int totalTokenCount = contentResponse.getUsageMetadata().getTotalTokenCount(); - - System.out.println("Prompt token Count: " + tokenCount); - System.out.println("Candidate Token Count: " + candidateTokenCount); - System.out.println("Total token Count: " + totalTokenCount); - - return promptTokenCount; - } - } -} -// [END generativeaionvertexai_gemini_token_count] diff --git a/vertexai/snippets/src/main/java/vertexai/gemini/GroundingWithPrivateData.java b/vertexai/snippets/src/main/java/vertexai/gemini/GroundingWithPrivateData.java deleted file mode 100644 index 7d803220b19..00000000000 --- a/vertexai/snippets/src/main/java/vertexai/gemini/GroundingWithPrivateData.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// package vertexai.gemini; - -// // [START generativeaionvertexai_grounding_private_data_basic] -// import com.google.cloud.vertexai.VertexAI; -// import com.google.cloud.vertexai.api.GenerateContentResponse; -// import com.google.cloud.vertexai.api.GroundingMetadata; -// import com.google.cloud.vertexai.api.Retrieval; -// import com.google.cloud.vertexai.api.Tool; -// import com.google.cloud.vertexai.api.VertexAISearch; -// import com.google.cloud.vertexai.generativeai.GenerativeModel; -// import com.google.cloud.vertexai.generativeai.ResponseHandler; -// import java.io.IOException; -// import java.util.Collections; - -// public class GroundingWithPrivateData { -// public static void main(String[] args) throws IOException { -// // TODO(developer): Replace these variables before running the sample. -// String projectId = "your-google-cloud-project-id"; -// String location = "us-central1"; -// String modelName = "gemini-2.0-flash-001"; -// String datastore = String.format( -// "projects/%s/locations/global/collections/default_collection/dataStores/%s", -// projectId, "datastore_id"); - -// groundWithPrivateData(projectId, location, modelName, datastore); -// } - -// // A request whose response will be "grounded" -// // with information found in Vertex AI Search datastores. -// public static String groundWithPrivateData(String projectId, String location, String modelName, -// String datastoreId) -// throws IOException { -// // Initialize client that will be used to send requests. -// // This client only needs to be created once, and can be reused for multiple requests. -// try (VertexAI vertexAI = new VertexAI(projectId, location)) { -// Tool datastoreTool = Tool.newBuilder() -// .setRetrieval( -// Retrieval.newBuilder() -// .setVertexAiSearch(VertexAISearch.newBuilder().setDatastore(datastoreId)) -// .setDisableAttribution(false)) -// .build(); - -// GenerativeModel model = new GenerativeModel(modelName, vertexAI).withTools( -// Collections.singletonList(datastoreTool) -// ); - -// GenerateContentResponse response = model.generateContent( -// "How do I make an appointment to renew my driver's license?"); - -// GroundingMetadata groundingMetadata = response.getCandidates(0).getGroundingMetadata(); -// String answer = ResponseHandler.getText(response); - -// System.out.println("Answer: " + answer); -// System.out.println("Grounding metadata: " + groundingMetadata); - -// return answer; -// } -// } -// } -// // [END generativeaionvertexai_grounding_private_data_basic] \ No newline at end of file diff --git a/vertexai/snippets/src/main/java/vertexai/gemini/GroundingWithPublicData.java b/vertexai/snippets/src/main/java/vertexai/gemini/GroundingWithPublicData.java deleted file mode 100644 index 727dba6abc5..00000000000 --- a/vertexai/snippets/src/main/java/vertexai/gemini/GroundingWithPublicData.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// package vertexai.gemini; - -// // [START generativeaionvertexai_grounding_public_data_basic] -// import com.google.cloud.vertexai.VertexAI; -// import com.google.cloud.vertexai.api.GenerateContentResponse; -// import com.google.cloud.vertexai.api.GoogleSearchRetrieval; -// import com.google.cloud.vertexai.api.GroundingMetadata; -// import com.google.cloud.vertexai.api.Tool; -// import com.google.cloud.vertexai.generativeai.GenerativeModel; -// import com.google.cloud.vertexai.generativeai.ResponseHandler; -// import java.io.IOException; -// import java.util.Collections; - -// public class GroundingWithPublicData { -// public static void main(String[] args) throws IOException { -// // TODO(developer): Replace these variables before running the sample. -// String projectId = "your-google-cloud-project-id"; -// String location = "us-central1"; -// String modelName = "gemini-2.0-flash-001"; - -// groundWithPublicData(projectId, location, modelName); -// } - -// // A request whose response will be "grounded" with information found in Google Search. -// public static String groundWithPublicData(String projectId, String location, String modelName) -// throws IOException { -// // Initialize client that will be used to send requests. -// // This client only needs to be created once, and can be reused for multiple requests. -// try (VertexAI vertexAI = new VertexAI(projectId, location)) { -// Tool googleSearchTool = -// Tool.newBuilder() -// .setGoogleSearchRetrieval( -// // Enable using the result from this tool in detecting grounding -// GoogleSearchRetrieval.newBuilder()) -// .build(); - -// GenerativeModel model = -// new GenerativeModel(modelName, vertexAI) -// .withTools(Collections.singletonList(googleSearchTool)); - -// GenerateContentResponse response = model.generateContent("Why is the sky blue?"); - -// GroundingMetadata groundingMetadata = response.getCandidates(0).getGroundingMetadata(); -// String answer = ResponseHandler.getText(response); - -// System.out.println("Answer: " + answer); -// System.out.println("Grounding metadata: " + groundingMetadata); - -// return answer; -// } -// } -// } -// // [END generativeaionvertexai_grounding_public_data_basic] diff --git a/vertexai/snippets/src/main/java/vertexai/gemini/Multimodal.java b/vertexai/snippets/src/main/java/vertexai/gemini/Multimodal.java deleted file mode 100644 index b8003b3b8d7..00000000000 --- a/vertexai/snippets/src/main/java/vertexai/gemini/Multimodal.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package vertexai.gemini; - -// [START generativeaionvertexai_non_stream_multimodality_basic] -import com.google.cloud.vertexai.VertexAI; -import com.google.cloud.vertexai.api.GenerateContentResponse; -import com.google.cloud.vertexai.generativeai.ContentMaker; -import com.google.cloud.vertexai.generativeai.GenerativeModel; -import com.google.cloud.vertexai.generativeai.PartMaker; -import com.google.cloud.vertexai.generativeai.ResponseHandler; - -public class Multimodal { - public static void main(String[] args) throws Exception { - // TODO(developer): Replace these variables before running the sample. - String projectId = "your-google-cloud-project-id"; - String location = "us-central1"; - String modelName = "gemini-2.0-flash-001"; - - String output = nonStreamingMultimodal(projectId, location, modelName); - System.out.println(output); - } - - // Ask a simple question and get the response. - public static String nonStreamingMultimodal(String projectId, String location, String modelName) - throws Exception { - // Initialize client that will be used to send requests. - // This client only needs to be created once, and can be reused for multiple requests. - try (VertexAI vertexAI = new VertexAI(projectId, location)) { - GenerativeModel model = new GenerativeModel(modelName, vertexAI); - - String videoUri = "gs://cloud-samples-data/video/animals.mp4"; - String imgUri = "gs://cloud-samples-data/generative-ai/image/character.jpg"; - - // Get the response from the model. - GenerateContentResponse response = model.generateContent( - ContentMaker.fromMultiModalData( - PartMaker.fromMimeTypeAndData("video/mp4", videoUri), - PartMaker.fromMimeTypeAndData("image/jpeg", imgUri), - "Are this video and image correlated?" - )); - - // Extract the generated text from the model's response. - String output = ResponseHandler.getText(response); - return output; - } - } -} -// [END generativeaionvertexai_non_stream_multimodality_basic] \ No newline at end of file diff --git a/vertexai/snippets/src/main/java/vertexai/gemini/QuestionAnswer.java b/vertexai/snippets/src/main/java/vertexai/gemini/QuestionAnswer.java deleted file mode 100644 index b985166d5f7..00000000000 --- a/vertexai/snippets/src/main/java/vertexai/gemini/QuestionAnswer.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package vertexai.gemini; - -// [START generativeaionvertexai_non_stream_text_basic] -import com.google.cloud.vertexai.VertexAI; -import com.google.cloud.vertexai.api.GenerateContentResponse; -import com.google.cloud.vertexai.generativeai.GenerativeModel; -import com.google.cloud.vertexai.generativeai.ResponseHandler; - -public class QuestionAnswer { - - public static void main(String[] args) throws Exception { - // TODO(developer): Replace these variables before running the sample. - String projectId = "your-google-cloud-project-id"; - String location = "us-central1"; - String modelName = "gemini-2.0-flash-001"; - - String output = simpleQuestion(projectId, location, modelName); - System.out.println(output); - } - - // Asks a question to the specified Vertex AI Gemini model and returns the generated answer. - public static String simpleQuestion(String projectId, String location, String modelName) - throws Exception { - // Initialize client that will be used to send requests. - // This client only needs to be created once, and can be reused for multiple requests. - try (VertexAI vertexAI = new VertexAI(projectId, location)) { - String output; - GenerativeModel model = new GenerativeModel(modelName, vertexAI); - // Send the question to the model for processing. - GenerateContentResponse response = model.generateContent("Why is the sky blue?"); - // Extract the generated text from the model's response. - output = ResponseHandler.getText(response); - return output; - } - } -} -// [END generativeaionvertexai_non_stream_text_basic] diff --git a/vertexai/snippets/src/main/java/vertexai/gemini/Quickstart.java b/vertexai/snippets/src/main/java/vertexai/gemini/Quickstart.java deleted file mode 100644 index 1824b6297bc..00000000000 --- a/vertexai/snippets/src/main/java/vertexai/gemini/Quickstart.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package vertexai.gemini; - -// [START generativeaionvertexai_gemini_get_started] -import com.google.cloud.vertexai.VertexAI; -import com.google.cloud.vertexai.api.GenerateContentResponse; -import com.google.cloud.vertexai.generativeai.ContentMaker; -import com.google.cloud.vertexai.generativeai.GenerativeModel; -import com.google.cloud.vertexai.generativeai.PartMaker; -import java.io.IOException; - -public class Quickstart { - - public static void main(String[] args) throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "your-google-cloud-project-id"; - String location = "us-central1"; - String modelName = "gemini-2.0-flash-001"; - - String output = quickstart(projectId, location, modelName); - System.out.println(output); - } - - // Analyzes the provided Multimodal input. - public static String quickstart(String projectId, String location, String modelName) - throws IOException { - // Initialize client that will be used to send requests. This client only needs - // to be created once, and can be reused for multiple requests. - try (VertexAI vertexAI = new VertexAI(projectId, location)) { - String imageUri = "gs://generativeai-downloads/images/scones.jpg"; - - GenerativeModel model = new GenerativeModel(modelName, vertexAI); - GenerateContentResponse response = model.generateContent(ContentMaker.fromMultiModalData( - PartMaker.fromMimeTypeAndData("image/png", imageUri), - "What's in this photo" - )); - - return response.toString(); - } - } -} -// [END generativeaionvertexai_gemini_get_started] diff --git a/vertexai/snippets/src/main/java/vertexai/gemini/StreamingMultimodal.java b/vertexai/snippets/src/main/java/vertexai/gemini/StreamingMultimodal.java deleted file mode 100644 index 7d5dd807c6a..00000000000 --- a/vertexai/snippets/src/main/java/vertexai/gemini/StreamingMultimodal.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package vertexai.gemini; - -// [START generativeaionvertexai_stream_multimodality_basic] -import com.google.cloud.vertexai.VertexAI; -import com.google.cloud.vertexai.generativeai.ContentMaker; -import com.google.cloud.vertexai.generativeai.GenerativeModel; -import com.google.cloud.vertexai.generativeai.PartMaker; - -public class StreamingMultimodal { - public static void main(String[] args) throws Exception { - // TODO(developer): Replace these variables before running the sample. - String projectId = "your-google-cloud-project-id"; - String location = "us-central1"; - String modelName = "gemini-2.0-flash-001"; - - streamingMultimodal(projectId, location, modelName); - } - - // Ask a simple question and get the response via streaming. - public static void streamingMultimodal(String projectId, String location, String modelName) - throws Exception { - // Initialize client that will be used to send requests. - // This client only needs to be created once, and can be reused for multiple requests. - try (VertexAI vertexAI = new VertexAI(projectId, location)) { - GenerativeModel model = new GenerativeModel(modelName, vertexAI); - - String videoUri = "gs://cloud-samples-data/video/animals.mp4"; - String imgUri = "gs://cloud-samples-data/generative-ai/image/character.jpg"; - - // Stream the result. - model.generateContentStream( - ContentMaker.fromMultiModalData( - PartMaker.fromMimeTypeAndData("video/mp4", videoUri), - PartMaker.fromMimeTypeAndData("image/jpeg", imgUri), - "Are this video and image correlated?" - )) - .stream() - .forEach(System.out::println); - } - } -} -// [END generativeaionvertexai_stream_multimodality_basic] \ No newline at end of file diff --git a/vertexai/snippets/src/main/java/vertexai/gemini/StreamingQuestionAnswer.java b/vertexai/snippets/src/main/java/vertexai/gemini/StreamingQuestionAnswer.java deleted file mode 100644 index 17bce1530e0..00000000000 --- a/vertexai/snippets/src/main/java/vertexai/gemini/StreamingQuestionAnswer.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package vertexai.gemini; - -// [START generativeaionvertexai_stream_text_basic] -import com.google.cloud.vertexai.VertexAI; -import com.google.cloud.vertexai.generativeai.GenerativeModel; - -public class StreamingQuestionAnswer { - - public static void main(String[] args) throws Exception { - // TODO(developer): Replace these variables before running the sample. - String projectId = "your-google-cloud-project-id"; - String location = "us-central1"; - String modelName = "gemini-2.0-flash-001"; - - streamingQuestion(projectId, location, modelName); - } - - // Ask a simple question and get the response via streaming. - public static void streamingQuestion(String projectId, String location, String modelName) - throws Exception { - // Initialize client that will be used to send requests. - // This client only needs to be created once, and can be reused for multiple requests. - try (VertexAI vertexAI = new VertexAI(projectId, location)) { - GenerativeModel model = new GenerativeModel(modelName, vertexAI); - - // Stream the result. - model.generateContentStream("Write a story about a magic backpack.") - .stream() - .forEach(System.out::println); - - System.out.println("Streaming complete."); - } - } -} -// [END generativeaionvertexai_stream_text_basic] \ No newline at end of file diff --git a/vertexai/snippets/src/main/java/vertexai/gemini/TextInput.java b/vertexai/snippets/src/main/java/vertexai/gemini/TextInput.java deleted file mode 100644 index fd582d021cd..00000000000 --- a/vertexai/snippets/src/main/java/vertexai/gemini/TextInput.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package vertexai.gemini; - -// [START generativeaionvertexai_gemini_generate_from_text_input] -import com.google.cloud.vertexai.VertexAI; -import com.google.cloud.vertexai.api.GenerateContentResponse; -import com.google.cloud.vertexai.generativeai.GenerativeModel; -import com.google.cloud.vertexai.generativeai.ResponseHandler; -import java.io.IOException; - -public class TextInput { - - public static void main(String[] args) throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "your-google-cloud-project-id"; - String location = "us-central1"; - String modelName = "gemini-2.0-flash-001"; - String textPrompt = - "What's a good name for a flower shop that specializes in selling bouquets of" - + " dried flowers?"; - - String output = textInput(projectId, location, modelName, textPrompt); - System.out.println(output); - } - - // Passes the provided text input to the Gemini model and returns the text-only response. - // For the specified textPrompt, the model returns a list of possible store names. - public static String textInput( - String projectId, String location, String modelName, String textPrompt) throws IOException { - // Initialize client that will be used to send requests. This client only needs - // to be created once, and can be reused for multiple requests. - try (VertexAI vertexAI = new VertexAI(projectId, location)) { - GenerativeModel model = new GenerativeModel(modelName, vertexAI); - - GenerateContentResponse response = model.generateContent(textPrompt); - String output = ResponseHandler.getText(response); - return output; - } - } -} -// [END generativeaionvertexai_gemini_generate_from_text_input] diff --git a/vertexai/snippets/src/main/java/vertexai/gemini/VideoInputWithAudio.java b/vertexai/snippets/src/main/java/vertexai/gemini/VideoInputWithAudio.java deleted file mode 100644 index 2bd4195e49a..00000000000 --- a/vertexai/snippets/src/main/java/vertexai/gemini/VideoInputWithAudio.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package vertexai.gemini; - -// [START generativeaionvertexai_gemini_video_with_audio] - -import com.google.cloud.vertexai.VertexAI; -import com.google.cloud.vertexai.api.GenerateContentResponse; -import com.google.cloud.vertexai.generativeai.ContentMaker; -import com.google.cloud.vertexai.generativeai.GenerativeModel; -import com.google.cloud.vertexai.generativeai.PartMaker; -import com.google.cloud.vertexai.generativeai.ResponseHandler; -import java.io.IOException; - -public class VideoInputWithAudio { - - public static void main(String[] args) throws IOException { - // TODO(developer): Replace these variables before running the sample. - String projectId = "your-google-cloud-project-id"; - String location = "us-central1"; - String modelName = "gemini-2.0-flash-001"; - - videoAudioInput(projectId, location, modelName); - } - - // Analyzes the given video input, including its audio track. - public static String videoAudioInput(String projectId, String location, String modelName) - throws IOException { - // Initialize client that will be used to send requests. This client only needs - // to be created once, and can be reused for multiple requests. - try (VertexAI vertexAI = new VertexAI(projectId, location)) { - String videoUri = "gs://cloud-samples-data/generative-ai/video/pixel8.mp4"; - - GenerativeModel model = new GenerativeModel(modelName, vertexAI); - GenerateContentResponse response = model.generateContent( - ContentMaker.fromMultiModalData( - "Provide a description of the video.\n The description should also " - + "contain anything important which people say in the video.", - PartMaker.fromMimeTypeAndData("video/mp4", videoUri) - )); - - String output = ResponseHandler.getText(response); - System.out.println(output); - - return output; - } - } -} -// [END generativeaionvertexai_gemini_video_with_audio] diff --git a/vertexai/snippets/src/test/java/vertexai/gemini/SnippetsIT.java b/vertexai/snippets/src/test/java/vertexai/gemini/SnippetsIT.java deleted file mode 100644 index b74908e59ec..00000000000 --- a/vertexai/snippets/src/test/java/vertexai/gemini/SnippetsIT.java +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Tests for Gemini code samples. - -package vertexai.gemini; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; - -import com.google.cloud.testing.junit4.MultipleAttemptsRule; -import com.google.gson.Gson; -import com.google.gson.annotations.SerializedName; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.PrintStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Arrays; -import java.util.Base64; -import java.util.stream.Collectors; -import javax.net.ssl.HttpsURLConnection; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class SnippetsIT { - - private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); - private static final String LOCATION = "us-central1"; - private static final String GEMINI_FLASH = "gemini-2.0-flash-001"; - private static final String GEMINI_FLASH_1_5 = "gemini-2.0-flash-001"; - private static final String DATASTORE_ID = "grounding-test-datastore_1716831150046"; - private static final int MAX_ATTEMPT_COUNT = 3; - private static final int INITIAL_BACKOFF_MILLIS = 120000; - private static final String TARGET_LANGUAGE_CODE = "fr"; - private static final String TEXT_TO_TRANSLATE = "Hello! How are you doing today?"; - - - // 2 minutes - - @Rule - public final MultipleAttemptsRule multipleAttemptsRule = - new MultipleAttemptsRule(MAX_ATTEMPT_COUNT, INITIAL_BACKOFF_MILLIS); - - private final PrintStream printStream = System.out; - private ByteArrayOutputStream bout; - - // Check if the required environment variables are set. - public static void requireEnvVar(String envVarName) { - assertWithMessage(String.format("Missing environment variable '%s' ", envVarName)) - .that(System.getenv(envVarName)) - .isNotEmpty(); - } - - @BeforeClass - public static void setUp() throws IOException { - try (PrintStream out = System.out) { - ByteArrayOutputStream stdOut = new ByteArrayOutputStream(); - System.setOut(new PrintStream(stdOut)); - - requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); - requireEnvVar("GOOGLE_CLOUD_PROJECT"); - - stdOut.close(); - System.setOut(out); - } - } - - // Reads the image data from the given URL. - public static byte[] readImageFile(String url) throws IOException { - if (url == null || url.isEmpty()) { - throw new IllegalArgumentException("Invalid URL: " + url); - } - URL urlObj = new URL(url); - HttpsURLConnection connection = null; - InputStream inputStream = null; - ByteArrayOutputStream outputStream = null; - - try { - connection = (HttpsURLConnection) urlObj.openConnection(); - connection.setRequestMethod("GET"); - - int responseCode = connection.getResponseCode(); - if (responseCode == HttpURLConnection.HTTP_OK) { - inputStream = connection.getInputStream(); - outputStream = new ByteArrayOutputStream(); - - byte[] buffer = new byte[1024]; - int bytesRead; - while ((bytesRead = inputStream.read(buffer)) != -1) { - outputStream.write(buffer, 0, bytesRead); - } - return outputStream.toByteArray(); - } else { - throw new IOException("Error fetching file: " + responseCode); - } - } finally { - if (inputStream != null) { - inputStream.close(); - } - if (outputStream != null) { - outputStream.close(); - } - if (connection != null) { - connection.disconnect(); - } - } - } - - @Before - public void beforeEach() { - bout = new ByteArrayOutputStream(); - System.setOut(new PrintStream(bout)); - } - - @After - public void afterEach() { - System.out.flush(); - System.setOut(printStream); - } - - @Test - public void testSimpleQuestionAnswer() throws Exception { - String output = QuestionAnswer.simpleQuestion(PROJECT_ID, LOCATION, GEMINI_FLASH); - assertThat(output).isNotEmpty(); - } - - @Test - public void testQuickstart() throws IOException { - String output = Quickstart.quickstart(PROJECT_ID, LOCATION, GEMINI_FLASH); - assertThat(output).isNotEmpty(); - } - - @Test - public void testStreamingQuestions() throws Exception { - StreamingQuestionAnswer.streamingQuestion(PROJECT_ID, LOCATION, GEMINI_FLASH); - assertThat(bout.toString()).isNotEmpty(); - } - - @Test - public void testTextInput() throws Exception { - String textPrompt = - "What's a good name for a flower shop that specializes in selling bouquets of" - + " dried flowers?"; - String output = TextInput.textInput(PROJECT_ID, LOCATION, GEMINI_FLASH, textPrompt); - assertThat(output).isNotEmpty(); - } - - @Test - public void testTokenCount() throws Exception { - int tokenCount = GetTokenCount.getTokenCount(PROJECT_ID, LOCATION, GEMINI_FLASH); - assertThat(tokenCount).isEqualTo(6); - } - - @Test - public void testMediaTokenCount() throws Exception { - int tokenCount = GetMediaTokenCount.getMediaTokenCount(PROJECT_ID, LOCATION, GEMINI_FLASH); - assertThat(tokenCount).isEqualTo(16252); - } - - @Test - public void testFunctionCalling() throws Exception { - String textPrompt = "What's the weather in Paris?"; - - String answer = - FunctionCalling.whatsTheWeatherLike(PROJECT_ID, LOCATION, GEMINI_FLASH, textPrompt); - assertThat(answer).ignoringCase().contains("Paris"); - assertThat(answer).ignoringCase().contains("sunny"); - } - - @Test - public void testVideoAudioInput() throws IOException { - String output = VideoInputWithAudio.videoAudioInput(PROJECT_ID, LOCATION, GEMINI_FLASH); - - assertThat(output).ignoringCase().contains("Pixel"); - assertThat(output).ignoringCase().contains("Tokyo"); - } - - // @Test - // public void testGroundingWithPublicData() throws Exception { - // String output = - // GroundingWithPublicData.groundWithPublicData(PROJECT_ID, LOCATION, GEMINI_FLASH_1_5); - - // assertThat(output).ignoringCase().contains("Rayleigh"); - // } - - // @Test - // public void testGroundingWithPrivateData() throws Exception { - // String output = - // GroundingWithPrivateData.groundWithPrivateData( - // PROJECT_ID, - // LOCATION, - // GEMINI_FLASH, - // String.format( - // "projects/%s/locations/global/collections/default_collection/dataStores/%s", - // PROJECT_ID, DATASTORE_ID)); - - // assertThat(output).ignoringCase().contains("DMV"); - // } - - @Test - public void testMultimodalStreaming() throws Exception { - StreamingMultimodal.streamingMultimodal(PROJECT_ID, LOCATION, GEMINI_FLASH); - assertThat(bout.toString()).ignoringCase().contains("no"); - } - - @Test - public void testMultimodalNonStreaming() throws Exception { - String output = Multimodal.nonStreamingMultimodal(PROJECT_ID, LOCATION, GEMINI_FLASH); - - assertThat(output).ignoringCase().contains("no"); - } - - private class Obj { - public String object; - } - - @Test - public void testControlledGenerationWithJsonSchema6() throws Exception { - String output = ControlledGenerationSchema6 - .controlGenerationWithJsonSchema6(PROJECT_ID, LOCATION, GEMINI_FLASH); - - Obj[] objects = new Gson().fromJson(output, Obj[].class); - String recognizedObjects = Arrays.stream(objects) - .map(obj -> obj.object.toLowerCase()) - .collect(Collectors.joining(" ")); - - assertThat(recognizedObjects).isNotEmpty(); - assertThat(recognizedObjects).contains("globe"); - assertThat(recognizedObjects).contains("keyboard"); - assertThat(recognizedObjects).contains("passport"); - assertThat(recognizedObjects).contains("pot"); - } - -} diff --git a/vision/face-detection/pom.xml b/vision/face-detection/pom.xml index 7af1f0d13ad..adeed0423b7 100644 --- a/vision/face-detection/pom.xml +++ b/vision/face-detection/pom.xml @@ -51,7 +51,7 @@ - + com.google.apis google-api-services-vision @@ -69,7 +69,7 @@ com.google.http-client google-http-client-jackson2 - + diff --git a/workflows/cloud-client/resources/myFirstWorkflow.workflows.yaml b/workflows/cloud-client/resources/myFirstWorkflow.workflows.yaml new file mode 100644 index 00000000000..524e9befdce --- /dev/null +++ b/workflows/cloud-client/resources/myFirstWorkflow.workflows.yaml @@ -0,0 +1,47 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START workflows_myfirstworkflow_yaml_java] +# This workflow accepts an optional "searchTerm" argument for the Wikipedia API. +# If no input arguments are provided or "searchTerm" is absent, +# it will fetch the day of the week in Amsterdam and use it as the search term. + +main: + params: [input] + steps: + - validateSearchTermAndRedirectToReadWikipedia: + switch: + - condition: '${map.get(input, "searchTerm") != null}' + assign: + - searchTerm: '${input.searchTerm}' + next: readWikipedia + - getCurrentTime: + call: http.get + args: + url: https://timeapi.io/api/Time/current/zone?timeZone=Europe/Amsterdam + result: currentTime + - setFromCallResult: + assign: + - searchTerm: '${currentTime.body.dayOfWeek}' + - readWikipedia: + call: http.get + args: + url: 'https://en.wikipedia.org/w/api.php' + query: + action: opensearch + search: '${searchTerm}' + result: wikiResult + - returnOutput: + return: '${wikiResult.body[1]}' +# [END workflows_myfirstworkflow_yaml_java] \ No newline at end of file diff --git a/workflows/cloud-client/src/main/java/com/example/workflows/ExecuteWithArguments.java b/workflows/cloud-client/src/main/java/com/example/workflows/ExecuteWithArguments.java new file mode 100644 index 00000000000..4f7487bc614 --- /dev/null +++ b/workflows/cloud-client/src/main/java/com/example/workflows/ExecuteWithArguments.java @@ -0,0 +1,78 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.workflows; + +import com.google.cloud.workflows.executions.v1.CreateExecutionRequest; +import com.google.cloud.workflows.executions.v1.Execution; +import com.google.cloud.workflows.executions.v1.ExecutionsClient; +import com.google.cloud.workflows.executions.v1.WorkflowName; +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +// [START workflows_execute_with_arguments] + +public class ExecuteWithArguments { + public static void main(String[] args) + throws IOException, InterruptedException, ExecutionException { + String projectId = "your-project-id"; + String location = "your-location"; // For example: us-central1 + String workflowId = "your-workflow-id"; + + executeWorkflowWithArguments(projectId, location, workflowId); + } + + public static void executeWorkflowWithArguments( + String projectId, String location, String workflowId) + throws IOException, InterruptedException, ExecutionException { + try (ExecutionsClient executionsClient = ExecutionsClient.create()) { + WorkflowName parent = WorkflowName.of(projectId, location, workflowId); + + CreateExecutionRequest executionRequest = + CreateExecutionRequest.newBuilder() + .setParent(parent.toString()) + .setExecution( + Execution.newBuilder().setArgument("{\"searchTerm\":\"Cloud\"}").build()) + .build(); + + Execution executionCreated = executionsClient.createExecution(executionRequest); + + System.out.println("Created execution: " + executionCreated.getName()); + + // Wait for execution to finish using exponential backoff + long backoffDelay = 1_000; + Execution execution; + System.out.println("Poll for result..."); + while (true) { + execution = executionsClient.getExecution(executionCreated.getName()); + + // Check if execution finished + if (execution.getState() != Execution.State.ACTIVE) { + break; + } + + // Wait using exponential backoff + System.out.println("- Waiting for results..."); + Thread.sleep(backoffDelay); + backoffDelay *= 2; + } + + System.out.println("Execution finished with state: " + execution.getState().name()); + System.out.println("Execution results: " + execution.getResult()); + } + } +} +// [END workflows_execute_with_arguments] diff --git a/workflows/cloud-client/src/main/java/com/example/workflows/ExecuteWithoutArguments.java b/workflows/cloud-client/src/main/java/com/example/workflows/ExecuteWithoutArguments.java new file mode 100644 index 00000000000..718973de0ed --- /dev/null +++ b/workflows/cloud-client/src/main/java/com/example/workflows/ExecuteWithoutArguments.java @@ -0,0 +1,77 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.workflows; + +// [START workflows_execute_without_arguments] + +import com.google.cloud.workflows.executions.v1.CreateExecutionRequest; +import com.google.cloud.workflows.executions.v1.Execution; +import com.google.cloud.workflows.executions.v1.ExecutionsClient; +import com.google.cloud.workflows.executions.v1.WorkflowName; +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +public class ExecuteWithoutArguments { + public static void main(String[] args) + throws IOException, InterruptedException, ExecutionException { + String projectId = "your-project-id"; + String location = "your-location"; // For example: us-central1 + String workflowId = "your-workflow-id"; + + executeWorkflowWithoutArguments(projectId, location, workflowId); + } + + public static void executeWorkflowWithoutArguments( + String projectId, String location, String workflowId) + throws IOException, InterruptedException, ExecutionException { + try (ExecutionsClient executionsClient = ExecutionsClient.create()) { + WorkflowName parent = WorkflowName.of(projectId, location, workflowId); + + CreateExecutionRequest executionRequest = + CreateExecutionRequest.newBuilder() + .setParent(parent.toString()) + .setExecution(Execution.newBuilder().build()) + .build(); + + Execution executionCreated = executionsClient.createExecution(executionRequest); + + System.out.println("Created execution: " + executionCreated.getName()); + + // Wait for execution to finish using exponential backoff + long backoffDelay = 1_000; + Execution execution; + System.out.println("Poll for result..."); + while (true) { + execution = executionsClient.getExecution(executionCreated.getName()); + + // Check if execution finished + if (execution.getState() != Execution.State.ACTIVE) { + break; + } + + // Wait using exponential backoff + System.out.println("- Waiting for results..."); + Thread.sleep(backoffDelay); + backoffDelay *= 2; + } + + System.out.println("Execution finished with state: " + execution.getState().name()); + System.out.println("Execution results: " + execution.getResult()); + } + } +} +// [END workflows_execute_without_arguments] diff --git a/workflows/cloud-client/src/test/java/com/example/workflows/ExecuteWithArgumentsIT.java b/workflows/cloud-client/src/test/java/com/example/workflows/ExecuteWithArgumentsIT.java new file mode 100644 index 00000000000..4dc5fe334f3 --- /dev/null +++ b/workflows/cloud-client/src/test/java/com/example/workflows/ExecuteWithArgumentsIT.java @@ -0,0 +1,72 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.workflows; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertNotNull; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class ExecuteWithArgumentsIT { + private static String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static String LOCATION = "us-central1"; + private static String WORKFLOW_ID = "java_myFirstWorkflow_" + UUID.randomUUID(); + private static ByteArrayOutputStream bout; + + private static void requireEnvVar(String varName) { + assertNotNull( + "Environment variable " + varName + " is required to perform these tests.", + System.getenv(varName)); + } + + @BeforeClass + public static void setUp() throws IOException, InterruptedException, ExecutionException { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + + // Create workflow + Utils.createWorkflow(PROJECT_ID, LOCATION, WORKFLOW_ID); + + bout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(bout)); + } + + @AfterClass + public static void tearDown() throws IOException, InterruptedException, ExecutionException { + // Delete workflow + Utils.deleteWorkflow(PROJECT_ID, LOCATION, WORKFLOW_ID); + + System.setOut(null); + } + + @Test + public void testExecuteWorkflowWithArguments() + throws IOException, InterruptedException, ExecutionException { + ExecuteWithArguments.executeWorkflowWithArguments(PROJECT_ID, LOCATION, WORKFLOW_ID); + + String output = bout.toString(); + assertThat(output).contains("Execution finished with state: SUCCEEDED"); + assertThat(output).contains("Execution results: "); + assertThat(output).contains("Cloud"); + } +} diff --git a/workflows/cloud-client/src/test/java/com/example/workflows/ExecuteWithoutArgumentsIT.java b/workflows/cloud-client/src/test/java/com/example/workflows/ExecuteWithoutArgumentsIT.java new file mode 100644 index 00000000000..91a29e42966 --- /dev/null +++ b/workflows/cloud-client/src/test/java/com/example/workflows/ExecuteWithoutArgumentsIT.java @@ -0,0 +1,71 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.workflows; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertNotNull; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class ExecuteWithoutArgumentsIT { + private static String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static String LOCATION = "us-central1"; + private static String WORKFLOW_ID = "java_myFirstWorkflow_" + UUID.randomUUID(); + private static ByteArrayOutputStream bout; + + private static void requireEnvVar(String varName) { + assertNotNull( + "Environment variable " + varName + " is required to perform these tests.", + System.getenv(varName)); + } + + @BeforeClass + public static void setUp() throws IOException, InterruptedException, ExecutionException { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + + // Create workflow + Utils.createWorkflow(PROJECT_ID, LOCATION, WORKFLOW_ID); + + bout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(bout)); + } + + @AfterClass + public static void tearDown() throws IOException, InterruptedException, ExecutionException { + // Delete workflow + Utils.deleteWorkflow(PROJECT_ID, LOCATION, WORKFLOW_ID); + + System.setOut(null); + } + + @Test + public void testExecuteWorkflowWithoutArguments() + throws IOException, InterruptedException, ExecutionException { + ExecuteWithoutArguments.executeWorkflowWithoutArguments(PROJECT_ID, LOCATION, WORKFLOW_ID); + + String output = bout.toString(); + assertThat(output).contains("Execution finished with state: SUCCEEDED"); + assertThat(output).contains("Execution results: "); + } +} diff --git a/workflows/cloud-client/src/test/java/com/example/workflows/Utils.java b/workflows/cloud-client/src/test/java/com/example/workflows/Utils.java new file mode 100644 index 00000000000..6c70f407635 --- /dev/null +++ b/workflows/cloud-client/src/test/java/com/example/workflows/Utils.java @@ -0,0 +1,49 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.workflows; + +import com.google.cloud.workflows.v1.LocationName; +import com.google.cloud.workflows.v1.Workflow; +import com.google.cloud.workflows.v1.WorkflowName; +import com.google.cloud.workflows.v1.WorkflowsClient; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.concurrent.ExecutionException; + +public class Utils { + public static void createWorkflow(String projectId, String location, String workflowId) + throws IOException, InterruptedException, ExecutionException { + + try (WorkflowsClient workflowsClient = WorkflowsClient.create()) { + LocationName parent = LocationName.of(projectId, location); + + String source = + new String(Files.readAllBytes(Paths.get("resources/myFirstWorkflow.workflows.yaml"))); + Workflow workflow = Workflow.newBuilder().setSourceContents(source).build(); + + workflowsClient.createWorkflowAsync(parent, workflow, workflowId).get(); + } + } + + public static void deleteWorkflow(String projectId, String location, String workflowId) + throws IOException, InterruptedException, ExecutionException { + try (WorkflowsClient workflowsClient = WorkflowsClient.create()) { + workflowsClient.deleteWorkflowAsync(WorkflowName.of(projectId, location, workflowId)).get(); + } + } +}